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