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