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.Renderer;
55 import net.sourceforge.pmd.cpd.XMLRenderer;
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 generateReport( locale );
172
173 if ( !isHtml() && !isXml() )
174 {
175 writeNonHtml( cpd );
176 }
177 }
178 finally
179 {
180 Thread.currentThread().setContextClassLoader( origLoader );
181 }
182
183 }
184 }
185
186 @Override
187 public boolean canGenerateReport()
188 {
189 if ( skip )
190 {
191 return false;
192 }
193
194 boolean result = super.canGenerateReport();
195 if ( result )
196 {
197 try
198 {
199 executeCpdWithClassloader();
200 if ( skipEmptyReport )
201 {
202 result = cpd.getMatches().hasNext();
203 if ( result )
204 {
205 getLog().debug( "Skipping report since skipEmptyReport is true and there are no CPD issues." );
206 }
207 }
208 }
209 catch ( MavenReportException e )
210 {
211 throw new RuntimeException( e );
212 }
213 }
214 return result;
215 }
216
217 private void executeCpdWithClassloader()
218 throws MavenReportException
219 {
220 ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
221 try
222 {
223 Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
224 executeCpd();
225 }
226 finally
227 {
228 Thread.currentThread().setContextClassLoader( origLoader );
229 }
230 }
231
232 private void executeCpd()
233 throws MavenReportException
234 {
235 if ( cpd != null )
236 {
237
238 getLog().debug( "CPD has already been run - skipping redundant execution." );
239 return;
240 }
241
242 setupPmdLogging();
243
244 Properties p = new Properties();
245 if ( ignoreLiterals )
246 {
247 p.setProperty( JavaTokenizer.IGNORE_LITERALS, "true" );
248 }
249 if ( ignoreIdentifiers )
250 {
251 p.setProperty( JavaTokenizer.IGNORE_IDENTIFIERS, "true" );
252 }
253 if ( ignoreAnnotations )
254 {
255 p.setProperty( JavaTokenizer.IGNORE_ANNOTATIONS, "true" );
256 }
257 try
258 {
259 if ( filesToProcess == null )
260 {
261 filesToProcess = getFilesToProcess();
262 }
263
264 try
265 {
266 excludeDuplicationsFromFile.loadExcludeFromFailuresData( excludeFromFailureFile );
267 }
268 catch ( MojoExecutionException e )
269 {
270 throw new MavenReportException( "Error loading exclusions", e );
271 }
272
273 String encoding = determineEncoding( !filesToProcess.isEmpty() );
274 Language cpdLanguage;
275 if ( "java".equals ( language ) || null == language )
276 {
277 cpdLanguage = new JavaLanguage( p );
278 }
279 else if ( "javascript".equals( language ) )
280 {
281 cpdLanguage = new EcmascriptLanguage();
282 }
283 else if ( "jsp".equals( language ) )
284 {
285 cpdLanguage = new JSPLanguage();
286 }
287 else
288 {
289 cpdLanguage = LanguageFactory.createLanguage( language, p );
290 }
291
292 CPDConfiguration cpdConfiguration = new CPDConfiguration();
293 cpdConfiguration.setMinimumTileSize( minimumTokens );
294 cpdConfiguration.setLanguage( cpdLanguage );
295 cpdConfiguration.setSourceEncoding( encoding );
296
297 cpd = new CPD( cpdConfiguration );
298
299 for ( File file : filesToProcess.keySet() )
300 {
301 cpd.add( file );
302 }
303 }
304 catch ( UnsupportedEncodingException e )
305 {
306 throw new MavenReportException( "Encoding '" + getSourceEncoding() + "' is not supported.", e );
307 }
308 catch ( IOException e )
309 {
310 throw new MavenReportException( e.getMessage(), e );
311 }
312 getLog().debug( "Executing CPD..." );
313 cpd.go();
314 getLog().debug( "CPD finished." );
315
316
317
318 if ( isXml() )
319 {
320 writeNonHtml( cpd );
321 }
322 }
323
324 private Iterator<Match> filterMatches( Iterator<Match> matches )
325 {
326 getLog().debug( "Filtering duplications. Using " + excludeDuplicationsFromFile.countExclusions()
327 + " configured exclusions." );
328
329 List<Match> filteredMatches = new ArrayList<>();
330 int excludedDuplications = 0;
331 while ( matches.hasNext() )
332 {
333 Match match = matches.next();
334 if ( excludeDuplicationsFromFile.isExcludedFromFailure( match ) )
335 {
336 excludedDuplications++;
337 }
338 else
339 {
340 filteredMatches.add( match );
341 }
342 }
343
344 getLog().debug( "Excluded " + excludedDuplications + " duplications." );
345 return filteredMatches.iterator();
346 }
347
348 private void generateReport( Locale locale )
349 {
350 CpdReportGenerator gen = new CpdReportGenerator( getSink(), filesToProcess, getBundle( locale ), aggregate );
351 Iterator<Match> matches = cpd.getMatches();
352 gen.generate( filterMatches( matches ) );
353 }
354
355 private String determineEncoding( boolean showWarn )
356 throws UnsupportedEncodingException
357 {
358 String encoding = WriterFactory.FILE_ENCODING;
359 if ( StringUtils.isNotEmpty( getSourceEncoding() ) )
360 {
361
362 encoding = getSourceEncoding();
363
364 WriterFactory.newWriter( new ByteArrayOutputStream(), encoding );
365
366 }
367 else if ( showWarn )
368 {
369 getLog().warn( "File encoding has not been set, using platform encoding " + WriterFactory.FILE_ENCODING
370 + ", i.e. build is platform dependent!" );
371 encoding = WriterFactory.FILE_ENCODING;
372 }
373 return encoding;
374 }
375
376 void writeNonHtml( CPD cpd )
377 throws MavenReportException
378 {
379 Renderer r = createRenderer();
380
381 if ( r == null )
382 {
383 return;
384 }
385
386 String buffer = r.render( filterMatches( cpd.getMatches() ) );
387 File targetFile = new File( targetDirectory, "cpd." + format );
388 targetDirectory.mkdirs();
389 try ( Writer writer = new OutputStreamWriter( new FileOutputStream( targetFile ), getOutputEncoding() ) )
390 {
391 writer.write( buffer );
392 writer.flush();
393
394 if ( includeXmlInSite )
395 {
396 File siteDir = getReportOutputDirectory();
397 siteDir.mkdirs();
398 FileUtils.copyFile( targetFile, new File( siteDir, "cpd." + format ) );
399 }
400 }
401 catch ( IOException ioe )
402 {
403 throw new MavenReportException( ioe.getMessage(), ioe );
404 }
405 }
406
407
408
409
410 public String getOutputName()
411 {
412 return "cpd";
413 }
414
415 private static ResourceBundle getBundle( Locale locale )
416 {
417 return ResourceBundle.getBundle( "cpd-report", locale, CpdReport.class.getClassLoader() );
418 }
419
420
421
422
423
424
425
426 public Renderer createRenderer()
427 throws MavenReportException
428 {
429 Renderer renderer = null;
430 if ( "xml".equals( format ) )
431 {
432 renderer = new XMLRenderer( getOutputEncoding() );
433 }
434 else if ( "csv".equals( format ) )
435 {
436 renderer = new CSVRenderer();
437 }
438 else if ( !"".equals( format ) && !"none".equals( format ) )
439 {
440 try
441 {
442 renderer = (Renderer) Class.forName( format ).newInstance();
443 }
444 catch ( Exception e )
445 {
446 throw new MavenReportException( "Can't find CPD custom format " + format + ": "
447 + e.getClass().getName(), e );
448 }
449 }
450
451 return renderer;
452 }
453 }