1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.pmd;
20
21 import javax.inject.Inject;
22
23 import java.io.File;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collection;
28 import java.util.List;
29 import java.util.Locale;
30
31 import net.sourceforge.pmd.renderers.Renderer;
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.plugins.pmd.exec.PmdServiceExecutor;
39 import org.apache.maven.project.MavenProject;
40 import org.apache.maven.reporting.MavenReportException;
41 import org.codehaus.plexus.i18n.I18N;
42 import org.codehaus.plexus.resource.ResourceManager;
43 import org.codehaus.plexus.resource.loader.FileResourceCreationException;
44 import org.codehaus.plexus.resource.loader.FileResourceLoader;
45 import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
46
47
48
49
50
51
52
53
54
55 @Mojo(name = "pmd", threadSafe = true, requiresDependencyResolution = ResolutionScope.TEST)
56 public class PmdReport extends AbstractPmdReport {
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 @Parameter(property = "targetJdk", defaultValue = "${maven.compiler.source}")
73 private String targetJdk;
74
75
76
77
78
79
80
81 @Parameter(defaultValue = "java")
82 private String language;
83
84
85
86
87
88
89 @Parameter(property = "minimumPriority", defaultValue = "5")
90 private int minimumPriority = 5;
91
92
93
94
95
96
97 @Parameter(property = "pmd.skip", defaultValue = "false")
98 private boolean skip;
99
100
101
102
103
104
105
106
107 @Parameter
108 String[] rulesets = new String[] {"/rulesets/java/maven-pmd-plugin-default.xml"};
109
110
111
112
113
114
115
116 @Parameter(property = "pmd.typeResolution", defaultValue = "true")
117 private boolean typeResolution;
118
119
120
121
122
123
124 @Parameter(property = "pmd.benchmark", defaultValue = "false")
125 private boolean benchmark;
126
127
128
129
130
131
132 @Parameter(property = "pmd.benchmarkOutputFilename", defaultValue = "${project.build.directory}/pmd-benchmark.txt")
133 private String benchmarkOutputFilename;
134
135
136
137
138
139
140
141
142 @Parameter(property = "pmd.suppressMarker")
143 private String suppressMarker;
144
145
146
147
148
149
150 @Parameter(property = "pmd.skipPmdError", defaultValue = "true")
151 private boolean skipPmdError;
152
153
154
155
156
157
158
159
160
161 @Parameter(property = "pmd.analysisCache", defaultValue = "false")
162 private boolean analysisCache;
163
164
165
166
167
168
169
170
171
172
173 @Parameter(property = "pmd.analysisCacheLocation", defaultValue = "${project.build.directory}/pmd/pmd.cache")
174 private String analysisCacheLocation;
175
176
177
178
179
180
181
182
183
184
185 @Parameter(property = "pmd.renderProcessingErrors", defaultValue = "true")
186 private boolean renderProcessingErrors = true;
187
188
189
190
191
192
193 @Parameter(property = "pmd.renderRuleViolationPriority", defaultValue = "true")
194 private boolean renderRuleViolationPriority = true;
195
196
197
198
199
200
201
202 @Parameter(property = "pmd.renderViolationsByPriority", defaultValue = "true")
203 private boolean renderViolationsByPriority = true;
204
205
206
207
208
209
210 @Parameter(property = "pmd.renderSuppressedViolations", defaultValue = "true")
211 private boolean renderSuppressedViolations = true;
212
213
214
215
216
217
218
219 @Parameter(property = "pmd.rulesetsTargetDirectory", defaultValue = "${project.build.directory}/pmd/rulesets")
220 private File rulesetsTargetDirectory;
221
222
223
224
225
226 private final ResourceManager locator;
227
228
229
230
231 private final I18N i18n;
232
233 private final PmdServiceExecutor serviceExecutor;
234
235 private final ConfigurationService configurationService;
236
237
238
239
240
241
242 private PmdResult pmdResult;
243
244 @Inject
245 public PmdReport(
246 ResourceManager locator,
247 ConfigurationService configurationService,
248 I18N i18n,
249 PmdServiceExecutor serviceExecutor) {
250 this.locator = locator;
251 this.configurationService = configurationService;
252 this.i18n = i18n;
253 this.serviceExecutor = serviceExecutor;
254 }
255
256
257 public String getName(Locale locale) {
258 return getI18nString(locale, "name");
259 }
260
261
262 public String getDescription(Locale locale) {
263 return getI18nString(locale, "description");
264 }
265
266
267
268
269
270
271 protected String getI18nString(Locale locale, String key) {
272 return i18n.getString("pmd-report", locale, "report.pmd." + key);
273 }
274
275
276
277
278
279
280
281
282 public void setRulesets(String[] rulesets) {
283 this.rulesets = Arrays.copyOf(rulesets, rulesets.length);
284 }
285
286
287
288
289 @Override
290 public void executeReport(Locale locale) throws MavenReportException {
291 ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
292 try {
293 Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
294
295 PmdReportRenderer renderer = new PmdReportRenderer(
296 getLog(),
297 getSink(),
298 i18n,
299 locale,
300 filesToProcess,
301 pmdResult.getViolations(),
302 renderRuleViolationPriority,
303 renderViolationsByPriority,
304 isAggregator());
305 if (renderSuppressedViolations) {
306 renderer.setSuppressedViolations(pmdResult.getSuppressedViolations());
307 }
308 if (renderProcessingErrors) {
309 renderer.setProcessingErrors(pmdResult.getErrors());
310 }
311
312 renderer.render();
313 } finally {
314 Thread.currentThread().setContextClassLoader(origLoader);
315 }
316 }
317
318 @Override
319 public boolean canGenerateReport() throws MavenReportException {
320 if (skip) {
321 return false;
322 }
323
324 boolean result = canGenerateReportInternal();
325 if (result) {
326 executePmd();
327 if (skipEmptyReport) {
328 result = pmdResult.hasViolations();
329 }
330 }
331 return result;
332 }
333
334 private void executePmd() throws MavenReportException {
335 if (pmdResult != null) {
336
337 getLog().debug("PMD has already been run - skipping redundant execution.");
338 return;
339 }
340
341 try {
342 filesToProcess = getFilesToProcess();
343
344 if (filesToProcess.isEmpty() && !"java".equals(language)) {
345 getLog().warn("No files found to process. Did you forget to add additional source directories?"
346 + " (see also build-helper-maven-plugin)");
347 }
348 } catch (IOException e) {
349 throw new MavenReportException("Can't get file list", e);
350 }
351
352 PmdRequest request = new PmdRequest();
353 request.setLanguageAndVersion(language, targetJdk);
354 request.setRulesets(resolveRulesets());
355 request.setAuxClasspath(typeResolution ? determineAuxClasspath() : null);
356 request.setSourceEncoding(getInputEncoding());
357 request.addFiles(filesToProcess.keySet());
358 request.setMinimumPriority(minimumPriority);
359 request.setSuppressMarker(suppressMarker);
360 request.setBenchmarkOutputLocation(benchmark ? benchmarkOutputFilename : null);
361 request.setAnalysisCacheLocation(analysisCache ? analysisCacheLocation : null);
362 request.setExcludeFromFailureFile(excludeFromFailureFile);
363 request.setTargetDirectory(targetDirectory.getAbsolutePath());
364 request.setOutputEncoding(getOutputEncoding());
365 request.setFormat(format);
366 request.setSkipPmdError(skipPmdError);
367 request.setIncludeXmlInReports(includeXmlInReports);
368 request.setReportOutputDirectory(getReportOutputDirectory().getAbsolutePath());
369 request.setJdkToolchain(getJdkToolchain());
370
371 getLog().info("PMD version: " + AbstractPmdReport.getPmdVersion());
372 pmdResult = serviceExecutor.execute(request);
373 }
374
375
376
377
378
379
380
381 private List<String> resolveRulesets() throws MavenReportException {
382
383
384 locator.addSearchPath(
385 FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath());
386
387 locator.addSearchPath(FileResourceLoader.ID, project.getBasedir().getAbsolutePath());
388
389
390 locator.addSearchPath(FileResourceLoader.ID, session.getRequest().getBaseDirectory());
391 locator.setOutputDirectory(rulesetsTargetDirectory);
392
393 String[] sets = new String[rulesets.length];
394 try {
395 for (int idx = 0; idx < rulesets.length; idx++) {
396 String set = rulesets[idx];
397 getLog().debug("Preparing ruleset: " + set);
398 String rulesetFilename = determineRulesetFilename(set);
399 File ruleset = locator.getResourceAsFile(rulesetFilename, getLocationTemp(set, idx + 1));
400 if (null == ruleset) {
401 throw new MavenReportException("Could not resolve " + set);
402 }
403 sets[idx] = ruleset.getAbsolutePath();
404 }
405 } catch (ResourceNotFoundException | FileResourceCreationException e) {
406 throw new MavenReportException(e.getMessage(), e);
407 }
408 return Arrays.asList(sets);
409 }
410
411 private String determineRulesetFilename(String ruleset) {
412 String result = ruleset.trim();
413 String lowercase = result.toLowerCase(Locale.ROOT);
414 if (lowercase.startsWith("http://") || lowercase.startsWith("https://") || lowercase.endsWith(".xml")) {
415 return result;
416 }
417
418
419 if (result.indexOf('/') > -1) {
420 String rulesetFilename = result.substring(0, result.lastIndexOf('/'));
421 if (rulesetFilename.toLowerCase(Locale.ROOT).endsWith(".xml")) {
422 return rulesetFilename;
423 }
424 }
425
426 int dashIndex = lowercase.indexOf('-');
427 if (dashIndex > -1 && lowercase.indexOf('-', dashIndex + 1) == -1) {
428 String language = result.substring(0, dashIndex);
429 String rulesetName = result.substring(dashIndex + 1);
430 return "rulesets/" + language + "/" + rulesetName + ".xml";
431 }
432
433 return result;
434 }
435
436
437
438
439
440
441
442
443 protected String getLocationTemp(String name, int position) {
444 String loc = name;
445 if (loc.indexOf('/') != -1) {
446 loc = loc.substring(loc.lastIndexOf('/') + 1);
447 }
448 if (loc.indexOf('\\') != -1) {
449 loc = loc.substring(loc.lastIndexOf('\\') + 1);
450 }
451
452
453
454
455
456 loc = loc.replaceAll("[\\?\\:\\&\\=\\%]", "_");
457
458 if (loc.endsWith(".xml")) {
459 loc = loc.substring(0, loc.length() - 4);
460 }
461 loc = String.format("%03d-%s.xml", position, loc);
462
463 getLog().debug("Before: " + name + " After: " + loc);
464 return loc;
465 }
466
467 private String determineAuxClasspath() throws MavenReportException {
468 try {
469 List<String> classpath = new ArrayList<>();
470 if (isAggregator()) {
471 List<String> dependencies = new ArrayList<>();
472 Collection<MavenProject> aggregatedProjects = getAggregatedProjects();
473 for (MavenProject localProject : aggregatedProjects) {
474 configurationService
475 .resolveDependenciesAsFile(localProject, aggregatedProjects, includeTests)
476 .forEach(file -> dependencies.add(file.getAbsolutePath()));
477
478 classpath.addAll(
479 includeTests
480 ? localProject.getTestClasspathElements()
481 : localProject.getCompileClasspathElements());
482 }
483
484
485 classpath.addAll(dependencies);
486
487 getLog().debug("Using aggregated aux classpath: " + classpath);
488 } else {
489 classpath.addAll(
490 includeTests ? project.getTestClasspathElements() : project.getCompileClasspathElements());
491
492 getLog().debug("Using aux classpath: " + classpath);
493 }
494 return String.join(File.pathSeparator, classpath);
495 } catch (Exception e) {
496 throw new MavenReportException(e.getMessage(), e);
497 }
498 }
499
500
501
502
503 @Override
504 @Deprecated
505 public String getOutputName() {
506 return "pmd";
507 }
508
509 @Override
510 public String getOutputPath() {
511 return "pmd";
512 }
513
514
515
516
517
518
519
520
521 @Deprecated
522 public final Renderer createRenderer() throws MavenReportException {
523 return PmdExecutor.createRenderer(format, getOutputEncoding());
524 }
525 }