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.Arrays;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.ResourceBundle;
29
30 import org.apache.maven.doxia.sink.Sink;
31 import org.apache.maven.plugins.annotations.Component;
32 import org.apache.maven.plugins.annotations.Mojo;
33 import org.apache.maven.plugins.annotations.Parameter;
34 import org.apache.maven.plugins.annotations.ResolutionScope;
35 import org.apache.maven.plugins.pmd.exec.PmdExecutor;
36 import org.apache.maven.plugins.pmd.exec.PmdRequest;
37 import org.apache.maven.plugins.pmd.exec.PmdResult;
38 import org.apache.maven.project.DefaultProjectBuildingRequest;
39 import org.apache.maven.project.MavenProject;
40 import org.apache.maven.project.ProjectBuildingRequest;
41 import org.apache.maven.reporting.MavenReportException;
42 import org.apache.maven.shared.artifact.filter.resolve.AndFilter;
43 import org.apache.maven.shared.artifact.filter.resolve.ExclusionsFilter;
44 import org.apache.maven.shared.artifact.filter.resolve.ScopeFilter;
45 import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
46 import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
47 import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
48 import org.apache.maven.toolchain.Toolchain;
49 import org.codehaus.plexus.resource.ResourceManager;
50 import org.codehaus.plexus.resource.loader.FileResourceCreationException;
51 import org.codehaus.plexus.resource.loader.FileResourceLoader;
52 import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
53 import org.codehaus.plexus.util.StringUtils;
54
55 import net.sourceforge.pmd.renderers.Renderer;
56
57
58
59
60
61
62
63
64
65 @Mojo( name = "pmd", threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST )
66 public class PmdReport
67 extends AbstractPmdReport
68 {
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83 @Parameter( property = "targetJdk", defaultValue = "${maven.compiler.source}" )
84 private String targetJdk;
85
86
87
88
89
90
91
92 @Parameter( defaultValue = "java" )
93 private String language;
94
95
96
97
98
99
100 @Parameter( property = "minimumPriority", defaultValue = "5" )
101 private int minimumPriority = 5;
102
103
104
105
106
107
108 @Parameter( property = "pmd.skip", defaultValue = "false" )
109 private boolean skip;
110
111
112
113
114
115
116
117
118 @Parameter
119 String[] rulesets = new String[] { "/rulesets/java/maven-pmd-plugin-default.xml" };
120
121
122
123
124
125
126
127 @Parameter( property = "pmd.typeResolution", defaultValue = "true" )
128 private boolean typeResolution;
129
130
131
132
133
134
135 @Parameter( property = "pmd.benchmark", defaultValue = "false" )
136 private boolean benchmark;
137
138
139
140
141
142
143 @Parameter( property = "pmd.benchmarkOutputFilename",
144 defaultValue = "${project.build.directory}/pmd-benchmark.txt" )
145 private String benchmarkOutputFilename;
146
147
148
149
150
151
152
153
154 @Parameter( property = "pmd.suppressMarker" )
155 private String suppressMarker;
156
157
158
159
160
161
162 @Parameter( property = "pmd.skipPmdError", defaultValue = "true" )
163 private boolean skipPmdError;
164
165
166
167
168
169
170
171
172
173 @Parameter( property = "pmd.analysisCache", defaultValue = "false" )
174 private boolean analysisCache;
175
176
177
178
179
180
181
182
183
184
185 @Parameter( property = "pmd.analysisCacheLocation", defaultValue = "${project.build.directory}/pmd/pmd.cache" )
186 private String analysisCacheLocation;
187
188
189
190
191
192
193
194
195
196
197 @Parameter( property = "pmd.renderProcessingErrors", defaultValue = "true" )
198 private boolean renderProcessingErrors = true;
199
200
201
202
203
204
205 @Parameter( property = "pmd.renderRuleViolationPriority", defaultValue = "true" )
206 private boolean renderRuleViolationPriority = true;
207
208
209
210
211
212
213
214 @Parameter( property = "pmd.renderViolationsByPriority", defaultValue = "true" )
215 private boolean renderViolationsByPriority = true;
216
217
218
219
220
221
222 @Parameter( property = "pmd.renderSuppressedViolations", defaultValue = "true" )
223 private boolean renderSuppressedViolations = true;
224
225
226
227
228
229
230
231 @Parameter( property = "pmd.rulesetsTargetDirectory", defaultValue = "${project.build.directory}/pmd/rulesets" )
232 private File rulesetsTargetDirectory;
233
234
235
236
237
238 @Component
239 private ResourceManager locator;
240
241 @Component
242 private DependencyResolver dependencyResolver;
243
244
245
246
247
248
249 private PmdResult pmdResult;
250
251
252
253
254 @Override
255 public String getName( Locale locale )
256 {
257 return getBundle( locale ).getString( "report.pmd.name" );
258 }
259
260
261
262
263 @Override
264 public String getDescription( Locale locale )
265 {
266 return getBundle( locale ).getString( "report.pmd.description" );
267 }
268
269
270
271
272
273
274
275
276 public void setRulesets( String[] rulesets )
277 {
278 this.rulesets = Arrays.copyOf( rulesets, rulesets.length );
279 }
280
281
282
283
284 @Override
285 public void executeReport( Locale locale )
286 throws MavenReportException
287 {
288 ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
289 try
290 {
291 Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
292
293 generateMavenSiteReport( locale );
294 }
295 finally
296 {
297 Thread.currentThread().setContextClassLoader( origLoader );
298 }
299 }
300
301 @Override
302 public boolean canGenerateReport()
303 {
304 if ( skip )
305 {
306 getLog().info( "Skipping PMD execution" );
307 return false;
308 }
309
310 boolean result = super.canGenerateReport();
311 if ( result )
312 {
313 try
314 {
315 executePmd();
316 if ( skipEmptyReport )
317 {
318 result = pmdResult.hasViolations();
319 if ( !result )
320 {
321 getLog().debug( "Skipping report since skipEmptyReport is true and "
322 + "there are no PMD violations." );
323 }
324 }
325 }
326 catch ( MavenReportException e )
327 {
328 throw new RuntimeException( e );
329 }
330 }
331 return result;
332 }
333
334 private void executePmd()
335 throws MavenReportException
336 {
337 if ( pmdResult != null )
338 {
339
340 getLog().debug( "PMD has already been run - skipping redundant execution." );
341 return;
342 }
343
344 try
345 {
346 filesToProcess = getFilesToProcess();
347
348 if ( filesToProcess.isEmpty() && !"java".equals( language ) )
349 {
350 getLog().warn( "No files found to process. Did you add your additional source folders like javascript?"
351 + " (see also build-helper-maven-plugin)" );
352 }
353 }
354 catch ( IOException e )
355 {
356 throw new MavenReportException( "Can't get file list", e );
357 }
358
359
360 PmdRequest request = new PmdRequest();
361 request.setLanguageAndVersion( language, targetJdk );
362 request.setRulesets( resolveRulesets() );
363 request.setAuxClasspath( typeResolution ? determineAuxClasspath() : null );
364 request.setSourceEncoding( getInputEncoding() );
365 request.addFiles( filesToProcess.keySet() );
366 request.setMinimumPriority( minimumPriority );
367 request.setSuppressMarker( suppressMarker );
368 request.setBenchmarkOutputLocation( benchmark ? benchmarkOutputFilename : null );
369 request.setAnalysisCacheLocation( analysisCache ? analysisCacheLocation : null );
370 request.setExcludeFromFailureFile( excludeFromFailureFile );
371
372 request.setTargetDirectory( targetDirectory.getAbsolutePath() );
373 request.setOutputEncoding( getOutputEncoding() );
374 request.setFormat( format );
375 request.setShowPmdLog( showPmdLog );
376 request.setSkipPmdError( skipPmdError );
377 request.setIncludeXmlInSite( includeXmlInSite );
378 request.setReportOutputDirectory( getReportOutputDirectory().getAbsolutePath() );
379 request.setLogLevel( determineCurrentRootLogLevel() );
380
381 Toolchain tc = getToolchain();
382 if ( tc != null )
383 {
384 getLog().info( "Toolchain in maven-pmd-plugin: " + tc );
385 String javaExecutable = tc.findTool( "java" );
386 request.setJavaExecutable( javaExecutable );
387 }
388
389 getLog().info( "PMD version: " + AbstractPmdReport.getPmdVersion() );
390 pmdResult = PmdExecutor.execute( request );
391 }
392
393
394
395
396
397
398
399 private List<String> resolveRulesets() throws MavenReportException
400 {
401
402
403 locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
404
405 locator.addSearchPath( FileResourceLoader.ID, project.getBasedir().getAbsolutePath() );
406
407
408 locator.addSearchPath( FileResourceLoader.ID, session.getRequest().getBaseDirectory() );
409 locator.setOutputDirectory( rulesetsTargetDirectory );
410
411 String[] sets = new String[rulesets.length];
412 try
413 {
414 for ( int idx = 0; idx < rulesets.length; idx++ )
415 {
416 String set = rulesets[idx];
417 getLog().debug( "Preparing ruleset: " + set );
418 String rulesetFilename = determineRulesetFilename( set );
419 File ruleset = locator.getResourceAsFile( rulesetFilename, getLocationTemp( set ) );
420 if ( null == ruleset )
421 {
422 throw new MavenReportException( "Could not resolve " + set );
423 }
424 sets[idx] = ruleset.getAbsolutePath();
425 }
426 }
427 catch ( ResourceNotFoundException | FileResourceCreationException e )
428 {
429 throw new MavenReportException( e.getMessage(), e );
430 }
431 return Arrays.asList( sets );
432 }
433
434 private String determineRulesetFilename( String ruleset )
435 {
436 String result = ruleset.trim();
437 String lowercase = result.toLowerCase( Locale.ROOT );
438 if ( lowercase.startsWith( "http://" ) || lowercase.startsWith( "https://" ) || lowercase.endsWith( ".xml" ) )
439 {
440 return result;
441 }
442
443
444 if ( result.indexOf( '/' ) > -1 )
445 {
446 String rulesetFilename = result.substring( 0, result.lastIndexOf( '/' ) );
447 if ( rulesetFilename.toLowerCase( Locale.ROOT ).endsWith( ".xml" ) )
448 {
449 return rulesetFilename;
450 }
451 }
452
453 int dashIndex = lowercase.indexOf( '-' );
454 if ( dashIndex > -1 && lowercase.indexOf( '-', dashIndex + 1 ) == -1 )
455 {
456 String language = result.substring( 0, dashIndex );
457 String rulesetName = result.substring( dashIndex + 1 );
458 return "rulesets/" + language + "/" + rulesetName + ".xml";
459 }
460
461 return result;
462 }
463
464 private void generateMavenSiteReport( Locale locale )
465 throws MavenReportException
466 {
467 Sink sink = getSink();
468 PmdReportGenerator doxiaRenderer = new PmdReportGenerator( getLog(), sink, getBundle( locale ),
469 isAggregator() );
470 doxiaRenderer.setRenderRuleViolationPriority( renderRuleViolationPriority );
471 doxiaRenderer.setRenderViolationsByPriority( renderViolationsByPriority );
472 doxiaRenderer.setFiles( filesToProcess );
473 doxiaRenderer.setViolations( pmdResult.getViolations() );
474 if ( renderSuppressedViolations )
475 {
476 doxiaRenderer.setSuppressedViolations( pmdResult.getSuppressedViolations() );
477 }
478 if ( renderProcessingErrors )
479 {
480 doxiaRenderer.setProcessingErrors( pmdResult.getErrors() );
481 }
482
483 try
484 {
485 doxiaRenderer.beginDocument();
486 doxiaRenderer.render();
487 doxiaRenderer.endDocument();
488 }
489 catch ( IOException e )
490 {
491 getLog().warn( "Failure creating the report: " + e.getLocalizedMessage(), e );
492 }
493 }
494
495
496
497
498
499
500
501 protected String getLocationTemp( String name )
502 {
503 String loc = name;
504 if ( loc.indexOf( '/' ) != -1 )
505 {
506 loc = loc.substring( loc.lastIndexOf( '/' ) + 1 );
507 }
508 if ( loc.indexOf( '\\' ) != -1 )
509 {
510 loc = loc.substring( loc.lastIndexOf( '\\' ) + 1 );
511 }
512
513
514
515
516
517 loc = loc.replaceAll( "[\\?\\:\\&\\=\\%]", "_" );
518
519 if ( !loc.endsWith( ".xml" ) )
520 {
521 loc = loc + ".xml";
522 }
523
524 getLog().debug( "Before: " + name + " After: " + loc );
525 return loc;
526 }
527
528 private String determineAuxClasspath() throws MavenReportException
529 {
530 try
531 {
532 List<String> classpath = new ArrayList<>();
533 if ( isAggregator() )
534 {
535 List<String> dependencies = new ArrayList<>();
536
537
538
539
540 List<String> exclusionPatterns = new ArrayList<>();
541 for ( MavenProject localProject : getAggregatedProjects() )
542 {
543 exclusionPatterns.add( localProject.getGroupId() + ":" + localProject.getArtifactId() );
544 }
545 TransformableFilter filter = new AndFilter( Arrays.asList(
546 new ExclusionsFilter( exclusionPatterns ),
547 includeTests ? ScopeFilter.including( "compile", "provided", "test" )
548 : ScopeFilter.including( "compile", "provided" )
549 ) );
550
551 for ( MavenProject localProject : getAggregatedProjects() )
552 {
553 ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(
554 session.getProjectBuildingRequest() );
555
556 Iterable<ArtifactResult> resolvedDependencies = dependencyResolver.resolveDependencies(
557 buildingRequest, localProject.getDependencies(), null, filter );
558
559 for ( ArtifactResult resolvedArtifact : resolvedDependencies )
560 {
561 dependencies.add( resolvedArtifact.getArtifact().getFile().toString() );
562 }
563
564 List<String> projectClasspath = includeTests ? localProject.getTestClasspathElements()
565 : localProject.getCompileClasspathElements();
566
567
568 classpath.addAll( projectClasspath );
569 if ( !localProject.isExecutionRoot() )
570 {
571 for ( String path : projectClasspath )
572 {
573 File pathFile = new File( path );
574 String[] children = pathFile.list();
575
576 if ( !pathFile.exists() || ( children != null && children.length == 0 ) )
577 {
578 getLog().warn( "The project " + localProject.getArtifactId()
579 + " does not seem to be compiled. PMD results might be inaccurate." );
580 }
581 }
582 }
583
584 }
585
586
587 classpath.addAll( dependencies );
588
589 getLog().debug( "Using aggregated aux classpath: " + classpath );
590 }
591 else
592 {
593 classpath.addAll( includeTests ? project.getTestClasspathElements()
594 : project.getCompileClasspathElements() );
595
596 getLog().debug( "Using aux classpath: " + classpath );
597 }
598 String path = StringUtils.join( classpath.iterator(), File.pathSeparator );
599 return path;
600 }
601 catch ( Exception e )
602 {
603 throw new MavenReportException( e.getMessage(), e );
604 }
605 }
606
607
608
609
610 @Override
611 public String getOutputName()
612 {
613 return "pmd";
614 }
615
616 private static ResourceBundle getBundle( Locale locale )
617 {
618 return ResourceBundle.getBundle( "pmd-report", locale, PmdReport.class.getClassLoader() );
619 }
620
621
622
623
624
625
626
627
628 @Deprecated
629 public final Renderer createRenderer() throws MavenReportException
630 {
631 return PmdExecutor.createRenderer( format, getOutputEncoding() );
632 }
633 }