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