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.util.ArrayList;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.Map;
34
35 import com.puppycrawl.tools.checkstyle.DefaultLogger;
36 import com.puppycrawl.tools.checkstyle.XMLLogger;
37 import com.puppycrawl.tools.checkstyle.api.AuditListener;
38 import com.puppycrawl.tools.checkstyle.api.AutomaticBean.OutputStreamOptions;
39 import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
40 import org.apache.commons.lang3.StringUtils;
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 private ByteArrayOutputStream stringOutputStream;
481
482 private File outputXmlFile;
483
484
485 public void execute() throws MojoExecutionException, MojoFailureException {
486 checkDeprecatedParameterUsage(sourceDirectory, "sourceDirectory", "sourceDirectories");
487 checkDeprecatedParameterUsage(testSourceDirectory, "testSourceDirectory", "testSourceDirectories");
488 if (skip) {
489 return;
490 }
491
492 outputXmlFile = outputFile;
493
494 if (!skipExec) {
495 String effectiveConfigLocation = configLocation;
496 if (checkstyleRules != null) {
497 if (!DEFAULT_CONFIG_LOCATION.equals(configLocation)) {
498 throw new MojoExecutionException(
499 "If you use inline configuration for rules, don't specify " + "a configLocation");
500 }
501 if (checkstyleRules.getChildCount() > 1) {
502 throw new MojoExecutionException("Currently only one root module is supported");
503 }
504
505 PlexusConfiguration checkerModule = checkstyleRules.getChild(0);
506
507 try {
508 FileUtils.forceMkdir(rulesFiles.getParentFile());
509 FileUtils.fileWrite(rulesFiles, checkstyleRulesHeader + checkerModule.toString());
510 } catch (final IOException e) {
511 throw new MojoExecutionException(e.getMessage(), e);
512 }
513 effectiveConfigLocation = rulesFiles.getAbsolutePath();
514 }
515
516 ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
517
518 try {
519 CheckstyleExecutorRequest request = new CheckstyleExecutorRequest();
520 request.setConsoleListener(getConsoleListener())
521 .setConsoleOutput(consoleOutput)
522 .setExcludes(excludes)
523 .setFailsOnError(failsOnError)
524 .setIncludes(includes)
525 .setResourceIncludes(resourceIncludes)
526 .setResourceExcludes(resourceExcludes)
527 .setIncludeResources(includeResources)
528 .setIncludeTestResources(includeTestResources)
529 .setIncludeTestSourceDirectory(includeTestSourceDirectory)
530 .setListener(getListener())
531 .setProject(project)
532 .setSourceDirectories(getSourceDirectories())
533 .setResources(resources)
534 .setTestResources(testResources)
535 .setStringOutputStream(stringOutputStream)
536 .setSuppressionsLocation(suppressionsLocation)
537 .setTestSourceDirectories(getTestSourceDirectories())
538 .setConfigLocation(effectiveConfigLocation)
539 .setConfigurationArtifacts(collectArtifacts("config"))
540 .setPropertyExpansion(propertyExpansion)
541 .setHeaderLocation(headerLocation)
542 .setLicenseArtifacts(collectArtifacts("license"))
543 .setCacheFile(cacheFile)
544 .setSuppressionsFileExpression(suppressionsFileExpression)
545 .setEncoding(inputEncoding)
546 .setPropertiesLocation(propertiesLocation)
547 .setOmitIgnoredModules(omitIgnoredModules);
548 checkstyleExecutor.executeCheckstyle(request);
549
550 } catch (CheckstyleException e) {
551 throw new MojoExecutionException("Failed during checkstyle configuration", e);
552 } catch (CheckstyleExecutorException e) {
553 throw new MojoExecutionException("Failed during checkstyle execution", e);
554 } finally {
555
556 Thread.currentThread().setContextClassLoader(currentClassLoader);
557 }
558 }
559
560 if (!"xml".equals(outputFileFormat) && skipExec) {
561 throw new MojoExecutionException("Output format is '" + outputFileFormat
562 + "', checkstyle:check requires format to be 'xml' when using skipExec.");
563 }
564
565 if (!outputXmlFile.exists()) {
566 getLog().info("Unable to perform checkstyle:check, unable to find checkstyle:checkstyle outputFile.");
567 return;
568 }
569
570 try (Reader reader = new BufferedReader(ReaderFactory.newXmlReader(outputXmlFile))) {
571 XmlPullParser xpp = new MXParser();
572 xpp.setInput(reader);
573
574 final List<Violation> violationsList = getViolations(xpp);
575 long violationCount = countViolations(violationsList);
576 printViolations(violationsList);
577
578 String msg = "You have " + violationCount + " Checkstyle violation"
579 + ((violationCount > 1 || violationCount == 0) ? "s" : "") + ".";
580
581 if (violationCount > maxAllowedViolations) {
582 if (failOnViolation) {
583 if (maxAllowedViolations > 0) {
584 msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
585 }
586 throw new MojoFailureException(msg);
587 }
588
589 getLog().warn("checkstyle:check violations detected but failOnViolation set to false");
590 }
591 if (logViolationCountToConsole) {
592 if (maxAllowedViolations > 0) {
593 msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
594 }
595 getLog().info(msg);
596 }
597 } catch (IOException | XmlPullParserException e) {
598 throw new MojoExecutionException(
599 "Unable to read Checkstyle results xml: " + outputXmlFile.getAbsolutePath(), e);
600 }
601 }
602
603 private void checkDeprecatedParameterUsage(Object parameter, String name, String replacement)
604 throws MojoFailureException {
605 if (parameter != null) {
606 throw new MojoFailureException("You are using '" + name + "' which has been removed"
607 + " from the maven-checkstyle-plugin. " + "Please use '" + replacement
608 + "' and refer to the >>Major Version Upgrade to version 3.0.0<< " + "on the plugin site.");
609 }
610 }
611
612 private List<Violation> getViolations(XmlPullParser xpp) throws XmlPullParserException, IOException {
613 List<Violation> violations = new ArrayList<>();
614
615 String basedir = project.getBasedir().getAbsolutePath();
616 String file = "";
617
618 for (int eventType = xpp.getEventType(); eventType != XmlPullParser.END_DOCUMENT; eventType = xpp.next()) {
619 if (eventType != XmlPullParser.START_TAG) {
620 continue;
621 } else if ("file".equals(xpp.getName())) {
622 file = PathTool.getRelativeFilePath(basedir, xpp.getAttributeValue("", "name"));
623 continue;
624 } else if (!"error".equals(xpp.getName())) {
625 continue;
626 }
627
628 String severity = xpp.getAttributeValue("", "severity");
629 String source = xpp.getAttributeValue("", "source");
630 String line = xpp.getAttributeValue("", "line");
631
632 String column = xpp.getAttributeValue("", "column");
633 String message = xpp.getAttributeValue("", "message");
634 String rule = RuleUtil.getName(source);
635 String category = RuleUtil.getCategory(source);
636
637 Violation violation = new Violation(source, file, line, severity, message, rule, category);
638 if (column != null) {
639 violation.setColumn(column);
640 }
641
642 violations.add(violation);
643 }
644
645 return violations;
646 }
647
648 private int countViolations(List<Violation> violations) {
649 List<RuleUtil.Matcher> ignores = violationIgnore == null
650 ? Collections.<RuleUtil.Matcher>emptyList()
651 : RuleUtil.parseMatchers(violationIgnore.split(","));
652
653 int ignored = 0;
654 int countedViolations = 0;
655
656 for (Violation violation : violations) {
657 if (!isViolation(violation.getSeverity())) {
658 continue;
659 }
660
661 if (ignore(ignores, violation.getSource())) {
662 ignored++;
663 continue;
664 }
665
666 countedViolations++;
667 }
668
669 if (ignored > 0) {
670 getLog().info("Ignored " + ignored + " error" + ((ignored > 1L) ? "s" : "") + ", " + countedViolations
671 + " violation" + ((countedViolations > 1) ? "s" : "") + " remaining.");
672 }
673
674 return countedViolations;
675 }
676
677 private void printViolations(List<Violation> violations) {
678 if (!logViolationsToConsole) {
679 return;
680 }
681
682 List<RuleUtil.Matcher> ignores = violationIgnore == null
683 ? Collections.<RuleUtil.Matcher>emptyList()
684 : RuleUtil.parseMatchers(violationIgnore.split(","));
685
686 violations.stream()
687 .filter(violation -> isViolation(violation.getSeverity()))
688 .filter(violation -> !ignore(ignores, violation.getSource()))
689 .forEach(violation -> {
690 final String message = String.format(
691 "%s:[%s%s] (%s) %s: %s",
692 violation.getFile(),
693 violation.getLine(),
694 (Violation.NO_COLUMN.equals(violation.getColumn())) ? "" : (',' + violation.getColumn()),
695 violation.getCategory(),
696 violation.getRuleName(),
697 violation.getMessage());
698 log(violation.getSeverity(), message);
699 });
700 }
701
702 private void log(String severity, String message) {
703 if ("info".equals(severity)) {
704 getLog().info(message);
705 } else if ("warning".equals(severity)) {
706 getLog().warn(message);
707 } else {
708 getLog().error(message);
709 }
710 }
711
712
713
714
715
716
717
718 private boolean isViolation(String severity) {
719 if ("error".equals(severity)) {
720 return "error".equals(violationSeverity)
721 || "warning".equals(violationSeverity)
722 || "info".equals(violationSeverity);
723 } else if ("warning".equals(severity)) {
724 return "warning".equals(violationSeverity) || "info".equals(violationSeverity);
725 } else if ("info".equals(severity)) {
726 return "info".equals(violationSeverity);
727 } else {
728 return false;
729 }
730 }
731
732 private boolean ignore(List<RuleUtil.Matcher> ignores, String source) {
733 for (RuleUtil.Matcher ignore : ignores) {
734 if (ignore.match(source)) {
735 return true;
736 }
737 }
738 return false;
739 }
740
741 private DefaultLogger getConsoleListener() throws MojoExecutionException {
742 DefaultLogger consoleListener;
743
744 if (useFile == null) {
745 stringOutputStream = new ByteArrayOutputStream();
746 consoleListener = new DefaultLogger(stringOutputStream, OutputStreamOptions.NONE);
747 } else {
748 OutputStream out = getOutputStream(useFile);
749
750 consoleListener = new DefaultLogger(out, OutputStreamOptions.CLOSE);
751 }
752
753 return consoleListener;
754 }
755
756 private OutputStream getOutputStream(File file) throws MojoExecutionException {
757 File parentFile = file.getAbsoluteFile().getParentFile();
758
759 if (!parentFile.exists()) {
760 parentFile.mkdirs();
761 }
762
763 FileOutputStream fileOutputStream;
764 try {
765 fileOutputStream = new FileOutputStream(file);
766 } catch (FileNotFoundException e) {
767 throw new MojoExecutionException("Unable to create output stream: " + file, e);
768 }
769 return fileOutputStream;
770 }
771
772 private AuditListener getListener() throws MojoFailureException, MojoExecutionException {
773 AuditListener listener = null;
774
775 if (StringUtils.isNotEmpty(outputFileFormat)) {
776 File resultFile = outputFile;
777
778 OutputStream out = getOutputStream(resultFile);
779
780 if ("xml".equals(outputFileFormat)) {
781 listener = new XMLLogger(out, OutputStreamOptions.CLOSE);
782 } else if ("plain".equals(outputFileFormat)) {
783 try {
784
785
786 outputXmlFile =
787 Files.createTempFile("checkstyle-result", ".xml").toFile();
788 outputXmlFile.deleteOnExit();
789 OutputStream xmlOut = getOutputStream(outputXmlFile);
790 CompositeAuditListener compoundListener = new CompositeAuditListener();
791 compoundListener.addListener(new XMLLogger(xmlOut, OutputStreamOptions.CLOSE));
792 compoundListener.addListener(new DefaultLogger(out, OutputStreamOptions.CLOSE));
793 listener = compoundListener;
794 } catch (IOException e) {
795 throw new MojoExecutionException("Unable to create temporary file", e);
796 }
797 } else {
798 throw new MojoFailureException(
799 "Invalid output file format: (" + outputFileFormat + "). Must be 'plain' or 'xml'.");
800 }
801 }
802
803 return listener;
804 }
805
806 private List<Artifact> collectArtifacts(String hint) {
807 List<Artifact> artifacts = new ArrayList<>();
808
809 PluginManagement pluginManagement = project.getBuild().getPluginManagement();
810 if (pluginManagement != null) {
811 artifacts.addAll(getCheckstylePluginDependenciesAsArtifacts(pluginManagement.getPluginsAsMap(), hint));
812 }
813
814 artifacts.addAll(
815 getCheckstylePluginDependenciesAsArtifacts(project.getBuild().getPluginsAsMap(), hint));
816
817 return artifacts;
818 }
819
820 private List<Artifact> getCheckstylePluginDependenciesAsArtifacts(Map<String, Plugin> plugins, String hint) {
821 List<Artifact> artifacts = new ArrayList<>();
822
823 Plugin checkstylePlugin = plugins.get(plugin.getGroupId() + ":" + plugin.getArtifactId());
824 if (checkstylePlugin != null) {
825 for (Dependency dep : checkstylePlugin.getDependencies()) {
826
827 String depKey = dep.getGroupId() + ":" + dep.getArtifactId();
828 artifacts.add(plugin.getArtifactMap().get(depKey));
829 }
830 }
831 return artifacts;
832 }
833
834 private List<File> getSourceDirectories() {
835 if (sourceDirectories == null) {
836 sourceDirectories = project.getCompileSourceRoots();
837 }
838 List<File> sourceDirs = new ArrayList<>(sourceDirectories.size());
839 for (String sourceDir : sourceDirectories) {
840 sourceDirs.add(FileUtils.resolveFile(project.getBasedir(), sourceDir));
841 }
842 return sourceDirs;
843 }
844
845 private List<File> getTestSourceDirectories() {
846 if (testSourceDirectories == null) {
847 testSourceDirectories = project.getTestCompileSourceRoots();
848 }
849 List<File> testSourceDirs = new ArrayList<>(testSourceDirectories.size());
850 for (String testSourceDir : testSourceDirectories) {
851 testSourceDirs.add(FileUtils.resolveFile(project.getBasedir(), testSourceDir));
852 }
853 return testSourceDirs;
854 }
855 }