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