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.lang.reflect.InvocationTargetException;
25 import java.lang.reflect.Method;
26 import java.nio.file.Path;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.Collections;
30 import java.util.HashMap;
31 import java.util.HashSet;
32 import java.util.LinkedHashSet;
33 import java.util.LinkedList;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37 import java.util.TreeMap;
38
39 import org.apache.maven.doxia.siterenderer.Renderer;
40 import org.apache.maven.execution.MavenSession;
41 import org.apache.maven.model.ReportPlugin;
42 import org.apache.maven.model.Reporting;
43 import org.apache.maven.plugins.annotations.Component;
44 import org.apache.maven.plugins.annotations.Parameter;
45 import org.apache.maven.project.MavenProject;
46 import org.apache.maven.reporting.AbstractMavenReport;
47 import org.apache.maven.reporting.MavenReportException;
48 import org.apache.maven.toolchain.Toolchain;
49 import org.apache.maven.toolchain.ToolchainManager;
50 import org.codehaus.plexus.util.FileUtils;
51 import org.codehaus.plexus.util.PathTool;
52 import org.codehaus.plexus.util.ReaderFactory;
53 import org.codehaus.plexus.util.StringUtils;
54
55 import net.sourceforge.pmd.PMDVersion;
56
57
58
59
60
61
62
63 public abstract class AbstractPmdReport
64 extends AbstractMavenReport
65 {
66
67
68
69
70
71
72
73 @Parameter( property = "project.build.directory", required = true )
74 protected File targetDirectory;
75
76
77
78
79
80
81 @Parameter( property = "project.reporting.outputDirectory", required = true )
82 protected File outputDirectory;
83
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
170
171 @Parameter( property = "aggregate", defaultValue = "false" )
172 @Deprecated
173 protected boolean aggregate;
174
175
176
177
178
179
180 @Parameter( property = "encoding", defaultValue = "${project.build.sourceEncoding}" )
181 private String sourceEncoding;
182
183
184
185
186
187
188 @Parameter( property = "outputEncoding", defaultValue = "${project.reporting.outputEncoding}" )
189 private String outputEncoding;
190
191
192
193
194
195
196 @Parameter( defaultValue = "false" )
197 protected boolean includeXmlInSite;
198
199
200
201
202
203
204
205
206
207 @Parameter( defaultValue = "false" )
208 protected boolean skipEmptyReport;
209
210
211
212
213
214
215
216
217
218 @Parameter( property = "pmd.excludeFromFailureFile", defaultValue = "" )
219 protected String excludeFromFailureFile;
220
221
222
223
224
225
226
227
228
229
230 @Parameter( defaultValue = "true", property = "pmd.showPmdLog" )
231 protected boolean showPmdLog = true;
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267 @Parameter
268 private Map<String, String> jdkToolchain;
269
270
271
272
273
274
275
276
277 @Parameter( defaultValue = "${project}", readonly = true, required = true )
278 protected MavenProject project;
279
280
281
282
283 @Parameter( property = "reactorProjects", readonly = true )
284 protected List<MavenProject> reactorProjects;
285
286
287
288
289
290 @Parameter( defaultValue = "${session}", required = true, readonly = true )
291 protected MavenSession session;
292
293
294
295
296 @Component
297 private Renderer siteRenderer;
298
299 @Component
300 private ToolchainManager toolchainManager;
301
302
303 protected Map<File, PmdFileInfo> filesToProcess;
304
305
306
307
308 @Override
309 protected MavenProject getProject()
310 {
311 return project;
312 }
313
314
315
316
317 @Override
318 protected Renderer getSiteRenderer()
319 {
320 return siteRenderer;
321 }
322
323 protected String constructXRefLocation( boolean test )
324 {
325 String location = null;
326 if ( linkXRef )
327 {
328 File xrefLoc = test ? xrefTestLocation : xrefLocation;
329
330 String relativePath =
331 PathTool.getRelativePath( outputDirectory.getAbsolutePath(), xrefLoc.getAbsolutePath() );
332 if ( StringUtils.isEmpty( relativePath ) )
333 {
334 relativePath = ".";
335 }
336 relativePath = relativePath + "/" + xrefLoc.getName();
337 if ( xrefLoc.exists() )
338 {
339
340 location = relativePath;
341 }
342 else
343 {
344
345 Reporting reporting = project.getModel().getReporting();
346 List<ReportPlugin> reportPlugins = reporting != null
347 ? reporting.getPlugins()
348 : Collections.<ReportPlugin>emptyList();
349 for ( ReportPlugin plugin : reportPlugins )
350 {
351 String artifactId = plugin.getArtifactId();
352 if ( "maven-jxr-plugin".equals( artifactId ) || "jxr-maven-plugin".equals( artifactId ) )
353 {
354 location = relativePath;
355 }
356 }
357 }
358
359 if ( location == null )
360 {
361 getLog().warn( "Unable to locate Source XRef to link to - DISABLED" );
362 }
363 }
364 return location;
365 }
366
367
368
369
370
371
372
373
374 protected Map<File, PmdFileInfo> getFilesToProcess()
375 throws IOException
376 {
377 if ( aggregate && !project.isExecutionRoot() )
378 {
379 return Collections.emptyMap();
380 }
381
382 if ( excludeRoots == null )
383 {
384 excludeRoots = new File[0];
385 }
386
387 Collection<File> excludeRootFiles = new HashSet<>( excludeRoots.length );
388
389 for ( File file : excludeRoots )
390 {
391 if ( file.isDirectory() )
392 {
393 excludeRootFiles.add( file );
394 }
395 }
396
397 List<PmdFileInfo> directories = new ArrayList<>();
398
399 if ( null == compileSourceRoots )
400 {
401 compileSourceRoots = project.getCompileSourceRoots();
402 }
403 if ( compileSourceRoots != null )
404 {
405 for ( String root : compileSourceRoots )
406 {
407 File sroot = new File( root );
408 if ( sroot.exists() )
409 {
410 String sourceXref = constructXRefLocation( false );
411 directories.add( new PmdFileInfo( project, sroot, sourceXref ) );
412 }
413 }
414 }
415
416 if ( null == testSourceRoots )
417 {
418 testSourceRoots = project.getTestCompileSourceRoots();
419 }
420 if ( includeTests && testSourceRoots != null )
421 {
422 for ( String root : testSourceRoots )
423 {
424 File sroot = new File( root );
425 if ( sroot.exists() )
426 {
427 String testXref = constructXRefLocation( true );
428 directories.add( new PmdFileInfo( project, sroot, testXref ) );
429 }
430 }
431 }
432 if ( isAggregator() )
433 {
434 for ( MavenProject localProject : getAggregatedProjects() )
435 {
436 List<String> localCompileSourceRoots = localProject.getCompileSourceRoots();
437 for ( String root : localCompileSourceRoots )
438 {
439 File sroot = new File( root );
440 if ( sroot.exists() )
441 {
442 String sourceXref = constructXRefLocation( false );
443 directories.add( new PmdFileInfo( localProject, sroot, sourceXref ) );
444 }
445 }
446 if ( includeTests )
447 {
448 List<String> localTestCompileSourceRoots = localProject.getTestCompileSourceRoots();
449 for ( String root : localTestCompileSourceRoots )
450 {
451 File sroot = new File( root );
452 if ( sroot.exists() )
453 {
454 String testXref = constructXRefLocation( true );
455 directories.add( new PmdFileInfo( localProject, sroot, testXref ) );
456 }
457 }
458 }
459 }
460
461 }
462
463 String excluding = getExcludes();
464 getLog().debug( "Exclusions: " + excluding );
465 String including = getIncludes();
466 getLog().debug( "Inclusions: " + including );
467
468 Map<File, PmdFileInfo> files = new TreeMap<>();
469
470 for ( PmdFileInfo finfo : directories )
471 {
472 getLog().debug( "Searching for files in directory " + finfo.getSourceDirectory().toString() );
473 File sourceDirectory = finfo.getSourceDirectory();
474 if ( sourceDirectory.isDirectory() && !isDirectoryExcluded( excludeRootFiles, sourceDirectory ) )
475 {
476 List<File> newfiles = FileUtils.getFiles( sourceDirectory, including, excluding );
477 for ( File newfile : newfiles )
478 {
479 files.put( newfile.getCanonicalFile(), finfo );
480 }
481 }
482 }
483
484 return files;
485 }
486
487 private boolean isDirectoryExcluded( Collection<File> excludeRootFiles, File sourceDirectoryToCheck )
488 {
489 boolean returnVal = false;
490 for ( File excludeDir : excludeRootFiles )
491 {
492 try
493 {
494 if ( sourceDirectoryToCheck.getCanonicalPath().startsWith( excludeDir.getCanonicalPath() ) )
495 {
496 getLog().debug( "Directory " + sourceDirectoryToCheck.getAbsolutePath()
497 + " has been excluded as it matches excludeRoot "
498 + excludeDir.getAbsolutePath() );
499 returnVal = true;
500 break;
501 }
502 }
503 catch ( IOException e )
504 {
505 getLog().warn( "Error while checking " + sourceDirectoryToCheck
506 + " whether it should be excluded.", e );
507 }
508 }
509 return returnVal;
510 }
511
512
513
514
515
516
517 private String getIncludes()
518 {
519 Collection<String> patterns = new LinkedHashSet<>();
520 if ( includes != null )
521 {
522 patterns.addAll( includes );
523 }
524 if ( patterns.isEmpty() )
525 {
526 patterns.add( "**/*.java" );
527 }
528 return StringUtils.join( patterns.iterator(), "," );
529 }
530
531
532
533
534
535
536 private String getExcludes()
537 {
538 Collection<String> patterns = new LinkedHashSet<>( FileUtils.getDefaultExcludesAsList() );
539 if ( excludes != null )
540 {
541 patterns.addAll( excludes );
542 }
543 return StringUtils.join( patterns.iterator(), "," );
544 }
545
546 protected boolean isXml()
547 {
548 return "xml".equals( format );
549 }
550
551
552
553
554 @Override
555 public boolean canGenerateReport()
556 {
557 if ( aggregate && !project.isExecutionRoot() )
558 {
559 return false;
560 }
561
562 if ( !isAggregator() && "pom".equalsIgnoreCase( project.getPackaging() ) )
563 {
564 return false;
565 }
566
567
568
569 if ( isXml() )
570 {
571 return true;
572 }
573 try
574 {
575 filesToProcess = getFilesToProcess();
576 if ( filesToProcess.isEmpty() )
577 {
578 return false;
579 }
580 }
581 catch ( IOException e )
582 {
583 getLog().error( e );
584 }
585 return true;
586 }
587
588
589
590
591 @Override
592 protected String getOutputDirectory()
593 {
594 return outputDirectory.getAbsolutePath();
595 }
596
597 protected String getSourceEncoding()
598 {
599 return sourceEncoding;
600 }
601
602
603
604
605
606
607
608 @Override
609 protected String getOutputEncoding()
610 {
611 return ( outputEncoding != null ) ? outputEncoding : ReaderFactory.UTF_8;
612 }
613
614 protected String determineCurrentRootLogLevel()
615 {
616 String logLevel = System.getProperty( "org.slf4j.simpleLogger.defaultLogLevel" );
617 if ( logLevel == null )
618 {
619 logLevel = System.getProperty( "maven.logging.root.level" );
620 }
621 if ( logLevel == null )
622 {
623
624 logLevel = "info";
625 }
626 return logLevel;
627 }
628
629 static String getPmdVersion()
630 {
631 return PMDVersion.VERSION;
632 }
633
634
635
636 protected final Toolchain getToolchain()
637 {
638 Toolchain tc = null;
639
640 if ( jdkToolchain != null )
641 {
642
643 try
644 {
645 Method getToolchainsMethod =
646 toolchainManager.getClass().getMethod( "getToolchains", MavenSession.class, String.class,
647 Map.class );
648
649 @SuppressWarnings( "unchecked" )
650 List<Toolchain> tcs =
651 (List<Toolchain>) getToolchainsMethod.invoke( toolchainManager, session, "jdk",
652 jdkToolchain );
653
654 if ( tcs != null && !tcs.isEmpty() )
655 {
656 tc = tcs.get( 0 );
657 }
658 }
659 catch ( NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
660 | InvocationTargetException e )
661 {
662
663 }
664 }
665
666 if ( tc == null )
667 {
668 tc = toolchainManager.getToolchainFromBuildContext( "jdk", session );
669 }
670
671 return tc;
672 }
673
674 protected boolean isAggregator()
675 {
676
677 return aggregate;
678 }
679
680
681 protected Collection<MavenProject> getAggregatedProjects()
682 {
683 Map<Path, MavenProject> reactorProjectsMap = new HashMap<>();
684 for ( MavenProject reactorProject : this.reactorProjects )
685 {
686 reactorProjectsMap.put( reactorProject.getBasedir().toPath(), reactorProject );
687 }
688
689 return modulesForAggregatedProject( project, reactorProjectsMap );
690 }
691
692
693
694
695
696
697
698
699 private Set<MavenProject> modulesForAggregatedProject( MavenProject aggregatedProject,
700 Map<Path, MavenProject> reactorProjectsMap )
701 {
702
703
704
705
706
707 if ( aggregatedProject.getModules().isEmpty() )
708 {
709 return Collections.singleton( aggregatedProject );
710 }
711
712 List<Path> modulePaths = new LinkedList<Path>();
713 for ( String module : aggregatedProject.getModules() )
714 {
715 modulePaths.add( new File( aggregatedProject.getBasedir(), module ).toPath() );
716 }
717
718 Set<MavenProject> aggregatedModules = new LinkedHashSet<>();
719
720 for ( Path modulePath : modulePaths )
721 {
722 MavenProject module = reactorProjectsMap.remove( modulePath );
723 if ( module != null )
724 {
725 aggregatedModules.addAll( modulesForAggregatedProject( module, reactorProjectsMap ) );
726 }
727 }
728
729 return aggregatedModules;
730 }
731 }