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