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 }