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