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.exec;
20
21 import java.io.Closeable;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.ObjectInputStream;
27 import java.io.ObjectOutputStream;
28 import java.io.OutputStreamWriter;
29 import java.io.Writer;
30 import java.util.ArrayList;
31 import java.util.List;
32 import java.util.Objects;
33
34 import net.sourceforge.pmd.PMDConfiguration;
35 import net.sourceforge.pmd.PmdAnalysis;
36 import net.sourceforge.pmd.Report;
37 import net.sourceforge.pmd.RulePriority;
38 import net.sourceforge.pmd.RuleSetLoadException;
39 import net.sourceforge.pmd.RuleSetLoader;
40 import net.sourceforge.pmd.RuleViolation;
41 import net.sourceforge.pmd.benchmark.TextTimingReportRenderer;
42 import net.sourceforge.pmd.benchmark.TimeTracker;
43 import net.sourceforge.pmd.benchmark.TimingReport;
44 import net.sourceforge.pmd.benchmark.TimingReportRenderer;
45 import net.sourceforge.pmd.lang.Language;
46 import net.sourceforge.pmd.lang.LanguageRegistry;
47 import net.sourceforge.pmd.lang.LanguageVersion;
48 import net.sourceforge.pmd.renderers.CSVRenderer;
49 import net.sourceforge.pmd.renderers.HTMLRenderer;
50 import net.sourceforge.pmd.renderers.Renderer;
51 import net.sourceforge.pmd.renderers.TextRenderer;
52 import net.sourceforge.pmd.renderers.XMLRenderer;
53 import net.sourceforge.pmd.util.Predicate;
54 import org.apache.maven.plugin.MojoExecutionException;
55 import org.apache.maven.plugins.pmd.ExcludeViolationsFromFile;
56 import org.apache.maven.reporting.MavenReportException;
57 import org.codehaus.plexus.util.FileUtils;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61
62
63
64 public class PmdExecutor extends Executor {
65 private static final Logger LOG = LoggerFactory.getLogger(PmdExecutor.class);
66
67 public static PmdResult execute(PmdRequest request) throws MavenReportException {
68 if (request.getJavaExecutable() != null) {
69 return fork(request);
70 }
71
72
73 ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
74 try {
75 Thread.currentThread().setContextClassLoader(PmdExecutor.class.getClassLoader());
76 PmdExecutor executor = new PmdExecutor(request);
77 return executor.run();
78 } finally {
79 Thread.currentThread().setContextClassLoader(origLoader);
80 }
81 }
82
83 private static PmdResult fork(PmdRequest request) throws MavenReportException {
84 File basePmdDir = new File(request.getTargetDirectory(), "pmd");
85 basePmdDir.mkdirs();
86 File pmdRequestFile = new File(basePmdDir, "pmdrequest.bin");
87 try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(pmdRequestFile))) {
88 out.writeObject(request);
89 } catch (IOException e) {
90 throw new MavenReportException(e.getMessage(), e);
91 }
92
93 String classpath = buildClasspath();
94 ProcessBuilder pb = new ProcessBuilder();
95
96 pb.environment().put("CLASSPATH", classpath);
97 pb.command().add(request.getJavaExecutable());
98 pb.command().add(PmdExecutor.class.getName());
99 pb.command().add(pmdRequestFile.getAbsolutePath());
100
101 LOG.debug("Executing: CLASSPATH={}, command={}", classpath, pb.command());
102 try {
103 final Process p = pb.start();
104
105
106 ProcessStreamHandler.start(p.getInputStream(), System.out);
107 ProcessStreamHandler.start(p.getErrorStream(), System.err);
108 int exit = p.waitFor();
109 LOG.debug("PmdExecutor exit code: {}", exit);
110 if (exit != 0) {
111 throw new MavenReportException("PmdExecutor exited with exit code " + exit);
112 }
113 return new PmdResult(new File(request.getTargetDirectory(), "pmd.xml"), request.getOutputEncoding());
114 } catch (IOException e) {
115 throw new MavenReportException(e.getMessage(), e);
116 } catch (InterruptedException e) {
117 Thread.currentThread().interrupt();
118 throw new MavenReportException(e.getMessage(), e);
119 }
120 }
121
122
123
124
125
126
127
128
129
130
131
132
133 public static void main(String[] args) {
134 File requestFile = new File(args[0]);
135 try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(requestFile))) {
136 PmdRequest request = (PmdRequest) in.readObject();
137 PmdExecutor pmdExecutor = new PmdExecutor(request);
138 pmdExecutor.setupLogLevel(request.getLogLevel());
139 pmdExecutor.run();
140 System.exit(0);
141 } catch (IOException | ClassNotFoundException | MavenReportException e) {
142 LOG.error(e.getMessage(), e);
143 }
144 System.exit(1);
145 }
146
147 private final PmdRequest request;
148
149 public PmdExecutor(PmdRequest request) {
150 this.request = Objects.requireNonNull(request);
151 }
152
153 private PmdResult run() throws MavenReportException {
154 setupPmdLogging(request.isShowPmdLog(), request.getLogLevel());
155
156 PMDConfiguration configuration = new PMDConfiguration();
157 LanguageVersion languageVersion = null;
158 Language language = LanguageRegistry.findLanguageByTerseName(
159 request.getLanguage() != null ? request.getLanguage() : "java");
160 if (language == null) {
161 throw new MavenReportException("Unsupported language: " + request.getLanguage());
162 }
163 if (request.getLanguageVersion() != null) {
164 languageVersion = language.getVersion(request.getLanguageVersion());
165 if (languageVersion == null) {
166 throw new MavenReportException("Unsupported targetJdk value '" + request.getLanguageVersion() + "'.");
167 }
168 } else {
169 languageVersion = language.getDefaultVersion();
170 }
171 LOG.debug("Using language " + languageVersion);
172 configuration.setDefaultLanguageVersion(languageVersion);
173
174 if (request.getSourceEncoding() != null) {
175 configuration.setSourceEncoding(request.getSourceEncoding());
176 }
177
178 configuration.prependAuxClasspath(request.getAuxClasspath());
179
180 if (request.getSuppressMarker() != null) {
181 configuration.setSuppressMarker(request.getSuppressMarker());
182 }
183 if (request.getAnalysisCacheLocation() != null) {
184 configuration.setAnalysisCacheLocation(request.getAnalysisCacheLocation());
185 LOG.debug("Using analysis cache location: " + request.getAnalysisCacheLocation());
186 } else {
187 configuration.setIgnoreIncrementalAnalysis(true);
188 }
189
190 configuration.setRuleSets(request.getRulesets());
191 configuration.setMinimumPriority(RulePriority.valueOf(request.getMinimumPriority()));
192 if (request.getBenchmarkOutputLocation() != null) {
193 configuration.setBenchmark(true);
194 }
195 List<File> files = request.getFiles();
196
197 Report report = null;
198
199 if (request.getRulesets().isEmpty()) {
200 LOG.debug("Skipping PMD execution as no rulesets are defined.");
201 } else {
202 if (request.getBenchmarkOutputLocation() != null) {
203 TimeTracker.startGlobalTracking();
204 }
205
206 try {
207 report = processFilesWithPMD(configuration, files);
208 } finally {
209 if (request.getAuxClasspath() != null) {
210 ClassLoader classLoader = configuration.getClassLoader();
211 if (classLoader instanceof Closeable) {
212 Closeable closeable = (Closeable) classLoader;
213 try {
214 closeable.close();
215 } catch (IOException ex) {
216
217 }
218 }
219 }
220 if (request.getBenchmarkOutputLocation() != null) {
221 TimingReport timingReport = TimeTracker.stopGlobalTracking();
222 writeBenchmarkReport(
223 timingReport, request.getBenchmarkOutputLocation(), request.getOutputEncoding());
224 }
225 }
226 }
227
228 if (report != null && !report.getProcessingErrors().isEmpty()) {
229 List<Report.ProcessingError> errors = report.getProcessingErrors();
230 if (!request.isSkipPmdError()) {
231 LOG.error("PMD processing errors:");
232 LOG.error(getErrorsAsString(errors, request.isDebugEnabled()));
233 throw new MavenReportException("Found " + errors.size() + " PMD processing errors");
234 }
235 LOG.warn("There are {} PMD processing errors:", errors.size());
236 LOG.warn(getErrorsAsString(errors, request.isDebugEnabled()));
237 }
238
239 report = removeExcludedViolations(report);
240
241
242
243 writeXmlReport(report);
244
245
246
247
248
249 String format = request.getFormat();
250 if (!"html".equals(format) && !"xml".equals(format)) {
251 writeFormattedReport(report);
252 }
253
254 return new PmdResult(new File(request.getTargetDirectory(), "pmd.xml"), request.getOutputEncoding());
255 }
256
257
258
259
260
261
262 private String getErrorsAsString(List<Report.ProcessingError> errors, boolean withDetails) {
263 List<String> errorsAsString = new ArrayList<>(errors.size());
264 for (Report.ProcessingError error : errors) {
265 errorsAsString.add(error.getFile() + ": " + error.getMsg());
266 if (withDetails) {
267 errorsAsString.add(error.getDetail());
268 }
269 }
270 return String.join(System.lineSeparator(), errorsAsString);
271 }
272
273 private void writeBenchmarkReport(TimingReport timingReport, String benchmarkOutputLocation, String encoding) {
274 try (Writer writer = new OutputStreamWriter(new FileOutputStream(benchmarkOutputLocation), encoding)) {
275 final TimingReportRenderer renderer = new TextTimingReportRenderer();
276 renderer.render(timingReport, writer);
277 } catch (IOException e) {
278 LOG.error("Unable to generate benchmark file: {}", benchmarkOutputLocation, e);
279 }
280 }
281
282 private Report processFilesWithPMD(PMDConfiguration pmdConfiguration, List<File> files)
283 throws MavenReportException {
284 Report report = null;
285 RuleSetLoader rulesetLoader =
286 RuleSetLoader.fromPmdConfig(pmdConfiguration).warnDeprecated(true);
287 try {
288
289 rulesetLoader.loadFromResources(pmdConfiguration.getRuleSetPaths());
290 } catch (RuleSetLoadException e1) {
291 throw new MavenReportException("The ruleset could not be loaded", e1);
292 }
293
294 try (PmdAnalysis pmdAnalysis = PmdAnalysis.create(pmdConfiguration)) {
295 for (File file : files) {
296 pmdAnalysis.files().addFile(file.toPath());
297 }
298 LOG.debug("Executing PMD...");
299 report = pmdAnalysis.performAnalysisAndCollectReport();
300 LOG.debug(
301 "PMD finished. Found {} violations.", report.getViolations().size());
302 } catch (Exception e) {
303 String message = "Failure executing PMD: " + e.getLocalizedMessage();
304 if (!request.isSkipPmdError()) {
305 throw new MavenReportException(message, e);
306 }
307 LOG.warn(message, e);
308 }
309 return report;
310 }
311
312
313
314
315
316
317
318
319 private void writeXmlReport(Report report) throws MavenReportException {
320 File targetFile = writeReport(report, new XMLRenderer(request.getOutputEncoding()));
321 if (request.isIncludeXmlInSite()) {
322 File siteDir = new File(request.getReportOutputDirectory());
323 siteDir.mkdirs();
324 try {
325 FileUtils.copyFile(targetFile, new File(siteDir, "pmd.xml"));
326 } catch (IOException e) {
327 throw new MavenReportException(e.getMessage(), e);
328 }
329 }
330 }
331
332 private File writeReport(Report report, Renderer r) throws MavenReportException {
333 if (r == null) {
334 return null;
335 }
336
337 File targetDir = new File(request.getTargetDirectory());
338 targetDir.mkdirs();
339 String extension = r.defaultFileExtension();
340 File targetFile = new File(targetDir, "pmd." + extension);
341 LOG.debug("Target PMD output file: {}", targetFile);
342 try (Writer writer = new OutputStreamWriter(new FileOutputStream(targetFile), request.getOutputEncoding())) {
343 r.setWriter(writer);
344 r.start();
345 if (report != null) {
346 r.renderFileReport(report);
347 }
348 r.end();
349 r.flush();
350 } catch (IOException ioe) {
351 throw new MavenReportException(ioe.getMessage(), ioe);
352 }
353
354 return targetFile;
355 }
356
357
358
359
360
361
362
363 private void writeFormattedReport(Report report) throws MavenReportException {
364 Renderer renderer = createRenderer(request.getFormat(), request.getOutputEncoding());
365 writeReport(report, renderer);
366 }
367
368
369
370
371
372
373
374
375 public static Renderer createRenderer(String format, String outputEncoding) throws MavenReportException {
376 LOG.debug("Renderer requested: {}", format);
377 Renderer result = null;
378 if ("xml".equals(format)) {
379 result = new XMLRenderer(outputEncoding);
380 } else if ("txt".equals(format)) {
381 result = new TextRenderer();
382 } else if ("csv".equals(format)) {
383 result = new CSVRenderer();
384 } else if ("html".equals(format)) {
385 result = new HTMLRenderer();
386 } else if (!"".equals(format) && !"none".equals(format)) {
387 try {
388 result = (Renderer) Class.forName(format).getConstructor().newInstance();
389 } catch (Exception e) {
390 throw new MavenReportException(
391 "Can't find PMD custom format " + format + ": "
392 + e.getClass().getName(),
393 e);
394 }
395 }
396
397 return result;
398 }
399
400 private Report removeExcludedViolations(Report report) throws MavenReportException {
401 if (report == null) {
402 return null;
403 }
404
405 ExcludeViolationsFromFile excludeFromFile = new ExcludeViolationsFromFile();
406
407 try {
408 excludeFromFile.loadExcludeFromFailuresData(request.getExcludeFromFailureFile());
409 } catch (MojoExecutionException e) {
410 throw new MavenReportException("Unable to load exclusions", e);
411 }
412
413 LOG.debug("Removing excluded violations. Using {} configured exclusions.", excludeFromFile.countExclusions());
414 int violationsBefore = report.getViolations().size();
415
416 Report filtered = report.filterViolations(new Predicate<RuleViolation>() {
417 @Override
418 public boolean test(RuleViolation ruleViolation) {
419 return !excludeFromFile.isExcludedFromFailure(ruleViolation);
420 }
421 });
422
423 int numberOfExcludedViolations =
424 violationsBefore - filtered.getViolations().size();
425 LOG.debug("Excluded {} violations.", numberOfExcludedViolations);
426 return filtered;
427 }
428 }