View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd;
5   
6   import java.io.BufferedInputStream;
7   import java.io.BufferedWriter;
8   import java.io.File;
9   import java.io.FileWriter;
10  import java.io.IOException;
11  import java.io.InputStream;
12  import java.io.InputStreamReader;
13  import java.io.OutputStreamWriter;
14  import java.io.Reader;
15  import java.io.UnsupportedEncodingException;
16  import java.io.Writer;
17  import java.util.ArrayList;
18  import java.util.Collections;
19  import java.util.Comparator;
20  import java.util.Enumeration;
21  import java.util.LinkedList;
22  import java.util.List;
23  import java.util.StringTokenizer;
24  import java.util.concurrent.Callable;
25  import java.util.concurrent.ExecutionException;
26  import java.util.concurrent.ExecutorService;
27  import java.util.concurrent.Executors;
28  import java.util.concurrent.Future;
29  import java.util.concurrent.ThreadFactory;
30  import java.util.concurrent.atomic.AtomicInteger;
31  import java.util.logging.Handler;
32  import java.util.logging.Level;
33  import java.util.logging.Logger;
34  import java.util.zip.ZipEntry;
35  import java.util.zip.ZipFile;
36  
37  import net.sourceforge.pmd.ast.CompilationUnit;
38  import net.sourceforge.pmd.ast.ParseException;
39  import net.sourceforge.pmd.cpd.SourceFileOrDirectoryFilter;
40  import net.sourceforge.pmd.parsers.Parser;
41  import net.sourceforge.pmd.renderers.Renderer;
42  import net.sourceforge.pmd.sourcetypehandlers.SourceTypeHandler;
43  import net.sourceforge.pmd.sourcetypehandlers.SourceTypeHandlerBroker;
44  import net.sourceforge.pmd.util.Benchmark;
45  import net.sourceforge.pmd.util.ClasspathClassLoader;
46  import net.sourceforge.pmd.util.ConsoleLogHandler;
47  import net.sourceforge.pmd.util.FileFinder;
48  
49  public class PMD {
50      public static final String EOL = System.getProperty("line.separator", "\n");
51      public static final String VERSION = "4.3";
52      public static final String EXCLUDE_MARKER = "NOPMD";
53  
54      private static final Logger LOG = Logger.getLogger(PMD.class.getName());
55  
56      private String excludeMarker = EXCLUDE_MARKER;
57      private SourceTypeDiscoverer sourceTypeDiscoverer = new SourceTypeDiscoverer();
58      private ClassLoader classLoader = getClass().getClassLoader();
59  
60      public PMD() {}
61  
62      /**
63       * Processes the file read by the reader against the rule set.
64       *
65       * @param reader   input stream reader
66       * @param ruleSets set of rules to process against the file
67       * @param ctx      context in which PMD is operating. This contains the Renderer and
68       *                 whatnot
69       * @throws PMDException if the input could not be parsed or processed
70       */
71      public void processFile(Reader reader, RuleSets ruleSets, RuleContext ctx)
72              throws PMDException {
73          SourceType sourceType = getSourceTypeOfFile(ctx.getSourceCodeFilename());
74  
75          processFile(reader, ruleSets, ctx, sourceType);
76      }
77  
78      /**
79       * Processes the file read by the reader against the rule set.
80       *
81       * @param reader     input stream reader
82       * @param ruleSets   set of rules to process against the file
83       * @param ctx        context in which PMD is operating. This contains the Renderer and
84       *                   whatnot
85       * @param sourceType the SourceType of the source
86       * @throws PMDException if the input could not be parsed or processed
87       */
88      public void processFile(Reader reader, RuleSets ruleSets, RuleContext ctx,
89                              SourceType sourceType) throws PMDException {
90          try {
91          	// Coarse check to see if any RuleSet applies to files, will need to do a finer RuleSet specific check later
92          	if (ruleSets.applies(ctx.getSourceCodeFile())) {
93  	            SourceTypeHandler sourceTypeHandler = SourceTypeHandlerBroker.getVisitorsFactoryForSourceType(sourceType);
94  	            ctx.setSourceType(sourceType);
95  	            Parser parser = sourceTypeHandler.getParser();
96  	            parser.setExcludeMarker(excludeMarker);
97  	            long start = System.nanoTime();
98  	            CompilationUnit rootNode = (CompilationUnit) parser.parse(reader);
99  	            ctx.excludeLines(parser.getExcludeMap());
100 	            long end = System.nanoTime();
101 	            Benchmark.mark(Benchmark.TYPE_PARSER, end - start, 0);
102 	            start = System.nanoTime();
103 	            sourceTypeHandler.getSymbolFacade().start(rootNode);
104 	            end = System.nanoTime();
105 	            Benchmark.mark(Benchmark.TYPE_SYMBOL_TABLE, end - start, 0);
106 	
107 	            Language language = SourceTypeToRuleLanguageMapper.getMappedLanguage(sourceType);
108 	
109 	            if (ruleSets.usesDFA(language)) {
110 	                start = System.nanoTime();
111 	                sourceTypeHandler.getDataFlowFacade().start(rootNode);
112 	                end = System.nanoTime();
113 	                Benchmark.mark(Benchmark.TYPE_DFA, end - start, 0);
114 	            }
115 	
116 	            if (ruleSets.usesTypeResolution(language)) {
117 	                start = System.nanoTime();
118 	                sourceTypeHandler.getTypeResolutionFacade(classLoader).start(rootNode);
119 	                end = System.nanoTime();
120 	                Benchmark.mark(Benchmark.TYPE_TYPE_RESOLUTION, end - start, 0);
121 	            }
122 	
123 	            List<CompilationUnit> acus = new ArrayList<CompilationUnit>();
124 	            acus.add(rootNode);
125 	
126 	            ruleSets.apply(acus, ctx, language);
127         	}
128         } catch (ParseException pe) {
129             throw new PMDException("Error while parsing "
130                     + ctx.getSourceCodeFilename(), pe);
131         } catch (Exception e) {
132             throw new PMDException("Error while processing "
133                     + ctx.getSourceCodeFilename(), e);
134         } finally {
135             try {
136                 reader.close();
137             } catch (IOException e) {}
138         }
139     }
140 
141     /**
142      * Get the SourceType of the source file with given name. This depends on the fileName
143      * extension, and the java version.
144      * <p/>
145      * For compatibility with older code that does not always pass in a correct filename,
146      * unrecognized files are assumed to be java files.
147      *
148      * @param fileName Name of the file, can be absolute, or simple.
149      * @return the SourceType
150      */
151     private SourceType getSourceTypeOfFile(String fileName) {
152         SourceType sourceType = sourceTypeDiscoverer.getSourceTypeOfFile(fileName);
153         if (sourceType == null) {
154             // For compatibility with older code that does not always pass in
155             // a correct filename.
156             sourceType = sourceTypeDiscoverer.getSourceTypeOfJavaFiles();
157         }
158         return sourceType;
159     }
160 
161     /**
162      * Processes the file read by the reader against the rule set.
163      *
164      * @param reader  input stream reader
165      * @param ruleSet set of rules to process against the file
166      * @param ctx     context in which PMD is operating. This contains the Renderer and
167      *                whatnot
168      * @throws PMDException if the input could not be parsed or processed
169      */
170     public void processFile(Reader reader, RuleSet ruleSet, RuleContext ctx)
171             throws PMDException {
172         processFile(reader, new RuleSets(ruleSet), ctx);
173     }
174 
175     /**
176      * Processes the input stream against a rule set using the given input encoding.
177      *
178      * @param fileContents an input stream to analyze
179      * @param encoding     input stream's encoding
180      * @param ruleSet      set of rules to process against the file
181      * @param ctx          context in which PMD is operating. This contains the Report and whatnot
182      * @throws PMDException if the input encoding is unsupported or the input stream could
183      *                      not be parsed
184      * @see #processFile(Reader, RuleSet, RuleContext)
185      */
186     public void processFile(InputStream fileContents, String encoding,
187                             RuleSet ruleSet, RuleContext ctx) throws PMDException {
188         try {
189             processFile(new InputStreamReader(fileContents, encoding), ruleSet, ctx);
190         } catch (UnsupportedEncodingException uee) {
191             throw new PMDException("Unsupported encoding exception: "
192                     + uee.getMessage());
193         }
194     }
195 
196     /**
197      * Processes the input stream against a rule set using the given input encoding.
198      *
199      * @param fileContents an input stream to analyze
200      * @param encoding     input stream's encoding
201      * @param ruleSets     set of rules to process against the file
202      * @param ctx          context in which PMD is operating. This contains the Report and whatnot
203      * @throws PMDException if the input encoding is unsupported or the input stream could
204      *                      not be parsed
205      * @see #processFile(Reader, RuleSet, RuleContext)
206      */
207     public void processFile(InputStream fileContents, String encoding,
208                             RuleSets ruleSets, RuleContext ctx) throws PMDException {
209         try {
210             processFile(new InputStreamReader(fileContents, encoding), ruleSets, ctx);
211         } catch (UnsupportedEncodingException uee) {
212             throw new PMDException("Unsupported encoding exception: "
213                     + uee.getMessage());
214         }
215     }
216 
217     /**
218      * Processes the input stream against a rule set assuming the platform character set.
219      *
220      * @param fileContents input stream to check
221      * @param ruleSet      the set of rules to process against the source code
222      * @param ctx          the context in which PMD is operating. This contains the Report and
223      *                     whatnot
224      * @throws PMDException if the input encoding is unsupported or the input input stream
225      *                      could not be parsed
226      * @see #processFile(InputStream, String, RuleSet, RuleContext)
227      */
228     public void processFile(InputStream fileContents, RuleSet ruleSet,
229                             RuleContext ctx) throws PMDException {
230         processFile(fileContents, System.getProperty("file.encoding"), ruleSet, ctx);
231     }
232 
233     public void setExcludeMarker(String marker) {
234         this.excludeMarker = marker;
235     }
236 
237     /**
238      * Set the SourceType to be used for ".java" files.
239      *
240      * @param javaVersion the SourceType that indicates the java version
241      */
242     public void setJavaVersion(SourceType javaVersion) {
243         sourceTypeDiscoverer.setSourceTypeOfJavaFiles(javaVersion);
244     }
245     
246     /**
247      * Get the ClassLoader being used by PMD when processing Rules.
248      * @return The ClassLoader being used
249      */
250     public ClassLoader getClassLoader() {
251 		return classLoader;
252 	}
253 
254     /**
255      * Set the ClassLoader being used by PMD when processing Rules.
256      * Setting a value of <code>null</code> will cause the default
257      * ClassLoader to be used.
258      * @param classLoader The ClassLoader to use
259      */
260 	public void setClassLoader(ClassLoader classLoader) {
261 		if (classLoader == null) {
262 			classLoader = getClass().getClassLoader();
263 		}
264 		this.classLoader = classLoader;
265 	}
266 	
267 	/**
268 	 * Create a ClassLoader which loads classes using a CLASSPATH like String.
269 	 * If the String looks like a URL to a file (e.g. starts with <code>file://</code>)
270 	 * the file will be read with each line representing an entry on the classpath.
271 	 * <p>
272 	 * The ClassLoader used to load the <code>net.sourceforge.pmd.PMD</code> class
273 	 * will be used as the parent ClassLoader of the created ClassLoader.
274 	 * 
275 	 * @param classpath The classpath String.
276 	 * @return A ClassLoader
277 	 * @throws IOException
278 	 * @see ClasspathClassLoader
279 	 */
280 	public static ClassLoader createClasspathClassLoader(String classpath) throws IOException {
281 		ClassLoader classLoader = PMD.class.getClassLoader();
282 		if (classpath != null) {
283 			classLoader = new ClasspathClassLoader(classpath, classLoader);
284 		}
285 		return classLoader;
286 	}
287 
288 	private static void doPMD(CommandLineOptions opts){
289         long startFiles = System.nanoTime();
290         SourceFileSelector fileSelector = new SourceFileSelector();
291 
292         fileSelector.setSelectJavaFiles(opts.isCheckJavaFiles());
293         fileSelector.setSelectJspFiles(opts.isCheckJspFiles());
294 
295         List<DataSource> files;
296         if (opts.containsCommaSeparatedFileList()) {
297             files = collectFromCommaDelimitedString(opts.getInputPath(),
298                     fileSelector);
299         } else {
300             files = collectFilesFromOneName(opts.getInputPath(), fileSelector);
301         }
302         long endFiles = System.nanoTime();
303         Benchmark.mark(Benchmark.TYPE_COLLECT_FILES, endFiles - startFiles, 0);
304 
305         SourceType sourceType;
306         if (opts.getTargetJDK().equals("1.3")) {
307             LOG.fine("In JDK 1.3 mode");
308             sourceType = SourceType.JAVA_13;
309         } else if (opts.getTargetJDK().equals("1.5")) {
310             LOG.fine("In JDK 1.5 mode");
311             sourceType = SourceType.JAVA_15;
312         } else if (opts.getTargetJDK().equals("1.6")) {
313             LOG.fine("In JDK 1.6 mode");
314             sourceType = SourceType.JAVA_16;
315         } else if (opts.getTargetJDK().equals("1.7")) {
316             LOG.fine("In JDK 1.7 mode");
317             sourceType = SourceType.JAVA_17;
318         } else {
319             LOG.fine("In JDK 1.4 mode");
320             sourceType = SourceType.JAVA_14;
321         }
322 
323         final ClassLoader classLoader;
324         try {
325         	classLoader = createClasspathClassLoader(opts.getAuxClasspath());
326         } catch (IOException e) {
327         	LOG.log(Level.SEVERE, "Bad -auxclasspath argument", e);
328         	System.out.println(opts.usage());        	
329         	return;
330         }
331         
332         long reportStart, reportEnd;
333         Renderer renderer;
334         Writer w = null;
335 
336         reportStart = System.nanoTime();
337         try {
338             renderer = opts.createRenderer();
339             List<Renderer> renderers = new LinkedList<Renderer>();
340             renderers.add(renderer);
341             if (opts.getReportFile() != null) {
342                 w = new BufferedWriter(new FileWriter(opts.getReportFile()));
343             } else {
344                 w = new OutputStreamWriter(System.out);
345             }
346             renderer.setWriter(w);
347             renderer.start();
348 
349             reportEnd = System.nanoTime();
350             Benchmark.mark(Benchmark.TYPE_REPORTING, reportEnd - reportStart, 0);
351     
352             RuleContext ctx = new RuleContext();
353 
354             try {
355                 long startLoadRules = System.nanoTime();
356                 RuleSetFactory ruleSetFactory = new RuleSetFactory();
357                 ruleSetFactory.setMinimumPriority(opts.getMinPriority());
358     
359                 RuleSets rulesets = ruleSetFactory.createRuleSets(opts.getRulesets());
360                 printRuleNamesInDebug(rulesets);
361                 long endLoadRules = System.nanoTime();
362                 Benchmark.mark(Benchmark.TYPE_LOAD_RULES, endLoadRules - startLoadRules, 0);
363     
364                 processFiles(opts.getCpus(), ruleSetFactory, sourceType, files, ctx, renderers,
365                         opts.stressTestEnabled(),
366                         opts.getRulesets(), opts.shortNamesEnabled(),
367                         opts.getInputPath(), opts.getEncoding(), opts.getExcludeMarker(), classLoader);
368             } catch (RuleSetNotFoundException rsnfe) {
369                 LOG.log(Level.SEVERE, "Ruleset not found", rsnfe);
370                 System.out.println(opts.usage());
371             }
372 
373             reportStart = System.nanoTime();
374             renderer.end();
375             w.write(EOL);
376             w.flush();
377             if (opts.getReportFile() != null) {
378                 w.close();
379                 w = null;
380             }
381         } catch (Exception e) {
382         	String message = e.getMessage();
383         	if (message != null) {
384         		LOG.severe(message);
385         	} else {
386                 LOG.log(Level.SEVERE, "Exception during processing", e);
387         	}
388             
389             LOG.log(Level.FINE, "Exception during processing", e);	//Only displayed when debug logging is on
390  
391             LOG.info(opts.usage());
392         } finally {
393             if (opts.getReportFile() != null && w != null) {
394                 try {
395                     w.close();
396                 } catch (Exception e) {
397                     System.out.println(e.getMessage());
398                 }
399             }
400             reportEnd = System.nanoTime();
401             Benchmark.mark(Benchmark.TYPE_REPORTING, reportEnd - reportStart, 0);
402         }
403     }
404 
405     public static void main(String[] args) {
406         long start = System.nanoTime();
407         final CommandLineOptions opts = new CommandLineOptions(args);
408 
409         final Level logLevel = opts.debugEnabled() ? Level.FINER : Level.INFO;
410         final Handler logHandler = new ConsoleLogHandler();
411         final ScopedLogHandlersManager logHandlerManager = new ScopedLogHandlersManager(logLevel, logHandler);
412         final Level oldLogLevel = LOG.getLevel();
413         LOG.setLevel(logLevel); //Need to do this, since the static logger has already been initialized at this point
414         try{
415             doPMD(opts);
416         }finally{
417             logHandlerManager.close();
418             LOG.setLevel(oldLogLevel);
419             if (opts.benchmark()) {
420                 long end = System.nanoTime();
421                 Benchmark.mark(Benchmark.TYPE_TOTAL_PMD, end - start, 0);
422                 System.err.println(Benchmark.report());
423             }
424         }
425     }
426 
427     private static class PmdRunnable extends PMD implements Callable<Report> {
428         private final ExecutorService executor;
429         private final DataSource dataSource;
430         private final String fileName;
431         private final String encoding;
432         private final String rulesets;
433         private final List<Renderer> renderers;
434 
435         public PmdRunnable(ExecutorService executor, DataSource dataSource, String fileName, SourceType sourceType,
436                 List<Renderer> renderers, String encoding, String rulesets, String excludeMarker, ClassLoader classLoader) {
437             this.executor = executor;
438             this.dataSource = dataSource;
439             this.fileName = fileName;
440             this.encoding = encoding;
441             this.rulesets = rulesets;
442             this.renderers = renderers;
443 
444             setJavaVersion(sourceType);
445             setExcludeMarker(excludeMarker);
446             setClassLoader(classLoader);
447         }
448 
449         public Report call() {
450             PmdThread thread = (PmdThread) Thread.currentThread();
451 
452             RuleContext ctx = thread.getRuleContext();
453             RuleSets rs = thread.getRuleSets(rulesets);
454 
455             Report report = new Report();
456             ctx.setReport(report);
457 
458             ctx.setSourceCodeFilename(fileName);
459             ctx.setSourceCodeFile(new File(fileName));
460             if (LOG.isLoggable(Level.FINE)) {
461                 LOG.fine("Processing " + ctx.getSourceCodeFilename());
462             }
463             for(Renderer r: renderers) {
464                 r.startFileAnalysis(dataSource);
465             }
466 
467             try {
468                 InputStream stream = new BufferedInputStream(dataSource.getInputStream());
469                 processFile(stream, encoding, rs, ctx);
470             } catch (PMDException pmde) {
471                 LOG.log(Level.FINE, "Error while processing file", pmde.getCause());
472 
473                 report.addError(
474                         new Report.ProcessingError(pmde.getMessage(),
475                         fileName));
476             } catch (IOException ioe) {
477                 // unexpected exception: log and stop executor service
478                 LOG.log(Level.FINE, "IOException during processing", ioe);
479 
480                 report.addError(
481                         new Report.ProcessingError(ioe.getMessage(),
482                         fileName));
483 
484                 executor.shutdownNow();
485             } catch (RuntimeException re) {
486                 // unexpected exception: log and stop executor service
487                 LOG.log(Level.FINE, "RuntimeException during processing", re);
488 
489                 report.addError(
490                         new Report.ProcessingError(re.getMessage(),
491                         fileName));
492 
493                 executor.shutdownNow();
494             }
495             return report;
496         }
497 
498     }
499 
500     private static class PmdThreadFactory implements ThreadFactory {
501 
502         private final RuleSetFactory ruleSetFactory;
503         private final RuleContext ctx;
504         private final AtomicInteger counter = new AtomicInteger();
505 
506         public PmdThreadFactory(RuleSetFactory ruleSetFactory, RuleContext ctx) {
507             this.ruleSetFactory = ruleSetFactory;
508             this.ctx = ctx;
509         }
510 
511         public Thread newThread(Runnable r) {
512             PmdThread t = new PmdThread(counter.incrementAndGet(), r, ruleSetFactory, ctx);
513             threadList.add(t);
514             return t;
515         }
516 
517         public List<PmdThread> threadList = Collections.synchronizedList(new LinkedList<PmdThread>());
518 
519     }
520 
521     private static class PmdThread extends Thread {
522 
523         public PmdThread(int id, Runnable r, RuleSetFactory ruleSetFactory, RuleContext ctx) {
524             super(r, "PmdThread " + id);
525             this.id = id;
526             context = new RuleContext(ctx);
527             this.ruleSetFactory = ruleSetFactory;
528         }
529         
530         private int id;
531         private RuleContext context;
532         private RuleSets rulesets;
533         private RuleSetFactory ruleSetFactory;
534         
535         public RuleContext getRuleContext() {
536             return context;
537         }
538 
539         public RuleSets getRuleSets(String rsList) {
540             if (rulesets == null) {
541                 try {
542                     rulesets = ruleSetFactory.createRuleSets(rsList);
543                 } catch (Exception e) {
544                     e.printStackTrace();
545                 }
546             }
547             return rulesets;
548         }
549 
550         public String toString() {
551             return "PmdThread " + id;
552         }
553 
554     }
555 
556     /**
557      * Do we have proper permissions to use multithreading?
558      */
559     private static final boolean mtSupported;
560     
561     static {
562         boolean error = false;
563         try {
564             /*
565              * ant task ran from Eclipse with jdk 1.5.0 raises an AccessControlException
566              * when shutdown is called. Standalone pmd or ant from command line are fine.
567              * 
568              * With jdk 1.6.0, ant task from Eclipse also works.
569              */
570             ExecutorService executor = Executors.newFixedThreadPool(1);
571             executor.shutdown();
572         } catch (RuntimeException e) {
573             error = true;
574         }
575         mtSupported = !error;
576     }
577 
578     /**
579      * Run PMD on a list of files using multiple threads.
580      *
581      * @throws IOException If one of the files could not be read
582      */
583     public static void processFiles(int threadCount, RuleSetFactory ruleSetFactory, SourceType sourceType, List<DataSource> files, RuleContext ctx,
584             List<Renderer> renderers, String rulesets, final boolean shortNamesEnabled, final String inputPath,
585             String encoding, String excludeMarker, ClassLoader classLoader) {
586         processFiles(threadCount, ruleSetFactory, sourceType, files, ctx,
587                 renderers, false, rulesets, shortNamesEnabled, inputPath,
588                 encoding, excludeMarker, classLoader);
589     }
590     /**
591      * Run PMD on a list of files using multiple threads.
592      *
593      * @throws IOException If one of the files could not be read
594      */
595     public static void processFiles(int threadCount, RuleSetFactory ruleSetFactory, SourceType sourceType, List<DataSource> files, RuleContext ctx,
596             List<Renderer> renderers, boolean stressTestEnabled, String rulesets, final boolean shortNamesEnabled, final String inputPath,
597             String encoding, String excludeMarker, ClassLoader classLoader) {
598 
599         /*
600          * Check if multithreaded is supported. 
601          * ExecutorService can also be disabled if threadCount is not positive, e.g. using the
602          * "-cpus 0" command line option.
603          */
604         boolean useMT = mtSupported && (threadCount > 0);
605 
606         if (stressTestEnabled) {
607             // randomize processing order
608             Collections.shuffle(files);
609         } else {
610             Collections.sort(files, new Comparator<DataSource>() {
611                 public int compare(DataSource d1,DataSource d2) {
612                     String s1 = d1.getNiceFileName(shortNamesEnabled, inputPath);
613                     String s2 = d2.getNiceFileName(shortNamesEnabled, inputPath);
614                     return s1.compareTo(s2);
615                 }
616             });
617         }
618 
619         if (useMT) {
620             RuleSets rs = null;
621             try {
622                 rs = ruleSetFactory.createRuleSets(rulesets);
623             } catch (RuleSetNotFoundException rsnfe) {
624                 // should not happen: parent already created a ruleset
625             }
626             rs.start(ctx);
627 
628             PmdThreadFactory factory = new PmdThreadFactory(ruleSetFactory, ctx);
629             ExecutorService executor = Executors.newFixedThreadPool(threadCount, factory);
630             List<Future<Report>> tasks = new LinkedList<Future<Report>>();
631 
632             for (DataSource dataSource:files) {
633                 String niceFileName = dataSource.getNiceFileName(shortNamesEnabled,
634                         inputPath);
635     
636                 PmdRunnable r = new PmdRunnable(executor, dataSource, niceFileName, sourceType, renderers,
637                                                 encoding, rulesets, excludeMarker, classLoader);
638     
639                 Future<Report> future = executor.submit(r);
640                 tasks.add(future);
641             }
642             executor.shutdown();
643     
644             while (!tasks.isEmpty()) {
645                 Future<Report> future = tasks.remove(0);
646                 Report report = null;
647                 try {
648                     report = future.get();
649                 } catch (InterruptedException ie) {
650                     Thread.currentThread().interrupt();
651                     future.cancel(true);
652                 } catch (ExecutionException ee) {
653                     Throwable t = ee.getCause();
654                     if (t instanceof RuntimeException) {
655                         throw (RuntimeException) t;
656                     } else if (t instanceof Error) {
657                         throw (Error) t;
658                     } else {
659                         throw new IllegalStateException("PmdRunnable exception", t);
660                     }
661                 }
662     
663                 try {
664                     long start = System.nanoTime();
665                     for (Renderer r: renderers) {
666                         r.renderFileReport(report);
667                     }
668                     long end = System.nanoTime();
669                     Benchmark.mark(Benchmark.TYPE_REPORTING, end - start, 1);
670                 } catch (IOException ioe) {
671                 }
672             }
673 
674             try {
675                 rs.end(ctx);
676                 long start = System.nanoTime();
677                 for (Renderer r: renderers) {
678                     r.renderFileReport(ctx.getReport());
679                 }
680                 long end = System.nanoTime();
681                 Benchmark.mark(Benchmark.TYPE_REPORTING, end - start, 1);
682             } catch (IOException ioe) {
683             }
684 
685 
686         } else {
687             // single threaded execution
688 
689             PMD pmd = new PMD();
690             pmd.setJavaVersion(sourceType);
691             pmd.setExcludeMarker(excludeMarker);
692 
693             RuleSets rs = null;
694             try {
695                 rs = ruleSetFactory.createRuleSets(rulesets);
696             } catch (RuleSetNotFoundException rsnfe) {
697                 // should not happen: parent already created a ruleset
698             }
699             for (DataSource dataSource: files) {
700                 String niceFileName = dataSource.getNiceFileName(shortNamesEnabled,
701                         inputPath);
702 
703                 Report report = new Report();
704                 ctx.setReport(report);
705 
706                 ctx.setSourceCodeFilename(niceFileName);
707                 ctx.setSourceCodeFile(new File(niceFileName));
708                 if (LOG.isLoggable(Level.FINE)) {
709                     LOG.fine("Processing " + ctx.getSourceCodeFilename());
710                 }
711                 rs.start(ctx);
712 
713                 for(Renderer r: renderers) {
714                     r.startFileAnalysis(dataSource);
715                 }
716 
717                 try {
718                     InputStream stream = new BufferedInputStream(dataSource.getInputStream());
719                     pmd.processFile(stream, encoding, rs, ctx);
720                 } catch (PMDException pmde) {
721                     LOG.log(Level.FINE, "Error while processing file", pmde.getCause());
722 
723                     report.addError(
724                             new Report.ProcessingError(pmde.getMessage(),
725                                     niceFileName));
726                 } catch (IOException ioe) {
727                     // unexpected exception: log and stop executor service
728                     LOG.log(Level.FINE, "Unable to read source file", ioe);
729 
730                     report.addError(
731                             new Report.ProcessingError(ioe.getMessage(),
732                                     niceFileName));
733                 } catch (RuntimeException re) {
734                     // unexpected exception: log and stop executor service
735                     LOG.log(Level.FINE, "RuntimeException while processing file", re);
736                     
737                     report.addError(
738                             new Report.ProcessingError(re.getMessage(),
739                                     niceFileName));
740                 }
741 
742                 rs.end(ctx);
743 
744                 try {
745                     long start = System.nanoTime();
746                     for (Renderer r: renderers) {
747                         r.renderFileReport(report);
748                     }
749                     long end = System.nanoTime();
750                     Benchmark.mark(Benchmark.TYPE_REPORTING, end - start, 1);
751                 } catch (IOException ioe) {
752                 }
753             }
754         }
755     }
756 
757     /**
758      * Run PMD on a list of files.
759      *
760      * @param files             the List of DataSource instances.
761      * @param ctx               the context in which PMD is operating. This contains the Report and
762      *                          whatnot
763      * @param rulesets          the RuleSets
764      * @param debugEnabled
765      * @param shortNamesEnabled
766      * @param inputPath
767      * @param encoding
768      * @throws IOException If one of the files could not be read
769      */
770     public void processFiles(List<DataSource> files, RuleContext ctx, RuleSets rulesets,
771                              boolean debugEnabled, boolean shortNamesEnabled, String inputPath,
772                              String encoding) throws IOException {
773         for (DataSource dataSource: files) {
774             String niceFileName = dataSource.getNiceFileName(shortNamesEnabled,
775                     inputPath);
776             ctx.setSourceCodeFilename(niceFileName);
777             ctx.setSourceCodeFile(new File(niceFileName));
778             LOG.fine("Processing " + ctx.getSourceCodeFilename());
779             
780             try {
781                 InputStream stream = new BufferedInputStream(dataSource
782                         .getInputStream());
783                 processFile(stream, encoding, rulesets, ctx);
784             } catch (PMDException pmde) {
785                 LOG.log(Level.FINE, "Error while processing files", pmde.getCause());
786 
787                 ctx.getReport().addError(new Report.ProcessingError(pmde.getMessage(), niceFileName));
788             }
789         }
790     }
791 
792     /**
793      * If in debug modus, print the names of the rules.
794      *
795      * @param rulesets     the RuleSets to print
796      */
797     private static void printRuleNamesInDebug(RuleSets rulesets) {
798         if (LOG.isLoggable(Level.FINER)) {
799             for (Rule r: rulesets.getAllRules()) {
800                 LOG.finer("Loaded rule " + r.getName());
801             }
802         }
803     }
804 
805     /**
806      * Collects the given file into a list.
807      *
808      * @param inputFileName a file name
809      * @param fileSelector  Filtering of wanted source files
810      * @return the list of files collected from the <code>inputFileName</code>
811      * @see #collect(String, SourceFileSelector)
812      */
813     private static List<DataSource> collectFilesFromOneName(String inputFileName,
814                                                 SourceFileSelector fileSelector) {
815         return collect(inputFileName, fileSelector);
816     }
817 
818     /**
819      * Collects the files from the given comma-separated list.
820      *
821      * @param fileList     comma-separated list of filenames
822      * @param fileSelector Filtering of wanted source files
823      * @return list of files collected from the <code>fileList</code>
824      */
825     private static List<DataSource> collectFromCommaDelimitedString(String fileList,
826                                                         SourceFileSelector fileSelector) {
827         List<DataSource> files = new ArrayList<DataSource>();
828         for (StringTokenizer st = new StringTokenizer(fileList, ","); st
829                 .hasMoreTokens();) {
830             files.addAll(collect(st.nextToken(), fileSelector));
831         }
832         return files;
833     }
834 
835     /**
836      * Collects the files from the given <code>filename</code>.
837      *
838      * @param filename     the source from which to collect files
839      * @param fileSelector Filtering of wanted source files
840      * @return a list of files found at the given <code>filename</code>
841      * @throws RuntimeException if <code>filename</code> is not found
842      */
843     private static List<DataSource> collect(String filename, SourceFileSelector fileSelector) {
844         File inputFile = new File(filename);
845         if (!inputFile.exists()) {
846             throw new RuntimeException("File " + inputFile.getName()
847                     + " doesn't exist");
848         }
849         List<DataSource> dataSources = new ArrayList<DataSource>();
850         if (!inputFile.isDirectory()) {
851             if (filename.endsWith(".zip") || filename.endsWith(".jar")) {
852                 ZipFile zipFile;
853                 try {
854                     zipFile = new ZipFile(inputFile);
855                     Enumeration e = zipFile.entries();
856                     while (e.hasMoreElements()) {
857                         ZipEntry zipEntry = (ZipEntry) e.nextElement();
858                         if (fileSelector.isWantedFile(zipEntry.getName())) {
859                             dataSources.add(new ZipDataSource(zipFile, zipEntry));
860                         }
861                     }
862                 } catch (IOException ze) {
863                     throw new RuntimeException("Zip file " + inputFile.getName()
864                             + " can't be opened");
865                 }
866             } else {
867                 dataSources.add(new FileDataSource(inputFile));
868             }
869         } else {
870             FileFinder finder = new FileFinder();
871             List<File> files = finder.findFilesFrom(inputFile.getAbsolutePath(),
872                     new SourceFileOrDirectoryFilter(fileSelector), true);
873             for (File f: files) {
874                 dataSources.add(new FileDataSource(f));
875             }
876         }
877         return dataSources;
878     }
879 
880 }