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