1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.checkstyle;
20
21 import java.io.BufferedReader;
22 import java.io.ByteArrayOutputStream;
23 import java.io.File;
24 import java.io.FileNotFoundException;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.OutputStream;
28 import java.io.Reader;
29 import java.nio.file.Files;
30 import java.nio.file.Path;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.List;
34 import java.util.Map;
35
36 import com.puppycrawl.tools.checkstyle.DefaultLogger;
37 import com.puppycrawl.tools.checkstyle.XMLLogger;
38 import com.puppycrawl.tools.checkstyle.api.AuditListener;
39 import com.puppycrawl.tools.checkstyle.api.AutomaticBean.OutputStreamOptions;
40 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
41 import org.apache.maven.artifact.Artifact;
42 import org.apache.maven.model.Dependency;
43 import org.apache.maven.model.Plugin;
44 import org.apache.maven.model.PluginManagement;
45 import org.apache.maven.model.Resource;
46 import org.apache.maven.plugin.AbstractMojo;
47 import org.apache.maven.plugin.MojoExecutionException;
48 import org.apache.maven.plugin.MojoFailureException;
49 import org.apache.maven.plugin.descriptor.PluginDescriptor;
50 import org.apache.maven.plugins.annotations.Component;
51 import org.apache.maven.plugins.annotations.LifecyclePhase;
52 import org.apache.maven.plugins.annotations.Mojo;
53 import org.apache.maven.plugins.annotations.Parameter;
54 import org.apache.maven.plugins.annotations.ResolutionScope;
55 import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutor;
56 import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutorException;
57 import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutorRequest;
58 import org.apache.maven.project.MavenProject;
59 import org.codehaus.plexus.configuration.PlexusConfiguration;
60 import org.codehaus.plexus.util.FileUtils;
61 import org.codehaus.plexus.util.PathTool;
62 import org.codehaus.plexus.util.ReaderFactory;
63 import org.codehaus.plexus.util.xml.pull.MXParser;
64 import org.codehaus.plexus.util.xml.pull.XmlPullParser;
65 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
66
67
68
69
70
71
72
73
74
75 @Mojo(
76 name = "check",
77 defaultPhase = LifecyclePhase.VERIFY,
78 requiresDependencyResolution = ResolutionScope.NONE,
79 threadSafe = true)
80 public class CheckstyleViolationCheckMojo extends AbstractMojo {
81
82 private static final String JAVA_FILES = "**\\/*.java";
83 private static final String DEFAULT_CONFIG_LOCATION = "sun_checks.xml";
84
85
86
87
88
89
90 @Parameter(property = "checkstyle.output.file", defaultValue = "${project.build.directory}/checkstyle-result.xml")
91 private File outputFile;
92
93
94
95
96
97 @Parameter(property = "checkstyle.output.format", defaultValue = "xml")
98 private String outputFileFormat;
99
100
101
102
103
104
105
106 @Parameter(property = "checkstyle.failOnViolation", defaultValue = "true")
107 private boolean failOnViolation;
108
109
110
111
112
113
114
115 @Parameter(property = "checkstyle.maxAllowedViolations", defaultValue = "0")
116 private int maxAllowedViolations;
117
118
119
120
121
122
123
124 @Parameter(property = "checkstyle.violationSeverity", defaultValue = "error")
125 private String violationSeverity = "error";
126
127
128
129
130
131
132
133 @Parameter(property = "checkstyle.violation.ignore")
134 private String violationIgnore;
135
136
137
138
139
140
141 @Parameter(property = "checkstyle.skip", defaultValue = "false")
142 private boolean skip;
143
144
145
146
147
148
149 @Parameter(property = "checkstyle.skipExec", defaultValue = "false")
150 private boolean skipExec;
151
152
153
154
155
156
157 @Parameter(property = "checkstyle.console", defaultValue = "true")
158 private boolean logViolationsToConsole;
159
160
161
162
163
164
165 @Parameter(property = "checkstyle.logViolationCount", defaultValue = "true")
166 private boolean logViolationCountToConsole;
167
168
169
170
171
172
173 @Parameter(defaultValue = "${project.resources}", readonly = true)
174 protected List<Resource> resources;
175
176
177
178
179
180
181 @Parameter(defaultValue = "${project.testResources}", readonly = true)
182 protected List<Resource> testResources;
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207 @Parameter(property = "checkstyle.config.location", defaultValue = DEFAULT_CONFIG_LOCATION)
208 private String configLocation;
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226 @Parameter(property = "checkstyle.properties.location")
227 private String propertiesLocation;
228
229
230
231
232 @Parameter
233 private String propertyExpansion;
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253 @Parameter(property = "checkstyle.header.file", defaultValue = "LICENSE.txt")
254 private String headerLocation;
255
256
257
258
259 @Parameter(defaultValue = "${project.build.directory}/checkstyle-cachefile")
260 private String cacheFile;
261
262
263
264
265
266
267 @Parameter(property = "checkstyle.suppression.expression", defaultValue = "checkstyle.suppressions.file")
268 private String suppressionsFileExpression;
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284 @Parameter(property = "checkstyle.suppressions.location")
285 private String suppressionsLocation;
286
287
288
289
290
291
292
293
294 @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}")
295 private String inputEncoding;
296
297
298
299
300 @Component(role = CheckstyleExecutor.class, hint = "default")
301 protected CheckstyleExecutor checkstyleExecutor;
302
303
304
305
306 @Parameter(property = "checkstyle.consoleOutput", defaultValue = "false")
307 private boolean consoleOutput;
308
309
310
311
312 @Parameter(defaultValue = "${project}", readonly = true, required = true)
313 protected MavenProject project;
314
315
316
317
318 @Parameter(defaultValue = "${plugin}", readonly = true, required = true)
319 private PluginDescriptor plugin;
320
321
322
323
324
325 @Parameter
326 private File useFile;
327
328
329
330
331
332 @Parameter(property = "checkstyle.excludes")
333 private String excludes;
334
335
336
337
338 @Parameter(property = "checkstyle.includes", defaultValue = JAVA_FILES, required = true)
339 private String includes;
340
341
342
343
344
345
346 @Parameter(property = "checkstyle.resourceExcludes")
347 private String resourceExcludes;
348
349
350
351
352
353 @Parameter(property = "checkstyle.resourceIncludes", defaultValue = "**/*.properties", required = true)
354 private String resourceIncludes;
355
356
357
358
359
360
361
362 @Parameter(defaultValue = "false")
363 private boolean failsOnError;
364
365
366
367
368
369
370
371
372 @Deprecated
373 @Parameter
374 private File testSourceDirectory;
375
376
377
378
379
380
381
382 @Parameter
383 private List<String> testSourceDirectories;
384
385
386
387
388
389
390 @Parameter(defaultValue = "false")
391 private boolean includeTestSourceDirectory;
392
393
394
395
396
397
398
399 @Deprecated
400 @Parameter
401 private File sourceDirectory;
402
403
404
405
406
407
408
409 @Parameter
410 private List<String> sourceDirectories;
411
412
413
414
415
416 @Parameter(property = "checkstyle.includeResources", defaultValue = "true", required = true)
417 private boolean includeResources = true;
418
419
420
421
422
423 @Parameter(property = "checkstyle.includeTestResources", defaultValue = "true", required = true)
424 private boolean includeTestResources = true;
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450 @Parameter
451 private PlexusConfiguration checkstyleRules;
452
453
454
455
456 @Parameter(
457 property = "checkstyle.output.rules.file",
458 defaultValue = "${project.build.directory}/checkstyle-rules.xml")
459 private File rulesFiles;
460
461
462
463
464
465 @Parameter(
466 defaultValue = "<?xml version=\"1.0\"?>\n"
467 + "<!DOCTYPE module PUBLIC \"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN\"\n"
468 + " \"https://checkstyle.org/dtds/configuration_1_3.dtd\">\n")
469 private String checkstyleRulesHeader;
470
471
472
473
474
475
476
477 @Parameter(defaultValue = "false")
478 private boolean omitIgnoredModules;
479
480
481
482
483
484
485 @Parameter(property = "checkstyle.excludeGeneratedSources", defaultValue = "false")
486 private boolean excludeGeneratedSources;
487
488 private ByteArrayOutputStream stringOutputStream;
489
490 private File outputXmlFile;
491
492
493 public void execute() throws MojoExecutionException, MojoFailureException {
494 checkDeprecatedParameterUsage(sourceDirectory, "sourceDirectory", "sourceDirectories");
495 checkDeprecatedParameterUsage(testSourceDirectory, "testSourceDirectory", "testSourceDirectories");
496 if (skip) {
497 return;
498 }
499
500 outputXmlFile = outputFile;
501
502 if (!skipExec) {
503 String effectiveConfigLocation = configLocation;
504 if (checkstyleRules != null) {
505 if (!DEFAULT_CONFIG_LOCATION.equals(configLocation)) {
506 throw new MojoExecutionException(
507 "If you use inline configuration for rules, don't specify " + "a configLocation");
508 }
509 if (checkstyleRules.getChildCount() > 1) {
510 throw new MojoExecutionException("Currently only one root module is supported");
511 }
512
513 PlexusConfiguration checkerModule = checkstyleRules.getChild(0);
514
515 try {
516 FileUtils.forceMkdir(rulesFiles.getParentFile());
517 FileUtils.fileWrite(rulesFiles, checkstyleRulesHeader + checkerModule.toString());
518 } catch (final IOException e) {
519 throw new MojoExecutionException(e.getMessage(), e);
520 }
521 effectiveConfigLocation = rulesFiles.getAbsolutePath();
522 }
523
524 ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
525
526 try {
527 CheckstyleExecutorRequest request = new CheckstyleExecutorRequest();
528 request.setConsoleListener(getConsoleListener())
529 .setConsoleOutput(consoleOutput)
530 .setExcludes(excludes)
531 .setFailsOnError(failsOnError)
532 .setIncludes(includes)
533 .setResourceIncludes(resourceIncludes)
534 .setResourceExcludes(resourceExcludes)
535 .setIncludeResources(includeResources)
536 .setIncludeTestResources(includeTestResources)
537 .setIncludeTestSourceDirectory(includeTestSourceDirectory)
538 .setListener(getListener())
539 .setProject(project)
540 .setSourceDirectories(getSourceDirectories())
541 .setResources(resources)
542 .setTestResources(testResources)
543 .setStringOutputStream(stringOutputStream)
544 .setSuppressionsLocation(suppressionsLocation)
545 .setTestSourceDirectories(getTestSourceDirectories())
546 .setConfigLocation(effectiveConfigLocation)
547 .setConfigurationArtifacts(collectArtifacts("config"))
548 .setPropertyExpansion(propertyExpansion)
549 .setHeaderLocation(headerLocation)
550 .setLicenseArtifacts(collectArtifacts("license"))
551 .setCacheFile(cacheFile)
552 .setSuppressionsFileExpression(suppressionsFileExpression)
553 .setEncoding(inputEncoding)
554 .setPropertiesLocation(propertiesLocation)
555 .setOmitIgnoredModules(omitIgnoredModules);
556 checkstyleExecutor.executeCheckstyle(request);
557
558 } catch (CheckstyleException e) {
559 throw new MojoExecutionException("Failed during checkstyle configuration", e);
560 } catch (CheckstyleExecutorException e) {
561 throw new MojoExecutionException("Failed during checkstyle execution", e);
562 } finally {
563
564 Thread.currentThread().setContextClassLoader(currentClassLoader);
565 }
566 }
567
568 if (!"xml".equals(outputFileFormat) && skipExec) {
569 throw new MojoExecutionException("Output format is '" + outputFileFormat
570 + "', checkstyle:check requires format to be 'xml' when using skipExec.");
571 }
572
573 if (!outputXmlFile.exists()) {
574 getLog().info("Unable to perform checkstyle:check, unable to find checkstyle:checkstyle outputFile.");
575 return;
576 }
577
578 try (Reader reader = new BufferedReader(ReaderFactory.newXmlReader(outputXmlFile))) {
579 XmlPullParser xpp = new MXParser();
580 xpp.setInput(reader);
581
582 final List<Violation> violationsList = getViolations(xpp);
583 long violationCount = countViolations(violationsList);
584 printViolations(violationsList);
585
586 String msg = "You have " + violationCount + " Checkstyle violation"
587 + ((violationCount > 1 || violationCount == 0) ? "s" : "") + ".";
588
589 if (violationCount > maxAllowedViolations) {
590 if (failOnViolation) {
591 if (maxAllowedViolations > 0) {
592 msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
593 }
594 throw new MojoFailureException(msg);
595 }
596
597 getLog().warn("checkstyle:check violations detected but failOnViolation set to false");
598 }
599 if (logViolationCountToConsole) {
600 if (maxAllowedViolations > 0) {
601 msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
602 }
603 getLog().info(msg);
604 }
605 } catch (IOException | XmlPullParserException e) {
606 throw new MojoExecutionException(
607 "Unable to read Checkstyle results xml: " + outputXmlFile.getAbsolutePath(), e);
608 }
609 }
610
611 private void checkDeprecatedParameterUsage(Object parameter, String name, String replacement)
612 throws MojoFailureException {
613 if (parameter != null) {
614 throw new MojoFailureException("You are using '" + name + "' which has been removed"
615 + " from the maven-checkstyle-plugin. " + "Please use '" + replacement
616 + "' and refer to the >>Major Version Upgrade to version 3.0.0<< " + "on the plugin site.");
617 }
618 }
619
620 private List<Violation> getViolations(XmlPullParser xpp) throws XmlPullParserException, IOException {
621 List<Violation> violations = new ArrayList<>();
622
623 String basedir = project.getBasedir().getAbsolutePath();
624 String file = "";
625
626 for (int eventType = xpp.getEventType(); eventType != XmlPullParser.END_DOCUMENT; eventType = xpp.next()) {
627 if (eventType != XmlPullParser.START_TAG) {
628 continue;
629 } else if ("file".equals(xpp.getName())) {
630 file = PathTool.getRelativeFilePath(basedir, xpp.getAttributeValue("", "name"));
631 continue;
632 } else if (!"error".equals(xpp.getName())) {
633 continue;
634 }
635
636 String severity = xpp.getAttributeValue("", "severity");
637 String source = xpp.getAttributeValue("", "source");
638 String line = xpp.getAttributeValue("", "line");
639
640 String column = xpp.getAttributeValue("", "column");
641 String message = xpp.getAttributeValue("", "message");
642 String rule = RuleUtil.getName(source);
643 String category = RuleUtil.getCategory(source);
644
645 Violation violation = new Violation(source, file, line, severity, message, rule, category);
646 if (column != null) {
647 violation.setColumn(column);
648 }
649
650 violations.add(violation);
651 }
652
653 return violations;
654 }
655
656 private int countViolations(List<Violation> violations) {
657 List<RuleUtil.Matcher> ignores = violationIgnore == null
658 ? Collections.<RuleUtil.Matcher>emptyList()
659 : RuleUtil.parseMatchers(violationIgnore.split(","));
660
661 int ignored = 0;
662 int countedViolations = 0;
663
664 for (Violation violation : violations) {
665 if (!isViolation(violation.getSeverity())) {
666 continue;
667 }
668
669 if (ignore(ignores, violation.getSource())) {
670 ignored++;
671 continue;
672 }
673
674 countedViolations++;
675 }
676
677 if (ignored > 0) {
678 getLog().info("Ignored " + ignored + " error" + ((ignored > 1L) ? "s" : "") + ", " + countedViolations
679 + " violation" + ((countedViolations > 1) ? "s" : "") + " remaining.");
680 }
681
682 return countedViolations;
683 }
684
685 private void printViolations(List<Violation> violations) {
686 if (!logViolationsToConsole) {
687 return;
688 }
689
690 List<RuleUtil.Matcher> ignores = violationIgnore == null
691 ? Collections.<RuleUtil.Matcher>emptyList()
692 : RuleUtil.parseMatchers(violationIgnore.split(","));
693
694 violations.stream()
695 .filter(violation -> isViolation(violation.getSeverity()))
696 .filter(violation -> !ignore(ignores, violation.getSource()))
697 .forEach(violation -> {
698 final String message = String.format(
699 "%s:[%s%s] (%s) %s: %s",
700 violation.getFile(),
701 violation.getLine(),
702 (Violation.NO_COLUMN.equals(violation.getColumn())) ? "" : (',' + violation.getColumn()),
703 violation.getCategory(),
704 violation.getRuleName(),
705 violation.getMessage());
706 log(violation.getSeverity(), message);
707 });
708 }
709
710 private void log(String severity, String message) {
711 if ("info".equals(severity)) {
712 getLog().info(message);
713 } else if ("warning".equals(severity)) {
714 getLog().warn(message);
715 } else {
716 getLog().error(message);
717 }
718 }
719
720
721
722
723
724
725
726 private boolean isViolation(String severity) {
727 if ("error".equals(severity)) {
728 return "error".equals(violationSeverity)
729 || "warning".equals(violationSeverity)
730 || "info".equals(violationSeverity);
731 } else if ("warning".equals(severity)) {
732 return "warning".equals(violationSeverity) || "info".equals(violationSeverity);
733 } else if ("info".equals(severity)) {
734 return "info".equals(violationSeverity);
735 } else {
736 return false;
737 }
738 }
739
740 private boolean ignore(List<RuleUtil.Matcher> ignores, String source) {
741 for (RuleUtil.Matcher ignore : ignores) {
742 if (ignore.match(source)) {
743 return true;
744 }
745 }
746 return false;
747 }
748
749 private DefaultLogger getConsoleListener() throws MojoExecutionException {
750 DefaultLogger consoleListener;
751
752 if (useFile == null) {
753 stringOutputStream = new ByteArrayOutputStream();
754 consoleListener = new DefaultLogger(stringOutputStream, OutputStreamOptions.NONE);
755 } else {
756 OutputStream out = getOutputStream(useFile);
757
758 consoleListener = new DefaultLogger(out, OutputStreamOptions.CLOSE);
759 }
760
761 return consoleListener;
762 }
763
764 private OutputStream getOutputStream(File file) throws MojoExecutionException {
765 File parentFile = file.getAbsoluteFile().getParentFile();
766
767 if (!parentFile.exists()) {
768 parentFile.mkdirs();
769 }
770
771 FileOutputStream fileOutputStream;
772 try {
773 fileOutputStream = new FileOutputStream(file);
774 } catch (FileNotFoundException e) {
775 throw new MojoExecutionException("Unable to create output stream: " + file, e);
776 }
777 return fileOutputStream;
778 }
779
780 private AuditListener getListener() throws MojoFailureException, MojoExecutionException {
781 AuditListener listener = null;
782
783 if (outputFileFormat != null && !outputFileFormat.isEmpty()) {
784 File resultFile = outputFile;
785
786 OutputStream out = getOutputStream(resultFile);
787
788 if ("xml".equals(outputFileFormat)) {
789 listener = new XMLLogger(out, OutputStreamOptions.CLOSE);
790 } else if ("plain".equals(outputFileFormat)) {
791 try {
792
793
794 outputXmlFile =
795 Files.createTempFile("checkstyle-result", ".xml").toFile();
796 outputXmlFile.deleteOnExit();
797 OutputStream xmlOut = getOutputStream(outputXmlFile);
798 CompositeAuditListener compoundListener = new CompositeAuditListener();
799 compoundListener.addListener(new XMLLogger(xmlOut, OutputStreamOptions.CLOSE));
800 compoundListener.addListener(new DefaultLogger(out, OutputStreamOptions.CLOSE));
801 listener = compoundListener;
802 } catch (IOException e) {
803 throw new MojoExecutionException("Unable to create temporary file", e);
804 }
805 } else {
806 throw new MojoFailureException(
807 "Invalid output file format: (" + outputFileFormat + "). Must be 'plain' or 'xml'.");
808 }
809 }
810
811 return listener;
812 }
813
814 private List<Artifact> collectArtifacts(String hint) {
815 List<Artifact> artifacts = new ArrayList<>();
816
817 PluginManagement pluginManagement = project.getBuild().getPluginManagement();
818 if (pluginManagement != null) {
819 artifacts.addAll(getCheckstylePluginDependenciesAsArtifacts(pluginManagement.getPluginsAsMap(), hint));
820 }
821
822 artifacts.addAll(
823 getCheckstylePluginDependenciesAsArtifacts(project.getBuild().getPluginsAsMap(), hint));
824
825 return artifacts;
826 }
827
828 private List<Artifact> getCheckstylePluginDependenciesAsArtifacts(Map<String, Plugin> plugins, String hint) {
829 List<Artifact> artifacts = new ArrayList<>();
830
831 Plugin checkstylePlugin = plugins.get(plugin.getGroupId() + ":" + plugin.getArtifactId());
832 if (checkstylePlugin != null) {
833 for (Dependency dep : checkstylePlugin.getDependencies()) {
834
835 String depKey = dep.getGroupId() + ":" + dep.getArtifactId();
836 artifacts.add(plugin.getArtifactMap().get(depKey));
837 }
838 }
839 return artifacts;
840 }
841
842 private List<File> getSourceDirectories() {
843 if (sourceDirectories == null) {
844 sourceDirectories = filterBuildTarget(project.getCompileSourceRoots());
845 }
846 List<File> sourceDirs = new ArrayList<>(sourceDirectories.size());
847 for (String sourceDir : sourceDirectories) {
848 sourceDirs.add(FileUtils.resolveFile(project.getBasedir(), sourceDir));
849 }
850 return sourceDirs;
851 }
852
853 private List<File> getTestSourceDirectories() {
854 if (testSourceDirectories == null) {
855 testSourceDirectories = filterBuildTarget(project.getTestCompileSourceRoots());
856 }
857 List<File> testSourceDirs = new ArrayList<>(testSourceDirectories.size());
858 for (String testSourceDir : testSourceDirectories) {
859 testSourceDirs.add(FileUtils.resolveFile(project.getBasedir(), testSourceDir));
860 }
861 return testSourceDirs;
862 }
863
864 private List<String> filterBuildTarget(List<String> sourceDirectories) {
865 if (!excludeGeneratedSources) {
866 return sourceDirectories;
867 }
868
869 List<String> filtered = new ArrayList<>(sourceDirectories.size());
870 Path buildTarget = FileUtils.resolveFile(
871 project.getBasedir(), project.getBuild().getDirectory())
872 .toPath();
873
874 for (String sourceDir : sourceDirectories) {
875 Path src = FileUtils.resolveFile(project.getBasedir(), sourceDir).toPath();
876 if (!src.startsWith(buildTarget)) {
877 filtered.add(sourceDir);
878 }
879 }
880 return filtered;
881 }
882 }