View Javadoc

1   package org.apache.maven.plugin.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 com.puppycrawl.tools.checkstyle.DefaultLogger;
23  import com.puppycrawl.tools.checkstyle.XMLLogger;
24  import com.puppycrawl.tools.checkstyle.api.AuditListener;
25  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
26  import org.apache.maven.plugin.AbstractMojo;
27  import org.apache.maven.plugin.MojoExecutionException;
28  import org.apache.maven.plugin.MojoFailureException;
29  import org.apache.maven.plugins.annotations.Component;
30  import org.apache.maven.plugins.annotations.LifecyclePhase;
31  import org.apache.maven.plugins.annotations.Mojo;
32  import org.apache.maven.plugins.annotations.Parameter;
33  import org.apache.maven.plugins.annotations.ResolutionScope;
34  import org.apache.maven.project.MavenProject;
35  import org.codehaus.plexus.util.ReaderFactory;
36  import org.codehaus.plexus.util.StringUtils;
37  import org.codehaus.plexus.util.xml.pull.MXParser;
38  import org.codehaus.plexus.util.xml.pull.XmlPullParser;
39  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
40  
41  import java.io.BufferedReader;
42  import java.io.ByteArrayOutputStream;
43  import java.io.File;
44  import java.io.FileNotFoundException;
45  import java.io.FileOutputStream;
46  import java.io.IOException;
47  import java.io.OutputStream;
48  import java.io.Reader;
49  
50  /**
51   * Perform a violation check against the last Checkstyle run to see if there are
52   * any violations. It reads the Checkstyle output file, counts the number of
53   * violations found and displays it on the console.
54   *
55   * @author <a href="mailto:joakim@erdfelt.net">Joakim Erdfelt</a>
56   * @version $Id: CheckstyleViolationCheckMojo.java 1384337 2012-09-13 13:53:19Z olamy $
57   */
58  @Mojo( name = "check", defaultPhase = LifecyclePhase.VERIFY, requiresDependencyResolution = ResolutionScope.TEST,
59         threadSafe = true )
60  public class CheckstyleViolationCheckMojo
61      extends AbstractMojo
62  {
63  
64      private static final String JAVA_FILES = "**\\/*.java";
65  
66      /**
67       * Specifies the path and filename to save the Checkstyle output. The format
68       * of the output file is determined by the <code>outputFileFormat</code>
69       * parameter.
70       */
71      @Parameter( property = "checkstyle.output.file", defaultValue = "${project.build.directory}/checkstyle-result.xml" )
72      private File outputFile;
73  
74      /**
75       * Specifies the format of the output to be used when writing to the output
76       * file. Valid values are "plain" and "xml".
77       */
78      @Parameter( property = "checkstyle.output.format", defaultValue = "xml" )
79      private String outputFileFormat;
80  
81      /**
82       * Do we fail the build on a violation?
83       */
84      @Parameter( property = "checkstyle.failOnViolation", defaultValue = "true" )
85      private boolean failOnViolation;
86  
87      /**
88       * The maximum number of allowed violations. The execution fails only if the
89       * number of violations is above this limit.
90       *
91       * @since 2.3
92       */
93      @Parameter( property = "checkstyle.maxAllowedViolations", defaultValue = "0" )
94      private int maxAllowedViolations = 0;
95  
96      /**
97       * The lowest severity level that is considered a violation.
98       * Valid values are "error", "warning" and "info".
99       *
100      * @since 2.2
101      */
102     @Parameter( property = "checkstyle.violationSeverity", defaultValue = "error" )
103     private String violationSeverity = "error";
104 
105     /**
106      * Skip entire check.
107      *
108      * @since 2.2
109      */
110     @Parameter( property = "checkstyle.skip", defaultValue = "false" )
111     private boolean skip;
112 
113     /**
114      * Skip checktyle execution will only scan the outputFile.
115      *
116      * @since 2.5
117      */
118     @Parameter( property = "checkstyle.skipExec", defaultValue = "false" )
119     private boolean skipExec;
120 
121     /**
122      * Output the detected violations to the console.
123      *
124      * @since 2.3
125      */
126     @Parameter( property = "checkstyle.console", defaultValue = "false" )
127     private boolean logViolationsToConsole;
128 
129     /**
130      * <p>
131      * Specifies the location of the XML configuration to use.
132      * </p>
133      * <p/>
134      * <p>
135      * Potential values are a filesystem path, a URL, or a classpath resource.
136      * This parameter expects that the contents of the location conform to the
137      * xml format (Checkstyle <a
138      * href="http://checkstyle.sourceforge.net/config.html#Modules">Checker
139      * module</a>) configuration of rulesets.
140      * </p>
141      * <p/>
142      * <p>
143      * This parameter is resolved as resource, URL, then file. If successfully
144      * resolved, the contents of the configuration is copied into the
145      * <code>${project.build.directory}/checkstyle-configuration.xml</code>
146      * file before being passed to Checkstyle as a configuration.
147      * </p>
148      * <p/>
149      * <p>
150      * There are 4 predefined rulesets.
151      * </p>
152      * <p/>
153      * <ul>
154      * <li><code>config/sun_checks.xml</code>: Sun Checks.</li>
155      * <li><code>config/turbine_checks.xml</code>: Turbine Checks.</li>
156      * <li><code>config/avalon_checks.xml</code>: Avalon Checks.</li>
157      * <li><code>config/maven_checks.xml</code>: Maven Source Checks.</li>
158      * </ul>
159      *
160      * @since 2.5
161      */
162     @Parameter( property = "checkstyle.config.location", defaultValue = "config/sun_checks.xml" )
163     private String configLocation;
164 
165     /**
166      * <p>
167      * Specifies the location of the properties file.
168      * </p>
169      * <p/>
170      * <p>
171      * This parameter is resolved as URL, File then resource. If successfully
172      * resolved, the contents of the properties location is copied into the
173      * <code>${project.build.directory}/checkstyle-checker.properties</code>
174      * file before being passed to Checkstyle for loading.
175      * </p>
176      * <p/>
177      * <p>
178      * The contents of the <code>propertiesLocation</code> will be made
179      * available to Checkstyle for specifying values for parameters within the
180      * xml configuration (specified in the <code>configLocation</code>
181      * parameter).
182      * </p>
183      *
184      * @since 2.5
185      */
186     @Parameter( property = "checkstyle.properties.location" )
187     private String propertiesLocation;
188 
189     /**
190      * Allows for specifying raw property expansion information.
191      */
192     @Parameter
193     private String propertyExpansion;
194 
195     /**
196      * <p>
197      * Specifies the location of the License file (a.k.a. the header file) that
198      * can be used by Checkstyle to verify that source code has the correct
199      * license header.
200      * </p>
201      * <p>
202      * You need to use ${checkstyle.header.file} in your Checkstyle xml
203      * configuration to reference the name of this header file.
204      * </p>
205      * <p>
206      * For instance:
207      * </p>
208      * <p>
209      * <code>
210      * &lt;module name="RegexpHeader">
211      * &lt;property name="headerFile" value="${checkstyle.header.file}"/>
212      * &lt;/module>
213      * </code>
214      * </p>
215      *
216      * @since 2.0-beta-2
217      */
218     @Parameter( property = "checkstyle.header.file", defaultValue = "LICENSE.txt" )
219     private String headerLocation;
220 
221     /**
222      * Specifies the cache file used to speed up Checkstyle on successive runs.
223      */
224     @Parameter( defaultValue = "${project.build.directory}/checkstyle-cachefile" )
225     private String cacheFile;
226 
227     /**
228      * The key to be used in the properties for the suppressions file.
229      *
230      * @since 2.1
231      */
232     @Parameter( property = "checkstyle.suppression.expression", defaultValue = "checkstyle.suppressions.file" )
233     private String suppressionsFileExpression;
234 
235     /**
236      * <p>
237      * Specifies the location of the suppressions XML file to use.
238      * </p>
239      * <p/>
240      * <p>
241      * This parameter is resolved as resource, URL, then file. If successfully
242      * resolved, the contents of the suppressions XML is copied into the
243      * <code>${project.build.directory}/checkstyle-supressions.xml</code> file
244      * before being passed to Checkstyle for loading.
245      * </p>
246      * <p/>
247      * <p>
248      * See <code>suppressionsFileExpression</code> for the property that will
249      * be made available to your checkstyle configuration.
250      * </p>
251      *
252      * @since 2.0-beta-2
253      */
254     @Parameter( property = "checkstyle.suppressions.location" )
255     private String suppressionsLocation;
256 
257     /**
258      * The file encoding to use when reading the source files. If the property <code>project.build.sourceEncoding</code>
259      * is not set, the platform default encoding is used. <strong>Note:</strong> This parameter always overrides the
260      * property <code>charset</code> from Checkstyle's <code>TreeWalker</code> module.
261      *
262      * @since 2.2
263      */
264     @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
265     private String encoding;
266 
267     /**
268      * @since 2.5
269      */
270     @Component( role = CheckstyleExecutor.class, hint = "default" )
271     protected CheckstyleExecutor checkstyleExecutor;
272 
273     /**
274      * Output errors to console.
275      */
276     @Parameter( defaultValue = "false" )
277     private boolean consoleOutput;
278 
279     /**
280      * The Maven Project Object.
281      */
282     @Component
283     protected MavenProject project;
284 
285     /**
286      * If <code>null</code>, the Checkstyle plugin will display violations on stdout.
287      * Otherwise, a text file will be created with the violations.
288      */
289     @Parameter
290     private File useFile;
291 
292     /**
293      * Specifies the names filter of the source files to be excluded for
294      * Checkstyle.
295      */
296     @Parameter( property = "checkstyle.excludes" )
297     private String excludes;
298 
299     /**
300      * Specifies the names filter of the source files to be used for Checkstyle.
301      *
302      * <strong>Note:</strong> default value is {@code **\/*.java}.
303      */
304     @Parameter( property = "checkstyle.includes", defaultValue = JAVA_FILES, required = true )
305     private String includes;
306 
307     /**
308      * Specifies if the build should fail upon a violation.
309      */
310     @Parameter( defaultValue = "false" )
311     private boolean failsOnError;
312 
313     /**
314      * Specifies the location of the test source directory to be used for
315      * Checkstyle.
316      *
317      * @since 2.2
318      */
319     @Parameter( defaultValue = "${project.build.testSourceDirectory}" )
320     private File testSourceDirectory;
321 
322     /**
323      * Include or not the test source directory to be used for Checkstyle.
324      *
325      * @since 2.2
326      */
327     @Parameter( defaultValue = "false" )
328     private boolean includeTestSourceDirectory;
329 
330     /**
331      * Specifies the location of the source directory to be used for Checkstyle.
332      */
333     @Parameter( defaultValue = "${project.build.sourceDirectory}", required = true )
334     private File sourceDirectory;
335 
336     private ByteArrayOutputStream stringOutputStream;
337 
338 
339     /** {@inheritDoc} */
340     public void execute()
341         throws MojoExecutionException, MojoFailureException
342     {
343 
344         if ( !skip )
345         {
346 
347             if ( !skipExec )
348             {
349 
350                 ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
351 
352                 try
353                 {
354                     CheckstyleExecutorRequest request = new CheckstyleExecutorRequest();
355                     request.setConsoleListener( getConsoleListener() ).setConsoleOutput( consoleOutput )
356                         .setExcludes( excludes ).setFailsOnError( failsOnError ).setIncludes( includes )
357                         .setIncludeTestSourceDirectory( includeTestSourceDirectory ).setListener( getListener() )
358                         .setLog( getLog() ).setProject( project ).setSourceDirectory( sourceDirectory )
359                         .setStringOutputStream( stringOutputStream ).setSuppressionsLocation( suppressionsLocation )
360                         .setTestSourceDirectory( testSourceDirectory ).setConfigLocation( configLocation )
361                         .setPropertyExpansion( propertyExpansion ).setHeaderLocation( headerLocation )
362                         .setCacheFile( cacheFile ).setSuppressionsFileExpression( suppressionsFileExpression )
363                         .setEncoding( encoding ).setPropertiesLocation( propertiesLocation );
364 
365                     checkstyleExecutor.executeCheckstyle( request );
366 
367                 }
368                 catch ( CheckstyleException e )
369                 {
370                     throw new MojoExecutionException( "Failed during checkstyle configuration", e );
371                 }
372                 catch ( CheckstyleExecutorException e )
373                 {
374                     throw new MojoExecutionException( "Failed during checkstyle execution", e );
375                 }
376                 finally
377                 {
378                     //be sure to restore original context classloader
379                     Thread.currentThread().setContextClassLoader( currentClassLoader );
380                 }
381 
382             }
383             if ( !"xml".equals( outputFileFormat ) )
384             {
385                 throw new MojoExecutionException( "Output format is '" + outputFileFormat
386                     + "', checkstyle:check requires format to be 'xml'." );
387             }
388 
389             if ( !outputFile.exists() )
390             {
391                 getLog().info(
392                                "Unable to perform checkstyle:check, "
393                                    + "unable to find checkstyle:checkstyle outputFile." );
394                 return;
395             }
396 
397             try
398             {
399                 XmlPullParser xpp = new MXParser();
400                 Reader freader = ReaderFactory.newXmlReader( outputFile );
401                 BufferedReader breader = new BufferedReader( freader );
402                 xpp.setInput( breader );
403 
404                 int violations = countViolations( xpp );
405                 if ( violations > maxAllowedViolations )
406                 {
407                     if ( failOnViolation )
408                     {
409                         String msg = "You have " + violations + " Checkstyle violation"
410                             + ( ( violations > 1 ) ? "s" : "" ) + ".";
411                         if ( maxAllowedViolations > 0 )
412                         {
413                             msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
414                         }
415                         throw new MojoFailureException( msg );
416                     }
417 
418                     getLog().warn( "checkstyle:check violations detected but failOnViolation set to false" );
419                 }
420             }
421             catch ( IOException e )
422             {
423                 throw new MojoExecutionException( "Unable to read Checkstyle results xml: "
424                     + outputFile.getAbsolutePath(), e );
425             }
426             catch ( XmlPullParserException e )
427             {
428                 throw new MojoExecutionException( "Unable to read Checkstyle results xml: "
429                     + outputFile.getAbsolutePath(), e );
430             }
431         }
432     }
433 
434     private int countViolations( XmlPullParser xpp )
435         throws XmlPullParserException, IOException
436     {
437         int count = 0;
438 
439         int eventType = xpp.getEventType();
440         String file = "";
441         while ( eventType != XmlPullParser.END_DOCUMENT )
442         {
443             if ( eventType == XmlPullParser.START_TAG && "file".equals( xpp.getName() ) )
444             {
445                 file = xpp.getAttributeValue( "", "name" );
446                 file = file.substring( file.lastIndexOf( File.separatorChar ) + 1 );
447             }
448 
449             if ( eventType == XmlPullParser.START_TAG && "error".equals( xpp.getName() )
450                 && isViolation( xpp.getAttributeValue( "", "severity" ) ) )
451             {
452                 if ( logViolationsToConsole )
453                 {
454                     StringBuilder stb = new StringBuilder();
455                     stb.append( file );
456                     stb.append( '[' );
457                     stb.append( xpp.getAttributeValue( "", "line" ) );
458                     stb.append( ':' );
459                     stb.append( xpp.getAttributeValue( "", "column" ) );
460                     stb.append( "] " );
461                     stb.append( xpp.getAttributeValue( "", "message" ) );
462                     getLog().error( stb.toString() );
463                 }
464                 count++;
465             }
466             eventType = xpp.next();
467         }
468 
469         return count;
470     }
471 
472     /**
473      * Checks if the given severity is considered a violation.
474      *
475      * @param severity The severity to check
476      * @return <code>true</code> if the given severity is a violation, otherwise <code>false</code>
477      */
478     private boolean isViolation( String severity )
479     {
480         if ( "error".equals( severity ) )
481         {
482             return "error".equals( violationSeverity ) || "warning".equals( violationSeverity )
483                 || "info".equals( violationSeverity );
484         }
485         else if ( "warning".equals( severity ) )
486         {
487             return "warning".equals( violationSeverity ) || "info".equals( violationSeverity );
488         }
489         else if ( "info".equals( severity ) )
490         {
491             return "info".equals( violationSeverity );
492         }
493         else
494         {
495             return false;
496         }
497     }
498     private DefaultLogger getConsoleListener()
499         throws MojoExecutionException
500     {
501         DefaultLogger consoleListener;
502 
503         if ( useFile == null )
504         {
505             stringOutputStream = new ByteArrayOutputStream();
506             consoleListener = new DefaultLogger( stringOutputStream, false );
507         }
508         else
509         {
510             OutputStream out = getOutputStream( useFile );
511 
512             consoleListener = new DefaultLogger( out, true );
513         }
514 
515         return consoleListener;
516     }
517 
518     private OutputStream getOutputStream( File file )
519         throws MojoExecutionException
520     {
521         File parentFile = file.getAbsoluteFile().getParentFile();
522 
523         if ( !parentFile.exists() )
524         {
525             parentFile.mkdirs();
526         }
527 
528         FileOutputStream fileOutputStream;
529         try
530         {
531             fileOutputStream = new FileOutputStream( file );
532         }
533         catch ( FileNotFoundException e )
534         {
535             throw new MojoExecutionException( "Unable to create output stream: " + file, e );
536         }
537         return fileOutputStream;
538     }
539 
540     private AuditListener getListener()
541         throws MojoFailureException, MojoExecutionException
542     {
543         AuditListener listener = null;
544 
545         if ( StringUtils.isNotEmpty( outputFileFormat ) )
546         {
547             File resultFile = outputFile;
548 
549             OutputStream out = getOutputStream( resultFile );
550 
551             if ( "xml".equals( outputFileFormat ) )
552             {
553                 listener = new XMLLogger( out, true );
554             }
555             else if ( "plain".equals( outputFileFormat ) )
556             {
557                 listener = new DefaultLogger( out, true );
558             }
559             else
560             {
561                 throw new MojoFailureException( "Invalid output file format: (" + outputFileFormat
562                     + "). Must be 'plain' or 'xml'." );
563             }
564         }
565 
566         return listener;
567     }
568 
569 }