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.AntClassLoader;
37 import org.apache.tools.ant.BuildException;
38 import org.apache.tools.ant.DirectoryScanner;
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 import org.apache.tools.ant.types.Path;
45 import org.apache.tools.ant.types.Reference;
46
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.ThreadModeSettings;
54 import com.puppycrawl.tools.checkstyle.XMLLogger;
55 import com.puppycrawl.tools.checkstyle.api.AuditListener;
56 import com.puppycrawl.tools.checkstyle.api.AutomaticBean;
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
68 public class CheckstyleAntTask extends Task {
69
70
71 private static final String E_XML = "xml";
72
73 private static final String E_PLAIN = "plain";
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 Path classpath;
92
93
94 private String fileName;
95
96
97 private String config;
98
99
100 private boolean failOnViolation = true;
101
102
103 private String failureProperty;
104
105
106 private File properties;
107
108
109 private int maxErrors;
110
111
112 private int maxWarnings = Integer.MAX_VALUE;
113
114
115
116
117
118
119 private boolean executeIgnoredModules;
120
121
122
123
124
125
126
127
128
129
130
131 public void setFailureProperty(String propertyName) {
132 failureProperty = propertyName;
133 }
134
135
136
137
138
139 public void setFailOnViolation(boolean fail) {
140 failOnViolation = fail;
141 }
142
143
144
145
146
147 public void setMaxErrors(int maxErrors) {
148 this.maxErrors = maxErrors;
149 }
150
151
152
153
154
155
156 public void setMaxWarnings(int maxWarnings) {
157 this.maxWarnings = maxWarnings;
158 }
159
160
161
162
163
164 public void addPath(Path path) {
165 paths.add(path);
166 }
167
168
169
170
171
172 public void addFileset(FileSet fileSet) {
173 fileSets.add(fileSet);
174 }
175
176
177
178
179
180 public void addFormatter(Formatter formatter) {
181 formatters.add(formatter);
182 }
183
184
185
186
187
188 public void addProperty(Property property) {
189 overrideProps.add(property);
190 }
191
192
193
194
195
196 public void setClasspath(Path classpath) {
197 if (this.classpath == null) {
198 this.classpath = classpath;
199 }
200 else {
201 this.classpath.append(classpath);
202 }
203 }
204
205
206
207
208
209 public void setClasspathRef(Reference classpathRef) {
210 createClasspath().setRefid(classpathRef);
211 }
212
213
214
215
216
217 public Path createClasspath() {
218 if (classpath == null) {
219 classpath = new Path(getProject());
220 }
221 return classpath.createPath();
222 }
223
224
225
226
227
228 public void setFile(File file) {
229 fileName = file.getAbsolutePath();
230 }
231
232
233
234
235
236 public void setConfig(String configuration) {
237 if (config != null) {
238 throw new BuildException("Attribute 'config' has already been set");
239 }
240 config = configuration;
241 }
242
243
244
245
246
247 public void setExecuteIgnoredModules(boolean omit) {
248 executeIgnoredModules = omit;
249 }
250
251
252
253
254
255
256
257
258
259
260 public void setProperties(File props) {
261 properties = props;
262 }
263
264
265
266
267
268 @Override
269 public void execute() {
270 final long startTime = System.currentTimeMillis();
271
272 try {
273 final String version = CheckstyleAntTask.class.getPackage().getImplementationVersion();
274
275 log("checkstyle version " + version, Project.MSG_VERBOSE);
276
277
278 if (fileName == null
279 && fileSets.isEmpty()
280 && paths.isEmpty()) {
281 throw new BuildException(
282 "Must specify at least one of 'file' or nested 'fileset' or 'path'.",
283 getLocation());
284 }
285 if (config == null) {
286 throw new BuildException("Must specify 'config'.", getLocation());
287 }
288 realExecute(version);
289 }
290 finally {
291 final long endTime = System.currentTimeMillis();
292 log("Total execution took " + (endTime - startTime) + TIME_SUFFIX,
293 Project.MSG_VERBOSE);
294 }
295 }
296
297
298
299
300
301 private void realExecute(String checkstyleVersion) {
302
303 RootModule rootModule = null;
304 try {
305 rootModule = createRootModule();
306
307
308 final AuditListener[] listeners = getListeners();
309 for (AuditListener element : listeners) {
310 rootModule.addListener(element);
311 }
312 final SeverityLevelCounter warningCounter =
313 new SeverityLevelCounter(SeverityLevel.WARNING);
314 rootModule.addListener(warningCounter);
315
316 processFiles(rootModule, warningCounter, checkstyleVersion);
317 }
318 finally {
319 if (rootModule != null) {
320 rootModule.destroy();
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 + Objects.toString(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 ex) {
355 throw new BuildException("Unable to process files: " + files, ex);
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 and " + numWarnings
364 + " 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 private RootModule createRootModule() {
380 final RootModule rootModule;
381 try {
382 final Properties props = createOverridingProperties();
383 final ThreadModeSettings threadModeSettings =
384 ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE;
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 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
405 if (rootModule instanceof Checker) {
406 final ClassLoader loader = new AntClassLoader(getProject(),
407 classpath);
408
409 ((Checker) rootModule).setClassLoader(loader);
410 }
411
412 rootModule.configure(configuration);
413 }
414 catch (final CheckstyleException ex) {
415 throw new BuildException(String.format(Locale.ROOT, "Unable to create Root Module: "
416 + "config {%s}, classpath {%s}.", config, classpath), ex);
417 }
418 return rootModule;
419 }
420
421
422
423
424
425
426
427 private Properties createOverridingProperties() {
428 final Properties returnValue = new Properties();
429
430
431 if (properties != null) {
432 try (InputStream inStream = Files.newInputStream(properties.toPath())) {
433 returnValue.load(inStream);
434 }
435 catch (final IOException ex) {
436 throw new BuildException("Error loading Properties file '"
437 + properties + "'", ex, getLocation());
438 }
439 }
440
441
442 final Map<String, Object> antProps = getProject().getProperties();
443 for (Map.Entry<String, Object> entry : antProps.entrySet()) {
444 final String value = String.valueOf(entry.getValue());
445 returnValue.setProperty(entry.getKey(), value);
446 }
447
448
449 for (Property p : overrideProps) {
450 returnValue.setProperty(p.getKey(), p.getValue());
451 }
452
453 return returnValue;
454 }
455
456
457
458
459
460 private AuditListener[] getListeners() {
461 final int formatterCount = Math.max(1, formatters.size());
462
463 final AuditListener[] listeners = new AuditListener[formatterCount];
464
465
466 try {
467 if (formatters.isEmpty()) {
468 final OutputStream debug = new LogOutputStream(this, Project.MSG_DEBUG);
469 final OutputStream err = new LogOutputStream(this, Project.MSG_ERR);
470 listeners[0] = new DefaultLogger(debug, AutomaticBean.OutputStreamOptions.CLOSE,
471 err, AutomaticBean.OutputStreamOptions.CLOSE);
472 }
473 else {
474 for (int i = 0; i < formatterCount; i++) {
475 final Formatter formatter = formatters.get(i);
476 listeners[i] = formatter.createListener(this);
477 }
478 }
479 }
480 catch (IOException ex) {
481 throw new BuildException(String.format(Locale.ROOT, "Unable to create listeners: "
482 + "formatters {%s}.", formatters), ex);
483 }
484 return listeners;
485 }
486
487
488
489
490
491 private List<File> getFilesToCheck() {
492 final List<File> allFiles = new ArrayList<>();
493 if (fileName != null) {
494
495
496 log("Adding standalone file for audit", Project.MSG_VERBOSE);
497 allFiles.add(new File(fileName));
498 }
499
500 final List<File> filesFromFileSets = scanFileSets();
501 allFiles.addAll(filesFromFileSets);
502
503 final List<File> filesFromPaths = scanPaths();
504 allFiles.addAll(filesFromPaths);
505
506 return allFiles;
507 }
508
509
510
511
512
513 private List<File> scanPaths() {
514 final List<File> allFiles = new ArrayList<>();
515
516 for (int i = 0; i < paths.size(); i++) {
517 final Path currentPath = paths.get(i);
518 final List<File> 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<File> scanPath(Path path, int pathIndex) {
533 final String[] resources = path.list();
534 log(pathIndex + ") Scanning path " + path, Project.MSG_VERBOSE);
535 final List<File> allFiles = new ArrayList<>();
536 int concreteFilesCount = 0;
537
538 for (String resource : resources) {
539 final File file = new File(resource);
540 if (file.isFile()) {
541 concreteFilesCount++;
542 allFiles.add(file);
543 }
544 else {
545 final DirectoryScanner scanner = new DirectoryScanner();
546 scanner.setBasedir(file);
547 scanner.scan();
548 final List<File> 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 protected List<File> scanFileSets() {
566 final List<File> 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<File> scannedFiles = retrieveAllScannedFiles(scanner, i);
572 allFiles.addAll(scannedFiles);
573 }
574
575 return allFiles;
576 }
577
578
579
580
581
582
583
584
585
586 private List<File> retrieveAllScannedFiles(DirectoryScanner scanner, int logIndex) {
587 final String[] fileNames = scanner.getIncludedFiles();
588 log(String.format(Locale.ROOT, "%d) Adding %d files from directory %s",
589 logIndex, fileNames.length, scanner.getBasedir()), Project.MSG_VERBOSE);
590
591 return Arrays.stream(fileNames)
592 .map(name -> scanner.getBasedir() + File.separator + name)
593 .map(File::new)
594 .collect(Collectors.toList());
595 }
596
597
598
599
600 public static class FormatterType extends EnumeratedAttribute {
601
602
603 private static final String[] VALUES = {E_XML, E_PLAIN};
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 public void setType(FormatterType type) {
629 this.type = type;
630 }
631
632
633
634
635
636 public void setTofile(File destination) {
637 toFile = destination;
638 }
639
640
641
642
643
644 public void setUseFile(boolean use) {
645 useFile = use;
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 {
661 listener = createDefaultLogger(task);
662 }
663 return listener;
664 }
665
666
667
668
669
670
671
672 private AuditListener createDefaultLogger(Task task)
673 throws IOException {
674 final AuditListener defaultLogger;
675 if (toFile == null || !useFile) {
676 defaultLogger = new DefaultLogger(
677 new LogOutputStream(task, Project.MSG_DEBUG),
678 AutomaticBean.OutputStreamOptions.CLOSE,
679 new LogOutputStream(task, Project.MSG_ERR),
680 AutomaticBean.OutputStreamOptions.CLOSE
681 );
682 }
683 else {
684 final OutputStream infoStream = Files.newOutputStream(toFile.toPath());
685 defaultLogger =
686 new DefaultLogger(infoStream, AutomaticBean.OutputStreamOptions.CLOSE,
687 infoStream, AutomaticBean.OutputStreamOptions.NONE);
688 }
689 return defaultLogger;
690 }
691
692
693
694
695
696
697
698 private AuditListener createXmlLogger(Task task) throws IOException {
699 final AuditListener xmlLogger;
700 if (toFile == null || !useFile) {
701 xmlLogger = new XMLLogger(new LogOutputStream(task, Project.MSG_INFO),
702 AutomaticBean.OutputStreamOptions.CLOSE);
703 }
704 else {
705 xmlLogger = new XMLLogger(Files.newOutputStream(toFile.toPath()),
706 AutomaticBean.OutputStreamOptions.CLOSE);
707 }
708 return xmlLogger;
709 }
710
711 }
712
713
714
715
716 public static class Property {
717
718
719 private String key;
720
721 private String value;
722
723
724
725
726
727 public String getKey() {
728 return key;
729 }
730
731
732
733
734
735 public void setKey(String key) {
736 this.key = key;
737 }
738
739
740
741
742
743 public String getValue() {
744 return value;
745 }
746
747
748
749
750
751 public void setValue(String value) {
752 this.value = value;
753 }
754
755
756
757
758
759 public void setFile(File file) {
760 value = file.getAbsolutePath();
761 }
762
763 }
764
765
766 public static class Listener {
767
768
769 private String className;
770
771
772
773
774
775 public String getClassname() {
776 return className;
777 }
778
779
780
781
782
783 public void setClassname(String name) {
784 className = name;
785 }
786
787 }
788
789 }