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