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