View Javadoc
1   package org.apache.maven.plugins.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.util.Iterator;
24  import java.util.Map;
25  import java.util.ResourceBundle;
26  
27  import net.sourceforge.pmd.cpd.Mark;
28  import net.sourceforge.pmd.cpd.Match;
29  import net.sourceforge.pmd.cpd.TokenEntry;
30  
31  import org.apache.maven.doxia.sink.Sink;
32  import org.apache.maven.project.MavenProject;
33  import org.codehaus.plexus.util.StringUtils;
34  
35  /**
36   * Class that generated the CPD report.
37   *
38   * @author mperham
39   * @version $Id$
40   */
41  public class CpdReportGenerator
42  {
43      private Sink sink;
44  
45      private Map<File, PmdFileInfo> fileMap;
46  
47      private ResourceBundle bundle;
48  
49      private boolean aggregate;
50  
51      public CpdReportGenerator( Sink sink, Map<File, PmdFileInfo> fileMap, ResourceBundle bundle, boolean aggregate )
52      {
53          this.sink = sink;
54          this.fileMap = fileMap;
55          this.bundle = bundle;
56          this.aggregate = aggregate;
57      }
58  
59      /**
60       * Method that returns the title of the CPD Report
61       *
62       * @return a String that contains the title
63       */
64      private String getTitle()
65      {
66          return bundle.getString( "report.cpd.title" );
67      }
68  
69      /**
70       * Method that generates the start of the CPD report.
71       */
72      public void beginDocument()
73      {
74          sink.head();
75          sink.title();
76          sink.text( getTitle() );
77          sink.title_();
78          sink.head_();
79  
80          sink.body();
81  
82          sink.section1();
83          sink.sectionTitle1();
84          sink.text( getTitle() );
85          sink.sectionTitle1_();
86  
87          sink.paragraph();
88          sink.text( bundle.getString( "report.cpd.cpdlink" ) + " " );
89          sink.link( "https://pmd.github.io/latest/pmd_userdocs_cpd.html" );
90          sink.text( "CPD" );
91          sink.link_();
92          sink.text( " " + AbstractPmdReport.getPmdVersion() + "." );
93          sink.paragraph_();
94  
95          sink.section1_();
96  
97          // TODO overall summary
98  
99          sink.section1();
100         sink.sectionTitle1();
101         sink.text( bundle.getString( "report.cpd.dupes" ) );
102         sink.sectionTitle1_();
103 
104         // TODO files summary
105     }
106 
107     /**
108      * Method that generates a line of CPD report according to a TokenEntry.
109      */
110     private void generateFileLine( TokenEntry tokenEntry )
111     {
112         // Get information for report generation
113         String filename = tokenEntry.getTokenSrcID();
114         File file = new File( filename );
115         PmdFileInfo fileInfo = fileMap.get( file );
116         File sourceDirectory = fileInfo.getSourceDirectory();
117         filename = StringUtils.substring( filename, sourceDirectory.getAbsolutePath().length() + 1 );
118         String xrefLocation = fileInfo.getXrefLocation();
119         MavenProject projectFile = fileInfo.getProject();
120         int line = tokenEntry.getBeginLine();
121 
122         sink.tableRow();
123         sink.tableCell();
124         sink.text( filename );
125         sink.tableCell_();
126         if ( aggregate )
127         {
128             sink.tableCell();
129             sink.text( projectFile.getName() );
130             sink.tableCell_();
131         }
132         sink.tableCell();
133 
134         if ( xrefLocation != null )
135         {
136             sink.link( xrefLocation + "/" + filename.replaceAll( "\\.java$", ".html" ).replace( '\\', '/' ) + "#L"
137                 + line );
138         }
139         sink.text( String.valueOf( line ) );
140         if ( xrefLocation != null )
141         {
142             sink.link_();
143         }
144 
145         sink.tableCell_();
146         sink.tableRow_();
147     }
148 
149     /**
150      * Method that generates the contents of the CPD report
151      *
152      * @param matches the found duplications
153      */
154     public void generate( Iterator<Match> matches )
155     {
156         beginDocument();
157 
158         if ( !matches.hasNext() )
159         {
160             sink.paragraph();
161             sink.text( bundle.getString( "report.cpd.noProblems" ) );
162             sink.paragraph_();
163         }
164 
165         while ( matches.hasNext() )
166         {
167             Match match = matches.next();
168 
169             String code = match.getSourceCodeSlice();
170 
171             sink.table();
172             sink.tableRow();
173             sink.tableHeaderCell();
174             sink.text( bundle.getString( "report.cpd.column.file" ) );
175             sink.tableHeaderCell_();
176             if ( aggregate )
177             {
178                 sink.tableHeaderCell();
179                 sink.text( bundle.getString( "report.cpd.column.project" ) );
180                 sink.tableHeaderCell_();
181             }
182             sink.tableHeaderCell();
183             sink.text( bundle.getString( "report.cpd.column.line" ) );
184             sink.tableHeaderCell_();
185             sink.tableRow_();
186 
187             // Iterating on every token entry
188             for ( Iterator<Mark> occurrences = match.iterator(); occurrences.hasNext(); )
189             {
190 
191                 TokenEntry mark = occurrences.next().getToken();
192                 generateFileLine( mark );
193             }
194 
195             // Source snippet
196             sink.tableRow();
197 
198             int colspan = 2;
199             if ( aggregate )
200             {
201                 ++colspan;
202             }
203             // TODO Cleaner way to do this?
204             sink.rawText( "<td colspan='" + colspan + "'>" );
205             sink.verbatim( null );
206             sink.text( code );
207             sink.verbatim_();
208             sink.rawText( "</td>" );
209             sink.tableRow_();
210             sink.table_();
211         }
212 
213         sink.section1_();
214         sink.body_();
215         sink.flush();
216         sink.close();
217     }
218 }