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