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