1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle;
21
22 import java.io.ByteArrayOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.ObjectOutputStream;
26 import java.io.OutputStream;
27 import java.io.Serializable;
28 import java.math.BigInteger;
29 import java.net.URI;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.security.MessageDigest;
33 import java.security.NoSuchAlgorithmException;
34 import java.util.HashSet;
35 import java.util.Locale;
36 import java.util.Objects;
37 import java.util.Properties;
38 import java.util.Set;
39
40 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
41 import com.puppycrawl.tools.checkstyle.api.Configuration;
42 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
43 import com.puppycrawl.tools.checkstyle.utils.OsSpecificUtil;
44
45
46
47
48
49
50
51
52
53
54
55
56 public final class PropertyCacheFile {
57
58
59
60
61
62
63
64 public static final String CONFIG_HASH_KEY = "configuration*?";
65
66
67
68
69
70
71
72 public static final String EXTERNAL_RESOURCE_KEY_PREFIX = "module-resource*?:";
73
74
75 private static final int BUFFER_SIZE = 1024;
76
77
78 private static final byte[] BUFFER = new byte[BUFFER_SIZE];
79
80
81 private static final int BASE_16 = 16;
82
83
84 private final Properties details = new Properties();
85
86
87 private final Configuration config;
88
89
90 private final String fileName;
91
92
93 private String configHash;
94
95
96
97
98
99
100
101
102 public PropertyCacheFile(Configuration config, String fileName) {
103 if (config == null) {
104 throw new IllegalArgumentException("config can not be null");
105 }
106 if (fileName == null) {
107 throw new IllegalArgumentException("fileName can not be null");
108 }
109 this.config = config;
110 this.fileName = fileName;
111 }
112
113
114
115
116
117
118 public void load() throws IOException {
119
120
121 configHash = getHashCodeBasedOnObjectContent(config);
122 final Path path = Path.of(fileName);
123 if (Files.exists(path)) {
124 try (InputStream inStream = Files.newInputStream(path)) {
125 details.load(inStream);
126 final String cachedConfigHash = details.getProperty(CONFIG_HASH_KEY);
127 if (!configHash.equals(cachedConfigHash)) {
128
129 reset();
130 }
131 }
132 }
133 else {
134
135 reset();
136 }
137 }
138
139
140
141
142
143
144 public void persist() throws IOException {
145 final Path path = Path.of(fileName);
146 final Path directory = path.getParent();
147
148 if (directory != null) {
149 OsSpecificUtil.updateDirectory(directory);
150 }
151 try (OutputStream out = Files.newOutputStream(path)) {
152 details.store(out, null);
153 }
154 }
155
156
157
158
159 public void reset() {
160 details.clear();
161 details.setProperty(CONFIG_HASH_KEY, configHash);
162 }
163
164
165
166
167
168
169
170
171 public boolean isInCache(String uncheckedFileName, long timestamp) {
172 final String lastChecked = details.getProperty(uncheckedFileName);
173 return Objects.equals(lastChecked, Long.toString(timestamp));
174 }
175
176
177
178
179
180
181
182 public void put(String checkedFileName, long timestamp) {
183 details.setProperty(checkedFileName, Long.toString(timestamp));
184 }
185
186
187
188
189
190
191
192 public String get(String name) {
193 return details.getProperty(name);
194 }
195
196
197
198
199
200
201 public void remove(String checkedFileName) {
202 details.remove(checkedFileName);
203 }
204
205
206
207
208
209
210
211
212 private static String getHashCodeBasedOnObjectContent(Serializable object) {
213 try {
214 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
215
216 serialize(object, outputStream);
217
218
219
220
221 final MessageDigest digest = MessageDigest.getInstance("SHA-1");
222 digest.update(outputStream.toByteArray());
223
224 return new BigInteger(1, digest.digest()).toString(BASE_16).toUpperCase(Locale.ROOT);
225 }
226 catch (final IOException | NoSuchAlgorithmException ex) {
227
228 throw new IllegalStateException("Unable to calculate hashcode.", ex);
229 }
230 }
231
232
233
234
235
236
237
238
239 private static void serialize(Serializable object,
240 OutputStream outputStream) throws IOException {
241 try (ObjectOutputStream oos = new ObjectOutputStream(outputStream)) {
242 oos.writeObject(object);
243 }
244 }
245
246
247
248
249
250
251
252 public void putExternalResources(Set<String> locations) {
253 final Set<ExternalResource> resources = loadExternalResources(locations);
254 if (areExternalResourcesChanged(resources)) {
255 reset();
256 fillCacheWithExternalResources(resources);
257 }
258 }
259
260
261
262
263
264
265
266 private static Set<ExternalResource> loadExternalResources(Set<String> resourceLocations) {
267 final Set<ExternalResource> resources = new HashSet<>();
268 for (String location : resourceLocations) {
269 try {
270 final byte[] content = loadExternalResource(location);
271 final String contentHashSum = getHashCodeBasedOnObjectContent(content);
272 resources.add(new ExternalResource(EXTERNAL_RESOURCE_KEY_PREFIX + location,
273 contentHashSum));
274 }
275 catch (CheckstyleException | IOException ex) {
276
277
278
279
280 final String contentHashSum = getHashCodeBasedOnObjectContent(ex);
281 resources.add(new ExternalResource(EXTERNAL_RESOURCE_KEY_PREFIX + location,
282 contentHashSum));
283 }
284 }
285 return resources;
286 }
287
288
289
290
291
292
293
294
295
296 private static byte[] loadExternalResource(String location)
297 throws IOException, CheckstyleException {
298 final URI uri = CommonUtil.getUriByFilename(location);
299
300 try (InputStream is = uri.toURL().openStream()) {
301 return toByteArray(is);
302 }
303 }
304
305
306
307
308
309
310
311
312 private static byte[] toByteArray(InputStream stream) throws IOException {
313 final ByteArrayOutputStream content = new ByteArrayOutputStream();
314
315 while (true) {
316 final int size = stream.read(BUFFER);
317 if (size == -1) {
318 break;
319 }
320
321 content.write(BUFFER, 0, size);
322 }
323
324 return content.toByteArray();
325 }
326
327
328
329
330
331
332
333 private boolean areExternalResourcesChanged(Set<ExternalResource> resources) {
334 return resources.stream().anyMatch(this::isResourceChanged);
335 }
336
337
338
339
340
341
342
343 private boolean isResourceChanged(ExternalResource resource) {
344 boolean changed = false;
345 if (isResourceLocationInCache(resource.location)) {
346 final String contentHashSum = resource.contentHashSum;
347 final String cachedHashSum = details.getProperty(resource.location);
348 if (!cachedHashSum.equals(contentHashSum)) {
349 changed = true;
350 }
351 }
352 else {
353 changed = true;
354 }
355 return changed;
356 }
357
358
359
360
361
362
363
364 private void fillCacheWithExternalResources(Set<ExternalResource> externalResources) {
365 externalResources
366 .forEach(resource -> details.setProperty(resource.location, resource.contentHashSum));
367 }
368
369
370
371
372
373
374
375 private boolean isResourceLocationInCache(String location) {
376 final String cachedHashSum = details.getProperty(location);
377 return cachedHashSum != null;
378 }
379
380
381
382
383 private static final class ExternalResource {
384
385
386 private final String location;
387
388 private final String contentHashSum;
389
390
391
392
393
394
395
396 private ExternalResource(String location, String contentHashSum) {
397 this.location = location;
398 this.contentHashSum = contentHashSum;
399 }
400
401 }
402
403 }