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