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