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