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.2.5";
    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 }