1 package org.apache.maven.plugin.pmd;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import net.sourceforge.pmd.cpd.CPD;
23 import net.sourceforge.pmd.cpd.CPDConfiguration;
24 import net.sourceforge.pmd.cpd.CSVRenderer;
25 import net.sourceforge.pmd.cpd.JavaLanguage;
26 import net.sourceforge.pmd.cpd.JavaTokenizer;
27 import net.sourceforge.pmd.cpd.Renderer;
28 import net.sourceforge.pmd.cpd.XMLRenderer;
29 import org.apache.maven.plugins.annotations.Mojo;
30 import org.apache.maven.plugins.annotations.Parameter;
31 import org.apache.maven.reporting.MavenReportException;
32 import org.codehaus.plexus.util.FileUtils;
33 import org.codehaus.plexus.util.IOUtil;
34 import org.codehaus.plexus.util.StringUtils;
35 import org.codehaus.plexus.util.WriterFactory;
36
37 import java.io.ByteArrayOutputStream;
38 import java.io.File;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.io.OutputStreamWriter;
42 import java.io.UnsupportedEncodingException;
43 import java.io.Writer;
44 import java.util.Locale;
45 import java.util.Properties;
46 import java.util.ResourceBundle;
47
48
49
50
51
52
53
54
55
56
57 @Mojo( name = "cpd", threadSafe = true )
58 public class CpdReport
59 extends AbstractPmdReport
60 {
61
62
63
64 @Parameter( property = "minimumTokens", defaultValue = "100" )
65 private int minimumTokens;
66
67
68
69
70
71
72
73 @Parameter( property = "cpd.skip", defaultValue = "false" )
74 private boolean skip;
75
76
77
78
79
80
81
82
83 @Parameter( property = "cpd.ignoreLiterals", defaultValue = "false" )
84 private boolean ignoreLiterals;
85
86
87
88
89
90
91 @Parameter( property = "cpd.ignoreIdentifiers", defaultValue = "false" )
92 private boolean ignoreIdentifiers;
93
94
95 private CPD cpd;
96
97
98
99
100 public String getName( Locale locale )
101 {
102 return getBundle( locale ).getString( "report.cpd.name" );
103 }
104
105
106
107
108 public String getDescription( Locale locale )
109 {
110 return getBundle( locale ).getString( "report.cpd.description" );
111 }
112
113
114
115
116 public void executeReport( Locale locale )
117 throws MavenReportException
118 {
119 try
120 {
121 execute( locale );
122 }
123 finally
124 {
125 if ( getSink() != null )
126 {
127 getSink().close();
128 }
129 }
130 }
131
132 private void execute( Locale locale )
133 throws MavenReportException
134 {
135 if ( !skip && canGenerateReport() )
136 {
137 ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
138 try
139 {
140 Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
141
142 generateReport( locale );
143
144 if ( !isHtml() && !isXml() )
145 {
146 writeNonHtml( cpd );
147 }
148 }
149 finally
150 {
151 Thread.currentThread().setContextClassLoader( origLoader );
152 }
153
154 }
155 }
156
157 public boolean canGenerateReport()
158 {
159 boolean result = super.canGenerateReport();
160 if ( result )
161 {
162 try
163 {
164 executeCpdWithClassloader();
165 if ( skipEmptyReport )
166 {
167 result = cpd.getMatches().hasNext();
168 if ( result )
169 {
170 getLog().debug( "Skipping Report as skipEmptyReport is true and there are no CPD issues." );
171 }
172 }
173 }
174 catch ( MavenReportException e )
175 {
176 throw new RuntimeException( e );
177 }
178 }
179 return result;
180 }
181
182 private void executeCpdWithClassloader()
183 throws MavenReportException
184 {
185 ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
186 try
187 {
188 Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
189 executeCpd();
190 }
191 finally
192 {
193 Thread.currentThread().setContextClassLoader( origLoader );
194 }
195 }
196
197 private void executeCpd()
198 throws MavenReportException
199 {
200 if ( cpd != null )
201 {
202
203 getLog().debug( "CPD has already been run - skipping redundant execution." );
204 return;
205 }
206
207 Properties p = new Properties();
208 if ( ignoreLiterals )
209 {
210 p.setProperty( JavaTokenizer.IGNORE_LITERALS, "true" );
211 }
212 if ( ignoreIdentifiers )
213 {
214 p.setProperty( JavaTokenizer.IGNORE_IDENTIFIERS, "true" );
215 }
216
217 try
218 {
219 if ( filesToProcess == null )
220 {
221 filesToProcess = getFilesToProcess();
222 }
223
224 String encoding = determineEncoding( !filesToProcess.isEmpty() );
225
226 CPDConfiguration cpdConfiguration = new CPDConfiguration();
227 cpdConfiguration.setMinimumTileSize( minimumTokens );
228 cpdConfiguration.setLanguage( new JavaLanguage( p ) );
229 cpdConfiguration.setEncoding( encoding );
230
231 cpd = new CPD( cpdConfiguration );
232
233 for ( File file : filesToProcess.keySet() )
234 {
235 cpd.add( file );
236 }
237 }
238 catch ( UnsupportedEncodingException e )
239 {
240 throw new MavenReportException( "Encoding '" + getSourceEncoding() + "' is not supported.", e );
241 }
242 catch ( IOException e )
243 {
244 throw new MavenReportException( e.getMessage(), e );
245 }
246 getLog().debug( "Executing CPD..." );
247 cpd.go();
248 getLog().debug( "CPD finished." );
249
250
251
252 if ( isXml() )
253 {
254 writeNonHtml( cpd );
255 }
256 }
257
258 private void generateReport( Locale locale )
259 {
260 CpdReportGenerator gen = new CpdReportGenerator( getSink(), filesToProcess, getBundle( locale ), aggregate );
261 gen.generate( cpd.getMatches() );
262 }
263
264 private String determineEncoding( boolean showWarn )
265 throws UnsupportedEncodingException
266 {
267 String encoding = WriterFactory.FILE_ENCODING;
268 if ( StringUtils.isNotEmpty( getSourceEncoding() ) )
269 {
270
271 encoding = getSourceEncoding();
272
273 WriterFactory.newWriter( new ByteArrayOutputStream(), encoding );
274
275 }
276 else if ( showWarn )
277 {
278 getLog().warn( "File encoding has not been set, using platform encoding " + WriterFactory.FILE_ENCODING
279 + ", i.e. build is platform dependent!" );
280 encoding = WriterFactory.FILE_ENCODING;
281 }
282 return encoding;
283 }
284
285 void writeNonHtml( CPD cpd )
286 throws MavenReportException
287 {
288 Renderer r = createRenderer();
289
290 if ( r == null )
291 {
292 return;
293 }
294
295 String buffer = r.render( cpd.getMatches() );
296 FileOutputStream tStream = null;
297 Writer writer = null;
298 try
299 {
300 targetDirectory.mkdirs();
301 File targetFile = new File( targetDirectory, "cpd." + format );
302 tStream = new FileOutputStream( targetFile );
303 writer = new OutputStreamWriter( tStream, getOutputEncoding() );
304 writer.write( buffer );
305 writer.close();
306
307 if ( includeXmlInSite )
308 {
309 File siteDir = getReportOutputDirectory();
310 siteDir.mkdirs();
311 FileUtils.copyFile( targetFile, new File( siteDir, "cpd." + format ) );
312 }
313 }
314 catch ( IOException ioe )
315 {
316 throw new MavenReportException( ioe.getMessage(), ioe );
317 }
318 finally
319 {
320 IOUtil.close( writer );
321 IOUtil.close( tStream );
322 }
323 }
324
325
326
327
328 public String getOutputName()
329 {
330 return "cpd";
331 }
332
333 private static ResourceBundle getBundle( Locale locale )
334 {
335 return ResourceBundle.getBundle( "cpd-report", locale, CpdReport.class.getClassLoader() );
336 }
337
338
339
340
341
342
343
344
345 public Renderer createRenderer()
346 throws MavenReportException
347 {
348 Renderer renderer = null;
349 if ( "xml".equals( format ) )
350 {
351 renderer = new XMLRenderer( getOutputEncoding() );
352 }
353 else if ( "csv".equals( format ) )
354 {
355 renderer = new CSVRenderer();
356 }
357 else if ( !"".equals( format ) && !"none".equals( format ) )
358 {
359 try
360 {
361 renderer = (Renderer) Class.forName( format ).newInstance();
362 }
363 catch ( Exception e )
364 {
365 throw new MavenReportException(
366 "Can't find CPD custom format " + format + ": " + e.getClass().getName(), e );
367 }
368 }
369
370 return renderer;
371 }
372 }