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 ctx) throws 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 ctx) throws 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 = (PmdThread) Thread.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 (t instanceof RuntimeException) {
506 throw (RuntimeException) t;
507 } else if (t instanceof Error) {
508 throw (Error) t;
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 }
|