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.File;
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashSet;
28 import java.util.LinkedHashSet;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.TreeMap;
32 import java.util.logging.Level;
33 import java.util.logging.Logger;
34 import java.util.logging.SimpleFormatter;
35
36 import net.sourceforge.pmd.PMD;
37
38 import org.apache.maven.doxia.siterenderer.Renderer;
39 import org.apache.maven.model.ReportPlugin;
40 import org.apache.maven.plugins.annotations.Component;
41 import org.apache.maven.plugins.annotations.Parameter;
42 import org.apache.maven.project.MavenProject;
43 import org.apache.maven.reporting.AbstractMavenReport;
44 import org.codehaus.plexus.util.FileUtils;
45 import org.codehaus.plexus.util.PathTool;
46 import org.codehaus.plexus.util.ReaderFactory;
47 import org.codehaus.plexus.util.StringUtils;
48 import org.slf4j.bridge.SLF4JBridgeHandler;
49
50
51
52
53
54
55
56 public abstract class AbstractPmdReport
57 extends AbstractMavenReport
58 {
59
60
61
62 @Parameter( property = "project.build.directory", required = true )
63 protected File targetDirectory;
64
65
66
67
68
69
70 @Parameter( property = "project.reporting.outputDirectory", required = true )
71 protected File outputDirectory;
72
73
74
75
76 @Component
77 private Renderer siteRenderer;
78
79
80
81
82 @Parameter( defaultValue = "${project}", readonly = true, required = true )
83 protected MavenProject project;
84
85
86
87
88
89
90 @Parameter( property = "format", defaultValue = "xml" )
91 protected String format = "xml";
92
93
94
95
96
97 @Parameter( property = "linkXRef", defaultValue = "true" )
98 private boolean linkXRef;
99
100
101
102
103 @Parameter( defaultValue = "${project.reporting.outputDirectory}/xref" )
104 private File xrefLocation;
105
106
107
108
109 @Parameter( defaultValue = "${project.reporting.outputDirectory}/xref-test" )
110 private File xrefTestLocation;
111
112
113
114
115
116
117
118
119
120 @Parameter
121 private List<String> excludes;
122
123
124
125
126
127
128
129 @Parameter
130 private List<String> includes;
131
132
133
134
135
136
137 @Parameter( defaultValue = "${project.compileSourceRoots}" )
138 private List<String> compileSourceRoots;
139
140
141
142
143
144
145 @Parameter( defaultValue = "${project.testCompileSourceRoots}" )
146 private List<String> testSourceRoots;
147
148
149
150
151
152
153 @Parameter
154 private File[] excludeRoots;
155
156
157
158
159
160
161 @Parameter( defaultValue = "false" )
162 protected boolean includeTests;
163
164
165
166
167
168
169 @Parameter( property = "aggregate", defaultValue = "false" )
170 protected boolean aggregate;
171
172
173
174
175
176
177 @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
178 private String sourceEncoding;
179
180
181
182
183
184
185 @Parameter( property = "outputEncoding", defaultValue = "${project.reporting.outputEncoding}" )
186 private String outputEncoding;
187
188
189
190
191 @Parameter( property = "reactorProjects", readonly = true )
192 protected List<MavenProject> reactorProjects;
193
194
195
196
197
198
199 @Parameter( defaultValue = "false" )
200 protected boolean includeXmlInSite;
201
202
203
204
205
206
207
208 @Parameter( defaultValue = "true" )
209 protected boolean skipEmptyReport;
210
211
212
213
214
215
216
217
218
219 @Parameter( property = "pmd.excludeFromFailureFile", defaultValue = "" )
220 protected String excludeFromFailureFile;
221
222
223
224
225
226
227
228
229
230
231 @Parameter( defaultValue = "true", property = "pmd.showPmdLog" )
232 protected boolean showPmdLog = true;
233
234
235 protected Map<File, PmdFileInfo> filesToProcess;
236
237
238
239
240 @Override
241 protected MavenProject getProject()
242 {
243 return project;
244 }
245
246
247
248
249 @Override
250 protected Renderer getSiteRenderer()
251 {
252 return siteRenderer;
253 }
254
255 protected String constructXRefLocation( boolean test )
256 {
257 String location = null;
258 if ( linkXRef )
259 {
260 File xrefLoc = test ? xrefTestLocation : xrefLocation;
261
262 String relativePath =
263 PathTool.getRelativePath( outputDirectory.getAbsolutePath(), xrefLoc.getAbsolutePath() );
264 if ( StringUtils.isEmpty( relativePath ) )
265 {
266 relativePath = ".";
267 }
268 relativePath = relativePath + "/" + xrefLoc.getName();
269 if ( xrefLoc.exists() )
270 {
271
272 location = relativePath;
273 }
274 else
275 {
276
277 List<ReportPlugin> reportPlugins = project.getReportPlugins();
278 for ( ReportPlugin plugin : reportPlugins )
279 {
280 String artifactId = plugin.getArtifactId();
281 if ( "maven-jxr-plugin".equals( artifactId ) || "jxr-maven-plugin".equals( artifactId ) )
282 {
283 location = relativePath;
284 }
285 }
286 }
287
288 if ( location == null )
289 {
290 getLog().warn( "Unable to locate Source XRef to link to - DISABLED" );
291 }
292 }
293 return location;
294 }
295
296
297
298
299
300
301
302
303 protected Map<File, PmdFileInfo> getFilesToProcess()
304 throws IOException
305 {
306 if ( aggregate && !project.isExecutionRoot() )
307 {
308 return Collections.emptyMap();
309 }
310
311 if ( excludeRoots == null )
312 {
313 excludeRoots = new File[0];
314 }
315
316 Collection<File> excludeRootFiles = new HashSet<>( excludeRoots.length );
317
318 for ( File file : excludeRoots )
319 {
320 if ( file.isDirectory() )
321 {
322 excludeRootFiles.add( file );
323 }
324 }
325
326 List<PmdFileInfo> directories = new ArrayList<>();
327
328 if ( null == compileSourceRoots )
329 {
330 compileSourceRoots = project.getCompileSourceRoots();
331 }
332 if ( compileSourceRoots != null )
333 {
334 for ( String root : compileSourceRoots )
335 {
336 File sroot = new File( root );
337 if ( sroot.exists() )
338 {
339 String sourceXref = constructXRefLocation( false );
340 directories.add( new PmdFileInfo( project, sroot, sourceXref ) );
341 }
342 }
343 }
344
345 if ( null == testSourceRoots )
346 {
347 testSourceRoots = project.getTestCompileSourceRoots();
348 }
349 if ( includeTests && testSourceRoots != null )
350 {
351 for ( String root : testSourceRoots )
352 {
353 File sroot = new File( root );
354 if ( sroot.exists() )
355 {
356 String testXref = constructXRefLocation( true );
357 directories.add( new PmdFileInfo( project, sroot, testXref ) );
358 }
359 }
360 }
361 if ( aggregate )
362 {
363 for ( MavenProject localProject : reactorProjects )
364 {
365 List<String> localCompileSourceRoots = localProject.getCompileSourceRoots();
366 for ( String root : localCompileSourceRoots )
367 {
368 File sroot = new File( root );
369 if ( sroot.exists() )
370 {
371 String sourceXref = constructXRefLocation( false );
372 directories.add( new PmdFileInfo( localProject, sroot, sourceXref ) );
373 }
374 }
375 if ( includeTests )
376 {
377 List<String> localTestCompileSourceRoots = localProject.getTestCompileSourceRoots();
378 for ( String root : localTestCompileSourceRoots )
379 {
380 File sroot = new File( root );
381 if ( sroot.exists() )
382 {
383 String testXref = constructXRefLocation( true );
384 directories.add( new PmdFileInfo( localProject, sroot, testXref ) );
385 }
386 }
387 }
388 }
389
390 }
391
392 String excluding = getExcludes();
393 getLog().debug( "Exclusions: " + excluding );
394 String including = getIncludes();
395 getLog().debug( "Inclusions: " + including );
396
397 Map<File, PmdFileInfo> files = new TreeMap<>();
398
399 for ( PmdFileInfo finfo : directories )
400 {
401 getLog().debug( "Searching for files in directory " + finfo.getSourceDirectory().toString() );
402 File sourceDirectory = finfo.getSourceDirectory();
403 if ( sourceDirectory.isDirectory() && !isDirectoryExcluded( excludeRootFiles, sourceDirectory ) )
404 {
405 List<File> newfiles = FileUtils.getFiles( sourceDirectory, including, excluding );
406 for ( File newfile : newfiles )
407 {
408 files.put( newfile.getCanonicalFile(), finfo );
409 }
410 }
411 }
412
413 return files;
414 }
415
416 private boolean isDirectoryExcluded( Collection<File> excludeRootFiles, File sourceDirectoryToCheck )
417 {
418 boolean returnVal = false;
419 for ( File excludeDir : excludeRootFiles )
420 {
421 try
422 {
423 if ( sourceDirectoryToCheck.getCanonicalPath().startsWith( excludeDir.getCanonicalPath() ) )
424 {
425 getLog().debug( "Directory " + sourceDirectoryToCheck.getAbsolutePath()
426 + " has been excluded as it matches excludeRoot "
427 + excludeDir.getAbsolutePath() );
428 returnVal = true;
429 break;
430 }
431 }
432 catch ( IOException e )
433 {
434 getLog().warn( "Error while checking " + sourceDirectoryToCheck
435 + " whether it should be excluded.", e );
436 }
437 }
438 return returnVal;
439 }
440
441
442
443
444
445
446 private String getIncludes()
447 {
448 Collection<String> patterns = new LinkedHashSet<>();
449 if ( includes != null )
450 {
451 patterns.addAll( includes );
452 }
453 if ( patterns.isEmpty() )
454 {
455 patterns.add( "**/*.java" );
456 }
457 return StringUtils.join( patterns.iterator(), "," );
458 }
459
460
461
462
463
464
465 private String getExcludes()
466 {
467 Collection<String> patterns = new LinkedHashSet<>( FileUtils.getDefaultExcludesAsList() );
468 if ( excludes != null )
469 {
470 patterns.addAll( excludes );
471 }
472 return StringUtils.join( patterns.iterator(), "," );
473 }
474
475 protected boolean isHtml()
476 {
477 return "html".equals( format );
478 }
479
480 protected boolean isXml()
481 {
482 return "xml".equals( format );
483 }
484
485
486
487
488 @Override
489 public boolean canGenerateReport()
490 {
491 if ( aggregate && !project.isExecutionRoot() )
492 {
493 return false;
494 }
495
496 if ( "pom".equals( project.getPackaging() ) && !aggregate )
497 {
498 return false;
499 }
500
501
502
503 if ( isXml() )
504 {
505 return true;
506 }
507 try
508 {
509 filesToProcess = getFilesToProcess();
510 if ( filesToProcess.isEmpty() )
511 {
512 return false;
513 }
514 }
515 catch ( IOException e )
516 {
517 getLog().error( e );
518 }
519 return true;
520 }
521
522
523
524
525 @Override
526 protected String getOutputDirectory()
527 {
528 return outputDirectory.getAbsolutePath();
529 }
530
531 protected String getSourceEncoding()
532 {
533 return sourceEncoding;
534 }
535
536
537
538
539
540
541
542 protected String getOutputEncoding()
543 {
544 return ( outputEncoding != null ) ? outputEncoding : ReaderFactory.UTF_8;
545 }
546
547 protected void setupPmdLogging()
548 {
549 if ( !showPmdLog )
550 {
551 return;
552 }
553
554 Logger logger = Logger.getLogger( "net.sourceforge.pmd" );
555
556 SLF4JBridgeHandler handler = new SLF4JBridgeHandler();
557 SimpleFormatter formatter = new SimpleFormatter();
558 handler.setFormatter( formatter );
559 logger.setUseParentHandlers( false );
560 logger.addHandler( handler );
561 handler.setLevel( Level.ALL );
562 logger.setLevel( Level.ALL );
563 getLog().debug( "Configured jul-to-slf4j bridge for " + logger.getName() );
564 }
565
566 static String getPmdVersion()
567 {
568 try
569 {
570 return (String) PMD.class.getField( "VERSION" ).get( null );
571 }
572 catch ( IllegalAccessException e )
573 {
574 throw new RuntimeException( "PMD VERSION field not accessible", e );
575 }
576 catch ( NoSuchFieldException e )
577 {
578 throw new RuntimeException( "PMD VERSION field not found", e );
579 }
580 }
581 }