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