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