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