/
PmdReport.java
671 lines (604 loc) · 24.4 KB
/
PmdReport.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
package org.apache.maven.plugins.pmd;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.plugins.pmd.exec.PmdExecutor;
import org.apache.maven.plugins.pmd.exec.PmdRequest;
import org.apache.maven.plugins.pmd.exec.PmdResult;
import org.apache.maven.project.DefaultProjectBuildingRequest;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingRequest;
import org.apache.maven.reporting.MavenReportException;
import org.apache.maven.shared.artifact.filter.resolve.AndFilter;
import org.apache.maven.shared.artifact.filter.resolve.ExclusionsFilter;
import org.apache.maven.shared.artifact.filter.resolve.ScopeFilter;
import org.apache.maven.shared.artifact.filter.resolve.TransformableFilter;
import org.apache.maven.shared.transfer.artifact.resolve.ArtifactResult;
import org.apache.maven.shared.transfer.dependencies.resolve.DependencyResolver;
import org.apache.maven.shared.utils.logging.MessageUtils;
import org.apache.maven.toolchain.Toolchain;
import org.codehaus.plexus.resource.ResourceManager;
import org.codehaus.plexus.resource.loader.FileResourceCreationException;
import org.codehaus.plexus.resource.loader.FileResourceLoader;
import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.StringUtils;
import net.sourceforge.pmd.renderers.Renderer;
/**
* Creates a PMD site report based on the rulesets and configuration set in the plugin.
* It can also generate a pmd output file aside from the site report in any of the following formats: xml, csv or txt.
*
* @author Brett Porter
* @version $Id$
* @since 2.0
*/
@Mojo( name = "pmd", threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST )
public class PmdReport
extends AbstractPmdReport
{
/**
* The target JDK to analyze based on. Should match the source used in the compiler plugin. Valid values
* with the default PMD version are
* currently <code>1.3</code>, <code>1.4</code>, <code>1.5</code>, <code>1.6</code>, <code>1.7</code>,
* <code>1.8</code>, <code>9</code>, <code>10</code>, <code>11</code>, <code>12</code>, <code>13</code>,
* <code>14</code>, <code>15</code>, <code>16</code>, <code>17</code>, and <code>18</code>.
*
* <p> You can override the default PMD version by specifying PMD as a dependency,
* see <a href="examples/upgrading-PMD-at-runtime.html">Upgrading PMD at Runtime</a>.</p>
*
* <p>
* <b>Note:</b> this parameter is only used if the language parameter is set to <code>java</code>.
* </p>
*/
@Parameter( property = "targetJdk", defaultValue = "${maven.compiler.source}" )
private String targetJdk;
/**
* The programming language to be analyzed by PMD. Valid values are currently <code>java</code>,
* <code>javascript</code> and <code>jsp</code>.
*
* @since 3.0
*/
@Parameter( defaultValue = "java" )
private String language;
/**
* The rule priority threshold; rules with lower priority than this will not be evaluated.
*
* @since 2.1
*/
@Parameter( property = "minimumPriority", defaultValue = "5" )
private int minimumPriority = 5;
/**
* Skip the PMD report generation. Most useful on the command line via "-Dpmd.skip=true".
*
* @since 2.1
*/
@Parameter( property = "pmd.skip", defaultValue = "false" )
private boolean skip;
/**
* The PMD rulesets to use. See the
* <a href="https://pmd.github.io/latest/pmd_rules_java.html">Stock Java Rulesets</a> for a
* list of available rules.
* Defaults to a custom ruleset provided by this maven plugin
* (<code>/rulesets/java/maven-pmd-plugin-default.xml</code>).
*/
@Parameter
String[] rulesets = new String[] { "/rulesets/java/maven-pmd-plugin-default.xml" };
/**
* Controls whether the project's compile/test classpath should be passed to PMD to enable its type resolution
* feature.
*
* @since 3.0
*/
@Parameter( property = "pmd.typeResolution", defaultValue = "true" )
private boolean typeResolution;
/**
* Controls whether PMD will track benchmark information.
*
* @since 3.1
*/
@Parameter( property = "pmd.benchmark", defaultValue = "false" )
private boolean benchmark;
/**
* Benchmark output filename.
*
* @since 3.1
*/
@Parameter( property = "pmd.benchmarkOutputFilename",
defaultValue = "${project.build.directory}/pmd-benchmark.txt" )
private String benchmarkOutputFilename;
/**
* Source level marker used to indicate whether a RuleViolation should be suppressed. If it is not set, PMD's
* default will be used, which is <code>NOPMD</code>. See also <a
* href="https://pmd.github.io/latest/pmd_userdocs_suppressing_warnings.html">PMD – Suppressing warnings</a>.
*
* @since 3.4
*/
@Parameter( property = "pmd.suppressMarker" )
private String suppressMarker;
/**
* per default pmd executions error are ignored to not break the whole
*
* @since 3.1
*/
@Parameter( property = "pmd.skipPmdError", defaultValue = "true" )
private boolean skipPmdError;
/**
* Enables the analysis cache, which speeds up PMD. This
* requires a cache file, that contains the results of the last
* PMD run. Thus the cache is only effective, if this file is
* not cleaned between runs.
*
* @since 3.8
*/
@Parameter( property = "pmd.analysisCache", defaultValue = "false" )
private boolean analysisCache;
/**
* The location of the analysis cache, if it is enabled.
* This file contains the results of the last PMD run and must not be cleaned
* between consecutive PMD runs. Otherwise the cache is not in use.
* If the file doesn't exist, PMD executes as if there is no cache enabled and
* all files are analyzed. Otherwise only changed files will be analyzed again.
*
* @since 3.8
*/
@Parameter( property = "pmd.analysisCacheLocation", defaultValue = "${project.build.directory}/pmd/pmd.cache" )
private String analysisCacheLocation;
/**
* Also render processing errors into the HTML report.
* Processing errors are problems, that PMD encountered while executing the rules.
* It can be parsing errors or exceptions during rule execution.
* Processing errors indicate a bug in PMD and the information provided help in
* reporting and fixing bugs in PMD.
*
* @since 3.9.0
*/
@Parameter( property = "pmd.renderProcessingErrors", defaultValue = "true" )
private boolean renderProcessingErrors = true;
/**
* Also render the rule priority into the HTML report.
*
* @since 3.10.0
*/
@Parameter( property = "pmd.renderRuleViolationPriority", defaultValue = "true" )
private boolean renderRuleViolationPriority = true;
/**
* Add a section in the HTML report, that groups the found violations by rule priority
* in addition to grouping by file.
*
* @since 3.12.0
*/
@Parameter( property = "pmd.renderViolationsByPriority", defaultValue = "true" )
private boolean renderViolationsByPriority = true;
/**
* Add a section in the HTML report that lists the suppressed violations.
*
* @since 3.17.0
*/
@Parameter( property = "pmd.renderSuppressedViolations", defaultValue = "true" )
private boolean renderSuppressedViolations = true;
/**
* Before PMD is executed, the configured rulesets are resolved and copied into this directory.
* <p>Note: Before 3.13.0, this was by default ${project.build.directory}.
*
* @since 3.13.0
*/
@Parameter( property = "pmd.rulesetsTargetDirectory", defaultValue = "${project.build.directory}/pmd/rulesets" )
private File rulesetsTargetDirectory;
/**
* Used to locate configured rulesets. The rulesets could be on the plugin
* classpath or in the local project file system.
*/
@Component
private ResourceManager locator;
@Component
private DependencyResolver dependencyResolver;
/**
* Contains the result of the last PMD execution.
* It might be <code>null</code> which means, that PMD
* has not been executed yet.
*/
private PmdResult pmdResult;
/**
* {@inheritDoc}
*/
@Override
public String getName( Locale locale )
{
return getBundle( locale ).getString( "report.pmd.name" );
}
/**
* {@inheritDoc}
*/
@Override
public String getDescription( Locale locale )
{
return getBundle( locale ).getString( "report.pmd.description" );
}
/**
* Configures the PMD rulesets to be used directly.
* Note: Usually the rulesets are configured via the property.
*
* @param rulesets the PMD rulesets to be used.
* @see #rulesets
*/
public void setRulesets( String[] rulesets )
{
this.rulesets = Arrays.copyOf( rulesets, rulesets.length );
}
/**
* {@inheritDoc}
*/
@Override
public void executeReport( Locale locale )
throws MavenReportException
{
try
{
execute( locale );
}
finally
{
if ( getSink() != null )
{
getSink().close();
}
}
}
private void execute( Locale locale )
throws MavenReportException
{
if ( !skip && canGenerateReport() )
{
ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader( this.getClass().getClassLoader() );
generateMavenSiteReport( locale );
}
finally
{
Thread.currentThread().setContextClassLoader( origLoader );
}
}
}
@Override
public boolean canGenerateReport()
{
if ( skip )
{
getLog().info( "Skipping PMD execution" );
return false;
}
boolean result = super.canGenerateReport();
if ( result )
{
try
{
executePmd();
if ( skipEmptyReport )
{
result = pmdResult.hasViolations();
if ( !result )
{
getLog().debug( "Skipping report since skipEmptyReport is true and "
+ "there are no PMD violations." );
}
}
}
catch ( MavenReportException e )
{
throw new RuntimeException( e );
}
}
return result;
}
private void executePmd()
throws MavenReportException
{
if ( pmdResult != null )
{
// PMD has already been run
getLog().debug( "PMD has already been run - skipping redundant execution." );
return;
}
try
{
filesToProcess = getFilesToProcess();
if ( filesToProcess.isEmpty() && !"java".equals( language ) )
{
getLog().warn( "No files found to process. Did you add your additional source folders like javascript?"
+ " (see also build-helper-maven-plugin)" );
}
}
catch ( IOException e )
{
throw new MavenReportException( "Can't get file list", e );
}
PmdRequest request = new PmdRequest();
request.setLanguageAndVersion( language, targetJdk );
request.setRulesets( resolveRulesets() );
request.setAuxClasspath( typeResolution ? determineAuxClasspath() : null );
request.setSourceEncoding( getSourceEncoding() );
request.addFiles( filesToProcess.keySet() );
request.setMinimumPriority( minimumPriority );
request.setSuppressMarker( suppressMarker );
request.setBenchmarkOutputLocation( benchmark ? benchmarkOutputFilename : null );
request.setAnalysisCacheLocation( analysisCache ? analysisCacheLocation : null );
request.setExcludeFromFailureFile( excludeFromFailureFile );
request.setTargetDirectory( targetDirectory.getAbsolutePath() );
request.setOutputEncoding( getOutputEncoding() );
request.setFormat( format );
request.setShowPmdLog( showPmdLog );
request.setColorizedLog( MessageUtils.isColorEnabled() );
request.setSkipPmdError( skipPmdError );
request.setIncludeXmlInSite( includeXmlInSite );
request.setReportOutputDirectory( getReportOutputDirectory().getAbsolutePath() );
request.setLogLevel( determineCurrentRootLogLevel() );
Toolchain tc = getToolchain();
if ( tc != null )
{
getLog().info( "Toolchain in maven-pmd-plugin: " + tc );
String javaExecutable = tc.findTool( "java" ); //NOI18N
request.setJavaExecutable( javaExecutable );
}
getLog().info( "PMD version: " + AbstractPmdReport.getPmdVersion() );
pmdResult = PmdExecutor.execute( request );
}
protected String getSourceEncoding()
{
String encoding = super.getSourceEncoding();
if ( StringUtils.isEmpty( encoding ) )
{
encoding = ReaderFactory.FILE_ENCODING;
if ( !filesToProcess.isEmpty() )
{
getLog().warn( "File encoding has not been set, using platform encoding " + ReaderFactory.FILE_ENCODING
+ ", i.e. build is platform dependent!" );
}
}
return encoding;
}
/**
* Resolves the configured rulesets and copies them as files into the {@link #rulesetsTargetDirectory}.
*
* @return comma separated list of absolute file paths of ruleset files
* @throws MavenReportException if a ruleset could not be found
*/
private String resolveRulesets() throws MavenReportException
{
// configure ResourceManager - will search for urls (URLResourceLoader) and files in various directories:
// in the directory of the current project's pom file - note: extensions might replace the pom file on the fly
locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
// in the current project's directory
locator.addSearchPath( FileResourceLoader.ID, project.getBasedir().getAbsolutePath() );
// in the base directory - that's the directory of the initial pom requested to build,
// e.g. the root of a multi module build
locator.addSearchPath( FileResourceLoader.ID, session.getRequest().getBaseDirectory() );
locator.setOutputDirectory( rulesetsTargetDirectory );
String[] sets = new String[rulesets.length];
try
{
for ( int idx = 0; idx < rulesets.length; idx++ )
{
String set = rulesets[idx];
getLog().debug( "Preparing ruleset: " + set );
String rulesetFilename = determineRulesetFilename( set );
File ruleset = locator.getResourceAsFile( rulesetFilename, getLocationTemp( set ) );
if ( null == ruleset )
{
throw new MavenReportException( "Could not resolve " + set );
}
sets[idx] = ruleset.getAbsolutePath();
}
}
catch ( ResourceNotFoundException | FileResourceCreationException e )
{
throw new MavenReportException( e.getMessage(), e );
}
return StringUtils.join( sets, "," );
}
private String determineRulesetFilename( String ruleset )
{
String result = ruleset.trim();
String lowercase = result.toLowerCase( Locale.ROOT );
if ( lowercase.startsWith( "http://" ) || lowercase.startsWith( "https://" ) || lowercase.endsWith( ".xml" ) )
{
return result;
}
// assume last part is a single rule, e.g. myruleset.xml/SingleRule
if ( result.indexOf( '/' ) > -1 )
{
String rulesetFilename = result.substring( 0, result.lastIndexOf( '/' ) );
if ( rulesetFilename.toLowerCase( Locale.ROOT ).endsWith( ".xml" ) )
{
return rulesetFilename;
}
}
// maybe a built-in ruleset name, e.g. java-design -> rulesets/java/design.xml
int dashIndex = lowercase.indexOf( '-' );
if ( dashIndex > -1 && lowercase.indexOf( '-', dashIndex + 1 ) == -1 )
{
String language = result.substring( 0, dashIndex );
String rulesetName = result.substring( dashIndex + 1 );
return "rulesets/" + language + "/" + rulesetName + ".xml";
}
// fallback - no change of the given ruleset specifier
return result;
}
private void generateMavenSiteReport( Locale locale )
throws MavenReportException
{
Sink sink = getSink();
PmdReportGenerator doxiaRenderer = new PmdReportGenerator( getLog(), sink, getBundle( locale ),
isAggregator() );
doxiaRenderer.setRenderRuleViolationPriority( renderRuleViolationPriority );
doxiaRenderer.setRenderViolationsByPriority( renderViolationsByPriority );
doxiaRenderer.setFiles( filesToProcess );
doxiaRenderer.setViolations( pmdResult.getViolations() );
if ( renderSuppressedViolations )
{
doxiaRenderer.setSuppressedViolations( pmdResult.getSuppressedViolations() );
}
if ( renderProcessingErrors )
{
doxiaRenderer.setProcessingErrors( pmdResult.getErrors() );
}
try
{
doxiaRenderer.beginDocument();
doxiaRenderer.render();
doxiaRenderer.endDocument();
}
catch ( IOException e )
{
getLog().warn( "Failure creating the report: " + e.getLocalizedMessage(), e );
}
}
/**
* Convenience method to get the location of the specified file name.
*
* @param name the name of the file whose location is to be resolved
* @return a String that contains the absolute file name of the file
*/
protected String getLocationTemp( String name )
{
String loc = name;
if ( loc.indexOf( '/' ) != -1 )
{
loc = loc.substring( loc.lastIndexOf( '/' ) + 1 );
}
if ( loc.indexOf( '\\' ) != -1 )
{
loc = loc.substring( loc.lastIndexOf( '\\' ) + 1 );
}
// MPMD-127 in the case that the rules are defined externally on a url
// we need to replace some special url characters that cannot be
// used in filenames on disk or produce ackward filenames.
// replace all occurrences of the following characters: ? : & = %
loc = loc.replaceAll( "[\\?\\:\\&\\=\\%]", "_" );
if ( !loc.endsWith( ".xml" ) )
{
loc = loc + ".xml";
}
getLog().debug( "Before: " + name + " After: " + loc );
return loc;
}
private String determineAuxClasspath() throws MavenReportException
{
try
{
List<String> classpath = new ArrayList<>();
if ( isAggregator() )
{
List<String> dependencies = new ArrayList<>();
// collect exclusions for projects within the reactor
// if module a depends on module b and both are in the reactor
// then we don't want to resolve the dependency as an artifact.
List<String> exclusionPatterns = new ArrayList<>();
for ( MavenProject localProject : getAggregatedProjects() )
{
exclusionPatterns.add( localProject.getGroupId() + ":" + localProject.getArtifactId() );
}
TransformableFilter filter = new AndFilter( Arrays.asList(
new ExclusionsFilter( exclusionPatterns ),
includeTests ? ScopeFilter.including( "compile", "provided", "test" )
: ScopeFilter.including( "compile", "provided" )
) );
for ( MavenProject localProject : getAggregatedProjects() )
{
ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest(
session.getProjectBuildingRequest() );
Iterable<ArtifactResult> resolvedDependencies = dependencyResolver.resolveDependencies(
buildingRequest, localProject.getDependencies(), null, filter );
for ( ArtifactResult resolvedArtifact : resolvedDependencies )
{
dependencies.add( resolvedArtifact.getArtifact().getFile().toString() );
}
List<String> projectClasspath = includeTests ? localProject.getTestClasspathElements()
: localProject.getCompileClasspathElements();
// Add the project's target folder first
classpath.addAll( projectClasspath );
if ( !localProject.isExecutionRoot() )
{
for ( String path : projectClasspath )
{
File pathFile = new File( path );
String[] children = pathFile.list();
if ( !pathFile.exists() || ( children != null && children.length == 0 ) )
{
getLog().warn( "The project " + localProject.getArtifactId()
+ " does not seem to be compiled. PMD results might be inaccurate." );
}
}
}
}
// Add the dependencies as last entries
classpath.addAll( dependencies );
getLog().debug( "Using aggregated aux classpath: " + classpath );
}
else
{
classpath.addAll( includeTests ? project.getTestClasspathElements()
: project.getCompileClasspathElements() );
getLog().debug( "Using aux classpath: " + classpath );
}
String path = StringUtils.join( classpath.iterator(), File.pathSeparator );
return path;
}
catch ( Exception e )
{
throw new MavenReportException( e.getMessage(), e );
}
}
/**
* {@inheritDoc}
*/
@Override
public String getOutputName()
{
return "pmd";
}
private static ResourceBundle getBundle( Locale locale )
{
return ResourceBundle.getBundle( "pmd-report", locale, PmdReport.class.getClassLoader() );
}
/**
* Create and return the correct renderer for the output type.
*
* @return the renderer based on the configured output
* @throws org.apache.maven.reporting.MavenReportException if no renderer found for the output type
* @deprecated Use {@link PmdExecutor#createRenderer(String, String)} instead.
*/
@Deprecated
public final Renderer createRenderer() throws MavenReportException
{
return PmdExecutor.createRenderer( format, getOutputEncoding() );
}
}