PMD.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd;
005 
006 import java.io.BufferedInputStream;
007 import java.io.BufferedWriter;
008 import java.io.File;
009 import java.io.FileWriter;
010 import java.io.IOException;
011 import java.io.InputStream;
012 import java.io.InputStreamReader;
013 import java.io.OutputStreamWriter;
014 import java.io.Reader;
015 import java.io.UnsupportedEncodingException;
016 import java.io.Writer;
017 import java.util.ArrayList;
018 import java.util.Collections;
019 import java.util.Comparator;
020 import java.util.LinkedList;
021 import java.util.List;
022 import java.util.concurrent.Callable;
023 import java.util.concurrent.ExecutionException;
024 import java.util.concurrent.ExecutorService;
025 import java.util.concurrent.Executors;
026 import java.util.concurrent.Future;
027 import java.util.concurrent.ThreadFactory;
028 import java.util.concurrent.atomic.AtomicInteger;
029 import java.util.logging.Handler;
030 import java.util.logging.Level;
031 import java.util.logging.Logger;
032 
033 import net.sourceforge.pmd.lang.Language;
034 import net.sourceforge.pmd.lang.LanguageFilenameFilter;
035 import net.sourceforge.pmd.lang.LanguageVersion;
036 import net.sourceforge.pmd.lang.LanguageVersionHandler;
037 import net.sourceforge.pmd.lang.Parser;
038 import net.sourceforge.pmd.lang.ast.Node;
039 import net.sourceforge.pmd.lang.java.ast.ParseException;
040 import net.sourceforge.pmd.lang.xpath.Initializer;
041 import net.sourceforge.pmd.renderers.Renderer;
042 import net.sourceforge.pmd.util.Benchmark;
043 import net.sourceforge.pmd.util.FileUtil;
044 import net.sourceforge.pmd.util.datasource.DataSource;
045 import net.sourceforge.pmd.util.log.ConsoleLogHandler;
046 import net.sourceforge.pmd.util.log.ScopedLogHandlersManager;
047 
048 /**
049  * This is the main class for interacting with PMD.  The primary flow of
050  * all Rule process is controlled via interactions with this class.  A command
051  * line interface is supported, as well as a programmatic API for integrating
052  * PMD with other software such as IDEs and Ant.
053  */
054 public class PMD {
055 
056     private static final Logger LOG = Logger.getLogger(PMD.class.getName());
057 
058     public static final String EOL = System.getProperty("line.separator""\n");
059     public static final String VERSION = "5.0-SNAPSHOT";
060     public static final String SUPPRESS_MARKER = "NOPMD";
061 
062     /**
063      * Do we have proper permissions to use multithreading?
064      */
065     // FUTURE Move this into the SystemUtils
066     private static final boolean MT_SUPPORTED;
067 
068     static {
069   boolean error = false;
070   try {
071       /*
072        * ant task ran from Eclipse with jdk 1.5.0 raises an AccessControlException
073        * when shutdown is called. Standalone pmd or ant from command line are fine.
074        *
075        * With jdk 1.6.0, ant task from Eclipse also works.
076        */
077       ExecutorService executor = Executors.newFixedThreadPool(1);
078       executor.shutdown();
079   catch (RuntimeException e) {
080       error = true;
081   }
082   MT_SUPPORTED = !error;
083     }
084 
085     protected final Configuration configuration;
086 
087     /**
088      * Create a PMD instance using a default Configuration.  Changes to the
089      * configuration may be required.
090      */
091     public PMD() {
092   this(new Configuration());
093     }
094 
095     /**
096      * Create a PMD instance using the specified Configuration.
097      @param configuration The runtime Configuration of PMD to use.
098      */
099     public PMD(Configuration configuration) {
100   this.configuration = configuration;
101     }
102 
103     /**
104      * Get the runtime configuration.  The configuration can be modified
105      * to affect how PMD behaves.
106      @return The configuration.
107      @see Configuration
108      */
109     public Configuration getConfiguration() {
110   return configuration;
111     }
112 
113     /**
114      * Processes the input stream against a rule set using the given input encoding.
115      *
116      @param inputStream The InputStream to analyze.
117      @param ruleSets The collection of rules to process against the file.
118      @param ctx The context in which PMD is operating.
119      @throws PMDException if the input encoding is unsupported, the input stream could
120      *                      not be parsed, or other error is encountered.
121      @see #processFile(Reader, RuleSets, RuleContext)
122      */
123     public void processFile(InputStream inputStream, RuleSets ruleSets, RuleContext ctxthrows PMDException {
124   try {
125       processFile(new InputStreamReader(inputStream, configuration.getSourceEncoding()), ruleSets, ctx);
126   catch (UnsupportedEncodingException uee) {
127       throw new PMDException("Unsupported encoding exception: " + uee.getMessage());
128   }
129     }
130 
131     /**
132      * Processes the input stream against a rule set using the given input encoding.
133      * If the LanguageVersion is <code>null</code>  on the RuleContext, it will
134      * be automatically determined.  Any code which wishes to process files for
135      * different Languages, will need to be sure to either properly set the
136      * Language on the RuleContext, or set it to <code>null</code> first.
137      
138      @see RuleContext#setLanguageVersion(LanguageVersion)
139      @see Configuration#getLanguageVersionOfFile(String)
140      *
141      @param reader The Reader to analyze.
142      @param ruleSets The collection of rules to process against the file.
143      @param ctx The context in which PMD is operating.
144      @throws PMDException if the input encoding is unsupported, the input stream could
145      *                      not be parsed, or other error is encountered.
146      */
147     public void processFile(Reader reader, RuleSets ruleSets, RuleContext ctxthrows PMDException {
148   // If LanguageVersion of the source file is not known, make a determination
149   if (ctx.getLanguageVersion() == null) {
150       LanguageVersion languageVersion = configuration.getLanguageVersionOfFile(ctx.getSourceCodeFilename());
151       ctx.setLanguageVersion(languageVersion);
152   }
153 
154   // make sure custom XPath functions are initialized
155   Initializer.initialize();
156 
157   try {
158       // Coarse check to see if any RuleSet applies to files, will need to do a finer RuleSet specific check later
159       if (ruleSets.applies(ctx.getSourceCodeFile())) {
160     LanguageVersion languageVersion = ctx.getLanguageVersion();
161     LanguageVersionHandler languageVersionHandler = languageVersion.getLanguageVersionHandler();
162     Parser parser = languageVersionHandler.getParser();
163     parser.setSuppressMarker(configuration.getSuppressMarker());
164     long start = System.nanoTime();
165     Node rootNode = parser.parse(ctx.getSourceCodeFilename(), reader);
166     ctx.getReport().suppress(parser.getSuppressMap());
167     long end = System.nanoTime();
168     Benchmark.mark(Benchmark.TYPE_PARSER, end - start, 0);
169     start = System.nanoTime();
170     languageVersionHandler.getSymbolFacade().start(rootNode);
171     end = System.nanoTime();
172     Benchmark.mark(Benchmark.TYPE_SYMBOL_TABLE, end - start, 0);
173 
174     Language language = languageVersion.getLanguage();
175 
176     if (ruleSets.usesDFA(language)) {
177         start = System.nanoTime();
178         languageVersionHandler.getDataFlowFacade().start(rootNode);
179         end = System.nanoTime();
180         Benchmark.mark(Benchmark.TYPE_DFA, end - start, 0);
181     }
182 
183     if (ruleSets.usesTypeResolution(language)) {
184         start = System.nanoTime();
185         languageVersionHandler.getTypeResolutionFacade(configuration.getClassLoader()).start(rootNode);
186         end = System.nanoTime();
187         Benchmark.mark(Benchmark.TYPE_TYPE_RESOLUTION, end - start, 0);
188     }
189 
190     List<Node> acus = new ArrayList<Node>();
191     acus.add(rootNode);
192 
193     ruleSets.apply(acus, ctx, language);
194       }
195   catch (ParseException pe) {
196       throw new PMDException("Error while parsing " + ctx.getSourceCodeFilename(), pe);
197   catch (Exception e) {
198       throw new PMDException("Error while processing " + ctx.getSourceCodeFilename(), e);
199   finally {
200       try {
201     reader.close();
202       catch (IOException e) {
203       }
204   }
205     }
206 
207     // This method is the main entry point for command line usage.
208     private static void doPMD(Configuration configuration) {
209 
210   // Load the RuleSets
211   long startLoadRules = System.nanoTime();
212   RuleSetFactory ruleSetFactory = new RuleSetFactory();
213   ruleSetFactory.setMinimumPriority(configuration.getMinimumPriority());
214   ruleSetFactory.setWarnDeprecated(true);
215   RuleSets ruleSets;
216 
217   try {
218       ruleSets = ruleSetFactory.createRuleSets(configuration.getRuleSets());
219       ruleSetFactory.setWarnDeprecated(false);
220       printRuleNamesInDebug(ruleSets);
221       long endLoadRules = System.nanoTime();
222       Benchmark.mark(Benchmark.TYPE_LOAD_RULES, endLoadRules - startLoadRules, 0);
223   catch (RuleSetNotFoundException rsnfe) {
224       LOG.log(Level.SEVERE, "Ruleset not found", rsnfe);
225       System.out.println(CommandLineOptions.usage());
226       return;
227   }
228 
229   // Determine applicable Languages
230   List<Language> languages = new ArrayList<Language>();
231   for (Rule rule : ruleSets.getAllRules()) {
232       Language language = rule.getLanguage();
233       if (!languages.contains(language)) {
234     if (RuleSet.applies(rule, configuration.getLanguageVersionDiscoverer().getDefaultLanguageVersion(
235       language))) {
236         languages.add(language);
237         LOG.fine("Using " + language.getShortName());
238     }
239       }
240   }
241 
242   // Find all files applicable to these Languages
243   long startFiles = System.nanoTime();
244   LanguageFilenameFilter fileSelector = new LanguageFilenameFilter(languages);
245   List<DataSource> files = FileUtil.collectFiles(configuration.getInputPaths(), fileSelector);
246   long endFiles = System.nanoTime();
247   Benchmark.mark(Benchmark.TYPE_COLLECT_FILES, endFiles - startFiles, 0);
248 
249   long reportStart;
250   long reportEnd;
251   Renderer renderer;
252   Writer w = null;
253 
254   reportStart = System.nanoTime();
255   try {
256       renderer = configuration.createRenderer();
257       List<Renderer> renderers = new LinkedList<Renderer>();
258       renderers.add(renderer);
259       if (configuration.getReportFile() != null) {
260     w = new BufferedWriter(new FileWriter(configuration.getReportFile()));
261       else {
262     w = new OutputStreamWriter(System.out);
263       }
264       renderer.setWriter(w);
265       renderer.start();
266 
267       reportEnd = System.nanoTime();
268       Benchmark.mark(Benchmark.TYPE_REPORTING, reportEnd - reportStart, 0);
269 
270       RuleContext ctx = new RuleContext();
271 
272       processFiles(configuration, ruleSetFactory, files, ctx, renderers);
273 
274       reportStart = System.nanoTime();
275       renderer.end();
276       w.flush();
277       if (configuration.getReportFile() != null) {
278     w.close();
279     w = null;
280       }
281   catch (Exception e) {
282       String message = e.getMessage();
283       if (message != null) {
284     LOG.severe(message);
285       else {
286     LOG.log(Level.SEVERE, "Exception during processing", e);
287       }
288 
289       LOG.log(Level.FINE, "Exception during processing", e)//Only displayed when debug logging is on
290 
291       LOG.info(CommandLineOptions.usage());
292   finally {
293       if (configuration.getReportFile() != null && w != null) {
294     try {
295         w.close();
296     catch (Exception e) {
297         System.out.println(e.getMessage());
298     }
299       }
300       reportEnd = System.nanoTime();
301       Benchmark.mark(Benchmark.TYPE_REPORTING, reportEnd - reportStart, 0);
302   }
303     }
304 
305     public static void main(String[] args) {
306   long start = System.nanoTime();
307   final CommandLineOptions opts = new CommandLineOptions(args);
308   final Configuration configuration = opts.getConfiguration();
309 
310   final Level logLevel = configuration.isDebug() ? Level.FINER : Level.INFO;
311   final Handler logHandler = new ConsoleLogHandler();
312   final ScopedLogHandlersManager logHandlerManager = new ScopedLogHandlersManager(logLevel, logHandler);
313   final Level oldLogLevel = LOG.getLevel();
314   LOG.setLevel(logLevel)//Need to do this, since the static logger has already been initialized at this point
315   try {
316       doPMD(opts.getConfiguration());
317   finally {
318       logHandlerManager.close();
319       LOG.setLevel(oldLogLevel);
320       if (configuration.isBenchmark()) {
321     long end = System.nanoTime();
322     Benchmark.mark(Benchmark.TYPE_TOTAL_PMD, end - start, 0);
323     System.err.println(Benchmark.report());
324       }
325   }
326     }
327 
328     private static class PmdRunnable extends PMD implements Callable<Report> {
329   private final ExecutorService executor;
330   private final DataSource dataSource;
331   private final String fileName;
332   private final List<Renderer> renderers;
333 
334   public PmdRunnable(ExecutorService executor, Configuration configuration, DataSource dataSource,
335     String fileName, List<Renderer> renderers) {
336       super(configuration);
337       this.executor = executor;
338       this.dataSource = dataSource;
339       this.fileName = fileName;
340       this.renderers = renderers;
341   }
342 
343   public Report call() {
344       PmdThread thread = (PmdThreadThread.currentThread();
345 
346       RuleContext ctx = thread.getRuleContext();
347       RuleSets rs = thread.getRuleSets(configuration.getRuleSets());
348 
349       Report report = new Report();
350       ctx.setReport(report);
351 
352       ctx.setSourceCodeFilename(fileName);
353       ctx.setSourceCodeFile(new File(fileName));
354       if (LOG.isLoggable(Level.FINE)) {
355     LOG.fine("Processing " + ctx.getSourceCodeFilename());
356       }
357       for (Renderer r : renderers) {
358     r.startFileAnalysis(dataSource);
359       }
360 
361       try {
362     InputStream stream = new BufferedInputStream(dataSource.getInputStream());
363     ctx.setLanguageVersion(null);
364     processFile(stream, rs, ctx);
365       catch (PMDException pmde) {
366     LOG.log(Level.FINE, "Error while processing file", pmde.getCause());
367 
368     report.addError(new Report.ProcessingError(pmde.getMessage(), fileName));
369       catch (IOException ioe) {
370     // unexpected exception: log and stop executor service
371     LOG.log(Level.FINE, "IOException during processing", ioe);
372 
373     report.addError(new Report.ProcessingError(ioe.getMessage(), fileName));
374 
375     executor.shutdownNow();
376       catch (RuntimeException re) {
377     // unexpected exception: log and stop executor service
378     LOG.log(Level.FINE, "RuntimeException during processing", re);
379 
380     report.addError(new Report.ProcessingError(re.getMessage(), fileName));
381 
382     executor.shutdownNow();
383       }
384       return report;
385   }
386 
387     }
388 
389     private static class PmdThreadFactory implements ThreadFactory {
390 
391   private final RuleSetFactory ruleSetFactory;
392   private final RuleContext ctx;
393   private final AtomicInteger counter = new AtomicInteger();
394 
395   public PmdThreadFactory(RuleSetFactory ruleSetFactory, RuleContext ctx) {
396       this.ruleSetFactory = ruleSetFactory;
397       this.ctx = ctx;
398   }
399 
400   public Thread newThread(Runnable r) {
401       PmdThread t = new PmdThread(counter.incrementAndGet(), r, ruleSetFactory, ctx);
402       threadList.add(t);
403       return t;
404   }
405 
406   public List<PmdThread> threadList = Collections.synchronizedList(new LinkedList<PmdThread>());
407 
408     }
409 
410     private static class PmdThread extends Thread {
411 
412   public PmdThread(int id, Runnable r, RuleSetFactory ruleSetFactory, RuleContext ctx) {
413       super(r, "PmdThread " + id);
414       this.id = id;
415       context = new RuleContext(ctx);
416       this.ruleSetFactory = ruleSetFactory;
417   }
418 
419   private int id;
420   private RuleContext context;
421   private RuleSets rulesets;
422   private RuleSetFactory ruleSetFactory;
423 
424   public RuleContext getRuleContext() {
425       return context;
426   }
427 
428   public RuleSets getRuleSets(String rsList) {
429       if (rulesets == null) {
430     try {
431         rulesets = ruleSetFactory.createRuleSets(rsList);
432     catch (Exception e) {
433         e.printStackTrace();
434     }
435       }
436       return rulesets;
437   }
438 
439   @Override
440   public String toString() {
441       return "PmdThread " + id;
442   }
443     }
444 
445     /**
446      * Run PMD on a list of files using multiple threads.
447      */
448     public static void processFiles(final Configuration configuration, final RuleSetFactory ruleSetFactory,
449       final List<DataSource> files, final RuleContext ctx, final List<Renderer> renderers) {
450 
451   final boolean reportShortNames = configuration.isReportShortNames();
452   final String inputPaths = configuration.getInputPaths();
453 
454   /*
455    * Check if multithreaded is supported.
456    * ExecutorService can also be disabled if threadCount is not positive, e.g. using the
457    * "-threads 0" command line option.
458    */
459   boolean useMT = MT_SUPPORTED && configuration.getThreads() 0;
460 
461   if (configuration.isStressTest()) {
462       // randomize processing order
463       Collections.shuffle(files);
464   else {
465       Collections.sort(files, new Comparator<DataSource>() {
466     public int compare(DataSource d1, DataSource d2) {
467         String s1 = d1.getNiceFileName(reportShortNames, inputPaths);
468         String s2 = d2.getNiceFileName(reportShortNames, inputPaths);
469         return s1.compareTo(s2);
470     }
471       });
472   }
473 
474   if (useMT) {
475       RuleSets rs = null;
476       try {
477     rs = ruleSetFactory.createRuleSets(configuration.getRuleSets());
478       catch (RuleSetNotFoundException rsnfe) {
479     // should not happen: parent already created a ruleset
480       }
481       rs.start(ctx);
482 
483       PmdThreadFactory factory = new PmdThreadFactory(ruleSetFactory, ctx);
484       ExecutorService executor = Executors.newFixedThreadPool(configuration.getThreads(), factory);
485       List<Future<Report>> tasks = new LinkedList<Future<Report>>();
486 
487       for (DataSource dataSource : files) {
488     String niceFileName = dataSource.getNiceFileName(reportShortNames, inputPaths);
489     PmdRunnable r = new PmdRunnable(executor, configuration, dataSource, niceFileName, renderers);
490     Future<Report> future = executor.submit(r);
491     tasks.add(future);
492       }
493       executor.shutdown();
494 
495       while (!tasks.isEmpty()) {
496     Future<Report> future = tasks.remove(0);
497     Report report = null;
498     try {
499         report = future.get();
500     catch (InterruptedException ie) {
501         Thread.currentThread().interrupt();
502         future.cancel(true);
503     catch (ExecutionException ee) {
504         Throwable t = ee.getCause();
505         if (instanceof RuntimeException) {
506       throw (RuntimeExceptiont;
507         else if (instanceof Error) {
508       throw (Errort;
509         else {
510       throw new IllegalStateException("PmdRunnable exception", t);
511         }
512     }
513 
514     try {
515         long start = System.nanoTime();
516         for (Renderer r : renderers) {
517       r.renderFileReport(report);
518         }
519         long end = System.nanoTime();
520         Benchmark.mark(Benchmark.TYPE_REPORTING, end - start, 1);
521     catch (IOException ioe) {
522     }
523       }
524 
525       try {
526     rs.end(ctx);
527     long start = System.nanoTime();
528     for (Renderer r : renderers) {
529         r.renderFileReport(ctx.getReport());
530     }
531     long end = System.nanoTime();
532     Benchmark.mark(Benchmark.TYPE_REPORTING, end - start, 1);
533       catch (IOException ioe) {
534       }
535 
536   else {
537       // single threaded execution
538       PMD pmd = new PMD(configuration);
539 
540       RuleSets rs = null;
541       try {
542     rs = ruleSetFactory.createRuleSets(configuration.getRuleSets());
543       catch (RuleSetNotFoundException rsnfe) {
544     // should not happen: parent already created a ruleset
545       }
546       for (DataSource dataSource : files) {
547     String niceFileName = dataSource.getNiceFileName(reportShortNames, inputPaths);
548 
549     Report report = new Report();
550     ctx.setReport(report);
551 
552     ctx.setSourceCodeFilename(niceFileName);
553     ctx.setSourceCodeFile(new File(niceFileName));
554     if (LOG.isLoggable(Level.FINE)) {
555         LOG.fine("Processing " + ctx.getSourceCodeFilename());
556     }
557     rs.start(ctx);
558 
559     for (Renderer r : renderers) {
560         r.startFileAnalysis(dataSource);
561     }
562 
563     try {
564         InputStream stream = new BufferedInputStream(dataSource.getInputStream());
565         ctx.setLanguageVersion(null);
566         pmd.processFile(stream, rs, ctx);
567     catch (PMDException pmde) {
568         LOG.log(Level.FINE, "Error while processing file", pmde.getCause());
569 
570         report.addError(new Report.ProcessingError(pmde.getMessage(), niceFileName));
571     catch (IOException ioe) {
572         // unexpected exception: log and stop executor service
573         LOG.log(Level.FINE, "Unable to read source file", ioe);
574 
575         report.addError(new Report.ProcessingError(ioe.getMessage(), niceFileName));
576     catch (RuntimeException re) {
577         // unexpected exception: log and stop executor service
578         LOG.log(Level.FINE, "RuntimeException while processing file", re);
579 
580         report.addError(new Report.ProcessingError(re.getMessage(), niceFileName));
581     }
582 
583     rs.end(ctx);
584 
585     try {
586         long start = System.nanoTime();
587         for (Renderer r : renderers) {
588       r.renderFileReport(report);
589         }
590         long end = System.nanoTime();
591         Benchmark.mark(Benchmark.TYPE_REPORTING, end - start, 1);
592     catch (IOException ioe) {
593     }
594       }
595   }
596     }
597 
598     /**
599      * If in debug modus, print the names of the rules.
600      *
601      @param rulesets     the RuleSets to print
602      */
603     private static void printRuleNamesInDebug(RuleSets rulesets) {
604   if (LOG.isLoggable(Level.FINER)) {
605       for (Rule r : rulesets.getAllRules()) {
606     LOG.finer("Loaded rule " + r.getName());
607       }
608   }
609     }
610 }