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
225 @Parameter( property = "pmd.rulesetsTargetDirectory", defaultValue = "${project.build.directory}/pmd/rulesets" )
226 private File rulesetsTargetDirectory;
227
228
229
230
231
232 @Component
233 private ResourceManager locator;
234
235 @Component
236 private DependencyResolver dependencyResolver;
237
238
239
240
241
242
243 private PmdResult pmdResult;
244
245
246
247
248 @Override
249 public String getName( Locale locale )
250 {
251 return getBundle( locale ).getString( "report.pmd.name" );
252 }
253
254
255
256
257 @Override
258 public String getDescription( Locale locale )
259 {
260 return getBundle( locale ).getString( "report.pmd.description" );
261 }
262
263
264
265
266
267
268
269
270 public void setRulesets( String[] rulesets )
271 {
272 this.rulesets = Arrays.copyOf( rulesets, rulesets.length );
273 }
274
275
276
277
278 @Override
279 public void executeReport( Locale locale )
280 throws MavenReportException
281 {
282 try
283 {
284 execute( locale );
285 }
286 finally
287 {
288 if ( getSink() != null )
289 {
290 getSink().close();
291 }
292 }
293 }
294
295 private void execute( Locale locale )
296 throws MavenReportException
297 {
298 if ( !skip && canGenerateReport() )
299 {
300 ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
301 try
302 {
303 Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
304
305 generateMavenSiteReport( locale );
306 }
307 finally
308 {
309 Thread.currentThread().setContextClassLoader( origLoader );
310 }
311 }
312 }
313
314 @Override
315 public boolean canGenerateReport()
316 {
317 if ( skip )
318 {
319 getLog().info( "Skipping PMD execution" );
320 return false;
321 }
322
323 boolean result = super.canGenerateReport();
324 if ( result )
325 {
326 try
327 {
328 executePmd();
329 if ( skipEmptyReport )
330 {
331 result = pmdResult.hasViolations();
332 if ( result )
333 {
334 getLog().debug( "Skipping report since skipEmptyReport is true and "
335 + "there are no PMD violations." );
336 }
337 }
338 }
339 catch ( MavenReportException e )
340 {
341 throw new RuntimeException( e );
342 }
343 }
344 return result;
345 }
346
347 private void executePmd()
348 throws MavenReportException
349 {
350 if ( pmdResult != null )
351 {
352
353 getLog().debug( "PMD has already been run - skipping redundant execution." );
354 return;
355 }
356
357 try
358 {
359 filesToProcess = getFilesToProcess();
360
361 if ( filesToProcess.isEmpty() && !"java".equals( language ) )
362 {
363 getLog().warn( "No files found to process. Did you add your additional source folders like javascript?"
364 + " (see also build-helper-maven-plugin)" );
365 }
366 }
367 catch ( IOException e )
368 {
369 throw new MavenReportException( "Can't get file list", e );
370 }
371
372
373 PmdRequest request = new PmdRequest();
374 request.setLanguageAndVersion( language, targetJdk );
375 request.setRulesets( resolveRulesets() );
376 request.setAuxClasspath( typeResolution ? determineAuxClasspath() : null );
377 request.setSourceEncoding( getSourceEncoding() );
378 request.addFiles( filesToProcess.keySet() );
379 request.setMinimumPriority( minimumPriority );
380 request.setSuppressMarker( suppressMarker );
381 request.setBenchmarkOutputLocation( benchmark ? benchmarkOutputFilename : null );
382 request.setAnalysisCacheLocation( analysisCache ? analysisCacheLocation : null );
383 request.setExcludeFromFailureFile( excludeFromFailureFile );
384
385 request.setTargetDirectory( targetDirectory.getAbsolutePath() );
386 request.setOutputEncoding( getOutputEncoding() );
387 request.setFormat( format );
388 request.setShowPmdLog( showPmdLog );
389 request.setColorizedLog( MessageUtils.isColorEnabled() );
390 request.setSkipPmdError( skipPmdError );
391 request.setIncludeXmlInSite( includeXmlInSite );
392 request.setReportOutputDirectory( getReportOutputDirectory().getAbsolutePath() );
393 request.setLogLevel( determineCurrentRootLogLevel() );
394
395 Toolchain tc = getToolchain();
396 if ( tc != null )
397 {
398 getLog().info( "Toolchain in maven-pmd-plugin: " + tc );
399 String javaExecutable = tc.findTool( "java" );
400 request.setJavaExecutable( javaExecutable );
401 }
402
403 getLog().info( "PMD version: " + AbstractPmdReport.getPmdVersion() );
404 pmdResult = PmdExecutor.execute( request );
405 }
406
407 protected String getSourceEncoding()
408 {
409 String encoding = super.getSourceEncoding();
410 if ( StringUtils.isEmpty( encoding ) )
411 {
412 encoding = ReaderFactory.FILE_ENCODING;
413 if ( !filesToProcess.isEmpty() )
414 {
415 getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
416 + ", i.e. build is platform dependent!" );
417 }
418 }
419 return encoding;
420 }
421
422
423
424
425
426
427
428
429 private String resolveRulesets() throws MavenReportException
430 {
431
432
433 locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
434
435 locator.addSearchPath( FileResourceLoader.ID, project.getBasedir().getAbsolutePath() );
436
437
438 locator.addSearchPath( FileResourceLoader.ID, session.getRequest().getBaseDirectory() );
439 locator.setOutputDirectory( rulesetsTargetDirectory );
440
441 String[] sets = new String[rulesets.length];
442 try
443 {
444 for ( int idx = 0; idx < rulesets.length; idx++ )
445 {
446 String set = rulesets[idx];
447 getLog().debug( "Preparing ruleset: " + set );
448 String rulesetFilename = determineRulesetFilename( set );
449 File ruleset = locator.getResourceAsFile( rulesetFilename, getLocationTemp( set ) );
450 if ( null == ruleset )
451 {
452 throw new MavenReportException( "Could not resolve " + set );
453 }
454 sets[idx] = ruleset.getAbsolutePath();
455 }
456 }
457 catch ( ResourceNotFoundException | FileResourceCreationException e )
458 {
459 throw new MavenReportException( e.getMessage(), e );
460 }
461 return StringUtils.join( sets, "," );
462 }
463
464 private String determineRulesetFilename( String ruleset )
465 {
466 String result = ruleset.trim();
467 String lowercase = result.toLowerCase( Locale.ROOT );
468 if ( lowercase.startsWith( "http://" ) || lowercase.startsWith( "https://" ) || lowercase.endsWith( ".xml" ) )
469 {
470 return result;
471 }
472
473
474 if ( result.indexOf( '/' ) > -1 )
475 {
476 String rulesetFilename = result.substring( 0, result.lastIndexOf( '/' ) );
477 if ( rulesetFilename.toLowerCase( Locale.ROOT ).endsWith( ".xml" ) )
478 {
479 return rulesetFilename;
480 }
481 }
482
483 int dashIndex = lowercase.indexOf( '-' );
484 if ( dashIndex > -1 && lowercase.indexOf( '-', dashIndex + 1 ) == -1 )
485 {
486 String language = result.substring( 0, dashIndex );
487 String rulesetName = result.substring( dashIndex + 1 );
488 return "rulesets/" + language + "/" + rulesetName + ".xml";
489 }
490
491 return result;
492 }
493
494 private void generateMavenSiteReport( Locale locale )
495 throws MavenReportException
496 {
497 Sink sink = getSink();
498 PmdReportGenerator doxiaRenderer = new PmdReportGenerator( getLog(), sink, getBundle( locale ),
499 isAggregator() );
500 doxiaRenderer.setRenderRuleViolationPriority( renderRuleViolationPriority );
501 doxiaRenderer.setRenderViolationsByPriority( renderViolationsByPriority );
502 doxiaRenderer.setFiles( filesToProcess );
503 doxiaRenderer.setViolations( pmdResult.getViolations() );
504 if ( renderProcessingErrors )
505 {
506 doxiaRenderer.setProcessingErrors( pmdResult.getErrors() );
507 }
508
509 try
510 {
511 doxiaRenderer.beginDocument();
512 doxiaRenderer.render();
513 doxiaRenderer.endDocument();
514 }
515 catch ( IOException e )
516 {
517 getLog().warn( "Failure creating the report: " + e.getLocalizedMessage(), e );
518 }
519 }
520
521
522
523
524
525
526
527 protected String getLocationTemp( String name )
528 {
529 String loc = name;
530 if ( loc.indexOf( '/' ) != -1 )
531 {
532 loc = loc.substring( loc.lastIndexOf( '/' ) + 1 );
533 }
534 if ( loc.indexOf( '\\' ) != -1 )
535 {
536 loc = loc.substring( loc.lastIndexOf( '\\' ) + 1 );
537 }
538
539
540
541
542
543 loc = loc.replaceAll( "[\\?\\:\\&\\=\\%]", "_" );
544
545 if ( !loc.endsWith( ".xml" ) )
546 {
547 loc = loc + ".xml";
548 }
549
550 getLog().debug( "Before: " + name + " After: " + loc );
551 return loc;
552 }
553
554 private String determineAuxClasspath() throws MavenReportException
555 {
556 try
557 {
558 List<String> classpath = new ArrayList<>();
559 if ( isAggregator() )
560 {
561 List<String> dependencies = new ArrayList<>();
562
563
564
565
566 List<String> exclusionPatterns = new ArrayList<>();
567 for ( MavenProject localProject : getAggregatedProjects() )
568 {
569 exclusionPatterns.add( localProject.getGroupId() + ":" + localProject.getArtifactId() );
570 }
571 TransformableFilter filter = new AndFilter( Arrays.asList(
572 new ExclusionsFilter( exclusionPatterns ),
573 includeTests ? ScopeFilter.including( "compile", "provided", "test" )
574 : ScopeFilter.including( "compile", "provided" )
575 ) );
576
577 for ( MavenProject localProject : getAggregatedProjects() )
578 {
579 ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(
580 session.getProjectBuildingRequest() );
581
582 Iterable<ArtifactResult> resolvedDependencies = dependencyResolver.resolveDependencies(
583 buildingRequest, localProject.getDependencies(), null, filter );
584
585 for ( ArtifactResult resolvedArtifact : resolvedDependencies )
586 {
587 dependencies.add( resolvedArtifact.getArtifact().getFile().toString() );
588 }
589
590 List<String> projectClasspath = includeTests ? localProject.getTestClasspathElements()
591 : localProject.getCompileClasspathElements();
592
593
594 classpath.addAll( projectClasspath );
595 if ( !localProject.isExecutionRoot() )
596 {
597 for ( String path : projectClasspath )
598 {
599 File pathFile = new File( path );
600 String[] children = pathFile.list();
601
602 if ( !pathFile.exists() || ( children != null && children.length == 0 ) )
603 {
604 getLog().warn( "The project " + localProject.getArtifactId()
605 + " does not seem to be compiled. PMD results might be inaccurate." );
606 }
607 }
608 }
609
610 }
611
612
613 classpath.addAll( dependencies );
614
615 getLog().debug( "Using aggregated aux classpath: " + classpath );
616 }
617 else
618 {
619 classpath.addAll( includeTests ? project.getTestClasspathElements()
620 : project.getCompileClasspathElements() );
621
622 getLog().debug( "Using aux classpath: " + classpath );
623 }
624 String path = StringUtils.join( classpath.iterator(), File.pathSeparator );
625 return path;
626 }
627 catch ( Exception e )
628 {
629 throw new MavenReportException( e.getMessage(), e );
630 }
631 }
632
633
634
635
636 @Override
637 public String getOutputName()
638 {
639 return "pmd";
640 }
641
642 private static ResourceBundle getBundle( Locale locale )
643 {
644 return ResourceBundle.getBundle( "pmd-report", locale, PmdReport.class.getClassLoader() );
645 }
646
647
648
649
650
651
652
653
654 @Deprecated
655 public final Renderer createRenderer() throws MavenReportException
656 {
657 return PmdExecutor.createRenderer( format, getOutputEncoding() );
658 }
659 }