1 package org.apache.maven.plugins.pmd;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.ByteArrayOutputStream;
23 import java.io.File;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.OutputStreamWriter;
27 import java.io.UnsupportedEncodingException;
28 import java.io.Writer;
29 import java.util.ArrayList;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Properties;
34 import java.util.ResourceBundle;
35
36 import org.apache.maven.plugin.MojoExecutionException;
37 import org.apache.maven.plugins.annotations.Mojo;
38 import org.apache.maven.plugins.annotations.Parameter;
39 import org.apache.maven.reporting.MavenReportException;
40 import org.codehaus.plexus.util.FileUtils;
41 import org.codehaus.plexus.util.StringUtils;
42 import org.codehaus.plexus.util.WriterFactory;
43
44 import net.sourceforge.pmd.cpd.CPD;
45 import net.sourceforge.pmd.cpd.CPDConfiguration;
46 import net.sourceforge.pmd.cpd.CSVRenderer;
47 import net.sourceforge.pmd.cpd.EcmascriptLanguage;
48 import net.sourceforge.pmd.cpd.JSPLanguage;
49 import net.sourceforge.pmd.cpd.JavaLanguage;
50 import net.sourceforge.pmd.cpd.JavaTokenizer;
51 import net.sourceforge.pmd.cpd.Language;
52 import net.sourceforge.pmd.cpd.LanguageFactory;
53 import net.sourceforge.pmd.cpd.Match;
54 import net.sourceforge.pmd.cpd.XMLRenderer;
55 import net.sourceforge.pmd.cpd.renderer.CPDRenderer;
56
57
58
59
60
61
62
63
64
65
66 @Mojo( name = "cpd", threadSafe = true )
67 public class CpdReport
68 extends AbstractPmdReport
69 {
70
71
72
73
74
75
76 @Parameter( defaultValue = "java" )
77 private String language;
78
79
80
81
82 @Parameter( property = "minimumTokens", defaultValue = "100" )
83 private int minimumTokens;
84
85
86
87
88
89
90 @Parameter( property = "cpd.skip", defaultValue = "false" )
91 private boolean skip;
92
93
94
95
96
97
98
99
100 @Parameter( property = "cpd.ignoreLiterals", defaultValue = "false" )
101 private boolean ignoreLiterals;
102
103
104
105
106
107
108 @Parameter( property = "cpd.ignoreIdentifiers", defaultValue = "false" )
109 private boolean ignoreIdentifiers;
110
111
112
113
114
115
116 @Parameter( property = "cpd.ignoreAnnotations", defaultValue = "false" )
117 private boolean ignoreAnnotations;
118
119
120 private CPD cpd;
121
122
123 private final ExcludeDuplicationsFromFile excludeDuplicationsFromFile = new ExcludeDuplicationsFromFile();
124
125
126
127
128 public String getName( Locale locale )
129 {
130 return getBundle( locale ).getString( "report.cpd.name" );
131 }
132
133
134
135
136 public String getDescription( Locale locale )
137 {
138 return getBundle( locale ).getString( "report.cpd.description" );
139 }
140
141
142
143
144 @Override
145 public void executeReport( Locale locale )
146 throws MavenReportException
147 {
148 try
149 {
150 execute( locale );
151 }
152 finally
153 {
154 if ( getSink() != null )
155 {
156 getSink().close();
157 }
158 }
159 }
160
161 private void execute( Locale locale )
162 throws MavenReportException
163 {
164 if ( !skip && canGenerateReport() )
165 {
166 ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
167 try
168 {
169 Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
170
171 generateMavenSiteReport( locale );
172 }
173 finally
174 {
175 Thread.currentThread().setContextClassLoader( origLoader );
176 }
177
178 }
179 }
180
181 @Override
182 public boolean canGenerateReport()
183 {
184 if ( skip )
185 {
186 return false;
187 }
188
189 boolean result = super.canGenerateReport();
190 if ( result )
191 {
192 try
193 {
194 executeCpdWithClassloader();
195 if ( skipEmptyReport )
196 {
197 result = cpd.getMatches().hasNext();
198 if ( result )
199 {
200 getLog().debug( "Skipping report since skipEmptyReport is true and there are no CPD issues." );
201 }
202 }
203 }
204 catch ( MavenReportException e )
205 {
206 throw new RuntimeException( e );
207 }
208 }
209 return result;
210 }
211
212 private void executeCpdWithClassloader()
213 throws MavenReportException
214 {
215 ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
216 try
217 {
218 Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
219 executeCpd();
220 }
221 finally
222 {
223 Thread.currentThread().setContextClassLoader( origLoader );
224 }
225 }
226
227 private void executeCpd()
228 throws MavenReportException
229 {
230 if ( cpd != null )
231 {
232
233 getLog().debug( "CPD has already been run - skipping redundant execution." );
234 return;
235 }
236
237 setupPmdLogging();
238
239 Properties p = new Properties();
240 if ( ignoreLiterals )
241 {
242 p.setProperty( JavaTokenizer.IGNORE_LITERALS, "true" );
243 }
244 if ( ignoreIdentifiers )
245 {
246 p.setProperty( JavaTokenizer.IGNORE_IDENTIFIERS, "true" );
247 }
248 if ( ignoreAnnotations )
249 {
250 p.setProperty( JavaTokenizer.IGNORE_ANNOTATIONS, "true" );
251 }
252 try
253 {
254 if ( filesToProcess == null )
255 {
256 filesToProcess = getFilesToProcess();
257 }
258
259 try
260 {
261 excludeDuplicationsFromFile.loadExcludeFromFailuresData( excludeFromFailureFile );
262 }
263 catch ( MojoExecutionException e )
264 {
265 throw new MavenReportException( "Error loading exclusions", e );
266 }
267
268 String encoding = determineEncoding( !filesToProcess.isEmpty() );
269 Language cpdLanguage;
270 if ( "java".equals ( language ) || null == language )
271 {
272 cpdLanguage = new JavaLanguage( p );
273 }
274 else if ( "javascript".equals( language ) )
275 {
276 cpdLanguage = new EcmascriptLanguage();
277 }
278 else if ( "jsp".equals( language ) )
279 {
280 cpdLanguage = new JSPLanguage();
281 }
282 else
283 {
284 cpdLanguage = LanguageFactory.createLanguage( language, p );
285 }
286
287 CPDConfiguration cpdConfiguration = new CPDConfiguration();
288 cpdConfiguration.setMinimumTileSize( minimumTokens );
289 cpdConfiguration.setLanguage( cpdLanguage );
290 cpdConfiguration.setSourceEncoding( encoding );
291
292 cpd = new CPD( cpdConfiguration );
293
294 for ( File file : filesToProcess.keySet() )
295 {
296 cpd.add( file );
297 }
298 }
299 catch ( UnsupportedEncodingException e )
300 {
301 throw new MavenReportException( "Encoding '" + getSourceEncoding() + "' is not supported.", e );
302 }
303 catch ( IOException e )
304 {
305 throw new MavenReportException( e.getMessage(), e );
306 }
307 getLog().debug( "Executing CPD..." );
308 cpd.go();
309 getLog().debug( "CPD finished." );
310
311
312
313 writeXmlReport( cpd );
314
315
316 if ( !isHtml() && !isXml() )
317 {
318 writeFormattedReport( cpd );
319 }
320 }
321
322 private Iterator<Match> filterMatches( Iterator<Match> matches )
323 {
324 getLog().debug( "Filtering duplications. Using " + excludeDuplicationsFromFile.countExclusions()
325 + " configured exclusions." );
326
327 List<Match> filteredMatches = new ArrayList<>();
328 int excludedDuplications = 0;
329 while ( matches.hasNext() )
330 {
331 Match match = matches.next();
332 if ( excludeDuplicationsFromFile.isExcludedFromFailure( match ) )
333 {
334 excludedDuplications++;
335 }
336 else
337 {
338 filteredMatches.add( match );
339 }
340 }
341
342 getLog().debug( "Excluded " + excludedDuplications + " duplications." );
343 return filteredMatches.iterator();
344 }
345
346 private void generateMavenSiteReport( Locale locale )
347 {
348 CpdReportGenerator gen = new CpdReportGenerator( getSink(), filesToProcess, getBundle( locale ), aggregate );
349 Iterator<Match> matches = cpd.getMatches();
350 gen.generate( filterMatches( matches ) );
351 }
352
353 private String determineEncoding( boolean showWarn )
354 throws UnsupportedEncodingException
355 {
356 String encoding = WriterFactory.FILE_ENCODING;
357 if ( StringUtils.isNotEmpty( getSourceEncoding() ) )
358 {
359
360 encoding = getSourceEncoding();
361
362 WriterFactory.newWriter( new ByteArrayOutputStream(), encoding );
363
364 }
365 else if ( showWarn )
366 {
367 getLog().warn( "File encoding has not been set, using platform encoding " + WriterFactory.FILE_ENCODING
368 + ", i.e. build is platform dependent!" );
369 encoding = WriterFactory.FILE_ENCODING;
370 }
371 return encoding;
372 }
373
374 private void writeFormattedReport( CPD cpd )
375 throws MavenReportException
376 {
377 CPDRenderer r = createRenderer();
378 writeReport( cpd, r, format );
379
380 }
381
382 void writeXmlReport( CPD cpd ) throws MavenReportException
383 {
384 File targetFile = writeReport( cpd, new XMLRenderer( getOutputEncoding() ), "xml" );
385 if ( includeXmlInSite )
386 {
387 File siteDir = getReportOutputDirectory();
388 siteDir.mkdirs();
389 try
390 {
391 FileUtils.copyFile( targetFile, new File( siteDir, "cpd.xml" ) );
392 }
393 catch ( IOException e )
394 {
395 throw new MavenReportException( e.getMessage(), e );
396 }
397 }
398 }
399
400 private File writeReport( CPD cpd, CPDRenderer r, String extension ) throws MavenReportException
401 {
402 if ( r == null )
403 {
404 return null;
405 }
406
407 File targetFile = new File( targetDirectory, "cpd." + extension );
408 targetDirectory.mkdirs();
409 try ( Writer writer = new OutputStreamWriter( new FileOutputStream( targetFile ), getOutputEncoding() ) )
410 {
411 r.render( filterMatches( cpd.getMatches() ), writer );
412 writer.flush();
413 }
414 catch ( IOException ioe )
415 {
416 throw new MavenReportException( ioe.getMessage(), ioe );
417 }
418 return targetFile;
419 }
420
421
422
423
424 public String getOutputName()
425 {
426 return "cpd";
427 }
428
429 private static ResourceBundle getBundle( Locale locale )
430 {
431 return ResourceBundle.getBundle( "cpd-report", locale, CpdReport.class.getClassLoader() );
432 }
433
434
435
436
437
438
439
440 public CPDRenderer createRenderer()
441 throws MavenReportException
442 {
443 CPDRenderer renderer = null;
444 if ( "xml".equals( format ) )
445 {
446 renderer = new XMLRenderer( getOutputEncoding() );
447 }
448 else if ( "csv".equals( format ) )
449 {
450 renderer = new CSVRenderer();
451 }
452 else if ( !"".equals( format ) && !"none".equals( format ) )
453 {
454 try
455 {
456 renderer = (CPDRenderer) Class.forName( format ).getConstructor().newInstance();
457 }
458 catch ( Exception e )
459 {
460 throw new MavenReportException( "Can't find CPD custom format " + format + ": "
461 + e.getClass().getName(), e );
462 }
463 }
464
465 return renderer;
466 }
467 }