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.RuleSetReferenceId;
58 import net.sourceforge.pmd.renderers.Renderer;
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 private 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 return false;
320 }
321
322 boolean result = super.canGenerateReport();
323 if ( result )
324 {
325 try
326 {
327 executePmd();
328 if ( skipEmptyReport )
329 {
330 result = pmdResult.hasViolations();
331 if ( result )
332 {
333 getLog().debug( "Skipping report since skipEmptyReport is true and "
334 + "there are no PMD violations." );
335 }
336 }
337 }
338 catch ( MavenReportException e )
339 {
340 throw new RuntimeException( e );
341 }
342 }
343 return result;
344 }
345
346 private void executePmd()
347 throws MavenReportException
348 {
349 if ( pmdResult != null )
350 {
351
352 getLog().debug( "PMD has already been run - skipping redundant execution." );
353 return;
354 }
355
356 try
357 {
358 filesToProcess = getFilesToProcess();
359
360 if ( filesToProcess.isEmpty() && !"java".equals( language ) )
361 {
362 getLog().warn( "No files found to process. Did you add your additional source folders like javascript?"
363 + " (see also build-helper-maven-plugin)" );
364 }
365 }
366 catch ( IOException e )
367 {
368 throw new MavenReportException( "Can't get file list", e );
369 }
370
371
372 PmdRequest request = new PmdRequest();
373 request.setLanguageAndVersion( language, targetJdk );
374 request.setRulesets( resolveRulesets() );
375 request.setAuxClasspath( typeResolution ? determineAuxClasspath() : null );
376 request.setSourceEncoding( getSourceEncoding() );
377 request.addFiles( filesToProcess.keySet() );
378 request.setMinimumPriority( minimumPriority );
379 request.setSuppressMarker( suppressMarker );
380 request.setBenchmarkOutputLocation( benchmark ? benchmarkOutputFilename : null );
381 request.setAnalysisCacheLocation( analysisCache ? analysisCacheLocation : null );
382 request.setExcludeFromFailureFile( excludeFromFailureFile );
383
384 request.setTargetDirectory( targetDirectory.getAbsolutePath() );
385 request.setOutputEncoding( getOutputEncoding() );
386 request.setFormat( format );
387 request.setShowPmdLog( showPmdLog );
388 request.setColorizedLog( MessageUtils.isColorEnabled() );
389 request.setSkipPmdError( skipPmdError );
390 request.setIncludeXmlInSite( includeXmlInSite );
391 request.setReportOutputDirectory( getReportOutputDirectory().getAbsolutePath() );
392 request.setLogLevel( determineCurrentRootLogLevel() );
393
394 Toolchain tc = getToolchain();
395 if ( tc != null )
396 {
397 getLog().info( "Toolchain in maven-pmd-plugin: " + tc );
398 String javaExecutable = tc.findTool( "java" );
399 request.setJavaExecutable( javaExecutable );
400 }
401
402 pmdResult = PmdExecutor.execute( request );
403 }
404
405 protected String getSourceEncoding()
406 {
407 String encoding = super.getSourceEncoding();
408 if ( StringUtils.isEmpty( encoding ) )
409 {
410 encoding = ReaderFactory.FILE_ENCODING;
411 if ( !filesToProcess.isEmpty() )
412 {
413 getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
414 + ", i.e. build is platform dependent!" );
415 }
416 }
417 return encoding;
418 }
419
420
421
422
423
424
425
426
427 private String resolveRulesets() throws MavenReportException
428 {
429
430 locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
431 locator.addSearchPath( "url", "" );
432 locator.setOutputDirectory( rulesetsTargetDirectory );
433
434 String[] sets = new String[rulesets.length];
435 try
436 {
437 for ( int idx = 0; idx < rulesets.length; idx++ )
438 {
439 String set = rulesets[idx];
440 getLog().debug( "Preparing ruleset: " + set );
441 RuleSetReferenceId id = new RuleSetReferenceId( set );
442 File ruleset = locator.getResourceAsFile( id.getRuleSetFileName(), getLocationTemp( set ) );
443 if ( null == ruleset )
444 {
445 throw new MavenReportException( "Could not resolve " + set );
446 }
447 sets[idx] = ruleset.getAbsolutePath();
448 }
449 }
450 catch ( ResourceNotFoundException | FileResourceCreationException e )
451 {
452 throw new MavenReportException( e.getMessage(), e );
453 }
454 return StringUtils.join( sets, "," );
455 }
456
457 private void generateMavenSiteReport( Locale locale )
458 throws MavenReportException
459 {
460 Sink sink = getSink();
461 PmdReportGenerator doxiaRenderer = new PmdReportGenerator( getLog(), sink, getBundle( locale ), aggregate );
462 doxiaRenderer.setRenderRuleViolationPriority( renderRuleViolationPriority );
463 doxiaRenderer.setRenderViolationsByPriority( renderViolationsByPriority );
464 doxiaRenderer.setFiles( filesToProcess );
465 doxiaRenderer.setViolations( pmdResult.getViolations() );
466 if ( renderProcessingErrors )
467 {
468 doxiaRenderer.setProcessingErrors( pmdResult.getErrors() );
469 }
470
471 try
472 {
473 doxiaRenderer.beginDocument();
474 doxiaRenderer.render();
475 doxiaRenderer.endDocument();
476 }
477 catch ( IOException e )
478 {
479 getLog().warn( "Failure creating the report: " + e.getLocalizedMessage(), e );
480 }
481 }
482
483
484
485
486
487
488
489 protected String getLocationTemp( String name )
490 {
491 String loc = name;
492 if ( loc.indexOf( '/' ) != -1 )
493 {
494 loc = loc.substring( loc.lastIndexOf( '/' ) + 1 );
495 }
496 if ( loc.indexOf( '\\' ) != -1 )
497 {
498 loc = loc.substring( loc.lastIndexOf( '\\' ) + 1 );
499 }
500
501
502
503
504
505 loc = loc.replaceAll( "[\\?\\:\\&\\=\\%]", "_" );
506
507 if ( !loc.endsWith( ".xml" ) )
508 {
509 loc = loc + ".xml";
510 }
511
512 getLog().debug( "Before: " + name + " After: " + loc );
513 return loc;
514 }
515
516 private String determineAuxClasspath() throws MavenReportException
517 {
518 try
519 {
520 List<String> classpath = new ArrayList<>();
521 if ( aggregate )
522 {
523 List<String> dependencies = new ArrayList<>();
524
525
526
527
528 List<String> exclusionPatterns = new ArrayList<>();
529 for ( MavenProject localProject : reactorProjects )
530 {
531 exclusionPatterns.add( localProject.getGroupId() + ":" + localProject.getArtifactId() );
532 }
533 TransformableFilter filter = new AndFilter( Arrays.asList(
534 new ExclusionsFilter( exclusionPatterns ),
535 includeTests ? ScopeFilter.including( "test" ) : ScopeFilter.including( "compile" )
536 ) );
537
538 for ( MavenProject localProject : reactorProjects )
539 {
540 ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(
541 session.getProjectBuildingRequest() );
542
543 Iterable<ArtifactResult> resolvedDependencies = dependencyResolver.resolveDependencies(
544 buildingRequest, localProject.getModel(), filter );
545
546 for ( ArtifactResult resolvedArtifact : resolvedDependencies )
547 {
548 dependencies.add( resolvedArtifact.getArtifact().getFile().toString() );
549 }
550
551 List<String> projectCompileClasspath = includeTests ? localProject.getTestClasspathElements()
552 : localProject.getCompileClasspathElements();
553
554 classpath.addAll( projectCompileClasspath );
555 if ( !localProject.isExecutionRoot() )
556 {
557 for ( String path : projectCompileClasspath )
558 {
559 File pathFile = new File( path );
560 String[] children = pathFile.list();
561
562 if ( !pathFile.exists() || ( children != null && children.length == 0 ) )
563 {
564 getLog().warn( "The project " + localProject.getArtifactId()
565 + " does not seem to be compiled. PMD results might be inaccurate." );
566 }
567 }
568 }
569
570 }
571
572
573 classpath.addAll( dependencies );
574
575 getLog().debug( "Using aggregated aux classpath: " + classpath );
576 }
577 else
578 {
579 classpath.addAll( includeTests ? project.getTestClasspathElements()
580 : project.getCompileClasspathElements() );
581 getLog().debug( "Using aux classpath: " + classpath );
582 }
583 String path = StringUtils.join( classpath.iterator(), File.pathSeparator );
584 return path;
585 }
586 catch ( Exception e )
587 {
588 throw new MavenReportException( e.getMessage(), e );
589 }
590 }
591
592
593
594
595 @Override
596 public String getOutputName()
597 {
598 return "pmd";
599 }
600
601 private static ResourceBundle getBundle( Locale locale )
602 {
603 return ResourceBundle.getBundle( "pmd-report", locale, PmdReport.class.getClassLoader() );
604 }
605
606
607
608
609
610
611
612
613 @Deprecated
614 public final Renderer createRenderer() throws MavenReportException
615 {
616 return PmdExecutor.createRenderer( format, getOutputEncoding() );
617 }
618 }