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.ant;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.nio.file.Files;
27  import java.nio.file.Path;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  import java.util.Objects;
34  import java.util.Properties;
35  
36  import org.apache.tools.ant.BuildException;
37  import org.apache.tools.ant.DirectoryScanner;
38  import org.apache.tools.ant.FileScanner;
39  import org.apache.tools.ant.Project;
40  import org.apache.tools.ant.Task;
41  import org.apache.tools.ant.taskdefs.LogOutputStream;
42  import org.apache.tools.ant.types.EnumeratedAttribute;
43  import org.apache.tools.ant.types.FileSet;
44  
45  import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean.OutputStreamOptions;
46  import com.puppycrawl.tools.checkstyle.Checker;
47  import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
48  import com.puppycrawl.tools.checkstyle.DefaultLogger;
49  import com.puppycrawl.tools.checkstyle.ModuleFactory;
50  import com.puppycrawl.tools.checkstyle.PackageObjectFactory;
51  import com.puppycrawl.tools.checkstyle.PropertiesExpander;
52  import com.puppycrawl.tools.checkstyle.SarifLogger;
53  import com.puppycrawl.tools.checkstyle.ThreadModeSettings;
54  import com.puppycrawl.tools.checkstyle.XMLLogger;
55  import com.puppycrawl.tools.checkstyle.api.AuditListener;
56  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
57  import com.puppycrawl.tools.checkstyle.api.Configuration;
58  import com.puppycrawl.tools.checkstyle.api.RootModule;
59  import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
60  import com.puppycrawl.tools.checkstyle.api.SeverityLevelCounter;
61  
62  
63  
64  
65  
66  public class CheckstyleAntTask extends Task {
67  
68      
69      private static final String E_XML = "xml";
70      
71      private static final String E_PLAIN = "plain";
72      
73      private static final String E_SARIF = "sarif";
74  
75      
76      private static final String TIME_SUFFIX = " ms.";
77  
78      
79      private final List<org.apache.tools.ant.types.Path> paths = new ArrayList<>();
80  
81      
82      private final List<FileSet> fileSets = new ArrayList<>();
83  
84      
85      private final List<Formatter> formatters = new ArrayList<>();
86  
87      
88      private final List<Property> overrideProps = new ArrayList<>();
89  
90      
91      private String fileName;
92  
93      
94      private String config;
95  
96      
97      private boolean failOnViolation = true;
98  
99      
100     private String failureProperty;
101 
102     
103     private Path properties;
104 
105     
106     private int maxErrors;
107 
108     
109     private int maxWarnings = Integer.MAX_VALUE;
110 
111     
112 
113 
114 
115 
116     private boolean executeIgnoredModules;
117 
118     
119     
120     
121 
122     
123 
124 
125 
126 
127 
128 
129     public void setFailureProperty(String propertyName) {
130         failureProperty = propertyName;
131     }
132 
133     
134 
135 
136 
137 
138     public void setFailOnViolation(boolean fail) {
139         failOnViolation = fail;
140     }
141 
142     
143 
144 
145 
146 
147     public void setMaxErrors(int maxErrors) {
148         this.maxErrors = maxErrors;
149     }
150 
151     
152 
153 
154 
155 
156 
157     public void setMaxWarnings(int maxWarnings) {
158         this.maxWarnings = maxWarnings;
159     }
160 
161     
162 
163 
164 
165 
166     public void addPath(org.apache.tools.ant.types.Path path) {
167         paths.add(path);
168     }
169 
170     
171 
172 
173 
174 
175     public void addFileset(FileSet fileSet) {
176         fileSets.add(fileSet);
177     }
178 
179     
180 
181 
182 
183 
184     public void addFormatter(Formatter formatter) {
185         formatters.add(formatter);
186     }
187 
188     
189 
190 
191 
192 
193     public void addProperty(Property property) {
194         overrideProps.add(property);
195     }
196 
197     
198 
199 
200 
201 
202 
203 
204 
205 
206     @Deprecated(since = "10.7.0")
207     public org.apache.tools.ant.types.Path createClasspath() {
208         return new org.apache.tools.ant.types.Path(getProject());
209     }
210 
211     
212 
213 
214 
215 
216     public void setFile(File file) {
217         fileName = file.getAbsolutePath();
218     }
219 
220     
221 
222 
223 
224 
225 
226     public void setConfig(String configuration) {
227         if (config != null) {
228             throw new BuildException("Attribute 'config' has already been set");
229         }
230         config = configuration;
231     }
232 
233     
234 
235 
236 
237 
238     public void setExecuteIgnoredModules(boolean omit) {
239         executeIgnoredModules = omit;
240     }
241 
242     
243     
244     
245 
246     
247 
248 
249 
250 
251 
252     public void setProperties(File props) {
253         properties = props.toPath();
254     }
255 
256     
257     
258     
259 
260     @Override
261     public void execute() {
262         final long startTime = System.currentTimeMillis();
263 
264         try {
265             final String version = Objects.toString(
266                     CheckstyleAntTask.class.getPackage().getImplementationVersion(),
267                     "");
268 
269             log("checkstyle version " + version, Project.MSG_VERBOSE);
270 
271             
272             if (fileName == null
273                     && fileSets.isEmpty()
274                     && paths.isEmpty()) {
275                 throw new BuildException(
276                         "Must specify at least one of 'file' or nested 'fileset' or 'path'.",
277                         getLocation());
278             }
279             if (config == null) {
280                 throw new BuildException("Must specify 'config'.", getLocation());
281             }
282             realExecute(version);
283         }
284         finally {
285             final long endTime = System.currentTimeMillis();
286             log("Total execution took " + (endTime - startTime) + TIME_SUFFIX,
287                 Project.MSG_VERBOSE);
288         }
289     }
290 
291     
292 
293 
294 
295 
296     private void realExecute(String checkstyleVersion) {
297         
298         RootModule rootModule = null;
299         try {
300             rootModule = createRootModule();
301 
302             
303             final AuditListener[] listeners = getListeners();
304             for (AuditListener element : listeners) {
305                 rootModule.addListener(element);
306             }
307             final SeverityLevelCounter warningCounter =
308                 new SeverityLevelCounter(SeverityLevel.WARNING);
309             rootModule.addListener(warningCounter);
310 
311             processFiles(rootModule, warningCounter, checkstyleVersion);
312         }
313         finally {
314             if (rootModule != null) {
315                 rootModule.destroy();
316             }
317         }
318     }
319 
320     
321 
322 
323 
324 
325 
326 
327 
328 
329     private void processFiles(RootModule rootModule, final SeverityLevelCounter warningCounter,
330             final String checkstyleVersion) {
331         final long startTime = System.currentTimeMillis();
332         final List<File> files = getFilesToCheck();
333         final long endTime = System.currentTimeMillis();
334         log("To locate the files took " + (endTime - startTime) + TIME_SUFFIX,
335             Project.MSG_VERBOSE);
336 
337         log("Running Checkstyle "
338                 + checkstyleVersion
339                 + " on " + files.size()
340                 + " files", Project.MSG_INFO);
341         log("Using configuration " + config, Project.MSG_VERBOSE);
342 
343         final int numErrs;
344 
345         try {
346             final long processingStartTime = System.currentTimeMillis();
347             numErrs = rootModule.process(files);
348             final long processingEndTime = System.currentTimeMillis();
349             log("To process the files took " + (processingEndTime - processingStartTime)
350                 + TIME_SUFFIX, Project.MSG_VERBOSE);
351         }
352         catch (CheckstyleException exc) {
353             throw new BuildException("Unable to process files: " + files, exc);
354         }
355         final int numWarnings = warningCounter.getCount();
356         final boolean okStatus = numErrs <= maxErrors && numWarnings <= maxWarnings;
357 
358         
359         if (!okStatus) {
360             final String failureMsg =
361                     "Got " + numErrs + " errors (max allowed: " + maxErrors + ") and "
362                             + numWarnings + " warnings.";
363             if (failureProperty != null) {
364                 getProject().setProperty(failureProperty, failureMsg);
365             }
366 
367             if (failOnViolation) {
368                 throw new BuildException(failureMsg, getLocation());
369             }
370         }
371     }
372 
373     
374 
375 
376 
377 
378 
379     private RootModule createRootModule() {
380         final RootModule rootModule;
381         try {
382             final Properties props = createOverridingProperties();
383             final ConfigurationLoader.IgnoredModulesOptions ignoredModulesOptions;
384             if (executeIgnoredModules) {
385                 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.EXECUTE;
386             }
387             else {
388                 ignoredModulesOptions = ConfigurationLoader.IgnoredModulesOptions.OMIT;
389             }
390 
391             final ThreadModeSettings threadModeSettings =
392                     ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE;
393             final Configuration configuration = ConfigurationLoader.loadConfiguration(config,
394                     new PropertiesExpander(props), ignoredModulesOptions, threadModeSettings);
395 
396             final ClassLoader moduleClassLoader =
397                 Checker.class.getClassLoader();
398 
399             final ModuleFactory factory = new PackageObjectFactory(
400                     Checker.class.getPackage().getName() + ".", moduleClassLoader);
401 
402             rootModule = (RootModule) factory.createModule(configuration.getName());
403             rootModule.setModuleClassLoader(moduleClassLoader);
404             rootModule.configure(configuration);
405         }
406         catch (final CheckstyleException exc) {
407             throw new BuildException(String.format(Locale.ROOT, "Unable to create Root Module: "
408                     + "config {%s}.", config), exc);
409         }
410         return rootModule;
411     }
412 
413     
414 
415 
416 
417 
418 
419 
420     private Properties createOverridingProperties() {
421         final Properties returnValue = new Properties();
422 
423         
424         if (properties != null) {
425             try (InputStream inStream = Files.newInputStream(properties)) {
426                 returnValue.load(inStream);
427             }
428             catch (final IOException exc) {
429                 throw new BuildException("Error loading Properties file '"
430                         + properties + "'", exc, getLocation());
431             }
432         }
433 
434         
435         final Map<String, Object> antProps = getProject().getProperties();
436         for (Map.Entry<String, Object> entry : antProps.entrySet()) {
437             final String value = String.valueOf(entry.getValue());
438             returnValue.setProperty(entry.getKey(), value);
439         }
440 
441         
442         for (Property p : overrideProps) {
443             returnValue.setProperty(p.getKey(), p.getValue());
444         }
445 
446         return returnValue;
447     }
448 
449     
450 
451 
452 
453 
454 
455     private AuditListener[] getListeners() {
456         final int formatterCount = Math.max(1, formatters.size());
457 
458         final AuditListener[] listeners = new AuditListener[formatterCount];
459 
460         
461         try {
462             if (formatters.isEmpty()) {
463                 final OutputStream debug = new LogOutputStream(this, Project.MSG_DEBUG);
464                 final OutputStream err = new LogOutputStream(this, Project.MSG_ERR);
465                 listeners[0] = new DefaultLogger(debug, OutputStreamOptions.CLOSE,
466                         err, OutputStreamOptions.CLOSE);
467             }
468             else {
469                 for (int i = 0; i < formatterCount; i++) {
470                     final Formatter formatter = formatters.get(i);
471                     listeners[i] = formatter.createListener(this);
472                 }
473             }
474         }
475         catch (IOException exc) {
476             throw new BuildException(String.format(Locale.ROOT, "Unable to create listeners: "
477                     + "formatters {%s}.", formatters), exc);
478         }
479         return listeners;
480     }
481 
482     
483 
484 
485 
486 
487     private List<File> getFilesToCheck() {
488         final List<File> allFiles = new ArrayList<>();
489         if (fileName != null) {
490             
491             
492             log("Adding standalone file for audit", Project.MSG_VERBOSE);
493             allFiles.add(Path.of(fileName).toFile());
494         }
495 
496         final List<File> filesFromFileSets = scanFileSets();
497         allFiles.addAll(filesFromFileSets);
498 
499         final List<Path> filesFromPaths = scanPaths();
500         allFiles.addAll(filesFromPaths.stream()
501             .map(Path::toFile)
502             .toList());
503 
504         return allFiles;
505     }
506 
507     
508 
509 
510 
511 
512     private List<Path> scanPaths() {
513         final List<Path> allFiles = new ArrayList<>();
514 
515         for (int i = 0; i < paths.size(); i++) {
516             final org.apache.tools.ant.types.Path currentPath = paths.get(i);
517             final List<Path> pathFiles = scanPath(currentPath, i + 1);
518             allFiles.addAll(pathFiles);
519         }
520 
521         return allFiles;
522     }
523 
524     
525 
526 
527 
528 
529 
530 
531     private List<Path> scanPath(org.apache.tools.ant.types.Path path, int pathIndex) {
532         final String[] resources = path.list();
533         log(pathIndex + ") Scanning path " + path, Project.MSG_VERBOSE);
534         final List<Path> allFiles = new ArrayList<>();
535         int concreteFilesCount = 0;
536 
537         for (String resource : resources) {
538             final Path file = Path.of(resource);
539             if (Files.isRegularFile(file)) {
540                 concreteFilesCount++;
541                 allFiles.add(file);
542             }
543             else {
544                 final DirectoryScanner scanner = new DirectoryScanner();
545                 scanner.setBasedir(file.toFile());
546                 scanner.scan();
547                 final List<Path> scannedFiles = retrieveAllScannedFiles(scanner, pathIndex);
548                 allFiles.addAll(scannedFiles);
549             }
550         }
551 
552         if (concreteFilesCount > 0) {
553             log(String.format(Locale.ROOT, "%d) Adding %d files from path %s",
554                 pathIndex, concreteFilesCount, path), Project.MSG_VERBOSE);
555         }
556 
557         return allFiles;
558     }
559 
560     
561 
562 
563 
564 
565     protected List<File> scanFileSets() {
566         final List<Path> allFiles = new ArrayList<>();
567 
568         for (int i = 0; i < fileSets.size(); i++) {
569             final FileSet fileSet = fileSets.get(i);
570             final DirectoryScanner scanner = fileSet.getDirectoryScanner(getProject());
571             final List<Path> scannedFiles = retrieveAllScannedFiles(scanner, i);
572             allFiles.addAll(scannedFiles);
573         }
574 
575         return allFiles.stream()
576             .map(Path::toFile)
577             .toList();
578     }
579 
580     
581 
582 
583 
584 
585 
586 
587 
588     private List<Path> retrieveAllScannedFiles(FileScanner scanner, int logIndex) {
589         final String[] fileNames = scanner.getIncludedFiles();
590         log(String.format(Locale.ROOT, "%d) Adding %d files from directory %s",
591                 logIndex, fileNames.length, scanner.getBasedir()), Project.MSG_VERBOSE);
592 
593         return Arrays.stream(fileNames)
594           .map(scanner.getBasedir().toPath()::resolve)
595           .toList();
596     }
597 
598     
599 
600 
601     public static class FormatterType extends EnumeratedAttribute {
602 
603         
604         private static final String[] VALUES = {E_XML, E_PLAIN, E_SARIF};
605 
606         @Override
607         public String[] getValues() {
608             return VALUES.clone();
609         }
610 
611     }
612 
613     
614 
615 
616     public static class Formatter {
617 
618         
619         private FormatterType type;
620         
621         private File toFile;
622         
623         private boolean useFile = true;
624 
625         
626 
627 
628 
629 
630         public void setType(FormatterType type) {
631             this.type = type;
632         }
633 
634         
635 
636 
637 
638 
639         public void setTofile(File destination) {
640             toFile = destination;
641         }
642 
643         
644 
645 
646 
647 
648         public void setUseFile(boolean use) {
649             useFile = use;
650         }
651 
652         
653 
654 
655 
656 
657 
658 
659         public AuditListener createListener(Task task) throws IOException {
660             final AuditListener listener;
661             if (type != null
662                     && E_XML.equals(type.getValue())) {
663                 listener = createXmlLogger(task);
664             }
665             else if (type != null
666                     && E_SARIF.equals(type.getValue())) {
667                 listener = createSarifLogger(task);
668             }
669             else {
670                 listener = createDefaultLogger(task);
671             }
672             return listener;
673         }
674 
675         
676 
677 
678 
679 
680 
681 
682         private AuditListener createSarifLogger(Task task) throws IOException {
683             final AuditListener sarifLogger;
684             if (toFile == null || !useFile) {
685                 sarifLogger = new SarifLogger(new LogOutputStream(task, Project.MSG_INFO),
686                         OutputStreamOptions.CLOSE);
687             }
688             else {
689                 sarifLogger = new SarifLogger(Files.newOutputStream(toFile.toPath()),
690                         OutputStreamOptions.CLOSE);
691             }
692             return sarifLogger;
693         }
694 
695         
696 
697 
698 
699 
700 
701 
702         private AuditListener createDefaultLogger(Task task)
703                 throws IOException {
704             final AuditListener defaultLogger;
705             if (toFile == null || !useFile) {
706                 defaultLogger = new DefaultLogger(
707                     new LogOutputStream(task, Project.MSG_DEBUG),
708                         OutputStreamOptions.CLOSE,
709                         new LogOutputStream(task, Project.MSG_ERR),
710                         OutputStreamOptions.CLOSE
711                 );
712             }
713             else {
714                 final OutputStream infoStream = Files.newOutputStream(toFile.toPath());
715                 defaultLogger =
716                         new DefaultLogger(infoStream, OutputStreamOptions.CLOSE,
717                                 infoStream, OutputStreamOptions.NONE);
718             }
719             return defaultLogger;
720         }
721 
722         
723 
724 
725 
726 
727 
728 
729         private AuditListener createXmlLogger(Task task) throws IOException {
730             final AuditListener xmlLogger;
731             if (toFile == null || !useFile) {
732                 xmlLogger = new XMLLogger(new LogOutputStream(task, Project.MSG_INFO),
733                         OutputStreamOptions.CLOSE);
734             }
735             else {
736                 xmlLogger = new XMLLogger(Files.newOutputStream(toFile.toPath()),
737                         OutputStreamOptions.CLOSE);
738             }
739             return xmlLogger;
740         }
741 
742     }
743 
744     
745 
746 
747     public static class Property {
748 
749         
750         private String key;
751         
752         private String value;
753 
754         
755 
756 
757 
758 
759         public String getKey() {
760             return key;
761         }
762 
763         
764 
765 
766 
767 
768         public void setKey(String key) {
769             this.key = key;
770         }
771 
772         
773 
774 
775 
776 
777         public String getValue() {
778             return value;
779         }
780 
781         
782 
783 
784 
785 
786         public void setValue(String value) {
787             this.value = value;
788         }
789 
790         
791 
792 
793 
794 
795         public void setFile(File file) {
796             value = file.getAbsolutePath();
797         }
798 
799     }
800 
801 }