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 = CommonUtil.relativizePath(basedir, 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 = CommonUtil.relativizePath(basedir, 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 = CommonUtil.relativizePath(basedir, 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 = CommonUtil.relativizePath(basedir, fileName);
431         final AuditEvent event = new AuditEvent(this, stripped);
432         for (final AuditListener listener : listeners) {
433             listener.fileFinished(event);
434         }
435     }
436 
437     
438 
439 
440 
441 
442 
443     @Override
444     protected void finishLocalSetup() throws CheckstyleException {
445         final Locale locale = new Locale(localeLanguage, localeCountry);
446         LocalizedMessage.setLocale(locale);
447 
448         if (moduleFactory == null) {
449             if (moduleClassLoader == null) {
450                 throw new CheckstyleException(getLocalizedMessage("Checker.finishLocalSetup"));
451             }
452 
453             final Set<String> packageNames = PackageNamesLoader
454                     .getPackageNames(moduleClassLoader);
455             moduleFactory = new PackageObjectFactory(packageNames,
456                     moduleClassLoader);
457         }
458 
459         final DefaultContext context = new DefaultContext();
460         context.add("charset", charset);
461         context.add("moduleFactory", moduleFactory);
462         context.add("severity", severity.getName());
463         context.add("basedir", basedir);
464         context.add("tabWidth", String.valueOf(tabWidth));
465         childContext = context;
466     }
467 
468     
469 
470 
471 
472 
473 
474     @Override
475     protected void setupChild(Configuration childConf)
476             throws CheckstyleException {
477         final String name = childConf.getName();
478         final Object child;
479 
480         try {
481             child = moduleFactory.createModule(name);
482 
483             if (child instanceof AbstractAutomaticBean bean) {
484                 bean.contextualize(childContext);
485                 bean.configure(childConf);
486             }
487         }
488         catch (final CheckstyleException exc) {
489             throw new CheckstyleException(
490                     getLocalizedMessage("Checker.setupChildModule", name, exc.getMessage()), exc);
491         }
492         if (child instanceof FileSetCheck fsc) {
493             fsc.init();
494             addFileSetCheck(fsc);
495         }
496         else if (child instanceof BeforeExecutionFileFilter filter) {
497             addBeforeExecutionFileFilter(filter);
498         }
499         else if (child instanceof Filter filter) {
500             addFilter(filter);
501         }
502         else if (child instanceof AuditListener listener) {
503             addListener(listener);
504         }
505         else {
506             throw new CheckstyleException(
507                     getLocalizedMessage("Checker.setupChildNotAllowed", name));
508         }
509     }
510 
511     
512 
513 
514 
515 
516 
517     public void addFileSetCheck(FileSetCheck fileSetCheck) {
518         fileSetCheck.setMessageDispatcher(this);
519         fileSetChecks.add(fileSetCheck);
520     }
521 
522     
523 
524 
525 
526 
527     public void addBeforeExecutionFileFilter(BeforeExecutionFileFilter filter) {
528         beforeExecutionFileFilters.addBeforeExecutionFileFilter(filter);
529     }
530 
531     
532 
533 
534 
535 
536     public void addFilter(Filter filter) {
537         filters.addFilter(filter);
538     }
539 
540     @Override
541     public final void addListener(AuditListener listener) {
542         listeners.add(listener);
543     }
544 
545     
546 
547 
548 
549 
550 
551 
552     public final void setFileExtensions(String... extensions) {
553         if (extensions != null) {
554             fileExtensions = new String[extensions.length];
555             for (int i = 0; i < extensions.length; i++) {
556                 final String extension = extensions[i];
557                 if (extension.startsWith(EXTENSION_SEPARATOR)) {
558                     fileExtensions[i] = extension;
559                 }
560                 else {
561                     fileExtensions[i] = EXTENSION_SEPARATOR + extension;
562                 }
563             }
564         }
565     }
566 
567     
568 
569 
570 
571 
572     public void setModuleFactory(ModuleFactory moduleFactory) {
573         this.moduleFactory = moduleFactory;
574     }
575 
576     
577 
578 
579 
580 
581     public void setLocaleCountry(String localeCountry) {
582         this.localeCountry = localeCountry;
583     }
584 
585     
586 
587 
588 
589 
590     public void setLocaleLanguage(String localeLanguage) {
591         this.localeLanguage = localeLanguage;
592     }
593 
594     
595 
596 
597 
598 
599 
600 
601     public final void setSeverity(String severity) {
602         this.severity = SeverityLevel.getInstance(severity);
603     }
604 
605     @Override
606     public final void setModuleClassLoader(ClassLoader moduleClassLoader) {
607         this.moduleClassLoader = moduleClassLoader;
608     }
609 
610     
611 
612 
613 
614 
615 
616     public void setCharset(String charset)
617             throws UnsupportedEncodingException {
618         if (!Charset.isSupported(charset)) {
619             throw new UnsupportedEncodingException(
620                     getLocalizedMessage("Checker.setCharset", charset));
621         }
622         this.charset = charset;
623     }
624 
625     
626 
627 
628 
629 
630     public void setHaltOnException(boolean haltOnException) {
631         this.haltOnException = haltOnException;
632     }
633 
634     
635 
636 
637 
638 
639     public final void setTabWidth(int tabWidth) {
640         this.tabWidth = tabWidth;
641     }
642 
643     
644 
645 
646     public void clearCache() {
647         if (cacheFile != null) {
648             cacheFile.reset();
649         }
650     }
651 
652     
653 
654 
655 
656 
657 
658 
659     private String getLocalizedMessage(String messageKey, Object... args) {
660         final LocalizedMessage localizedMessage = new LocalizedMessage(
661             Definitions.CHECKSTYLE_BUNDLE, getClass(),
662                     messageKey, args);
663 
664         return localizedMessage.getMessage();
665     }
666 
667 }