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 exc) {
188 throw new IllegalStateException(
189 getLocalizedMessage("Checker.cacheFilesException"), exc);
190 }
191 }
192 }
193
194
195
196
197
198
199 public void removeListener(AuditListener listener) {
200 listeners.remove(listener);
201 }
202
203
204
205
206
207
208 public void setBasedir(String basedir) {
209 this.basedir = basedir;
210 }
211
212 @Override
213 public int process(List<File> files) throws CheckstyleException {
214 if (cacheFile != null) {
215 cacheFile.putExternalResources(getExternalResourceLocations());
216 }
217
218
219 fireAuditStarted();
220 for (final FileSetCheck fsc : fileSetChecks) {
221 fsc.beginProcessing(charset);
222 }
223
224 final List<File> targetFiles = files.stream()
225 .filter(file -> CommonUtil.matchesFileExtension(file, fileExtensions))
226 .toList();
227 processFiles(targetFiles);
228
229
230
231 fileSetChecks.forEach(FileSetCheck::finishProcessing);
232
233
234 fileSetChecks.forEach(FileSetCheck::destroy);
235
236 final int errorCount = counter.getCount();
237 fireAuditFinished();
238 return errorCount;
239 }
240
241
242
243
244
245
246
247
248 private Set<String> getExternalResourceLocations() {
249 return Stream.concat(fileSetChecks.stream(), filters.getFilters().stream())
250 .filter(ExternalResourceHolder.class::isInstance)
251 .flatMap(resource -> {
252 return ((ExternalResourceHolder) resource)
253 .getExternalResourceLocations().stream();
254 })
255 .collect(Collectors.toUnmodifiableSet());
256 }
257
258
259 private void fireAuditStarted() {
260 final AuditEvent event = new AuditEvent(this);
261 for (final AuditListener listener : listeners) {
262 listener.auditStarted(event);
263 }
264 }
265
266
267 private void fireAuditFinished() {
268 final AuditEvent event = new AuditEvent(this);
269 for (final AuditListener listener : listeners) {
270 listener.auditFinished(event);
271 }
272 }
273
274
275
276
277
278
279
280
281
282
283
284
285 private void processFiles(List<File> files) throws CheckstyleException {
286 for (final File file : files) {
287 String fileName = null;
288 final String filePath = file.getPath();
289 try {
290 fileName = file.getAbsolutePath();
291 final long timestamp = file.lastModified();
292 if (cacheFile != null && cacheFile.isInCache(fileName, timestamp)
293 || !acceptFileStarted(fileName)) {
294 continue;
295 }
296 if (cacheFile != null) {
297 cacheFile.put(fileName, timestamp);
298 }
299 fireFileStarted(fileName);
300 final SortedSet<Violation> fileMessages = processFile(file);
301 fireErrors(fileName, fileMessages);
302 fireFileFinished(fileName);
303 }
304
305
306 catch (Exception exc) {
307 if (fileName != null && cacheFile != null) {
308 cacheFile.remove(fileName);
309 }
310
311
312 throw new CheckstyleException(
313 getLocalizedMessage("Checker.processFilesException", filePath), exc);
314 }
315 catch (Error error) {
316 if (fileName != null && cacheFile != null) {
317 cacheFile.remove(fileName);
318 }
319
320
321 throw new Error(getLocalizedMessage("Checker.error", filePath), error);
322 }
323 }
324 }
325
326
327
328
329
330
331
332
333
334
335
336 private SortedSet<Violation> processFile(File file) throws CheckstyleException {
337 final SortedSet<Violation> fileMessages = new TreeSet<>();
338 try {
339 final FileText theText = new FileText(file.getAbsoluteFile(), charset);
340 for (final FileSetCheck fsc : fileSetChecks) {
341 fileMessages.addAll(fsc.process(file, theText));
342 }
343 }
344 catch (final IOException ioe) {
345 log.debug("IOException occurred.", ioe);
346 fileMessages.add(new Violation(1,
347 Definitions.CHECKSTYLE_BUNDLE, EXCEPTION_MSG,
348 new String[] {ioe.getMessage()}, null, getClass(), null));
349 }
350
351 catch (Exception exc) {
352 if (haltOnException) {
353 throw exc;
354 }
355
356 log.debug("Exception occurred.", exc);
357
358 final StringWriter sw = new StringWriter();
359 final PrintWriter pw = new PrintWriter(sw, true);
360
361 exc.printStackTrace(pw);
362
363 fileMessages.add(new Violation(1,
364 Definitions.CHECKSTYLE_BUNDLE, EXCEPTION_MSG,
365 new String[] {sw.getBuffer().toString()},
366 null, getClass(), null));
367 }
368 return fileMessages;
369 }
370
371
372
373
374
375
376
377
378 private boolean acceptFileStarted(String fileName) {
379 final String stripped = relativizePathWithCatch(fileName);
380 return beforeExecutionFileFilters.accept(stripped);
381 }
382
383
384
385
386
387
388
389 @Override
390 public void fireFileStarted(String fileName) {
391 final String stripped = relativizePathWithCatch(fileName);
392 final AuditEvent event = new AuditEvent(this, stripped);
393 for (final AuditListener listener : listeners) {
394 listener.fileStarted(event);
395 }
396 }
397
398
399
400
401
402
403
404 @Override
405 public void fireErrors(String fileName, SortedSet<Violation> errors) {
406 final String stripped = relativizePathWithCatch(fileName);
407 boolean hasNonFilteredViolations = false;
408 for (final Violation element : errors) {
409 final AuditEvent event = new AuditEvent(this, stripped, element);
410 if (filters.accept(event)) {
411 hasNonFilteredViolations = true;
412 for (final AuditListener listener : listeners) {
413 listener.addError(event);
414 }
415 }
416 }
417 if (hasNonFilteredViolations && cacheFile != null) {
418 cacheFile.remove(fileName);
419 }
420 }
421
422
423
424
425
426
427
428 @Override
429 public void fireFileFinished(String fileName) {
430 final String stripped = relativizePathWithCatch(fileName);
431 final AuditEvent event = new AuditEvent(this, stripped);
432 for (final AuditListener listener : listeners) {
433 listener.fileFinished(event);
434 }
435 }
436
437 @Override
438 protected void finishLocalSetup() throws CheckstyleException {
439 final Locale locale = Locale.of(localeLanguage, localeCountry);
440 LocalizedMessage.setLocale(locale);
441
442 if (moduleFactory == null) {
443 if (moduleClassLoader == null) {
444 throw new CheckstyleException(getLocalizedMessage("Checker.finishLocalSetup"));
445 }
446
447 final Set<String> packageNames = PackageNamesLoader
448 .getPackageNames(moduleClassLoader);
449 moduleFactory = new PackageObjectFactory(packageNames,
450 moduleClassLoader);
451 }
452
453 final DefaultContext context = new DefaultContext();
454 context.add("charset", charset);
455 context.add("moduleFactory", moduleFactory);
456 context.add("severity", severity.getName());
457 context.add("basedir", basedir);
458 context.add("tabWidth", String.valueOf(tabWidth));
459 childContext = context;
460 }
461
462
463
464
465 @Override
466 protected void setupChild(Configuration childConf)
467 throws CheckstyleException {
468 final String name = childConf.getName();
469 final Object child;
470
471 try {
472 child = moduleFactory.createModule(name);
473
474 if (child instanceof AbstractAutomaticBean bean) {
475 bean.contextualize(childContext);
476 bean.configure(childConf);
477 }
478 }
479 catch (final CheckstyleException exc) {
480 throw new CheckstyleException(
481 getLocalizedMessage("Checker.setupChildModule", name, exc.getMessage()), exc);
482 }
483 switch (child) {
484 case FileSetCheck fsc -> {
485 fsc.init();
486 addFileSetCheck(fsc);
487 }
488 case BeforeExecutionFileFilter filter -> addBeforeExecutionFileFilter(filter);
489 case Filter filter -> addFilter(filter);
490 case AuditListener listener -> addListener(listener);
491 case null, default -> throw new CheckstyleException(
492 getLocalizedMessage("Checker.setupChildNotAllowed", name));
493 }
494 }
495
496
497
498
499
500
501
502 public void addFileSetCheck(FileSetCheck fileSetCheck) {
503 fileSetCheck.setMessageDispatcher(this);
504 fileSetChecks.add(fileSetCheck);
505 }
506
507
508
509
510
511
512 public void addBeforeExecutionFileFilter(BeforeExecutionFileFilter filter) {
513 beforeExecutionFileFilters.addBeforeExecutionFileFilter(filter);
514 }
515
516
517
518
519
520
521 public void addFilter(Filter filter) {
522 filters.addFilter(filter);
523 }
524
525 @Override
526 public final void addListener(AuditListener listener) {
527 listeners.add(listener);
528 }
529
530
531
532
533
534
535
536
537 public final void setFileExtensions(String... extensions) {
538 if (extensions != null) {
539 fileExtensions = new String[extensions.length];
540 for (int i = 0; i < extensions.length; i++) {
541 final String extension = extensions[i];
542 if (extension.startsWith(EXTENSION_SEPARATOR)) {
543 fileExtensions[i] = extension;
544 }
545 else {
546 fileExtensions[i] = EXTENSION_SEPARATOR + extension;
547 }
548 }
549 }
550 }
551
552
553
554
555
556
557 public void setModuleFactory(ModuleFactory moduleFactory) {
558 this.moduleFactory = moduleFactory;
559 }
560
561
562
563
564
565
566 public void setLocaleCountry(String localeCountry) {
567 this.localeCountry = localeCountry;
568 }
569
570
571
572
573
574
575 public void setLocaleLanguage(String localeLanguage) {
576 this.localeLanguage = localeLanguage;
577 }
578
579
580
581
582
583
584
585
586 public final void setSeverity(String severity) {
587 this.severity = SeverityLevel.getInstance(severity);
588 }
589
590 @Override
591 public final void setModuleClassLoader(ClassLoader moduleClassLoader) {
592 this.moduleClassLoader = moduleClassLoader;
593 }
594
595
596
597
598
599
600
601 public void setCharset(String charset)
602 throws UnsupportedEncodingException {
603 if (!Charset.isSupported(charset)) {
604 throw new UnsupportedEncodingException(
605 getLocalizedMessage("Checker.setCharset", charset));
606 }
607 this.charset = charset;
608 }
609
610
611
612
613
614
615 public void setHaltOnException(boolean haltOnException) {
616 this.haltOnException = haltOnException;
617 }
618
619
620
621
622
623
624 public final void setTabWidth(int tabWidth) {
625 this.tabWidth = tabWidth;
626 }
627
628
629
630
631 public void clearCache() {
632 if (cacheFile != null) {
633 cacheFile.reset();
634 }
635 }
636
637
638
639
640
641
642
643
644 private String getLocalizedMessage(String messageKey, Object... args) {
645 final LocalizedMessage localizedMessage = new LocalizedMessage(
646 Definitions.CHECKSTYLE_BUNDLE, getClass(),
647 messageKey, args);
648
649 return localizedMessage.getMessage();
650 }
651
652
653
654
655
656
657
658
659 private String relativizePathWithCatch(String fileName) {
660 try {
661 return CommonUtil.relativizePath(basedir, fileName);
662 }
663
664 catch (Exception exception) {
665 throw new IllegalStateException(
666 getLocalizedMessage("general.relativizePath",
667 fileName, basedir), exception);
668 }
669 }
670
671 }