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
210
211 @Parameter( defaultValue = "false" )
212 protected boolean skipEmptyReport;
213
214
215
216
217
218
219
220
221
222 @Parameter( property = "pmd.excludeFromFailureFile", defaultValue = "" )
223 protected String excludeFromFailureFile;
224
225
226
227
228
229
230
231
232
233
234 @Parameter( defaultValue = "true", property = "pmd.showPmdLog" )
235 protected boolean showPmdLog = true;
236
237
238
239
240
241
242 private Logger julLogger;
243
244
245 protected Map<File, PmdFileInfo> filesToProcess;
246
247
248
249
250 @Override
251 protected MavenProject getProject()
252 {
253 return project;
254 }
255
256
257
258
259 @Override
260 protected Renderer getSiteRenderer()
261 {
262 return siteRenderer;
263 }
264
265 protected String constructXRefLocation( boolean test )
266 {
267 String location = null;
268 if ( linkXRef )
269 {
270 File xrefLoc = test ? xrefTestLocation : xrefLocation;
271
272 String relativePath =
273 PathTool.getRelativePath( outputDirectory.getAbsolutePath(), xrefLoc.getAbsolutePath() );
274 if ( StringUtils.isEmpty( relativePath ) )
275 {
276 relativePath = ".";
277 }
278 relativePath = relativePath + "/" + xrefLoc.getName();
279 if ( xrefLoc.exists() )
280 {
281
282 location = relativePath;
283 }
284 else
285 {
286
287 List<ReportPlugin> reportPlugins = project.getReportPlugins();
288 for ( ReportPlugin plugin : reportPlugins )
289 {
290 String artifactId = plugin.getArtifactId();
291 if ( "maven-jxr-plugin".equals( artifactId ) || "jxr-maven-plugin".equals( artifactId ) )
292 {
293 location = relativePath;
294 }
295 }
296 }
297
298 if ( location == null )
299 {
300 getLog().warn( "Unable to locate Source XRef to link to - DISABLED" );
301 }
302 }
303 return location;
304 }
305
306
307
308
309
310
311
312
313 protected Map<File, PmdFileInfo> getFilesToProcess()
314 throws IOException
315 {
316 if ( aggregate && !project.isExecutionRoot() )
317 {
318 return Collections.emptyMap();
319 }
320
321 if ( excludeRoots == null )
322 {
323 excludeRoots = new File[0];
324 }
325
326 Collection<File> excludeRootFiles = new HashSet<>( excludeRoots.length );
327
328 for ( File file : excludeRoots )
329 {
330 if ( file.isDirectory() )
331 {
332 excludeRootFiles.add( file );
333 }
334 }
335
336 List<PmdFileInfo> directories = new ArrayList<>();
337
338 if ( null == compileSourceRoots )
339 {
340 compileSourceRoots = project.getCompileSourceRoots();
341 }
342 if ( compileSourceRoots != null )
343 {
344 for ( String root : compileSourceRoots )
345 {
346 File sroot = new File( root );
347 if ( sroot.exists() )
348 {
349 String sourceXref = constructXRefLocation( false );
350 directories.add( new PmdFileInfo( project, sroot, sourceXref ) );
351 }
352 }
353 }
354
355 if ( null == testSourceRoots )
356 {
357 testSourceRoots = project.getTestCompileSourceRoots();
358 }
359 if ( includeTests && testSourceRoots != null )
360 {
361 for ( String root : testSourceRoots )
362 {
363 File sroot = new File( root );
364 if ( sroot.exists() )
365 {
366 String testXref = constructXRefLocation( true );
367 directories.add( new PmdFileInfo( project, sroot, testXref ) );
368 }
369 }
370 }
371 if ( aggregate )
372 {
373 for ( MavenProject localProject : reactorProjects )
374 {
375 List<String> localCompileSourceRoots = localProject.getCompileSourceRoots();
376 for ( String root : localCompileSourceRoots )
377 {
378 File sroot = new File( root );
379 if ( sroot.exists() )
380 {
381 String sourceXref = constructXRefLocation( false );
382 directories.add( new PmdFileInfo( localProject, sroot, sourceXref ) );
383 }
384 }
385 if ( includeTests )
386 {
387 List<String> localTestCompileSourceRoots = localProject.getTestCompileSourceRoots();
388 for ( String root : localTestCompileSourceRoots )
389 {
390 File sroot = new File( root );
391 if ( sroot.exists() )
392 {
393 String testXref = constructXRefLocation( true );
394 directories.add( new PmdFileInfo( localProject, sroot, testXref ) );
395 }
396 }
397 }
398 }
399
400 }
401
402 String excluding = getExcludes();
403 getLog().debug( "Exclusions: " + excluding );
404 String including = getIncludes();
405 getLog().debug( "Inclusions: " + including );
406
407 Map<File, PmdFileInfo> files = new TreeMap<>();
408
409 for ( PmdFileInfo finfo : directories )
410 {
411 getLog().debug( "Searching for files in directory " + finfo.getSourceDirectory().toString() );
412 File sourceDirectory = finfo.getSourceDirectory();
413 if ( sourceDirectory.isDirectory() && !isDirectoryExcluded( excludeRootFiles, sourceDirectory ) )
414 {
415 List<File> newfiles = FileUtils.getFiles( sourceDirectory, including, excluding );
416 for ( File newfile : newfiles )
417 {
418 files.put( newfile.getCanonicalFile(), finfo );
419 }
420 }
421 }
422
423 return files;
424 }
425
426 private boolean isDirectoryExcluded( Collection<File> excludeRootFiles, File sourceDirectoryToCheck )
427 {
428 boolean returnVal = false;
429 for ( File excludeDir : excludeRootFiles )
430 {
431 try
432 {
433 if ( sourceDirectoryToCheck.getCanonicalPath().startsWith( excludeDir.getCanonicalPath() ) )
434 {
435 getLog().debug( "Directory " + sourceDirectoryToCheck.getAbsolutePath()
436 + " has been excluded as it matches excludeRoot "
437 + excludeDir.getAbsolutePath() );
438 returnVal = true;
439 break;
440 }
441 }
442 catch ( IOException e )
443 {
444 getLog().warn( "Error while checking " + sourceDirectoryToCheck
445 + " whether it should be excluded.", e );
446 }
447 }
448 return returnVal;
449 }
450
451
452
453
454
455
456 private String getIncludes()
457 {
458 Collection<String> patterns = new LinkedHashSet<>();
459 if ( includes != null )
460 {
461 patterns.addAll( includes );
462 }
463 if ( patterns.isEmpty() )
464 {
465 patterns.add( "**/*.java" );
466 }
467 return StringUtils.join( patterns.iterator(), "," );
468 }
469
470
471
472
473
474
475 private String getExcludes()
476 {
477 Collection<String> patterns = new LinkedHashSet<>( FileUtils.getDefaultExcludesAsList() );
478 if ( excludes != null )
479 {
480 patterns.addAll( excludes );
481 }
482 return StringUtils.join( patterns.iterator(), "," );
483 }
484
485 protected boolean isHtml()
486 {
487 return "html".equals( format );
488 }
489
490 protected boolean isXml()
491 {
492 return "xml".equals( format );
493 }
494
495
496
497
498 @Override
499 public boolean canGenerateReport()
500 {
501 if ( aggregate && !project.isExecutionRoot() )
502 {
503 return false;
504 }
505
506 if ( "pom".equals( project.getPackaging() ) && !aggregate )
507 {
508 return false;
509 }
510
511
512
513 if ( isXml() )
514 {
515 return true;
516 }
517 try
518 {
519 filesToProcess = getFilesToProcess();
520 if ( filesToProcess.isEmpty() )
521 {
522 return false;
523 }
524 }
525 catch ( IOException e )
526 {
527 getLog().error( e );
528 }
529 return true;
530 }
531
532
533
534
535 @Override
536 protected String getOutputDirectory()
537 {
538 return outputDirectory.getAbsolutePath();
539 }
540
541 protected String getSourceEncoding()
542 {
543 return sourceEncoding;
544 }
545
546
547
548
549
550
551
552 protected String getOutputEncoding()
553 {
554 return ( outputEncoding != null ) ? outputEncoding : ReaderFactory.UTF_8;
555 }
556
557 protected void setupPmdLogging()
558 {
559 if ( !showPmdLog )
560 {
561 return;
562 }
563
564 Logger logger = Logger.getLogger( "net.sourceforge.pmd" );
565
566 boolean slf4jBridgeAlreadyAdded = false;
567 for ( Handler handler : logger.getHandlers() )
568 {
569 if ( handler instanceof SLF4JBridgeHandler )
570 {
571 slf4jBridgeAlreadyAdded = true;
572 break;
573 }
574 }
575
576 if ( slf4jBridgeAlreadyAdded )
577 {
578 return;
579 }
580
581 SLF4JBridgeHandler handler = new SLF4JBridgeHandler();
582 SimpleFormatter formatter = new SimpleFormatter();
583 handler.setFormatter( formatter );
584 logger.setUseParentHandlers( false );
585 logger.addHandler( handler );
586 handler.setLevel( Level.ALL );
587 logger.setLevel( Level.ALL );
588 julLogger = logger;
589 julLogger.fine( "Configured jul-to-slf4j bridge for " + logger.getName() );
590 }
591
592 static String getPmdVersion()
593 {
594 try
595 {
596 return (String) PMD.class.getField( "VERSION" ).get( null );
597 }
598 catch ( IllegalAccessException e )
599 {
600 throw new RuntimeException( "PMD VERSION field not accessible", e );
601 }
602 catch ( NoSuchFieldException e )
603 {
604 throw new RuntimeException( "PMD VERSION field not found", e );
605 }
606 }
607 }