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