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