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.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   * Performs Checkstyle analysis and outputs violations or a count of violations
69   * to the console, potentially failing the build.
70   * It can also be configured to re-use an earlier analysis.
71   *
72   * @author <a href="mailto:joakim@erdfelt.net">Joakim Erdfelt</a>
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       * Specifies the path and filename to save the Checkstyle output. The format
87       * of the output file is determined by the <code>outputFileFormat</code>
88       * parameter.
89       */
90      @Parameter(property = "checkstyle.output.file", defaultValue = "${project.build.directory}/checkstyle-result.xml")
91      private File outputFile;
92  
93      /**
94       * Specifies the format of the output to be used when writing to the output
95       * file. Valid values are "<code>plain</code>" and "<code>xml</code>".
96       */
97      @Parameter(property = "checkstyle.output.format", defaultValue = "xml")
98      private String outputFileFormat;
99  
100     /**
101      * Fail the build on a violation. The goal checks for the violations
102      * after logging them (if {@link #logViolationsToConsole} is {@code true}).
103      * Compare this to {@link #failsOnError} which fails the build immediately
104      * before examining the output log.
105      */
106     @Parameter(property = "checkstyle.failOnViolation", defaultValue = "true")
107     private boolean failOnViolation;
108 
109     /**
110      * The maximum number of allowed violations. The execution fails only if the
111      * number of violations is above this limit.
112      *
113      * @since 2.3
114      */
115     @Parameter(property = "checkstyle.maxAllowedViolations", defaultValue = "0")
116     private int maxAllowedViolations;
117 
118     /**
119      * The lowest severity level that is considered a violation.
120      * Valid values are "<code>error</code>", "<code>warning</code>" and "<code>info</code>".
121      *
122      * @since 2.2
123      */
124     @Parameter(property = "checkstyle.violationSeverity", defaultValue = "error")
125     private String violationSeverity = "error";
126 
127     /**
128      * Violations to ignore. This is a comma-separated list, each value being either
129      * a rule name, a rule category or a java package name of rule class.
130      *
131      * @since 2.13
132      */
133     @Parameter(property = "checkstyle.violation.ignore")
134     private String violationIgnore;
135 
136     /**
137      * Skip entire check.
138      *
139      * @since 2.2
140      */
141     @Parameter(property = "checkstyle.skip", defaultValue = "false")
142     private boolean skip;
143 
144     /**
145      * Skip Checkstyle execution will only scan the outputFile.
146      *
147      * @since 2.5
148      */
149     @Parameter(property = "checkstyle.skipExec", defaultValue = "false")
150     private boolean skipExec;
151 
152     /**
153      * Output the detected violations to the console.
154      *
155      * @since 2.3
156      */
157     @Parameter(property = "checkstyle.console", defaultValue = "true")
158     private boolean logViolationsToConsole;
159 
160     /**
161      * Output the detected violation count to the console.
162      *
163      * @since 3.0.1
164      */
165     @Parameter(property = "checkstyle.logViolationCount", defaultValue = "true")
166     private boolean logViolationCountToConsole;
167 
168     /**
169      * Specifies the location of the resources to be used for Checkstyle.
170      *
171      * @since 2.11
172      */
173     @Parameter(defaultValue = "${project.resources}", readonly = true)
174     protected List<Resource> resources;
175 
176     /**
177      * Specifies the location of the test resources to be used for Checkstyle.
178      *
179      * @since 2.16
180      */
181     @Parameter(defaultValue = "${project.testResources}", readonly = true)
182     protected List<Resource> testResources;
183 
184     /**
185      * <p>
186      * Specifies the location of the XML configuration to use.
187      * <p>
188      * Potential values are a filesystem path, a URL, or a classpath resource.
189      * This parameter expects that the contents of the location conform to the
190      * xml format (Checkstyle <a
191      * href="https://checkstyle.org/config.html#Modules">Checker
192      * module</a>) configuration of rulesets.
193      * <p>
194      * This parameter is resolved as resource, URL, then file. If successfully
195      * resolved, the contents of the configuration is copied into the
196      * <code>${project.build.directory}/checkstyle-configuration.xml</code>
197      * file before being passed to Checkstyle as a configuration.
198      * <p>
199      * There are 2 predefined rulesets.
200      * <ul>
201      * <li><code>sun_checks.xml</code>: Sun Checks.</li>
202      * <li><code>google_checks.xml</code>: Google Checks.</li>
203      * </ul>
204      *
205      * @since 2.5
206      */
207     @Parameter(property = "checkstyle.config.location", defaultValue = DEFAULT_CONFIG_LOCATION)
208     private String configLocation;
209 
210     /**
211      * <p>
212      * Specifies the location of the properties file.
213      * <p>
214      * This parameter is resolved as URL, File then resource. If successfully
215      * resolved, the contents of the properties location is copied into the
216      * <code>${project.build.directory}/checkstyle-checker.properties</code>
217      * file before being passed to Checkstyle for loading.
218      * <p>
219      * The contents of the <code>propertiesLocation</code> will be made
220      * available to Checkstyle for specifying values for parameters within the
221      * xml configuration (specified in the <code>configLocation</code>
222      * parameter).
223      *
224      * @since 2.5
225      */
226     @Parameter(property = "checkstyle.properties.location")
227     private String propertiesLocation;
228 
229     /**
230      * Allows for specifying raw property expansion information.
231      */
232     @Parameter
233     private String propertyExpansion;
234 
235     /**
236      * <p>
237      * Specifies the location of the License file (a.k.a. the header file) that
238      * can be used by Checkstyle to verify that source code has the correct
239      * license header.
240      * <p>
241      * You need to use <code>${checkstyle.header.file}</code> in your Checkstyle xml
242      * configuration to reference the name of this header file.
243      * <p>
244      * For instance:
245      * <pre>
246      * &lt;module name="RegexpHeader"&gt;
247      *   &lt;property name="headerFile" value="${checkstyle.header.file}"/&gt;
248      * &lt;/module&gt;
249      * </pre>
250      *
251      * @since 2.0-beta-2
252      */
253     @Parameter(property = "checkstyle.header.file", defaultValue = "LICENSE.txt")
254     private String headerLocation;
255 
256     /**
257      * Specifies the cache file used to speed up Checkstyle on successive runs.
258      */
259     @Parameter(defaultValue = "${project.build.directory}/checkstyle-cachefile")
260     private String cacheFile;
261 
262     /**
263      * The key to be used in the properties for the suppressions file.
264      *
265      * @since 2.1
266      */
267     @Parameter(property = "checkstyle.suppression.expression", defaultValue = "checkstyle.suppressions.file")
268     private String suppressionsFileExpression;
269 
270     /**
271      * <p>
272      * Specifies the location of the suppressions XML file to use.
273      * <p>
274      * This parameter is resolved as resource, URL, then file. If successfully
275      * resolved, the contents of the suppressions XML is copied into the
276      * <code>${project.build.directory}/checkstyle-suppressions.xml</code> file
277      * before being passed to Checkstyle for loading.
278      * <p>
279      * See <code>suppressionsFileExpression</code> for the property that will
280      * be made available to your Checkstyle configuration.
281      *
282      * @since 2.0-beta-2
283      */
284     @Parameter(property = "checkstyle.suppressions.location")
285     private String suppressionsLocation;
286 
287     /**
288      * The file encoding to use when reading the source files. If the property <code>project.build.sourceEncoding</code>
289      * is not set, the platform default encoding is used. <strong>Note:</strong> This parameter always overrides the
290      * property <code>charset</code> from Checkstyle's <code>TreeWalker</code> module.
291      *
292      * @since 2.2
293      */
294     @Parameter(property = "encoding", defaultValue = "${project.build.sourceEncoding}")
295     private String inputEncoding;
296 
297     /**
298      * @since 2.5
299      */
300     @Component(role = CheckstyleExecutor.class, hint = "default")
301     protected CheckstyleExecutor checkstyleExecutor;
302 
303     /**
304      * Output errors to console.
305      */
306     @Parameter(property = "checkstyle.consoleOutput", defaultValue = "false")
307     private boolean consoleOutput;
308 
309     /**
310      * The Maven Project Object.
311      */
312     @Parameter(defaultValue = "${project}", readonly = true, required = true)
313     protected MavenProject project;
314 
315     /**
316      * The Plugin Descriptor
317      */
318     @Parameter(defaultValue = "${plugin}", readonly = true, required = true)
319     private PluginDescriptor plugin;
320 
321     /**
322      * If <code>null</code>, the Checkstyle plugin will display violations on stdout.
323      * Otherwise, a text file will be created with the violations.
324      */
325     @Parameter
326     private File useFile;
327 
328     /**
329      * Specifies the names filter of the source files to be excluded for
330      * Checkstyle.
331      */
332     @Parameter(property = "checkstyle.excludes")
333     private String excludes;
334 
335     /**
336      * Specifies the names filter of the source files to be used for Checkstyle.
337      */
338     @Parameter(property = "checkstyle.includes", defaultValue = JAVA_FILES, required = true)
339     private String includes;
340 
341     /**
342      * Specifies the names filter of the files to be excluded for
343      * Checkstyle when checking resources.
344      * @since 2.11
345      */
346     @Parameter(property = "checkstyle.resourceExcludes")
347     private String resourceExcludes;
348 
349     /**
350      * Specifies the names filter of the files to be used for Checkstyle when checking resources.
351      * @since 2.11
352      */
353     @Parameter(property = "checkstyle.resourceIncludes", defaultValue = "**/*.properties", required = true)
354     private String resourceIncludes;
355 
356     /**
357      * If this is true, and Checkstyle reported any violations or errors,
358      * the build fails immediately after running Checkstyle, before checking the log
359      * for {@link #logViolationsToConsole}. If you want to use {@link #logViolationsToConsole},
360      * use {@link #failOnViolation} instead of this.
361      */
362     @Parameter(defaultValue = "false")
363     private boolean failsOnError;
364 
365     /**
366      * Specifies the location of the test source directory to be used for Checkstyle.
367      *
368      * @since 2.2
369      * @deprecated instead use {@link #testSourceDirectories}. For version 3.0.0, this parameter is only defined to
370      *             break the build if you use it!
371      */
372     @Deprecated
373     @Parameter
374     private File testSourceDirectory;
375 
376     /**
377      * Specifies the location of the test source directories to be used for Checkstyle.
378      * Default value is <code>${project.testCompileSourceRoots}</code>.
379      * @since 2.13
380      */
381     // Compatibility with all Maven 3: default of 'project.testCompileSourceRoots' is done manually because of MNG-5440
382     @Parameter
383     private List<String> testSourceDirectories;
384 
385     /**
386      * Include or not the test source directory to be used for Checkstyle.
387      *
388      * @since 2.2
389      */
390     @Parameter(defaultValue = "false")
391     private boolean includeTestSourceDirectory;
392 
393     /**
394      * Specifies the location of the source directory to be used for Checkstyle.
395      *
396      * @deprecated instead use {@link #sourceDirectories}. For version 3.0.0, this parameter is only defined to break
397      *             the build if you use it!
398      */
399     @Deprecated
400     @Parameter
401     private File sourceDirectory;
402 
403     /**
404      * Specifies the location of the source directories to be used for Checkstyle.
405      * Default value is <code>${project.compileSourceRoots}</code>.
406      * @since 2.13
407      */
408     // Compatibility with all Maven 3: default of 'project.compileSourceRoots' is done manually because of MNG-5440
409     @Parameter
410     private List<String> sourceDirectories;
411 
412     /**
413      * Whether to apply Checkstyle to resource directories.
414      * @since 2.11
415      */
416     @Parameter(property = "checkstyle.includeResources", defaultValue = "true", required = true)
417     private boolean includeResources = true;
418 
419     /**
420      * Whether to apply Checkstyle to test resource directories.
421      * @since 2.11
422      */
423     @Parameter(property = "checkstyle.includeTestResources", defaultValue = "true", required = true)
424     private boolean includeTestResources = true;
425 
426     /**
427      * By using this property, you can specify the whole Checkstyle rules
428      * inline directly inside this pom.
429      *
430      * <pre>
431      * &lt;plugin&gt;
432      *   ...
433      *   &lt;configuration&gt;
434      *     &lt;checkstyleRules&gt;
435      *       &lt;module name="Checker"&gt;
436      *         &lt;module name="FileTabCharacter"&gt;
437      *           &lt;property name="eachLine" value="true" /&gt;
438      *         &lt;/module&gt;
439      *         &lt;module name="TreeWalker"&gt;
440      *           &lt;module name="EmptyBlock"/&gt;
441      *         &lt;/module&gt;
442      *       &lt;/module&gt;
443      *     &lt;/checkstyleRules&gt;
444      *   &lt;/configuration&gt;
445      *   ...
446      * </pre>
447      *
448      * @since 2.12
449      */
450     @Parameter
451     private PlexusConfiguration checkstyleRules;
452 
453     /**
454      * Dump file for inlined Checkstyle rules.
455      */
456     @Parameter(
457             property = "checkstyle.output.rules.file",
458             defaultValue = "${project.build.directory}/checkstyle-rules.xml")
459     private File rulesFiles;
460 
461     /**
462      * The header to use for the inline configuration.
463      * Only used when you specify {@code checkstyleRules}.
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      * Specifies whether modules with a configured severity of <code>ignore</code> should be omitted during Checkstyle
473      * invocation.
474      *
475      * @since 3.0.0
476      */
477     @Parameter(defaultValue = "false")
478     private boolean omitIgnoredModules;
479 
480     /**
481      * Specifies whether generated source files should be excluded from Checkstyle.
482      *
483      * @since 3.3.1
484      */
485     @Parameter(property = "checkstyle.excludeGeneratedSources", defaultValue = "false")
486     private boolean excludeGeneratedSources;
487 
488     private ByteArrayOutputStream stringOutputStream;
489 
490     private File outputXmlFile;
491 
492     /** {@inheritDoc} */
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                 // be sure to restore original context classloader
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             /* Nullable */
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      * Checks if the given severity is considered a violation.
722      *
723      * @param severity The severity to check
724      * @return <code>true</code> if the given severity is a violation, otherwise <code>false</code>
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                     // Write a plain output file to the standard output file,
793                     // and write an XML output file to the temp directory that can be used to count violations
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                 // @todo if we can filter on hints, it should be done here...
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 }