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.IOException;
24  import java.util.ArrayList;
25  import java.util.Collection;
26  import java.util.Collections;
27  import java.util.Comparator;
28  import java.util.HashSet;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.ResourceBundle;
32  
33  import net.sourceforge.pmd.RuleViolation;
34  
35  import org.apache.maven.doxia.sink.Sink;
36  import org.apache.maven.plugin.logging.Log;
37  import org.codehaus.plexus.util.StringUtils;
38  
39  /**
40   * Render the PMD violations into Doxia events.
41   *
42   * @author Brett Porter
43   * @version $Id: PmdReportGenerator.html 901148 2014-03-11 20:12:51Z dennisl $
44   */
45  public class PmdReportGenerator
46  {
47      private Log log;
48  
49      private Sink sink;
50  
51      private String currentFilename;
52  
53      private ResourceBundle bundle;
54  
55      private HashSet<RuleViolation> violations = new HashSet<RuleViolation>();
56  
57      private boolean aggregate;
58  
59      // The number of erroneous files
60      private int fileCount = 0;
61  
62      private Map<File, PmdFileInfo> files;
63  
64  //    private List<Metric> metrics = new ArrayList<Metric>();
65  
66      public PmdReportGenerator( Log log, Sink sink, ResourceBundle bundle, boolean aggregate )
67      {
68          this.log = log;
69          this.sink = sink;
70          this.bundle = bundle;
71          this.aggregate = aggregate;
72      }
73  
74      private String getTitle()
75      {
76          return bundle.getString( "report.pmd.title" );
77      }
78  
79      public void setViolations( Collection<RuleViolation> violations )
80      {
81          this.violations = new HashSet<RuleViolation>( violations );
82      }
83  
84      public List<RuleViolation> getViolations()
85      {
86          return new ArrayList<RuleViolation>( violations );
87      }
88  
89  //    public List<Metric> getMetrics()
90  //    {
91  //        return metrics;
92  //    }
93  //
94  //    public void setMetrics( List<Metric> metrics )
95  //    {
96  //        this.metrics = metrics;
97  //    }
98  
99      private void startFileSection( String currentFilename, PmdFileInfo fileInfo )
100     {
101         sink.section2();
102         sink.sectionTitle2();
103 
104         // prepare the filename
105         this.currentFilename = currentFilename;
106         if ( fileInfo != null && fileInfo.getSourceDirectory() != null )
107         {
108             this.currentFilename =
109                 StringUtils.substring( currentFilename, fileInfo.getSourceDirectory().getAbsolutePath().length() + 1 );
110         }
111         this.currentFilename = StringUtils.replace( this.currentFilename, "\\", "/" );
112 
113         String title = this.currentFilename;
114         if ( aggregate && fileInfo != null && fileInfo.getProject() != null )
115         {
116             title = fileInfo.getProject().getName() + " - " + this.currentFilename;
117         }
118         sink.text( title );
119         sink.sectionTitle2_();
120 
121         sink.table();
122         sink.tableRow();
123         sink.tableHeaderCell();
124         sink.text( bundle.getString( "report.pmd.column.violation" ) );
125         sink.tableHeaderCell_();
126         sink.tableHeaderCell();
127         sink.text( bundle.getString( "report.pmd.column.line" ) );
128         sink.tableHeaderCell_();
129         sink.tableRow_();
130     }
131 
132     private void endFileSection()
133     {
134         sink.table_();
135         sink.section2_();
136     }
137 
138     private void processSingleRuleViolation( RuleViolation ruleViolation, PmdFileInfo fileInfo )
139     {
140         sink.tableRow();
141         sink.tableCell();
142         sink.text( ruleViolation.getDescription() );
143         sink.tableCell_();
144         sink.tableCell();
145 
146         int beginLine = ruleViolation.getBeginLine();
147         outputLineLink( beginLine, fileInfo );
148         int endLine = ruleViolation.getEndLine();
149         if ( endLine != beginLine )
150         {
151             sink.text( "&#x2013;" );
152             outputLineLink( endLine, fileInfo );
153         }
154 
155         sink.tableCell_();
156         sink.tableRow_();
157     }
158 
159     // PMD might run the analysis multi-threaded, so the violations might be reported
160     // out of order. We sort them here by filename and line number before writing them to
161     // the report.
162     private void processViolations()
163         throws IOException
164     {
165         fileCount = files.size();
166         ArrayList<RuleViolation> violations2 = new ArrayList<RuleViolation>( violations );
167         Collections.sort( violations2, new Comparator<RuleViolation>()
168         {
169             /** {@inheritDoc} */
170             public int compare( RuleViolation o1, RuleViolation o2 )
171             {
172                 int filenames = o1.getFilename().compareTo( o2.getFilename() );
173                 if ( filenames == 0 )
174                 {
175                     return o1.getBeginLine() - o2.getBeginLine();
176                 }
177                 else
178                 {
179                     return filenames;
180                 }
181             }
182         } );
183 
184         boolean fileSectionStarted = false;
185         String previousFilename = null;
186         for ( RuleViolation ruleViolation : violations2 )
187         {
188             String currentFn = ruleViolation.getFilename();
189             File canonicalFilename = new File( currentFn ).getCanonicalFile();
190             PmdFileInfo fileInfo = files.get( canonicalFilename );
191             if ( fileInfo == null )
192             {
193                 log.warn( "Couldn't determine PmdFileInfo for file " + currentFn + " (canonical: " + canonicalFilename
194                               + "). XRef links won't be available." );
195             }
196 
197             if ( !currentFn.equalsIgnoreCase( previousFilename ) && fileSectionStarted )
198             {
199                 endFileSection();
200                 fileSectionStarted = false;
201             }
202             if ( !fileSectionStarted )
203             {
204                 startFileSection( currentFn, fileInfo );
205                 fileSectionStarted = true;
206             }
207 
208             processSingleRuleViolation( ruleViolation, fileInfo );
209 
210             previousFilename = currentFn;
211         }
212 
213         if ( fileSectionStarted )
214         {
215             endFileSection();
216         }
217     }
218 
219     private void outputLineLink( int line, PmdFileInfo fileInfo )
220     {
221         String xrefLocation = null;
222         if ( fileInfo != null )
223         {
224             xrefLocation = fileInfo.getXrefLocation();
225         }
226 
227         if ( xrefLocation != null )
228         {
229             sink.link( xrefLocation + "/" + currentFilename.replaceAll( "\\.java$", ".html" ) + "#L" + line );
230         }
231         sink.text( String.valueOf( line ) );
232         if ( xrefLocation != null )
233         {
234             sink.link_();
235         }
236     }
237 
238     public void beginDocument()
239     {
240         sink.head();
241         sink.title();
242         sink.text( getTitle() );
243         sink.title_();
244         sink.head_();
245 
246         sink.body();
247 
248         sink.section1();
249         sink.sectionTitle1();
250         sink.text( getTitle() );
251         sink.sectionTitle1_();
252 
253         sink.paragraph();
254         sink.text( bundle.getString( "report.pmd.pmdlink" ) + " " );
255         sink.link( "http://pmd.sourceforge.net/" );
256         sink.text( "PMD" );
257         sink.link_();
258         sink.text( " " + AbstractPmdReport.getPmdVersion() + "." );
259         sink.paragraph_();
260 
261         sink.section1_();
262 
263         // TODO overall summary
264 
265         sink.section1();
266         sink.sectionTitle1();
267         sink.text( bundle.getString( "report.pmd.files" ) );
268         sink.sectionTitle1_();
269 
270         // TODO files summary
271     }
272 
273 /*
274     private void processMetrics()
275     {
276         if ( metrics.size() == 0 )
277         {
278             return;
279         }
280 
281         sink.section1();
282         sink.sectionTitle1();
283         sink.text( "Metrics" );
284         sink.sectionTitle1_();
285 
286         sink.table();
287         sink.tableRow();
288         sink.tableHeaderCell();
289         sink.text( "Name" );
290         sink.tableHeaderCell_();
291         sink.tableHeaderCell();
292         sink.text( "Count" );
293         sink.tableHeaderCell_();
294         sink.tableHeaderCell();
295         sink.text( "High" );
296         sink.tableHeaderCell_();
297         sink.tableHeaderCell();
298         sink.text( "Low" );
299         sink.tableHeaderCell_();
300         sink.tableHeaderCell();
301         sink.text( "Average" );
302         sink.tableHeaderCell_();
303         sink.tableRow_();
304 
305         for ( Metric met : metrics )
306         {
307             sink.tableRow();
308             sink.tableCell();
309             sink.text( met.getMetricName() );
310             sink.tableCell_();
311             sink.tableCell();
312             sink.text( String.valueOf( met.getCount() ) );
313             sink.tableCell_();
314             sink.tableCell();
315             sink.text( String.valueOf( met.getHighValue() ) );
316             sink.tableCell_();
317             sink.tableCell();
318             sink.text( String.valueOf( met.getLowValue() ) );
319             sink.tableCell_();
320             sink.tableCell();
321             sink.text( String.valueOf( met.getAverage() ) );
322             sink.tableCell_();
323             sink.tableRow_();
324         }
325         sink.table_();
326         sink.section1_();
327     }
328 */
329 
330     public void render()
331         throws IOException
332     {
333         processViolations();
334     }
335 
336     public void endDocument()
337         throws IOException
338     {
339         if ( fileCount == 0 )
340         {
341             sink.paragraph();
342             sink.text( bundle.getString( "report.pmd.noProblems" ) );
343             sink.paragraph_();
344         }
345 
346         sink.section1_();
347 
348         // The Metrics report useless with the current PMD metrics impl.
349         // For instance, run the coupling ruleset and you will get a boatload
350         // of excessive imports metrics, none of which is really any use.
351         // TODO Determine if we are going to just ignore metrics.
352 
353 //        processMetrics();
354 
355         sink.body_();
356 
357         sink.flush();
358 
359         sink.close();
360     }
361 
362     public void setFiles( Map<File, PmdFileInfo> files )
363     {
364         this.files = files;
365     }
366 }