Coverage Report - org.apache.maven.plugin.checkstyle.CheckstyleViolationCheckMojo
 
Classes in this File Line Coverage Branch Coverage Complexity
CheckstyleViolationCheckMojo
41%
37/90
44%
25/56
8,167
 
 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: org.apache.maven.plugin.checkstyle.CheckstyleViolationCheckMojo.html 816673 2012-05-08 14:06:16Z hboutemy $
 53  
  * @goal check
 54  
  * @phase verify
 55  
  * @requiresDependencyResolution test
 56  
  * @threadSafe
 57  
  */
 58  4
 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  4
     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  4
     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  4
         if ( !skip )
 360  
         {
 361  
 
 362  4
             if ( !skipExec )
 363  
             {
 364  
 
 365  0
                 ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
 366  
 
 367  
                 try
 368  
                 {
 369  0
                     CheckstyleExecutorRequest request = new CheckstyleExecutorRequest();
 370  0
                     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  0
                     checkstyleExecutor.executeCheckstyle( request );
 381  
 
 382  
                 }
 383  0
                 catch ( CheckstyleException e )
 384  
                 {
 385  0
                     throw new MojoExecutionException( "Failed during checkstyle configuration", e );
 386  
                 }
 387  0
                 catch ( CheckstyleExecutorException e )
 388  
                 {
 389  0
                     throw new MojoExecutionException( "Failed during checkstyle execution", e );
 390  
                 }
 391  
                 finally
 392  
                 {
 393  
                     //be sure to restore original context classloader
 394  0
                     Thread.currentThread().setContextClassLoader( currentClassLoader );
 395  0
                 }
 396  
 
 397  
             }
 398  4
             if ( !"xml".equals( outputFileFormat ) )
 399  
             {
 400  1
                 throw new MojoExecutionException( "Output format is '" + outputFileFormat
 401  
                     + "', checkstyle:check requires format to be 'xml'." );
 402  
             }
 403  
 
 404  3
             if ( !outputFile.exists() )
 405  
             {
 406  1
                 getLog().info(
 407  
                                "Unable to perform checkstyle:check, "
 408  
                                    + "unable to find checkstyle:checkstyle outputFile." );
 409  1
                 return;
 410  
             }
 411  
 
 412  
             try
 413  
             {
 414  2
                 XmlPullParser xpp = new MXParser();
 415  2
                 Reader freader = ReaderFactory.newXmlReader( outputFile );
 416  2
                 BufferedReader breader = new BufferedReader( freader );
 417  2
                 xpp.setInput( breader );
 418  
 
 419  2
                 int violations = countViolations( xpp );
 420  2
                 if ( violations > maxAllowedViolations )
 421  
                 {
 422  2
                     if ( failOnViolation )
 423  
                     {
 424  1
                         String msg = "You have " + violations + " Checkstyle violation"
 425  
                             + ( ( violations > 1 ) ? "s" : "" ) + ".";
 426  1
                         if ( maxAllowedViolations > 0 )
 427  
                         {
 428  0
                             msg += " The maximum number of allowed violations is " + maxAllowedViolations + ".";
 429  
                         }
 430  1
                         throw new MojoFailureException( msg );
 431  
                     }
 432  
 
 433  1
                     getLog().warn( "checkstyle:check violations detected but failOnViolation set to false" );
 434  
                 }
 435  
             }
 436  0
             catch ( IOException e )
 437  
             {
 438  0
                 throw new MojoExecutionException( "Unable to read Checkstyle results xml: "
 439  
                     + outputFile.getAbsolutePath(), e );
 440  
             }
 441  0
             catch ( XmlPullParserException e )
 442  
             {
 443  0
                 throw new MojoExecutionException( "Unable to read Checkstyle results xml: "
 444  
                     + outputFile.getAbsolutePath(), e );
 445  1
             }
 446  
         }
 447  1
     }
 448  
 
 449  
     private int countViolations( XmlPullParser xpp )
 450  
         throws XmlPullParserException, IOException
 451  
     {
 452  2
         int count = 0;
 453  
 
 454  2
         int eventType = xpp.getEventType();
 455  2
         String file = "";
 456  64
         while ( eventType != XmlPullParser.END_DOCUMENT )
 457  
         {
 458  62
             if ( eventType == XmlPullParser.START_TAG && "file".equals( xpp.getName() ) )
 459  
             {
 460  6
                 file = xpp.getAttributeValue( "", "name" );
 461  6
                 file = file.substring( file.lastIndexOf( File.separatorChar ) + 1 );
 462  
             }
 463  
 
 464  62
             if ( eventType == XmlPullParser.START_TAG && "error".equals( xpp.getName() )
 465  
                 && isViolation( xpp.getAttributeValue( "", "severity" ) ) )
 466  
             {
 467  10
                 if ( logViolationsToConsole )
 468  
                 {
 469  0
                     StringBuffer stb = new StringBuffer();
 470  0
                     stb.append( file );
 471  0
                     stb.append( '[' );
 472  0
                     stb.append( xpp.getAttributeValue( "", "line" ) );
 473  0
                     stb.append( ':' );
 474  0
                     stb.append( xpp.getAttributeValue( "", "column" ) );
 475  0
                     stb.append( "] " );
 476  0
                     stb.append( xpp.getAttributeValue( "", "message" ) );
 477  0
                     getLog().error( stb.toString() );
 478  
                 }
 479  10
                 count++;
 480  
             }
 481  62
             eventType = xpp.next();
 482  
         }
 483  
 
 484  2
         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  10
         if ( "error".equals( severity ) )
 496  
         {
 497  10
             return "error".equals( violationSeverity ) || "warning".equals( violationSeverity )
 498  
                 || "info".equals( violationSeverity );
 499  
         }
 500  0
         else if ( "warning".equals( severity ) )
 501  
         {
 502  0
             return "warning".equals( violationSeverity ) || "info".equals( violationSeverity );
 503  
         }
 504  0
         else if ( "info".equals( severity ) )
 505  
         {
 506  0
             return "info".equals( violationSeverity );
 507  
         }
 508  
         else
 509  
         {
 510  0
             return false;
 511  
         }
 512  
     }
 513  
     private DefaultLogger getConsoleListener()
 514  
         throws MojoExecutionException
 515  
     {
 516  
         DefaultLogger consoleListener;
 517  
 
 518  0
         if ( useFile == null )
 519  
         {
 520  0
             stringOutputStream = new ByteArrayOutputStream();
 521  0
             consoleListener = new DefaultLogger( stringOutputStream, false );
 522  
         }
 523  
         else
 524  
         {
 525  0
             OutputStream out = getOutputStream( useFile );
 526  
 
 527  0
             consoleListener = new DefaultLogger( out, true );
 528  
         }
 529  
 
 530  0
         return consoleListener;
 531  
     }
 532  
 
 533  
     private OutputStream getOutputStream( File file )
 534  
         throws MojoExecutionException
 535  
     {
 536  0
         File parentFile = file.getAbsoluteFile().getParentFile();
 537  
 
 538  0
         if ( !parentFile.exists() )
 539  
         {
 540  0
             parentFile.mkdirs();
 541  
         }
 542  
 
 543  
         FileOutputStream fileOutputStream;
 544  
         try
 545  
         {
 546  0
             fileOutputStream = new FileOutputStream( file );
 547  
         }
 548  0
         catch ( FileNotFoundException e )
 549  
         {
 550  0
             throw new MojoExecutionException( "Unable to create output stream: " + file, e );
 551  0
         }
 552  0
         return fileOutputStream;
 553  
     }
 554  
 
 555  
     private AuditListener getListener()
 556  
         throws MojoFailureException, MojoExecutionException
 557  
     {
 558  0
         AuditListener listener = null;
 559  
 
 560  0
         if ( StringUtils.isNotEmpty( outputFileFormat ) )
 561  
         {
 562  0
             File resultFile = outputFile;
 563  
 
 564  0
             OutputStream out = getOutputStream( resultFile );
 565  
 
 566  0
             if ( "xml".equals( outputFileFormat ) )
 567  
             {
 568  0
                 listener = new XMLLogger( out, true );
 569  
             }
 570  0
             else if ( "plain".equals( outputFileFormat ) )
 571  
             {
 572  0
                 listener = new DefaultLogger( out, true );
 573  
             }
 574  
             else
 575  
             {
 576  0
                 throw new MojoFailureException( "Invalid output file format: (" + outputFileFormat
 577  
                     + "). Must be 'plain' or 'xml'." );
 578  
             }
 579  
         }
 580  
 
 581  0
         return listener;
 582  
     }
 583  
 
 584  
 }