View Javadoc
1   package org.apache.maven.plugins.checkstyle;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.BufferedReader;
23  import java.io.ByteArrayOutputStream;
24  import java.io.File;
25  import java.io.FileNotFoundException;
26  import java.io.FileOutputStream;
27  import java.io.IOException;
28  import java.io.OutputStream;
29  import java.io.Reader;
30  import java.nio.file.Files;
31  import java.util.ArrayList;
32  import java.util.Collections;
33  import java.util.List;
34  import java.util.Map;
35  
36  import org.apache.commons.lang3.StringUtils;
37  import org.apache.maven.artifact.Artifact;
38  import org.apache.maven.model.Dependency;
39  import org.apache.maven.model.Plugin;
40  import org.apache.maven.model.PluginManagement;
41  import org.apache.maven.model.Resource;
42  import org.apache.maven.plugin.AbstractMojo;
43  import org.apache.maven.plugin.MojoExecutionException;
44  import org.apache.maven.plugin.MojoFailureException;
45  import org.apache.maven.plugin.descriptor.PluginDescriptor;
46  import org.apache.maven.plugins.annotations.Component;
47  import org.apache.maven.plugins.annotations.LifecyclePhase;
48  import org.apache.maven.plugins.annotations.Mojo;
49  import org.apache.maven.plugins.annotations.Parameter;
50  import org.apache.maven.plugins.annotations.ResolutionScope;
51  import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutor;
52  import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutorException;
53  import org.apache.maven.plugins.checkstyle.exec.CheckstyleExecutorRequest;
54  import org.apache.maven.project.MavenProject;
55  import org.codehaus.plexus.configuration.PlexusConfiguration;
56  import org.codehaus.plexus.util.FileUtils;
57  import org.codehaus.plexus.util.PathTool;
58  import org.codehaus.plexus.util.ReaderFactory;
59  import org.codehaus.plexus.util.xml.pull.MXParser;
60  import org.codehaus.plexus.util.xml.pull.XmlPullParser;
61  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
62  
63  import com.puppycrawl.tools.checkstyle.DefaultLogger;
64  import com.puppycrawl.tools.checkstyle.XMLLogger;
65  import com.puppycrawl.tools.checkstyle.api.AuditListener;
66  import com.puppycrawl.tools.checkstyle.api.AutomaticBean.OutputStreamOptions;
67  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
68  
69  /**
70   * Performs Checkstyle analysis and outputs violations or a count of violations
71   * to the console, potentially failing the build.
72   * It can also be configured to re-use an earlier analysis.
73   *
74   * @author <a href="mailto:joakim@erdfelt.net">Joakim Erdfelt</a>
75   *
76   */
77  @Mojo( name = "check", defaultPhase = LifecyclePhase.VERIFY, requiresDependencyResolution = ResolutionScope.NONE,
78         threadSafe = true )
79  public class CheckstyleViolationCheckMojo
80      extends AbstractMojo
81  {
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>" 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( 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( defaultValue = "<?xml version=\"1.0\"?>\n"
466             + "<!DOCTYPE module PUBLIC \"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN\"\n"
467             + "        \"https://checkstyle.org/dtds/configuration_1_3.dtd\">\n" )
468     private String checkstyleRulesHeader;
469 
470     /**
471      * Specifies whether modules with a configured severity of <code>ignore</code> should be omitted during Checkstyle
472      * invocation.
473      *
474      * @since 3.0.0
475      */
476     @Parameter( defaultValue = "false" )
477     private boolean omitIgnoredModules;
478 
479     private ByteArrayOutputStream stringOutputStream;
480 
481     private File outputXmlFile;
482 
483     /** {@inheritDoc} */
484     public void execute()
485         throws MojoExecutionException, MojoFailureException
486     {
487         checkDeprecatedParameterUsage( sourceDirectory, "sourceDirectory", "sourceDirectories" );
488         checkDeprecatedParameterUsage( testSourceDirectory, "testSourceDirectory", "testSourceDirectories" );
489         if ( skip )
490         {
491             return;
492         }
493 
494         outputXmlFile = outputFile;
495 
496         if ( !skipExec )
497         {
498             String effectiveConfigLocation = configLocation;
499             if ( checkstyleRules != null )
500             {
501                 if ( !DEFAULT_CONFIG_LOCATION.equals( configLocation ) )
502                 {
503                     throw new MojoExecutionException( "If you use inline configuration for rules, don't specify "
504                         + "a configLocation" );
505                 }
506                 if ( checkstyleRules.getChildCount() > 1 )
507                 {
508                     throw new MojoExecutionException( "Currently only one root module is supported" );
509                 }
510 
511                 PlexusConfiguration checkerModule = checkstyleRules.getChild( 0 );
512 
513                 try
514                 {
515                     FileUtils.forceMkdir( rulesFiles.getParentFile() );
516                     FileUtils.fileWrite( rulesFiles, checkstyleRulesHeader + checkerModule.toString() );
517                 }
518                 catch ( final IOException e )
519                 {
520                     throw new MojoExecutionException( e.getMessage(), e );
521                 }
522                 effectiveConfigLocation = rulesFiles.getAbsolutePath();
523             }
524 
525             ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
526 
527             try
528             {
529                 CheckstyleExecutorRequest request = new CheckstyleExecutorRequest();
530                 request.setConsoleListener( getConsoleListener() ).setConsoleOutput( consoleOutput )
531                     .setExcludes( excludes ).setFailsOnError( failsOnError ).setIncludes( includes )
532                     .setResourceIncludes( resourceIncludes )
533                     .setResourceExcludes( resourceExcludes )
534                     .setIncludeResources( includeResources )
535                     .setIncludeTestResources( includeTestResources )
536                     .setIncludeTestSourceDirectory( includeTestSourceDirectory ).setListener( getListener() )
537                     .setProject( project ).setSourceDirectories( getSourceDirectories() )
538                     .setResources( resources ).setTestResources( testResources )
539                     .setStringOutputStream( stringOutputStream ).setSuppressionsLocation( suppressionsLocation )
540                     .setTestSourceDirectories( getTestSourceDirectories() ).setConfigLocation( effectiveConfigLocation )
541                     .setConfigurationArtifacts( collectArtifacts( "config" ) )
542                     .setPropertyExpansion( propertyExpansion )
543                     .setHeaderLocation( headerLocation ).setLicenseArtifacts( collectArtifacts( "license" ) )
544                     .setCacheFile( cacheFile ).setSuppressionsFileExpression( suppressionsFileExpression )
545                     .setEncoding( inputEncoding ).setPropertiesLocation( propertiesLocation )
546                     .setOmitIgnoredModules( omitIgnoredModules );
547                 checkstyleExecutor.executeCheckstyle( request );
548 
549             }
550             catch ( CheckstyleException e )
551             {
552                 throw new MojoExecutionException( "Failed during checkstyle configuration", e );
553             }
554             catch ( CheckstyleExecutorException e )
555             {
556                 throw new MojoExecutionException( "Failed during checkstyle execution", e );
557             }
558             finally
559             {
560                 //be sure to restore original context classloader
561                 Thread.currentThread().setContextClassLoader( currentClassLoader );
562             }
563         }
564 
565         if ( !"xml".equals( outputFileFormat ) && skipExec )
566         {
567             throw new MojoExecutionException( "Output format is '" + outputFileFormat
568                 + "', checkstyle:check requires format to be 'xml' when using skipExec." );
569         }
570 
571         if ( !outputXmlFile.exists() )
572         {
573             getLog().info( "Unable to perform checkstyle:check, unable to find checkstyle:checkstyle outputFile." );
574             return;
575         }
576 
577         try ( Reader reader = new BufferedReader( ReaderFactory.newXmlReader( outputXmlFile ) ) )
578         {
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             {
591                 if ( failOnViolation )
592                 {
593                     if ( maxAllowedViolations > 0 )
594                     {
595                         msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
596                     }
597                     throw new MojoFailureException( msg );
598                 }
599 
600                 getLog().warn( "checkstyle:check violations detected but failOnViolation set to false" );
601             }
602             if ( logViolationCountToConsole )
603             {
604                 if ( maxAllowedViolations > 0 )
605                 {
606                   msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
607                 }
608                 getLog().info( msg );
609             }
610         }
611         catch ( IOException | XmlPullParserException e )
612         {
613             throw new MojoExecutionException( "Unable to read Checkstyle results xml: "
614                 + outputXmlFile.getAbsolutePath(), e );
615         }
616     }
617 
618     private void checkDeprecatedParameterUsage( Object parameter, String name, String replacement )
619         throws MojoFailureException
620     {
621         if ( parameter != null )
622         {
623             throw new MojoFailureException( "You are using '" + name + "' which has been removed"
624                 + " from the maven-checkstyle-plugin. " + "Please use '" + replacement
625                 + "' and refer to the >>Major Version Upgrade to version 3.0.0<< " + "on the plugin site." );
626         }
627     }
628 
629     private List<Violation> getViolations( XmlPullParser xpp )
630         throws XmlPullParserException, IOException
631     {
632         List<Violation> violations = new ArrayList<>();
633 
634         String basedir = project.getBasedir().getAbsolutePath();
635         String file = "";
636 
637         for ( int eventType = xpp.getEventType(); eventType != XmlPullParser.END_DOCUMENT; eventType = xpp.next() )
638         {
639             if ( eventType != XmlPullParser.START_TAG )
640             {
641                 continue;
642             }
643             else if ( "file".equals( xpp.getName() ) )
644             {
645                 file = PathTool.getRelativeFilePath( basedir, xpp.getAttributeValue( "", "name" ) );
646                 continue;
647             }
648             else if ( ! "error".equals( xpp.getName() ) )
649             {
650                 continue;
651             }
652 
653             String severity = xpp.getAttributeValue( "", "severity" );
654             String source = xpp.getAttributeValue( "", "source" );
655             String line = xpp.getAttributeValue( "", "line" );
656             /* Nullable */
657             String column = xpp.getAttributeValue( "", "column" );
658             String message = xpp.getAttributeValue( "", "message" );
659             String rule = RuleUtil.getName( source );
660             String category = RuleUtil.getCategory( source );
661 
662             Violation violation = new Violation(
663                 source,
664                 file,
665                 line,
666                 severity,
667                 message,
668                 rule,
669                 category
670             );
671             if ( column != null )
672             {
673                 violation.setColumn( column );
674             }
675 
676             violations.add( violation );
677         }
678 
679         return violations;
680     }
681 
682     private int countViolations( List<Violation> violations )
683     {
684         List<RuleUtil.Matcher> ignores = violationIgnore == null ? Collections.<RuleUtil.Matcher>emptyList()
685             : RuleUtil.parseMatchers( violationIgnore.split( "," ) );
686 
687         int ignored = 0;
688         int countedViolations = 0;
689 
690         for ( Violation violation : violations )
691         {
692             if ( ! isViolation( violation.getSeverity() ) )
693             {
694                 continue;
695             }
696 
697             if ( ignore( ignores, violation.getSource() ) )
698             {
699                 ignored++;
700                 continue;
701             }
702 
703             countedViolations++;
704         }
705 
706         if ( ignored > 0 )
707         {
708             getLog().info( "Ignored " + ignored + " error" + ( ( ignored > 1L ) ? "s" : "" ) + ", " + countedViolations
709                 + " violation" + ( ( countedViolations > 1 ) ? "s" : "" ) + " remaining." );
710         }
711 
712         return countedViolations;
713     }
714 
715     private void printViolations( List<Violation> violations )
716     {
717         if ( ! logViolationsToConsole )
718         {
719             return;
720         }
721 
722         List<RuleUtil.Matcher> ignores = violationIgnore == null ? Collections.<RuleUtil.Matcher>emptyList()
723             : RuleUtil.parseMatchers( violationIgnore.split( "," ) );
724 
725         violations.stream()
726             .filter( violation -> isViolation( violation.getSeverity() ) )
727             .filter( violation -> !ignore( ignores, violation.getSource() ) )
728             .forEach( violation ->
729             {
730                 final String message = String.format( "%s:[%s%s] (%s) %s: %s",
731                     violation.getFile(),
732                     violation.getLine(),
733                     ( Violation.NO_COLUMN.equals( violation.getColumn() ) ) ? "" : ( ',' + violation.getColumn() ),
734                     violation.getCategory(),
735                     violation.getRuleName(),
736                     violation.getMessage() );
737                 log( violation.getSeverity(), message );
738             } );
739     }
740 
741     private void log( String severity, String message )
742     {
743         if ( "info".equals( severity ) )
744         {
745             getLog().info( message );
746         }
747         else if ( "warning".equals( severity ) )
748         {
749             getLog().warn( message );
750         }
751         else
752         {
753             getLog().error( message );
754         }
755     }
756 
757     /**
758      * Checks if the given severity is considered a violation.
759      *
760      * @param severity The severity to check
761      * @return <code>true</code> if the given severity is a violation, otherwise <code>false</code>
762      */
763     private boolean isViolation( String severity )
764     {
765         if ( "error".equals( severity ) )
766         {
767             return "error".equals( violationSeverity ) || "warning".equals( violationSeverity )
768                 || "info".equals( violationSeverity );
769         }
770         else if ( "warning".equals( severity ) )
771         {
772             return "warning".equals( violationSeverity ) || "info".equals( violationSeverity );
773         }
774         else if ( "info".equals( severity ) )
775         {
776             return "info".equals( violationSeverity );
777         }
778         else
779         {
780             return false;
781         }
782     }
783 
784     private boolean ignore( List<RuleUtil.Matcher> ignores, String source )
785     {
786         for ( RuleUtil.Matcher ignore : ignores )
787         {
788             if ( ignore.match( source ) )
789             {
790                 return true;
791             }
792         }
793         return false;
794     }
795 
796     private DefaultLogger getConsoleListener()
797         throws MojoExecutionException
798     {
799         DefaultLogger consoleListener;
800 
801         if ( useFile == null )
802         {
803             stringOutputStream = new ByteArrayOutputStream();
804             consoleListener = new DefaultLogger( stringOutputStream, OutputStreamOptions.NONE );
805         }
806         else
807         {
808             OutputStream out = getOutputStream( useFile );
809 
810             consoleListener = new DefaultLogger( out, OutputStreamOptions.CLOSE );
811         }
812 
813         return consoleListener;
814     }
815 
816     private OutputStream getOutputStream( File file )
817         throws MojoExecutionException
818     {
819         File parentFile = file.getAbsoluteFile().getParentFile();
820 
821         if ( !parentFile.exists() )
822         {
823             parentFile.mkdirs();
824         }
825 
826         FileOutputStream fileOutputStream;
827         try
828         {
829             fileOutputStream = new FileOutputStream( file );
830         }
831         catch ( FileNotFoundException e )
832         {
833             throw new MojoExecutionException( "Unable to create output stream: " + file, e );
834         }
835         return fileOutputStream;
836     }
837 
838     private AuditListener getListener()
839         throws MojoFailureException, MojoExecutionException
840     {
841         AuditListener listener = null;
842 
843         if ( StringUtils.isNotEmpty( outputFileFormat ) )
844         {
845             File resultFile = outputFile;
846 
847             OutputStream out = getOutputStream( resultFile );
848 
849             if ( "xml".equals( outputFileFormat ) )
850             {
851                 listener = new XMLLogger( out, OutputStreamOptions.CLOSE );
852             }
853             else if ( "plain".equals( outputFileFormat ) )
854             {
855                 try
856                 {
857                     // Write a plain output file to the standard output file,
858                     // and write an XML output file to the temp directory that can be used to count violations
859                     outputXmlFile = Files.createTempFile( "checkstyle-result", ".xml" ).toFile();
860                     outputXmlFile.deleteOnExit();
861                     OutputStream xmlOut = getOutputStream( outputXmlFile );
862                     CompositeAuditListener compoundListener = new CompositeAuditListener();
863                     compoundListener.addListener( new XMLLogger( xmlOut, OutputStreamOptions.CLOSE ) );
864                     compoundListener.addListener( new DefaultLogger( out, OutputStreamOptions.CLOSE ) );
865                     listener = compoundListener;
866                 }
867                 catch ( IOException e )
868                 {
869                     throw new MojoExecutionException( "Unable to create temporary file", e );
870                 }
871             }
872             else
873             {
874                 throw new MojoFailureException( "Invalid output file format: (" + outputFileFormat
875                     + "). Must be 'plain' or 'xml'." );
876             }
877         }
878 
879         return listener;
880     }
881 
882     private List<Artifact> collectArtifacts( String hint )
883     {
884         List<Artifact> artifacts = new ArrayList<>();
885 
886         PluginManagement pluginManagement = project.getBuild().getPluginManagement();
887         if ( pluginManagement != null )
888         {
889             artifacts.addAll( getCheckstylePluginDependenciesAsArtifacts( pluginManagement.getPluginsAsMap(), hint ) );
890         }
891 
892         artifacts.addAll( getCheckstylePluginDependenciesAsArtifacts( project.getBuild().getPluginsAsMap(), hint ) );
893 
894         return artifacts;
895     }
896 
897     private List<Artifact> getCheckstylePluginDependenciesAsArtifacts( Map<String, Plugin> plugins, String hint )
898     {
899         List<Artifact> artifacts = new ArrayList<>();
900 
901         Plugin checkstylePlugin = plugins.get( plugin.getGroupId() + ":" + plugin.getArtifactId() );
902         if ( checkstylePlugin != null )
903         {
904             for ( Dependency dep : checkstylePlugin.getDependencies() )
905             {
906              // @todo if we can filter on hints, it should be done here...
907                 String depKey = dep.getGroupId() + ":" + dep.getArtifactId();
908                 artifacts.add( plugin.getArtifactMap().get( depKey ) );
909             }
910         }
911         return artifacts;
912     }
913 
914     private List<File> getSourceDirectories()
915     {
916         if ( sourceDirectories == null )
917         {
918             sourceDirectories = project.getCompileSourceRoots();
919         }
920         List<File> sourceDirs = new ArrayList<>( sourceDirectories.size() );
921         for ( String sourceDir : sourceDirectories )
922         {
923             sourceDirs.add( FileUtils.resolveFile( project.getBasedir(), sourceDir ) );
924         }
925         return sourceDirs;
926     }
927 
928     private List<File> getTestSourceDirectories()
929     {
930         if ( testSourceDirectories == null )
931         {
932             testSourceDirectories = project.getTestCompileSourceRoots();
933         }
934         List<File> testSourceDirs = new ArrayList<>( testSourceDirectories.size() );
935         for ( String testSourceDir : testSourceDirectories )
936         {
937             testSourceDirs.add( FileUtils.resolveFile( project.getBasedir(), testSourceDir ) );
938         }
939         return testSourceDirs;
940     }
941 
942 }