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.File;
23 import java.io.IOException;
24 import java.io.PrintWriter;
25 import java.io.StringWriter;
26 import java.io.UnsupportedEncodingException;
27 import java.nio.charset.Charset;
28 import java.nio.charset.StandardCharsets;
29 import java.util.ArrayList;
30 import java.util.HashSet;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Set;
34 import java.util.SortedSet;
35 import java.util.TreeSet;
36 import java.util.stream.Collectors;
37
38 import org.apache.commons.logging.Log;
39 import org.apache.commons.logging.LogFactory;
40
41 import com.puppycrawl.tools.checkstyle.api.AuditEvent;
42 import com.puppycrawl.tools.checkstyle.api.AuditListener;
43 import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
44 import com.puppycrawl.tools.checkstyle.api.BeforeExecutionFileFilter;
45 import com.puppycrawl.tools.checkstyle.api.BeforeExecutionFileFilterSet;
46 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
47 import com.puppycrawl.tools.checkstyle.api.Configuration;
48 import com.puppycrawl.tools.checkstyle.api.Context;
49 import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
50 import com.puppycrawl.tools.checkstyle.api.FileSetCheck;
51 import com.puppycrawl.tools.checkstyle.api.FileText;
52 import com.puppycrawl.tools.checkstyle.api.Filter;
53 import com.puppycrawl.tools.checkstyle.api.FilterSet;
54 import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
55 import com.puppycrawl.tools.checkstyle.api.MessageDispatcher;
56 import com.puppycrawl.tools.checkstyle.api.RootModule;
57 import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
58 import com.puppycrawl.tools.checkstyle.api.SeverityLevelCounter;
59 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
60
61
62
63
64 public class Checker extends AutomaticBean implements MessageDispatcher, RootModule {
65
66
67 public static final String EXCEPTION_MSG = "general.exception";
68
69
70 private final Log log;
71
72
73 private final SeverityLevelCounter counter = new SeverityLevelCounter(
74 SeverityLevel.ERROR);
75
76
77 private final List<AuditListener> listeners = new ArrayList<>();
78
79
80 private final List<FileSetCheck> fileSetChecks = new ArrayList<>();
81
82
83 private final BeforeExecutionFileFilterSet beforeExecutionFileFilters =
84 new BeforeExecutionFileFilterSet();
85
86
87 private final FilterSet filters = new FilterSet();
88
89
90 private ClassLoader classLoader = Thread.currentThread()
91 .getContextClassLoader();
92
93
94 private String basedir;
95
96
97 private String localeCountry = Locale.getDefault().getCountry();
98
99 private String localeLanguage = Locale.getDefault().getLanguage();
100
101
102 private ModuleFactory moduleFactory;
103
104
105 private ClassLoader moduleClassLoader;
106
107
108 private Context childContext;
109
110
111 private String[] fileExtensions = CommonUtil.EMPTY_STRING_ARRAY;
112
113
114
115
116
117
118
119
120
121
122
123 private SeverityLevel severity = SeverityLevel.ERROR;
124
125
126 private String charset = System.getProperty("file.encoding", StandardCharsets.UTF_8.name());
127
128
129 private PropertyCacheFile cacheFile;
130
131
132 private boolean haltOnException = true;
133
134
135 private int tabWidth = CommonUtil.DEFAULT_TAB_WIDTH;
136
137
138
139
140
141 public Checker() {
142 addListener(counter);
143 log = LogFactory.getLog(Checker.class);
144 }
145
146
147
148
149
150
151 public void setCacheFile(String fileName) throws IOException {
152 final Configuration configuration = getConfiguration();
153 cacheFile = new PropertyCacheFile(configuration, fileName);
154 cacheFile.load();
155 }
156
157
158
159
160
161 public void removeBeforeExecutionFileFilter(BeforeExecutionFileFilter filter) {
162 beforeExecutionFileFilters.removeBeforeExecutionFileFilter(filter);
163 }
164
165
166
167
168
169 public void removeFilter(Filter filter) {
170 filters.removeFilter(filter);
171 }
172
173 @Override
174 public void destroy() {
175 listeners.clear();
176 fileSetChecks.clear();
177 beforeExecutionFileFilters.clear();
178 filters.clear();
179 if (cacheFile != null) {
180 try {
181 cacheFile.persist();
182 }
183 catch (IOException ex) {
184 throw new IllegalStateException("Unable to persist cache file.", ex);
185 }
186 }
187 }
188
189
190
191
192
193 public void removeListener(AuditListener listener) {
194 listeners.remove(listener);
195 }
196
197
198
199
200
201 public void setBasedir(String basedir) {
202 this.basedir = basedir;
203 }
204
205 @Override
206 public int process(List<File> files) throws CheckstyleException {
207 if (cacheFile != null) {
208 cacheFile.putExternalResources(getExternalResourceLocations());
209 }
210
211
212 fireAuditStarted();
213 for (final FileSetCheck fsc : fileSetChecks) {
214 fsc.beginProcessing(charset);
215 }
216
217 final List<File> targetFiles = files.stream()
218 .filter(file -> CommonUtil.matchesFileExtension(file, fileExtensions))
219 .collect(Collectors.toList());
220 processFiles(targetFiles);
221
222
223
224 fileSetChecks.forEach(FileSetCheck::finishProcessing);
225
226
227 fileSetChecks.forEach(FileSetCheck::destroy);
228
229 final int errorCount = counter.getCount();
230 fireAuditFinished();
231 return errorCount;
232 }
233
234
235
236
237
238
239
240 private Set<String> getExternalResourceLocations() {
241 final Set<String> externalResources = new HashSet<>();
242 fileSetChecks.stream().filter(check -> check instanceof ExternalResourceHolder)
243 .forEach(check -> {
244 final Set<String> locations =
245 ((ExternalResourceHolder) check).getExternalResourceLocations();
246 externalResources.addAll(locations);
247 });
248 filters.getFilters().stream().filter(filter -> filter instanceof ExternalResourceHolder)
249 .forEach(filter -> {
250 final Set<String> locations =
251 ((ExternalResourceHolder) filter).getExternalResourceLocations();
252 externalResources.addAll(locations);
253 });
254 return externalResources;
255 }
256
257
258 private void fireAuditStarted() {
259 final AuditEvent event = new AuditEvent(this);
260 for (final AuditListener listener : listeners) {
261 listener.auditStarted(event);
262 }
263 }
264
265
266 private void fireAuditFinished() {
267 final AuditEvent event = new AuditEvent(this);
268 for (final AuditListener listener : listeners) {
269 listener.auditFinished(event);
270 }
271 }
272
273
274
275
276
277
278
279
280 private void processFiles(List<File> files) throws CheckstyleException {
281 for (final File file : files) {
282 String fileName = null;
283 try {
284 fileName = file.getAbsolutePath();
285 final long timestamp = file.lastModified();
286 if (cacheFile != null && cacheFile.isInCache(fileName, timestamp)
287 || !acceptFileStarted(fileName)) {
288 continue;
289 }
290 if (cacheFile != null) {
291 cacheFile.put(fileName, timestamp);
292 }
293 fireFileStarted(fileName);
294 final SortedSet<LocalizedMessage> fileMessages = processFile(file);
295 fireErrors(fileName, fileMessages);
296 fireFileFinished(fileName);
297 }
298
299
300 catch (Exception ex) {
301 if (fileName != null && cacheFile != null) {
302 cacheFile.remove(fileName);
303 }
304
305
306 throw new CheckstyleException("Exception was thrown while processing "
307 + file.getPath(), ex);
308 }
309 catch (Error error) {
310 if (fileName != null && cacheFile != null) {
311 cacheFile.remove(fileName);
312 }
313
314
315 throw new Error("Error was thrown while processing " + file.getPath(), error);
316 }
317 }
318 }
319
320
321
322
323
324
325
326
327 private SortedSet<LocalizedMessage> processFile(File file) throws CheckstyleException {
328 final SortedSet<LocalizedMessage> fileMessages = new TreeSet<>();
329 try {
330 final FileText theText = new FileText(file.getAbsoluteFile(), charset);
331 for (final FileSetCheck fsc : fileSetChecks) {
332 fileMessages.addAll(fsc.process(file, theText));
333 }
334 }
335 catch (final IOException ioe) {
336 log.debug("IOException occurred.", ioe);
337 fileMessages.add(new LocalizedMessage(1,
338 Definitions.CHECKSTYLE_BUNDLE, EXCEPTION_MSG,
339 new String[] {ioe.getMessage()}, null, getClass(), null));
340 }
341
342 catch (Exception ex) {
343 if (haltOnException) {
344 throw ex;
345 }
346
347 log.debug("Exception occurred.", ex);
348
349 final StringWriter sw = new StringWriter();
350 final PrintWriter pw = new PrintWriter(sw, true);
351
352 ex.printStackTrace(pw);
353
354 fileMessages.add(new LocalizedMessage(1,
355 Definitions.CHECKSTYLE_BUNDLE, EXCEPTION_MSG,
356 new String[] {sw.getBuffer().toString()},
357 null, getClass(), null));
358 }
359 return fileMessages;
360 }
361
362
363
364
365
366
367
368
369 private boolean acceptFileStarted(String fileName) {
370 final String stripped = CommonUtil.relativizeAndNormalizePath(basedir, fileName);
371 return beforeExecutionFileFilters.accept(stripped);
372 }
373
374
375
376
377
378
379
380 @Override
381 public void fireFileStarted(String fileName) {
382 final String stripped = CommonUtil.relativizeAndNormalizePath(basedir, fileName);
383 final AuditEvent event = new AuditEvent(this, stripped);
384 for (final AuditListener listener : listeners) {
385 listener.fileStarted(event);
386 }
387 }
388
389
390
391
392
393
394
395 @Override
396 public void fireErrors(String fileName, SortedSet<LocalizedMessage> errors) {
397 final String stripped = CommonUtil.relativizeAndNormalizePath(basedir, fileName);
398 boolean hasNonFilteredViolations = false;
399 for (final LocalizedMessage element : errors) {
400 final AuditEvent event = new AuditEvent(this, stripped, element);
401 if (filters.accept(event)) {
402 hasNonFilteredViolations = true;
403 for (final AuditListener listener : listeners) {
404 listener.addError(event);
405 }
406 }
407 }
408 if (hasNonFilteredViolations && cacheFile != null) {
409 cacheFile.remove(fileName);
410 }
411 }
412
413
414
415
416
417
418
419 @Override
420 public void fireFileFinished(String fileName) {
421 final String stripped = CommonUtil.relativizeAndNormalizePath(basedir, fileName);
422 final AuditEvent event = new AuditEvent(this, stripped);
423 for (final AuditListener listener : listeners) {
424 listener.fileFinished(event);
425 }
426 }
427
428 @Override
429 protected void finishLocalSetup() throws CheckstyleException {
430 final Locale locale = new Locale(localeLanguage, localeCountry);
431 LocalizedMessage.setLocale(locale);
432
433 if (moduleFactory == null) {
434 if (moduleClassLoader == null) {
435 throw new CheckstyleException(
436 "if no custom moduleFactory is set, "
437 + "moduleClassLoader must be specified");
438 }
439
440 final Set<String> packageNames = PackageNamesLoader
441 .getPackageNames(moduleClassLoader);
442 moduleFactory = new PackageObjectFactory(packageNames,
443 moduleClassLoader);
444 }
445
446 final DefaultContext context = new DefaultContext();
447 context.add("charset", charset);
448 context.add("classLoader", classLoader);
449 context.add("moduleFactory", moduleFactory);
450 context.add("severity", severity.getName());
451 context.add("basedir", basedir);
452 context.add("tabWidth", String.valueOf(tabWidth));
453 childContext = context;
454 }
455
456
457
458
459
460 @Override
461 protected void setupChild(Configuration childConf)
462 throws CheckstyleException {
463 final String name = childConf.getName();
464 final Object child;
465
466 try {
467 child = moduleFactory.createModule(name);
468
469 if (child instanceof AutomaticBean) {
470 final AutomaticBean bean = (AutomaticBean) child;
471 bean.contextualize(childContext);
472 bean.configure(childConf);
473 }
474 }
475 catch (final CheckstyleException ex) {
476 throw new CheckstyleException("cannot initialize module " + name
477 + " - " + ex.getMessage(), ex);
478 }
479 if (child instanceof FileSetCheck) {
480 final FileSetCheck fsc = (FileSetCheck) child;
481 fsc.init();
482 addFileSetCheck(fsc);
483 }
484 else if (child instanceof BeforeExecutionFileFilter) {
485 final BeforeExecutionFileFilter filter = (BeforeExecutionFileFilter) child;
486 addBeforeExecutionFileFilter(filter);
487 }
488 else if (child instanceof Filter) {
489 final Filter filter = (Filter) child;
490 addFilter(filter);
491 }
492 else if (child instanceof AuditListener) {
493 final AuditListener listener = (AuditListener) child;
494 addListener(listener);
495 }
496 else {
497 throw new CheckstyleException(name
498 + " is not allowed as a child in Checker");
499 }
500 }
501
502
503
504
505
506
507 public void addFileSetCheck(FileSetCheck fileSetCheck) {
508 fileSetCheck.setMessageDispatcher(this);
509 fileSetChecks.add(fileSetCheck);
510 }
511
512
513
514
515
516 public void addBeforeExecutionFileFilter(BeforeExecutionFileFilter filter) {
517 beforeExecutionFileFilters.addBeforeExecutionFileFilter(filter);
518 }
519
520
521
522
523
524 public void addFilter(Filter filter) {
525 filters.addFilter(filter);
526 }
527
528 @Override
529 public final void addListener(AuditListener listener) {
530 listeners.add(listener);
531 }
532
533
534
535
536
537
538
539 public final void setFileExtensions(String... extensions) {
540 if (extensions == null) {
541 fileExtensions = null;
542 }
543 else {
544 fileExtensions = new String[extensions.length];
545 for (int i = 0; i < extensions.length; i++) {
546 final String extension = extensions[i];
547 if (CommonUtil.startsWithChar(extension, '.')) {
548 fileExtensions[i] = extension;
549 }
550 else {
551 fileExtensions[i] = "." + extension;
552 }
553 }
554 }
555 }
556
557
558
559
560
561
562 public void setModuleFactory(ModuleFactory moduleFactory) {
563 this.moduleFactory = moduleFactory;
564 }
565
566
567
568
569
570 public void setLocaleCountry(String localeCountry) {
571 this.localeCountry = localeCountry;
572 }
573
574
575
576
577
578 public void setLocaleLanguage(String localeLanguage) {
579 this.localeLanguage = localeLanguage;
580 }
581
582
583
584
585
586
587
588
589 public final void setSeverity(String severity) {
590 this.severity = SeverityLevel.getInstance(severity);
591 }
592
593
594
595
596
597
598
599
600 public final void setClassLoader(ClassLoader classLoader) {
601 this.classLoader = classLoader;
602 }
603
604 @Override
605 public final void setModuleClassLoader(ClassLoader moduleClassLoader) {
606 this.moduleClassLoader = moduleClassLoader;
607 }
608
609
610
611
612
613
614 public void setCharset(String charset)
615 throws UnsupportedEncodingException {
616 if (!Charset.isSupported(charset)) {
617 final String message = "unsupported charset: '" + charset + "'";
618 throw new UnsupportedEncodingException(message);
619 }
620 this.charset = charset;
621 }
622
623
624
625
626
627 public void setHaltOnException(boolean haltOnException) {
628 this.haltOnException = haltOnException;
629 }
630
631
632
633
634
635 public final void setTabWidth(int tabWidth) {
636 this.tabWidth = tabWidth;
637 }
638
639
640
641
642 public void clearCache() {
643 if (cacheFile != null) {
644 cacheFile.reset();
645 }
646 }
647
648 }