View Javadoc

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