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