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