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.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.Map;
32 import java.util.Objects;
33 import java.util.Properties;
34 import java.util.stream.Collectors;
35
36 import org.apache.tools.ant.BuildException;
37 import org.apache.tools.ant.DirectoryScanner;
38 import org.apache.tools.ant.Project;
39 import org.apache.tools.ant.Task;
40 import org.apache.tools.ant.taskdefs.LogOutputStream;
41 import org.apache.tools.ant.types.EnumeratedAttribute;
42 import org.apache.tools.ant.types.FileSet;
43 import org.apache.tools.ant.types.Path;
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<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 File 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(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 Path createClasspath() {
208 return new 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;
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 = CheckstyleAntTask.class.getPackage().getImplementationVersion();
266
267 log("checkstyle version " + version, Project.MSG_VERBOSE);
268
269
270 if (fileName == null
271 && fileSets.isEmpty()
272 && paths.isEmpty()) {
273 throw new BuildException(
274 "Must specify at least one of 'file' or nested 'fileset' or 'path'.",
275 getLocation());
276 }
277 if (config == null) {
278 throw new BuildException("Must specify 'config'.", getLocation());
279 }
280 realExecute(version);
281 }
282 finally {
283 final long endTime = System.currentTimeMillis();
284 log("Total execution took " + (endTime - startTime) + TIME_SUFFIX,
285 Project.MSG_VERBOSE);
286 }
287 }
288
289
290
291
292
293
294 private void realExecute(String checkstyleVersion) {
295
296 RootModule rootModule = null;
297 try {
298 rootModule = createRootModule();
299
300
301 final AuditListener[] listeners = getListeners();
302 for (AuditListener element : listeners) {
303 rootModule.addListener(element);
304 }
305 final SeverityLevelCounter warningCounter =
306 new SeverityLevelCounter(SeverityLevel.WARNING);
307 rootModule.addListener(warningCounter);
308
309 processFiles(rootModule, warningCounter, checkstyleVersion);
310 }
311 finally {
312 if (rootModule != null) {
313 rootModule.destroy();
314 }
315 }
316 }
317
318
319
320
321
322
323
324
325
326
327 private void processFiles(RootModule rootModule, final SeverityLevelCounter warningCounter,
328 final String checkstyleVersion) {
329 final long startTime = System.currentTimeMillis();
330 final List<File> files = getFilesToCheck();
331 final long endTime = System.currentTimeMillis();
332 log("To locate the files took " + (endTime - startTime) + TIME_SUFFIX,
333 Project.MSG_VERBOSE);
334
335 log("Running Checkstyle "
336 + Objects.toString(checkstyleVersion, "")
337 + " on " + files.size()
338 + " files", Project.MSG_INFO);
339 log("Using configuration " + config, Project.MSG_VERBOSE);
340
341 final int numErrs;
342
343 try {
344 final long processingStartTime = System.currentTimeMillis();
345 numErrs = rootModule.process(files);
346 final long processingEndTime = System.currentTimeMillis();
347 log("To process the files took " + (processingEndTime - processingStartTime)
348 + TIME_SUFFIX, Project.MSG_VERBOSE);
349 }
350 catch (CheckstyleException ex) {
351 throw new BuildException("Unable to process files: " + files, ex);
352 }
353 final int numWarnings = warningCounter.getCount();
354 final boolean okStatus = numErrs <= maxErrors && numWarnings <= maxWarnings;
355
356
357 if (!okStatus) {
358 final String failureMsg =
359 "Got " + numErrs + " errors and " + numWarnings
360 + " warnings.";
361 if (failureProperty != null) {
362 getProject().setProperty(failureProperty, failureMsg);
363 }
364
365 if (failOnViolation) {
366 throw new BuildException(failureMsg, getLocation());
367 }
368 }
369 }
370
371
372
373
374
375
376
377 private RootModule createRootModule() {
378 final RootModule rootModule;
379 try {
380 final Properties props = createOverridingProperties();
381 final ThreadModeSettings threadModeSettings =
382 ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE;
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 Configuration configuration = ConfigurationLoader.loadConfiguration(config,
392 new PropertiesExpander(props), ignoredModulesOptions, threadModeSettings);
393
394 final ClassLoader moduleClassLoader =
395 Checker.class.getClassLoader();
396
397 final ModuleFactory factory = new PackageObjectFactory(
398 Checker.class.getPackage().getName() + ".", moduleClassLoader);
399
400 rootModule = (RootModule) factory.createModule(configuration.getName());
401 rootModule.setModuleClassLoader(moduleClassLoader);
402 rootModule.configure(configuration);
403 }
404 catch (final CheckstyleException ex) {
405 throw new BuildException(String.format(Locale.ROOT, "Unable to create Root Module: "
406 + "config {%s}.", config), ex);
407 }
408 return rootModule;
409 }
410
411
412
413
414
415
416
417
418 private Properties createOverridingProperties() {
419 final Properties returnValue = new Properties();
420
421
422 if (properties != null) {
423 try (InputStream inStream = Files.newInputStream(properties.toPath())) {
424 returnValue.load(inStream);
425 }
426 catch (final IOException ex) {
427 throw new BuildException("Error loading Properties file '"
428 + properties + "'", ex, getLocation());
429 }
430 }
431
432
433 final Map<String, Object> antProps = getProject().getProperties();
434 for (Map.Entry<String, Object> entry : antProps.entrySet()) {
435 final String value = String.valueOf(entry.getValue());
436 returnValue.setProperty(entry.getKey(), value);
437 }
438
439
440 for (Property p : overrideProps) {
441 returnValue.setProperty(p.getKey(), p.getValue());
442 }
443
444 return returnValue;
445 }
446
447
448
449
450
451
452
453 private AuditListener[] getListeners() {
454 final int formatterCount = Math.max(1, formatters.size());
455
456 final AuditListener[] listeners = new AuditListener[formatterCount];
457
458
459 try {
460 if (formatters.isEmpty()) {
461 final OutputStream debug = new LogOutputStream(this, Project.MSG_DEBUG);
462 final OutputStream err = new LogOutputStream(this, Project.MSG_ERR);
463 listeners[0] = new DefaultLogger(debug, OutputStreamOptions.CLOSE,
464 err, OutputStreamOptions.CLOSE);
465 }
466 else {
467 for (int i = 0; i < formatterCount; i++) {
468 final Formatter formatter = formatters.get(i);
469 listeners[i] = formatter.createListener(this);
470 }
471 }
472 }
473 catch (IOException ex) {
474 throw new BuildException(String.format(Locale.ROOT, "Unable to create listeners: "
475 + "formatters {%s}.", formatters), ex);
476 }
477 return listeners;
478 }
479
480
481
482
483
484
485 private List<File> getFilesToCheck() {
486 final List<File> allFiles = new ArrayList<>();
487 if (fileName != null) {
488
489
490 log("Adding standalone file for audit", Project.MSG_VERBOSE);
491 allFiles.add(new File(fileName));
492 }
493
494 final List<File> filesFromFileSets = scanFileSets();
495 allFiles.addAll(filesFromFileSets);
496
497 final List<File> filesFromPaths = scanPaths();
498 allFiles.addAll(filesFromPaths);
499
500 return allFiles;
501 }
502
503
504
505
506
507
508 private List<File> scanPaths() {
509 final List<File> allFiles = new ArrayList<>();
510
511 for (int i = 0; i < paths.size(); i++) {
512 final Path currentPath = paths.get(i);
513 final List<File> pathFiles = scanPath(currentPath, i + 1);
514 allFiles.addAll(pathFiles);
515 }
516
517 return allFiles;
518 }
519
520
521
522
523
524
525
526
527 private List<File> scanPath(Path path, int pathIndex) {
528 final String[] resources = path.list();
529 log(pathIndex + ") Scanning path " + path, Project.MSG_VERBOSE);
530 final List<File> allFiles = new ArrayList<>();
531 int concreteFilesCount = 0;
532
533 for (String resource : resources) {
534 final File file = new File(resource);
535 if (file.isFile()) {
536 concreteFilesCount++;
537 allFiles.add(file);
538 }
539 else {
540 final DirectoryScanner scanner = new DirectoryScanner();
541 scanner.setBasedir(file);
542 scanner.scan();
543 final List<File> scannedFiles = retrieveAllScannedFiles(scanner, pathIndex);
544 allFiles.addAll(scannedFiles);
545 }
546 }
547
548 if (concreteFilesCount > 0) {
549 log(String.format(Locale.ROOT, "%d) Adding %d files from path %s",
550 pathIndex, concreteFilesCount, path), Project.MSG_VERBOSE);
551 }
552
553 return allFiles;
554 }
555
556
557
558
559
560
561 protected List<File> scanFileSets() {
562 final List<File> allFiles = new ArrayList<>();
563
564 for (int i = 0; i < fileSets.size(); i++) {
565 final FileSet fileSet = fileSets.get(i);
566 final DirectoryScanner scanner = fileSet.getDirectoryScanner(getProject());
567 final List<File> scannedFiles = retrieveAllScannedFiles(scanner, i);
568 allFiles.addAll(scannedFiles);
569 }
570
571 return allFiles;
572 }
573
574
575
576
577
578
579
580
581
582 private List<File> retrieveAllScannedFiles(DirectoryScanner scanner, int logIndex) {
583 final String[] fileNames = scanner.getIncludedFiles();
584 log(String.format(Locale.ROOT, "%d) Adding %d files from directory %s",
585 logIndex, fileNames.length, scanner.getBasedir()), Project.MSG_VERBOSE);
586
587 return Arrays.stream(fileNames)
588 .map(name -> scanner.getBasedir() + File.separator + name)
589 .map(File::new)
590 .collect(Collectors.toUnmodifiableList());
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 }