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 999063 2016-10-08 16:53:12Z adangel $
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<>();
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<>( violations );
82      }
83  
84      public List<RuleViolation> getViolations()
85      {
86          return new ArrayList<>( 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<>( 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() { if ( metrics.size() == 0 ) { return; } sink.section1(); sink.sectionTitle1();
275      * sink.text( "Metrics" ); sink.sectionTitle1_(); sink.table(); sink.tableRow(); sink.tableHeaderCell(); sink.text(
276      * "Name" ); sink.tableHeaderCell_(); sink.tableHeaderCell(); sink.text( "Count" ); sink.tableHeaderCell_();
277      * sink.tableHeaderCell(); sink.text( "High" ); sink.tableHeaderCell_(); sink.tableHeaderCell(); sink.text( "Low" );
278      * sink.tableHeaderCell_(); sink.tableHeaderCell(); sink.text( "Average" ); sink.tableHeaderCell_();
279      * sink.tableRow_(); for ( Metric met : metrics ) { sink.tableRow(); sink.tableCell(); sink.text(
280      * met.getMetricName() ); sink.tableCell_(); sink.tableCell(); sink.text( String.valueOf( met.getCount() ) );
281      * sink.tableCell_(); sink.tableCell(); sink.text( String.valueOf( met.getHighValue() ) ); sink.tableCell_();
282      * sink.tableCell(); sink.text( String.valueOf( met.getLowValue() ) ); sink.tableCell_(); sink.tableCell();
283      * sink.text( String.valueOf( met.getAverage() ) ); sink.tableCell_(); sink.tableRow_(); } sink.table_();
284      * sink.section1_(); }
285      */
286 
287     public void render()
288         throws IOException
289     {
290         processViolations();
291     }
292 
293     public void endDocument()
294         throws IOException
295     {
296         if ( fileCount == 0 )
297         {
298             sink.paragraph();
299             sink.text( bundle.getString( "report.pmd.noProblems" ) );
300             sink.paragraph_();
301         }
302 
303         sink.section1_();
304 
305         // The Metrics report useless with the current PMD metrics impl.
306         // For instance, run the coupling ruleset and you will get a boatload
307         // of excessive imports metrics, none of which is really any use.
308         // TODO Determine if we are going to just ignore metrics.
309 
310         // processMetrics();
311 
312         sink.body_();
313 
314         sink.flush();
315 
316         sink.close();
317     }
318 
319     public void setFiles( Map<File, PmdFileInfo> files )
320     {
321         this.files = files;
322     }
323 }