Coverage Report - org.apache.maven.plugin.pmd.PmdReport
 
Classes in this File Line Coverage Branch Coverage Complexity
PmdReport
73%
108/147
56%
27/48
2,692
PmdReport$PmdXMLRenderer
100%
3/3
N/A
2,692
PmdReport$ProcessingErrorRuleViolation
0%
0/16
N/A
2,692
 
 1  
 package org.apache.maven.plugin.pmd;
 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.File;
 23  
 import java.io.FileInputStream;
 24  
 import java.io.FileNotFoundException;
 25  
 import java.io.FileOutputStream;
 26  
 import java.io.IOException;
 27  
 import java.io.InputStream;
 28  
 import java.io.OutputStreamWriter;
 29  
 import java.io.Reader;
 30  
 import java.io.UnsupportedEncodingException;
 31  
 import java.io.Writer;
 32  
 import java.util.Locale;
 33  
 import java.util.Map;
 34  
 import java.util.ResourceBundle;
 35  
 
 36  
 import net.sourceforge.pmd.IRuleViolation;
 37  
 import net.sourceforge.pmd.PMD;
 38  
 import net.sourceforge.pmd.PMDException;
 39  
 import net.sourceforge.pmd.Report;
 40  
 import net.sourceforge.pmd.Rule;
 41  
 import net.sourceforge.pmd.RuleContext;
 42  
 import net.sourceforge.pmd.RuleSet;
 43  
 import net.sourceforge.pmd.RuleSetFactory;
 44  
 import net.sourceforge.pmd.SourceType;
 45  
 import net.sourceforge.pmd.renderers.CSVRenderer;
 46  
 import net.sourceforge.pmd.renderers.HTMLRenderer;
 47  
 import net.sourceforge.pmd.renderers.Renderer;
 48  
 import net.sourceforge.pmd.renderers.TextRenderer;
 49  
 import net.sourceforge.pmd.renderers.XMLRenderer;
 50  
 
 51  
 import org.apache.maven.doxia.sink.Sink;
 52  
 import org.apache.maven.reporting.MavenReportException;
 53  
 import org.codehaus.plexus.resource.ResourceManager;
 54  
 import org.codehaus.plexus.resource.loader.FileResourceCreationException;
 55  
 import org.codehaus.plexus.resource.loader.FileResourceLoader;
 56  
 import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
 57  
 import org.codehaus.plexus.util.FileUtils;
 58  
 import org.codehaus.plexus.util.IOUtil;
 59  
 import org.codehaus.plexus.util.ReaderFactory;
 60  
 import org.codehaus.plexus.util.StringUtils;
 61  
 
 62  
 /**
 63  
  * Creates a PMD report.
 64  
  *
 65  
  * @author Brett Porter
 66  
  * @version $Id: org.apache.maven.plugin.pmd.PmdReport.html 816698 2012-05-08 15:21:08Z hboutemy $
 67  
  * @goal pmd
 68  
  * @threadSafe
 69  
  * @since 2.0
 70  
  */
 71  8
 public class PmdReport
 72  
     extends AbstractPmdReport
 73  
 {
 74  
     /**
 75  
      * The target JDK to analyze based on. Should match the target used in the compiler plugin. Valid values are
 76  
      * currently <code>1.3</code>, <code>1.4</code>, <code>1.5</code> and <code>1.6</code>.
 77  
      * <p>
 78  
      * <b>Note:</b> support for <code>1.6</code> was added in version 2.3 of this plugin.
 79  
      * </p>
 80  
      *
 81  
      * @parameter expression="${targetJdk}"
 82  
      */
 83  
     private String targetJdk;
 84  
 
 85  
     /**
 86  
      * The rule priority threshold; rules with lower priority
 87  
      * than this will not be evaluated.
 88  
      *
 89  
      * @parameter expression="${minimumPriority}" default-value="5"
 90  
      * @since 2.1
 91  
      */
 92  8
     private int minimumPriority = 5;
 93  
 
 94  
     /**
 95  
      * Skip the PMD report generation.  Most useful on the command line
 96  
      * via "-Dpmd.skip=true".
 97  
      *
 98  
      * @parameter expression="${pmd.skip}" default-value="false"
 99  
      * @since 2.1
 100  
      */
 101  
     private boolean skip;
 102  
 
 103  
     /**
 104  
      * The PMD rulesets to use. See the <a href="http://pmd.sourceforge.net/rules/index.html">Stock Rulesets</a> for a
 105  
      * list of some included. Since version 2.5, the ruleset "rulesets/maven.xml" is also available. Defaults to the
 106  
      * basic, imports and unusedcode rulesets.
 107  
      *
 108  
      * @parameter
 109  
      */
 110  8
     private String[] rulesets =
 111  
         new String[]{ "rulesets/basic.xml", "rulesets/unusedcode.xml", "rulesets/imports.xml", };
 112  
 
 113  
     /**
 114  
      * @component
 115  
      * @required
 116  
      * @readonly
 117  
      */
 118  
     private ResourceManager locator;
 119  
 
 120  
     /**
 121  
      * {@inheritDoc}
 122  
      */
 123  
     public String getName( Locale locale )
 124  
     {
 125  1
         return getBundle( locale ).getString( "report.pmd.name" );
 126  
     }
 127  
 
 128  
     /**
 129  
      * {@inheritDoc}
 130  
      */
 131  
     public String getDescription( Locale locale )
 132  
     {
 133  0
         return getBundle( locale ).getString( "report.pmd.description" );
 134  
     }
 135  
 
 136  
     public void setRulesets( String[] rules )
 137  
     {
 138  2
         rulesets = rules;
 139  2
     }
 140  
 
 141  
     /**
 142  
      * {@inheritDoc}
 143  
      */
 144  
     public void executeReport( Locale locale )
 145  
         throws MavenReportException
 146  
     {
 147  
         try
 148  
         {
 149  6
             execute( locale );
 150  
         }
 151  
         finally
 152  
         {
 153  6
             if ( getSink() != null )
 154  
             {
 155  5
                 getSink().close();
 156  
             }
 157  
         }
 158  4
     }
 159  
 
 160  
     private void execute( Locale locale )
 161  
         throws MavenReportException
 162  
     {
 163  
         //configure ResourceManager
 164  6
         locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
 165  6
         locator.addSearchPath( "url", "" );
 166  6
         locator.setOutputDirectory( targetDirectory );
 167  
 
 168  6
         if ( !skip && canGenerateReport() )
 169  
         {
 170  6
             ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
 171  
             try
 172  
             {
 173  6
                 Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
 174  
 
 175  6
                 Report report = generateReport( locale );
 176  
 
 177  4
                 if ( !isHtml() )
 178  
                 {
 179  4
                     writeNonHtml( report );
 180  
                 }
 181  
             }
 182  
             finally
 183  
             {
 184  6
                 Thread.currentThread().setContextClassLoader( origLoader );
 185  4
             }
 186  
         }
 187  4
     }
 188  
 
 189  
     private Report generateReport( Locale locale )
 190  
         throws MavenReportException
 191  
     {
 192  6
         Sink sink = getSink();
 193  
 
 194  6
         PMD pmd = getPMD();
 195  5
         RuleContext ruleContext = new RuleContext();
 196  5
         Report report = new Report();
 197  5
         PmdReportListener reportSink = new PmdReportListener( sink, getBundle( locale ), aggregate );
 198  
 
 199  5
         report.addListener( reportSink );
 200  5
         ruleContext.setReport( report );
 201  5
         reportSink.beginDocument();
 202  
 
 203  4
         RuleSetFactory ruleSetFactory = new RuleSetFactory();
 204  4
         ruleSetFactory.setMinimumPriority( this.minimumPriority );
 205  4
         RuleSet[] sets = new RuleSet[rulesets.length];
 206  
         try
 207  
         {
 208  17
             for ( int idx = 0; idx < rulesets.length; idx++ )
 209  
             {
 210  13
                 String set = rulesets[idx];
 211  13
                 getLog().debug( "Preparing ruleset: " + set );
 212  13
                 File ruleset = locator.getResourceAsFile( set, getLocationTemp( set ) );
 213  
 
 214  13
                 if ( null == ruleset )
 215  
                 {
 216  0
                     throw new MavenReportException( "Could not resolve " + set );
 217  
                 }
 218  
 
 219  13
                 InputStream rulesInput = new FileInputStream( ruleset );
 220  
                 try
 221  
                 {
 222  13
                     RuleSet ruleSet = ruleSetFactory.createRuleSet( rulesInput );
 223  13
                     sets[idx] = ruleSet;
 224  
 
 225  13
                     ruleSet.start( ruleContext );
 226  
                 }
 227  
                 finally
 228  
                 {
 229  13
                     rulesInput.close();
 230  13
                 }
 231  
             }
 232  
         }
 233  0
         catch ( IOException e )
 234  
         {
 235  0
             throw new MavenReportException( e.getMessage(), e );
 236  
         }
 237  0
         catch ( ResourceNotFoundException e )
 238  
         {
 239  0
             throw new MavenReportException( e.getMessage(), e );
 240  
         }
 241  0
         catch ( FileResourceCreationException e )
 242  
         {
 243  0
             throw new MavenReportException( e.getMessage(), e );
 244  4
         }
 245  
 
 246  
         Map<File, PmdFileInfo> files;
 247  
         try
 248  
         {
 249  4
             files = getFilesToProcess();
 250  
         }
 251  0
         catch ( IOException e )
 252  
         {
 253  0
             throw new MavenReportException( "Can't get file list", e );
 254  4
         }
 255  
 
 256  4
         if ( StringUtils.isEmpty( getSourceEncoding() ) && !files.isEmpty() )
 257  
         {
 258  0
             getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
 259  
                                + ", i.e. build is platform dependent!" );
 260  
         }
 261  
 
 262  4
         for ( Map.Entry<File, PmdFileInfo> entry : files.entrySet() )
 263  
         {
 264  11
             File file = entry.getKey();
 265  11
             PmdFileInfo fileInfo = entry.getValue();
 266  
 
 267  
             // TODO: lazily call beginFile in case there are no rules
 268  
 
 269  11
             reportSink.beginFile( file, fileInfo );
 270  11
             ruleContext.setSourceCodeFilename( file.getAbsolutePath() );
 271  46
             for ( int idx = 0; idx < rulesets.length; idx++ )
 272  
             {
 273  
                 try
 274  
                 {
 275  
                     // PMD closes this Reader even though it did not open it so we have
 276  
                     // to open a new one with every call to processFile().
 277  
                     Reader reader;
 278  35
                     if ( StringUtils.isNotEmpty( getSourceEncoding() ) )
 279  
                     {
 280  35
                         reader = ReaderFactory.newReader( file, getSourceEncoding() );
 281  
                     }
 282  
                     else
 283  
                     {
 284  0
                         reader = ReaderFactory.newPlatformReader( file );
 285  
                     }
 286  
 
 287  
                     try
 288  
                     {
 289  35
                         pmd.processFile( reader, sets[idx], ruleContext );
 290  
                     }
 291  
                     finally
 292  
                     {
 293  35
                         reader.close();
 294  35
                     }
 295  
                 }
 296  0
                 catch ( UnsupportedEncodingException e1 )
 297  
                 {
 298  0
                     throw new MavenReportException( "Encoding '" + getSourceEncoding() + "' is not supported.", e1 );
 299  
                 }
 300  0
                 catch ( PMDException pe )
 301  
                 {
 302  0
                     String msg = pe.getLocalizedMessage();
 303  0
                     Throwable r = pe.getCause();
 304  0
                     if ( r != null )
 305  
                     {
 306  0
                         msg = msg + ": " + r.getLocalizedMessage();
 307  
                     }
 308  0
                     getLog().warn( msg );
 309  0
                     reportSink.ruleViolationAdded( new ProcessingErrorRuleViolation( file, msg ) );
 310  
                 }
 311  0
                 catch ( FileNotFoundException e2 )
 312  
                 {
 313  0
                     getLog().warn( "Error opening source file: " + file );
 314  0
                     reportSink.ruleViolationAdded( new ProcessingErrorRuleViolation( file, e2.getLocalizedMessage() ) );
 315  
                 }
 316  0
                 catch ( Exception e3 )
 317  
                 {
 318  0
                     getLog().warn( "Failure executing PMD for: " + file, e3 );
 319  0
                     reportSink.ruleViolationAdded( new ProcessingErrorRuleViolation( file, e3.getLocalizedMessage() ) );
 320  35
                 }
 321  
             }
 322  11
             reportSink.endFile( file );
 323  11
         }
 324  
 
 325  17
         for ( int idx = 0; idx < rulesets.length; idx++ )
 326  
         {
 327  13
             sets[idx].end( ruleContext );
 328  
         }
 329  
 
 330  4
         reportSink.endDocument();
 331  
 
 332  4
         return report;
 333  
     }
 334  
 
 335  
     /**
 336  
      * Use the PMD renderers to render in any format aside from HTML.
 337  
      *
 338  
      * @param report
 339  
      * @throws MavenReportException
 340  
      */
 341  
     private void writeNonHtml( Report report )
 342  
         throws MavenReportException
 343  
     {
 344  4
         Renderer r = createRenderer();
 345  
 
 346  4
         if ( r == null )
 347  
         {
 348  0
             return;
 349  
         }
 350  
 
 351  4
         Writer writer = null;
 352  
 
 353  
         try
 354  
         {
 355  4
             targetDirectory.mkdirs();
 356  4
             File targetFile = new File( targetDirectory, "pmd." + format );
 357  4
             FileOutputStream tStream = new FileOutputStream( targetFile );
 358  4
             writer = new OutputStreamWriter( tStream, getOutputEncoding() );
 359  
 
 360  4
             r.setWriter( writer );
 361  4
             r.start();
 362  4
             r.renderFileReport( report );
 363  4
             r.end();
 364  4
             writer.close();
 365  
 
 366  4
             File siteDir = getReportOutputDirectory();
 367  4
             siteDir.mkdirs();
 368  4
             FileUtils.copyFile( targetFile, new File( siteDir, "pmd." + format ) );
 369  
         }
 370  0
         catch ( IOException ioe )
 371  
         {
 372  0
             throw new MavenReportException( ioe.getMessage(), ioe );
 373  
         }
 374  
         finally
 375  
         {
 376  4
             IOUtil.close( writer );
 377  4
         }
 378  4
     }
 379  
 
 380  
     /**
 381  
      * Convenience method to get the location of the specified file name.
 382  
      *
 383  
      * @param name the name of the file whose location is to be resolved
 384  
      * @return a String that contains the absolute file name of the file
 385  
      */
 386  
     protected String getLocationTemp( String name )
 387  
     {
 388  14
         String loc = name;
 389  14
         if ( loc.indexOf( '/' ) != -1 )
 390  
         {
 391  14
             loc = loc.substring( loc.lastIndexOf( '/' ) + 1 );
 392  
         }
 393  14
         if ( loc.indexOf( '\\' ) != -1 )
 394  
         {
 395  0
             loc = loc.substring( loc.lastIndexOf( '\\' ) + 1 );
 396  
         }
 397  
 
 398  
         // MPMD-127 in the case that the rules are defined externally on a url
 399  
         // we need to replace some special url characters that cannot be
 400  
         // used in filenames on disk or produce ackward filenames.
 401  
         // replace all occurrences of the following characters:  ? : & = %
 402  14
         loc = loc.replaceAll( "[\\?\\:\\&\\=\\%]", "_" );
 403  
 
 404  14
         getLog().debug( "Before: " + name + " After: " + loc );
 405  14
         return loc;
 406  
     }
 407  
 
 408  
     /**
 409  
      * Constructs the PMD class, passing it an argument
 410  
      * that configures the target JDK.
 411  
      *
 412  
      * @return the resulting PMD
 413  
      * @throws org.apache.maven.reporting.MavenReportException
 414  
      *          if targetJdk is not supported
 415  
      */
 416  
     public PMD getPMD()
 417  
         throws MavenReportException
 418  
     {
 419  6
         PMD pmd = new PMD();
 420  
 
 421  6
         if ( null != targetJdk )
 422  
         {
 423  2
             SourceType sourceType = SourceType.getSourceTypeForId( "java " + targetJdk );
 424  2
             if ( sourceType == null )
 425  
             {
 426  1
                 throw new MavenReportException( "Unsupported targetJdk value '" + targetJdk + "'." );
 427  
             }
 428  1
             pmd.setJavaVersion( sourceType );
 429  
         }
 430  
 
 431  5
         return pmd;
 432  
     }
 433  
 
 434  
     /**
 435  
      * {@inheritDoc}
 436  
      */
 437  
     public String getOutputName()
 438  
     {
 439  5
         return "pmd";
 440  
     }
 441  
 
 442  
     private static ResourceBundle getBundle( Locale locale )
 443  
     {
 444  6
         return ResourceBundle.getBundle( "pmd-report", locale, PmdReport.class.getClassLoader() );
 445  
     }
 446  
 
 447  
     /**
 448  
      * Create and return the correct renderer for the output type.
 449  
      *
 450  
      * @return the renderer based on the configured output
 451  
      * @throws org.apache.maven.reporting.MavenReportException
 452  
      *          if no renderer found for the output type
 453  
      */
 454  
     public final Renderer createRenderer()
 455  
         throws MavenReportException
 456  
     {
 457  4
         Renderer renderer = null;
 458  4
         if ( "xml".equals( format ) )
 459  
         {
 460  3
             renderer = new PmdXMLRenderer( getOutputEncoding() );
 461  
         }
 462  1
         else if ( "txt".equals( format ) )
 463  
         {
 464  0
             renderer = new TextRenderer();
 465  
         }
 466  1
         else if ( "csv".equals( format ) )
 467  
         {
 468  1
             renderer = new CSVRenderer();
 469  
         }
 470  0
         else if ( "html".equals( format ) )
 471  
         {
 472  0
             renderer = new HTMLRenderer();
 473  
         }
 474  0
         else if ( !"".equals( format ) && !"none".equals( format ) )
 475  
         {
 476  
             try
 477  
             {
 478  0
                 renderer = (Renderer) Class.forName( format ).newInstance();
 479  
             }
 480  0
             catch ( Exception e )
 481  
             {
 482  0
                 throw new MavenReportException(
 483  
                     "Can't find PMD custom format " + format + ": " + e.getClass().getName(), e );
 484  0
             }
 485  
         }
 486  
 
 487  4
         return renderer;
 488  
     }
 489  
 
 490  
     private static class PmdXMLRenderer
 491  
         extends XMLRenderer
 492  
     {
 493  
         public PmdXMLRenderer( String encoding )
 494  
         {
 495  3
             super();
 496  3
             this.encoding = encoding;
 497  3
         }
 498  
     }
 499  
 
 500  
     /**
 501  
      * @author <a href="mailto:douglass.doug@gmail.com">Doug Douglass</a>
 502  
      */
 503  8
     private static class ProcessingErrorRuleViolation
 504  
         implements IRuleViolation
 505  
     {
 506  
 
 507  
         private String filename;
 508  
 
 509  
         private String description;
 510  
 
 511  
         public ProcessingErrorRuleViolation( File file, String description )
 512  0
         {
 513  0
             filename = file.getPath();
 514  0
             this.description = description;
 515  0
         }
 516  
 
 517  
         /**
 518  
          * {@inheritDoc}
 519  
          */
 520  
         public String getFilename()
 521  
         {
 522  0
             return this.filename;
 523  
         }
 524  
 
 525  
         /**
 526  
          * {@inheritDoc}
 527  
          */
 528  
         public int getBeginLine()
 529  
         {
 530  0
             return 0;
 531  
         }
 532  
 
 533  
         /**
 534  
          * {@inheritDoc}
 535  
          */
 536  
         public int getBeginColumn()
 537  
         {
 538  0
             return 0;
 539  
         }
 540  
 
 541  
         /**
 542  
          * {@inheritDoc}
 543  
          */
 544  
         public int getEndLine()
 545  
         {
 546  0
             return 0;
 547  
         }
 548  
 
 549  
         /**
 550  
          * {@inheritDoc}
 551  
          */
 552  
         public int getEndColumn()
 553  
         {
 554  0
             return 0;
 555  
         }
 556  
 
 557  
         /**
 558  
          * {@inheritDoc}
 559  
          */
 560  
         public Rule getRule()
 561  
         {
 562  0
             return null;
 563  
         }
 564  
 
 565  
         /**
 566  
          * {@inheritDoc}
 567  
          */
 568  
         public String getDescription()
 569  
         {
 570  0
             return this.description;
 571  
         }
 572  
 
 573  
         /**
 574  
          * {@inheritDoc}
 575  
          */
 576  
         public String getPackageName()
 577  
         {
 578  0
             return null;
 579  
         }
 580  
 
 581  
         /**
 582  
          * {@inheritDoc}
 583  
          */
 584  
         public String getMethodName()
 585  
         {
 586  0
             return null;
 587  
         }
 588  
 
 589  
         /**
 590  
          * {@inheritDoc}
 591  
          */
 592  
         public String getClassName()
 593  
         {
 594  0
             return null;
 595  
         }
 596  
 
 597  
         /**
 598  
          * {@inheritDoc}
 599  
          */
 600  
         public boolean isSuppressed()
 601  
         {
 602  0
             return false;
 603  
         }
 604  
 
 605  
         /**
 606  
          * {@inheritDoc}
 607  
          */
 608  
         public String getVariableName()
 609  
         {
 610  0
             return null;
 611  
         }
 612  
     }
 613  
 }