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