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