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