Coverage Report - org.apache.maven.plugin.pmd.PmdReport
 
Classes in this File Line Coverage Branch Coverage Complexity
PmdReport
73 %
109/148
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 net.sourceforge.pmd.IRuleViolation;
 23  
 import net.sourceforge.pmd.PMD;
 24  
 import net.sourceforge.pmd.PMDException;
 25  
 import net.sourceforge.pmd.Report;
 26  
 import net.sourceforge.pmd.Rule;
 27  
 import net.sourceforge.pmd.RuleContext;
 28  
 import net.sourceforge.pmd.RuleSet;
 29  
 import net.sourceforge.pmd.RuleSetFactory;
 30  
 import net.sourceforge.pmd.SourceType;
 31  
 import net.sourceforge.pmd.renderers.CSVRenderer;
 32  
 import net.sourceforge.pmd.renderers.HTMLRenderer;
 33  
 import net.sourceforge.pmd.renderers.Renderer;
 34  
 import net.sourceforge.pmd.renderers.TextRenderer;
 35  
 import net.sourceforge.pmd.renderers.XMLRenderer;
 36  
 import org.apache.maven.doxia.sink.Sink;
 37  
 import org.apache.maven.reporting.MavenReportException;
 38  
 import org.codehaus.plexus.resource.ResourceManager;
 39  
 import org.codehaus.plexus.resource.loader.FileResourceCreationException;
 40  
 import org.codehaus.plexus.resource.loader.FileResourceLoader;
 41  
 import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
 42  
 import org.codehaus.plexus.util.FileUtils;
 43  
 import org.codehaus.plexus.util.IOUtil;
 44  
 import org.codehaus.plexus.util.ReaderFactory;
 45  
 import org.codehaus.plexus.util.StringUtils;
 46  
 
 47  
 import java.io.File;
 48  
 import java.io.FileInputStream;
 49  
 import java.io.FileNotFoundException;
 50  
 import java.io.FileOutputStream;
 51  
 import java.io.IOException;
 52  
 import java.io.InputStream;
 53  
 import java.io.OutputStreamWriter;
 54  
 import java.io.Reader;
 55  
 import java.io.UnsupportedEncodingException;
 56  
 import java.io.Writer;
 57  
 import java.util.Iterator;
 58  
 import java.util.Locale;
 59  
 import java.util.Map;
 60  
 import java.util.ResourceBundle;
 61  
 
 62  
 /**
 63  
  * Creates a PMD report.
 64  
  *
 65  
  * @author Brett Porter
 66  
  * @version $Id: org.apache.maven.plugin.pmd.PmdReport.html 816691 2012-05-08 15:16:42Z 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 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 ( Iterator i = files.entrySet().iterator(); i.hasNext(); )
 263  
         {
 264  11
             Map.Entry entry = (Map.Entry) i.next();
 265  11
             File file = (File) entry.getKey();
 266  11
             PmdFileInfo fileInfo = (PmdFileInfo) entry.getValue();
 267  
 
 268  
             // TODO: lazily call beginFile in case there are no rules
 269  
 
 270  11
             reportSink.beginFile( file, fileInfo );
 271  11
             ruleContext.setSourceCodeFilename( file.getAbsolutePath() );
 272  46
             for ( int idx = 0; idx < rulesets.length; idx++ )
 273  
             {
 274  
                 try
 275  
                 {
 276  
                     // PMD closes this Reader even though it did not open it so we have
 277  
                     // to open a new one with every call to processFile().
 278  
                     Reader reader;
 279  35
                     if ( StringUtils.isNotEmpty( getSourceEncoding() ) )
 280  
                     {
 281  35
                         reader = ReaderFactory.newReader( file, getSourceEncoding() );
 282  
                     }
 283  
                     else
 284  
                     {
 285  0
                         reader = ReaderFactory.newPlatformReader( file );
 286  
                     }
 287  
 
 288  
                     try
 289  
                     {
 290  35
                         pmd.processFile( reader, sets[idx], ruleContext );
 291  
                     }
 292  
                     finally
 293  
                     {
 294  35
                         reader.close();
 295  35
                     }
 296  
                 }
 297  0
                 catch ( UnsupportedEncodingException e1 )
 298  
                 {
 299  0
                     throw new MavenReportException( "Encoding '" + getSourceEncoding() + "' is not supported.", e1 );
 300  
                 }
 301  0
                 catch ( PMDException pe )
 302  
                 {
 303  0
                     String msg = pe.getLocalizedMessage();
 304  0
                     Throwable r = pe.getCause();
 305  0
                     if ( r != null )
 306  
                     {
 307  0
                         msg = msg + ": " + r.getLocalizedMessage();
 308  
                     }
 309  0
                     getLog().warn( msg );
 310  0
                     reportSink.ruleViolationAdded( new ProcessingErrorRuleViolation( file, msg ) );
 311  
                 }
 312  0
                 catch ( FileNotFoundException e2 )
 313  
                 {
 314  0
                     getLog().warn( "Error opening source file: " + file );
 315  0
                     reportSink.ruleViolationAdded( new ProcessingErrorRuleViolation( file, e2.getLocalizedMessage() ) );
 316  
                 }
 317  0
                 catch ( Exception e3 )
 318  
                 {
 319  0
                     getLog().warn( "Failure executing PMD for: " + file, e3 );
 320  0
                     reportSink.ruleViolationAdded( new ProcessingErrorRuleViolation( file, e3.getLocalizedMessage() ) );
 321  35
                 }
 322  
             }
 323  11
             reportSink.endFile( file );
 324  11
         }
 325  
 
 326  17
         for ( int idx = 0; idx < rulesets.length; idx++ )
 327  
         {
 328  13
             sets[idx].end( ruleContext );
 329  
         }
 330  
 
 331  4
         reportSink.endDocument();
 332  
 
 333  4
         return report;
 334  
     }
 335  
 
 336  
     /**
 337  
      * Use the PMD renderers to render in any format aside from HTML.
 338  
      *
 339  
      * @param report
 340  
      * @throws MavenReportException
 341  
      */
 342  
     private void writeNonHtml( Report report )
 343  
         throws MavenReportException
 344  
     {
 345  4
         Renderer r = createRenderer();
 346  
 
 347  4
         if ( r == null )
 348  
         {
 349  0
             return;
 350  
         }
 351  
 
 352  4
         Writer writer = null;
 353  
 
 354  
         try
 355  
         {
 356  4
             targetDirectory.mkdirs();
 357  4
             File targetFile = new File( targetDirectory, "pmd." + format );
 358  4
             FileOutputStream tStream = new FileOutputStream( targetFile );
 359  4
             writer = new OutputStreamWriter( tStream, getOutputEncoding() );
 360  
 
 361  4
             r.setWriter( writer );
 362  4
             r.start();
 363  4
             r.renderFileReport( report );
 364  4
             r.end();
 365  4
             writer.close();
 366  
 
 367  4
             File siteDir = getReportOutputDirectory();
 368  4
             siteDir.mkdirs();
 369  4
             FileUtils.copyFile( targetFile, new File( siteDir, "pmd." + format ) );
 370  
         }
 371  0
         catch ( IOException ioe )
 372  
         {
 373  0
             throw new MavenReportException( ioe.getMessage(), ioe );
 374  
         }
 375  
         finally
 376  
         {
 377  4
             IOUtil.close( writer );
 378  4
         }
 379  4
     }
 380  
 
 381  
     /**
 382  
      * Convenience method to get the location of the specified file name.
 383  
      *
 384  
      * @param name the name of the file whose location is to be resolved
 385  
      * @return a String that contains the absolute file name of the file
 386  
      */
 387  
     protected String getLocationTemp( String name )
 388  
     {
 389  14
         String loc = name;
 390  14
         if ( loc.indexOf( '/' ) != -1 )
 391  
         {
 392  14
             loc = loc.substring( loc.lastIndexOf( '/' ) + 1 );
 393  
         }
 394  14
         if ( loc.indexOf( '\\' ) != -1 )
 395  
         {
 396  0
             loc = loc.substring( loc.lastIndexOf( '\\' ) + 1 );
 397  
         }
 398  
 
 399  
         // MPMD-127 in the case that the rules are defined externally on a url
 400  
         // we need to replace some special url characters that cannot be
 401  
         // used in filenames on disk or produce ackward filenames.
 402  
         // replace all occurrences of the following characters:  ? : & = %
 403  14
         loc = loc.replaceAll( "[\\?\\:\\&\\=\\%]", "_" );
 404  
 
 405  14
         getLog().debug( "Before: " + name + " After: " + loc );
 406  14
         return loc;
 407  
     }
 408  
 
 409  
     /**
 410  
      * Constructs the PMD class, passing it an argument
 411  
      * that configures the target JDK.
 412  
      *
 413  
      * @return the resulting PMD
 414  
      * @throws org.apache.maven.reporting.MavenReportException
 415  
      *          if targetJdk is not supported
 416  
      */
 417  
     public PMD getPMD()
 418  
         throws MavenReportException
 419  
     {
 420  6
         PMD pmd = new PMD();
 421  
 
 422  6
         if ( null != targetJdk )
 423  
         {
 424  2
             SourceType sourceType = SourceType.getSourceTypeForId( "java " + targetJdk );
 425  2
             if ( sourceType == null )
 426  
             {
 427  1
                 throw new MavenReportException( "Unsupported targetJdk value '" + targetJdk + "'." );
 428  
             }
 429  1
             pmd.setJavaVersion( sourceType );
 430  
         }
 431  
 
 432  5
         return pmd;
 433  
     }
 434  
 
 435  
     /**
 436  
      * {@inheritDoc}
 437  
      */
 438  
     public String getOutputName()
 439  
     {
 440  5
         return "pmd";
 441  
     }
 442  
 
 443  
     private static ResourceBundle getBundle( Locale locale )
 444  
     {
 445  6
         return ResourceBundle.getBundle( "pmd-report", locale, PmdReport.class.getClassLoader() );
 446  
     }
 447  
 
 448  
     /**
 449  
      * Create and return the correct renderer for the output type.
 450  
      *
 451  
      * @return the renderer based on the configured output
 452  
      * @throws org.apache.maven.reporting.MavenReportException
 453  
      *          if no renderer found for the output type
 454  
      */
 455  
     public final Renderer createRenderer()
 456  
         throws MavenReportException
 457  
     {
 458  4
         Renderer renderer = null;
 459  4
         if ( "xml".equals( format ) )
 460  
         {
 461  3
             renderer = new PmdXMLRenderer( getOutputEncoding() );
 462  
         }
 463  1
         else if ( "txt".equals( format ) )
 464  
         {
 465  0
             renderer = new TextRenderer();
 466  
         }
 467  1
         else if ( "csv".equals( format ) )
 468  
         {
 469  1
             renderer = new CSVRenderer();
 470  
         }
 471  0
         else if ( "html".equals( format ) )
 472  
         {
 473  0
             renderer = new HTMLRenderer();
 474  
         }
 475  0
         else if ( !"".equals( format ) && !"none".equals( format ) )
 476  
         {
 477  
             try
 478  
             {
 479  0
                 renderer = (Renderer) Class.forName( format ).newInstance();
 480  
             }
 481  0
             catch ( Exception e )
 482  
             {
 483  0
                 throw new MavenReportException(
 484  
                     "Can't find PMD custom format " + format + ": " + e.getClass().getName(), e );
 485  0
             }
 486  
         }
 487  
 
 488  4
         return renderer;
 489  
     }
 490  
 
 491  
     private static class PmdXMLRenderer
 492  
         extends XMLRenderer
 493  
     {
 494  
         public PmdXMLRenderer( String encoding )
 495  
         {
 496  3
             super();
 497  3
             this.encoding = encoding;
 498  3
         }
 499  
     }
 500  
 
 501  
     /**
 502  
      * @author <a href="mailto:douglass.doug@gmail.com">Doug Douglass</a>
 503  
      */
 504  8
     private static class ProcessingErrorRuleViolation
 505  
         implements IRuleViolation
 506  
     {
 507  
 
 508  
         private String filename;
 509  
 
 510  
         private String description;
 511  
 
 512  
         public ProcessingErrorRuleViolation( File file, String description )
 513  0
         {
 514  0
             filename = file.getPath();
 515  0
             this.description = description;
 516  0
         }
 517  
 
 518  
         /**
 519  
          * {@inheritDoc}
 520  
          */
 521  
         public String getFilename()
 522  
         {
 523  0
             return this.filename;
 524  
         }
 525  
 
 526  
         /**
 527  
          * {@inheritDoc}
 528  
          */
 529  
         public int getBeginLine()
 530  
         {
 531  0
             return 0;
 532  
         }
 533  
 
 534  
         /**
 535  
          * {@inheritDoc}
 536  
          */
 537  
         public int getBeginColumn()
 538  
         {
 539  0
             return 0;
 540  
         }
 541  
 
 542  
         /**
 543  
          * {@inheritDoc}
 544  
          */
 545  
         public int getEndLine()
 546  
         {
 547  0
             return 0;
 548  
         }
 549  
 
 550  
         /**
 551  
          * {@inheritDoc}
 552  
          */
 553  
         public int getEndColumn()
 554  
         {
 555  0
             return 0;
 556  
         }
 557  
 
 558  
         /**
 559  
          * {@inheritDoc}
 560  
          */
 561  
         public Rule getRule()
 562  
         {
 563  0
             return null;
 564  
         }
 565  
 
 566  
         /**
 567  
          * {@inheritDoc}
 568  
          */
 569  
         public String getDescription()
 570  
         {
 571  0
             return this.description;
 572  
         }
 573  
 
 574  
         /**
 575  
          * {@inheritDoc}
 576  
          */
 577  
         public String getPackageName()
 578  
         {
 579  0
             return null;
 580  
         }
 581  
 
 582  
         /**
 583  
          * {@inheritDoc}
 584  
          */
 585  
         public String getMethodName()
 586  
         {
 587  0
             return null;
 588  
         }
 589  
 
 590  
         /**
 591  
          * {@inheritDoc}
 592  
          */
 593  
         public String getClassName()
 594  
         {
 595  0
             return null;
 596  
         }
 597  
 
 598  
         /**
 599  
          * {@inheritDoc}
 600  
          */
 601  
         public boolean isSuppressed()
 602  
         {
 603  0
             return false;
 604  
         }
 605  
 
 606  
         /**
 607  
          * {@inheritDoc}
 608  
          */
 609  
         public String getVariableName()
 610  
         {
 611  0
             return null;
 612  
         }
 613  
     }
 614  
 }