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