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