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