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 net.sourceforge.pmd.ast.ParseException;
    7   import net.sourceforge.pmd.cpd.FileFinder;
    8   import net.sourceforge.pmd.cpd.SourceFileOrDirectoryFilter;
    9   import net.sourceforge.pmd.parsers.Parser;
    10  import net.sourceforge.pmd.renderers.Renderer;
    11  import net.sourceforge.pmd.sourcetypehandlers.SourceTypeHandler;
    12  import net.sourceforge.pmd.sourcetypehandlers.SourceTypeHandlerBroker;
    13
    14  import java.io.BufferedInputStream;
    15  import java.io.BufferedWriter;
    16  import java.io.File;
    17  import java.io.FileWriter;
    18  import java.io.IOException;
    19  import java.io.InputStream;
    20  import java.io.InputStreamReader;
    21  import java.io.OutputStreamWriter;
    22  import java.io.Reader;
    23  import java.io.UnsupportedEncodingException;
    24  import java.io.Writer;
    25  import java.util.ArrayList;
    26  import java.util.Enumeration;
    27  import java.util.Iterator;
    28  import java.util.LinkedList;
    29  import java.util.List;
    30  import java.util.StringTokenizer;
    31  import edu.emory.mathcs.backport.java.util.concurrent.ExecutorService;
    32  import edu.emory.mathcs.backport.java.util.concurrent.Executors;
    33  import edu.emory.mathcs.backport.java.util.concurrent.ThreadFactory;
    34  import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
    35  import edu.emory.mathcs.backport.java.util.concurrent.atomic.AtomicInteger;
    36  import edu.emory.mathcs.backport.java.util.Collections;
    37  import java.util.zip.ZipEntry;
    38  import java.util.zip.ZipFile;
    39
    40  public class PMD {
    41      public static final String EOL = System.getProperty("line.separator", "\n");
    42      public static final String VERSION = "3.9";
    43      public static final String EXCLUDE_MARKER = "NOPMD";
    44
    45
    46      private String excludeMarker = EXCLUDE_MARKER;
    47      private SourceTypeDiscoverer sourceTypeDiscoverer = new SourceTypeDiscoverer();
    48
    49      public PMD() {}
    50
    51      /***
    52       * Processes the file read by the reader agains the rule set.
    53       *
    54       * @param reader   input stream reader
    55       * @param ruleSets set of rules to process against the file
    56       * @param ctx      context in which PMD is operating. This contains the Renderer and
    57       *                 whatnot
    58       * @throws PMDException if the input could not be parsed or processed
    59       */
    60      public void processFile(Reader reader, RuleSets ruleSets, RuleContext ctx)
    61              throws PMDException {
    62          SourceType sourceType = getSourceTypeOfFile(ctx.getSourceCodeFilename());
    63
    64          processFile(reader, ruleSets, ctx, sourceType);
    65      }
    66
    67      /***
    68       * Processes the file read by the reader against the rule set.
    69       *
    70       * @param reader     input stream reader
    71       * @param ruleSets   set of rules to process against the file
    72       * @param ctx        context in which PMD is operating. This contains the Renderer and
    73       *                   whatnot
    74       * @param sourceType the SourceType of the source
    75       * @throws PMDException if the input could not be parsed or processed
    76       */
    77      public void processFile(Reader reader, RuleSets ruleSets, RuleContext ctx,
    78                              SourceType sourceType) throws PMDException {
    79          try {
    80              SourceTypeHandler sourceTypeHandler = SourceTypeHandlerBroker.getVisitorsFactoryForSourceType(sourceType);
    81              ctx.setSourceType(sourceType);
    82              Parser parser = sourceTypeHandler.getParser();
    83              parser.setExcludeMarker(excludeMarker);
    84              Object rootNode = parser.parse(reader);
    85              ctx.excludeLines(parser.getExcludeMap());
    86              Thread.yield();
    87              sourceTypeHandler.getSymbolFacade().start(rootNode);
    88
    89              Language language = SourceTypeToRuleLanguageMapper.getMappedLanguage(sourceType);
    90
    91              if (ruleSets.usesDFA(language)) {
    92                  sourceTypeHandler.getDataFlowFacade().start(rootNode);
    93              }
    94
    95              if (ruleSets.usesTypeResolution(language)) {
    96                  sourceTypeHandler.getTypeResolutionFacade().start(rootNode);
    97              }
    98
    99              List acus = new ArrayList();
    100             acus.add(rootNode);
    101
    102             ruleSets.apply(acus, ctx, language);
    103         } catch (ParseException pe) {
    104             throw new PMDException("Error while parsing "
    105                     + ctx.getSourceCodeFilename(), pe);
    106         } catch (Exception e) {
    107             throw new PMDException("Error while processing "
    108                     + ctx.getSourceCodeFilename(), e);
    109         } finally {
    110             try {
    111                 reader.close();
    112             } catch (IOException e) {
    113                 throw new PMDException("Error while closing "
    114                         + ctx.getSourceCodeFilename(), e);
    115             }
    116         }
    117     }
    118
    119     /***
    120      * Get the SourceType of the source file with given name. This depends on the fileName
    121      * extension, and the java version.
    122      * <p/>
    123      * For compatibility with older code that does not always pass in a correct filename,
    124      * unrecognized files are assumed to be java files.
    125      *
    126      * @param fileName Name of the file, can be absolute, or simple.
    127      * @return the SourceType
    128      */
    129     private SourceType getSourceTypeOfFile(String fileName) {
    130         SourceType sourceType = sourceTypeDiscoverer.getSourceTypeOfFile(fileName);
    131         if (sourceType == null) {
    132             // For compatibility with older code that does not always pass in
    133             // a correct filename.
    134             sourceType = sourceTypeDiscoverer.getSourceTypeOfJavaFiles();
    135         }
    136         return sourceType;
    137     }
    138
    139     /***
    140      * Processes the file read by the reader agains the rule set.
    141      *
    142      * @param reader  input stream reader
    143      * @param ruleSet set of rules to process against the file
    144      * @param ctx     context in which PMD is operating. This contains the Renderer and
    145      *                whatnot
    146      * @throws PMDException if the input could not be parsed or processed
    147      */
    148     public void processFile(Reader reader, RuleSet ruleSet, RuleContext ctx)
    149             throws PMDException {
    150         processFile(reader, new RuleSets(ruleSet), ctx);
    151     }
    152
    153     /***
    154      * Processes the input stream agains a rule set using the given input encoding.
    155      *
    156      * @param fileContents an input stream to analyze
    157      * @param encoding     input stream's encoding
    158      * @param ruleSet      set of rules to process against the file
    159      * @param ctx          context in which PMD is operating. This contains the Report and whatnot
    160      * @throws PMDException if the input encoding is unsupported or the input stream could
    161      *                      not be parsed
    162      * @see #processFile(Reader, RuleSet, RuleContext)
    163      */
    164     public void processFile(InputStream fileContents, String encoding,
    165                             RuleSet ruleSet, RuleContext ctx) throws PMDException {
    166         try {
    167             processFile(new InputStreamReader(fileContents, encoding), ruleSet, ctx);
    168         } catch (UnsupportedEncodingException uee) {
    169             throw new PMDException("Unsupported encoding exception: "
    170                     + uee.getMessage());
    171         }
    172     }
    173
    174     /***
    175      * Processes the input stream agains a rule set using the given input encoding.
    176      *
    177      * @param fileContents an input stream to analyze
    178      * @param encoding     input stream's encoding
    179      * @param ruleSets     set of rules to process against the file
    180      * @param ctx          context in which PMD is operating. This contains the Report and whatnot
    181      * @throws PMDException if the input encoding is unsupported or the input stream could
    182      *                      not be parsed
    183      * @see #processFile(Reader, RuleSet, RuleContext)
    184      */
    185     public void processFile(InputStream fileContents, String encoding,
    186                             RuleSets ruleSets, RuleContext ctx) throws PMDException {
    187         try {
    188             processFile(new InputStreamReader(fileContents, encoding), ruleSets, ctx);
    189         } catch (UnsupportedEncodingException uee) {
    190             throw new PMDException("Unsupported encoding exception: "
    191                     + uee.getMessage());
    192         }
    193     }
    194
    195     /***
    196      * Processes the input stream against a rule set assuming the platform character set.
    197      *
    198      * @param fileContents input stream to check
    199      * @param ruleSet      the set of rules to process against the source code
    200      * @param ctx          the context in which PMD is operating. This contains the Report and
    201      *                     whatnot
    202      * @throws PMDException if the input encoding is unsupported or the input input stream
    203      *                      could not be parsed
    204      * @see #processFile(InputStream, String, RuleSet, RuleContext)
    205      */
    206     public void processFile(InputStream fileContents, RuleSet ruleSet,
    207                             RuleContext ctx) throws PMDException {
    208         processFile(fileContents, System.getProperty("file.encoding"), ruleSet, ctx);
    209     }
    210
    211     public void setExcludeMarker(String marker) {
    212         this.excludeMarker = marker;
    213     }
    214
    215     /***
    216      * Set the SourceType to be used for ".java" files.
    217      *
    218      * @param javaVersion the SourceType that indicates the java version
    219      */
    220     public void setJavaVersion(SourceType javaVersion) {
    221         sourceTypeDiscoverer.setSourceTypeOfJavaFiles(javaVersion);
    222     }
    223
    224     public static void main(String[] args) {
    225         CommandLineOptions opts = new CommandLineOptions(args);
    226
    227         SourceFileSelector fileSelector = new SourceFileSelector();
    228
    229         fileSelector.setSelectJavaFiles(opts.isCheckJavaFiles());
    230         fileSelector.setSelectJspFiles(opts.isCheckJspFiles());
    231
    232         List files;
    233         if (opts.containsCommaSeparatedFileList()) {
    234             files = collectFromCommaDelimitedString(opts.getInputPath(),
    235                     fileSelector);
    236         } else {
    237             files = collectFilesFromOneName(opts.getInputPath(), fileSelector);
    238         }
    239
    240         SourceType sourceType;
    241         if (opts.getTargetJDK().equals("1.3")) {
    242             if (opts.debugEnabled())
    243                 System.out.println("In JDK 1.3 mode");
    244             sourceType = SourceType.JAVA_13;
    245         } else if (opts.getTargetJDK().equals("1.5")) {
    246             if (opts.debugEnabled())
    247                 System.out.println("In JDK 1.5 mode");
    248             sourceType = SourceType.JAVA_15;
    249         } else if (opts.getTargetJDK().equals("1.6")) {
    250             if (opts.debugEnabled())
    251                 System.out.println("In JDK 1.6 mode");
    252             sourceType = SourceType.JAVA_16;
    253         } else {
    254             if (opts.debugEnabled())
    255                 System.out.println("In JDK 1.4 mode");
    256             sourceType = SourceType.JAVA_14;
    257         }
    258
    259         RuleContext ctx = new RuleContext();
    260         Report report = new Report();
    261         ctx.setReport(report);
    262         report.start();
    263
    264         try {
    265             RuleSetFactory ruleSetFactory = new RuleSetFactory();
    266             ruleSetFactory.setMinimumPriority(opts.getMinPriority());
    267
    268             RuleSets rulesets = ruleSetFactory.createRuleSets(opts.getRulesets());
    269             printRuleNamesInDebug(opts.debugEnabled(), rulesets);
    270
    271             processFiles(opts.getCpus(), ruleSetFactory, sourceType, files, ctx,
    272                     opts.getRulesets(), opts.debugEnabled(), opts.shortNamesEnabled(),
    273                     opts.getInputPath(), opts.getEncoding(), opts.getExcludeMarker());
    274         } catch (RuleSetNotFoundException rsnfe) {
    275             System.out.println(opts.usage());
    276             rsnfe.printStackTrace();
    277         }
    278         report.end();
    279
    280         Writer w = null;
    281         try {
    282             Renderer r = opts.createRenderer();
    283             if (opts.getReportFile() != null) {
    284                 w = new BufferedWriter(new FileWriter(opts.getReportFile()));
    285             } else {
    286                 w = new OutputStreamWriter(System.out);
    287             }
    288             r.render(w, ctx.getReport());
    289             w.write(EOL);
    290             w.flush();
    291             if (opts.getReportFile() != null) {
    292                 w.close();
    293             }
    294         } catch (Exception e) {
    295             System.out.println(e.getMessage());
    296             System.out.println(opts.usage());
    297             if (opts.debugEnabled()) {
    298                 e.printStackTrace();
    299             }
    300         } finally {
    301             if (opts.getReportFile() != null && w != null) {
    302                 try {
    303                     w.close();
    304                 } catch (Exception e) {
    305                     System.out.println(e.getMessage());
    306                 }
    307             }
    308         }
    309     }
    310
    311     private static class PmdRunnable extends PMD implements Runnable {
    312         private final ExecutorService executor;
    313         private final DataSource dataSource;
    314         private final String fileName;
    315         private final boolean debugEnabled;
    316         private final String encoding;
    317         private final String rulesets;
    318
    319         public PmdRunnable(ExecutorService executor, DataSource dataSource, String fileName, SourceType sourceType,
    320                 boolean debugEnabled, String encoding, String rulesets, String excludeMarker) {
    321             this.executor = executor;
    322             this.dataSource = dataSource;
    323             this.fileName = fileName;
    324             this.debugEnabled = debugEnabled;
    325             this.encoding = encoding;
    326             this.rulesets = rulesets;
    327
    328             setJavaVersion(sourceType);
    329             setExcludeMarker(excludeMarker);
    330         }
    331
    332         public void run() {
    333             PmdThread thread = (PmdThread) Thread.currentThread();
    334
    335             RuleContext ctx = thread.getRuleContext();
    336             RuleSets rs = thread.getRuleSets(rulesets);
    337
    338             ctx.setSourceCodeFilename(fileName);
    339             if (debugEnabled) {
    340                 System.out.println("Processing " + ctx.getSourceCodeFilename());
    341             }
    342
    343             try {
    344                 InputStream stream = new BufferedInputStream(dataSource.getInputStream());
    345                 processFile(stream, encoding, rs, ctx);
    346             } catch (PMDException pmde) {
    347                 if (debugEnabled) {
    348                     pmde.getReason().printStackTrace();
    349                 }
    350                 ctx.getReport().addError(
    351                         new Report.ProcessingError(pmde.getMessage(),
    352                         fileName));
    353             } catch (Throwable t) {
    354                 // unexepected exception: log and stop executor service
    355                 if (debugEnabled) {
    356                     t.printStackTrace();
    357                 }
    358                 ctx.getReport().addError(
    359                         new Report.ProcessingError(t.getMessage(),
    360                         fileName));
    361
    362                 executor.shutdownNow();
    363             }
    364         }
    365
    366     }
    367
    368     private static class PmdThreadFactory implements ThreadFactory {
    369
    370         public PmdThreadFactory(RuleSetFactory ruleSetFactory) {
    371             this.ruleSetFactory = ruleSetFactory;
    372         }
    373
    374         private final RuleSetFactory ruleSetFactory;
    375         private final AtomicInteger counter = new AtomicInteger();
    376
    377         public Thread newThread(Runnable r) {
    378             PmdThread t = new PmdThread(counter.incrementAndGet(), r, ruleSetFactory);
    379             threadList.add(t);
    380             return t;
    381         }
    382
    383         public List threadList = Collections.synchronizedList(new LinkedList());
    384
    385     }
    386
    387     private static class PmdThread extends Thread {
    388
    389         public PmdThread(int id, Runnable r, RuleSetFactory ruleSetFactory) {
    390             super(r, "PmdThread " + id);
    391             this.id = id;
    392             context = new RuleContext();
    393             context.setReport(new Report());
    394             this.ruleSetFactory = ruleSetFactory;
    395         }
    396
    397         private int id;
    398         private RuleContext context;
    399         private RuleSets rulesets;
    400         private RuleSetFactory ruleSetFactory;
    401
    402         public RuleContext getRuleContext() {
    403             return context;
    404         }
    405
    406         public RuleSets getRuleSets(String rsList) {
    407             if (rulesets == null) {
    408                 try {
    409                     rulesets = ruleSetFactory.createRuleSets(rsList);
    410                 } catch (Exception e) {
    411                     e.printStackTrace();
    412                 }
    413             }
    414             return rulesets;
    415         }
    416
    417         public String toString() {
    418             return "PmdThread " + id;
    419         }
    420
    421     }
    422
    423     /***
    424      * Run PMD on a list of files using multiple threads.
    425      *
    426      * @throws IOException If one of the files could not be read
    427      */
    428     public static void processFiles(int threadCount, RuleSetFactory ruleSetFactory, SourceType sourceType, List files, RuleContext ctx, String rulesets,
    429             boolean debugEnabled, boolean shortNamesEnabled, String inputPath,
    430             String encoding, String excludeMarker) {
    431
    432         PmdThreadFactory factory = new PmdThreadFactory(ruleSetFactory);
    433         ExecutorService executor = Executors.newFixedThreadPool(threadCount, factory);
    434
    435         for (Iterator i = files.iterator(); i.hasNext();) {
    436             DataSource dataSource = (DataSource) i.next();
    437             String niceFileName = dataSource.getNiceFileName(shortNamesEnabled,
    438                     inputPath);
    439
    440             Runnable r = new PmdRunnable(executor, dataSource, niceFileName, sourceType, debugEnabled,
    441                                             encoding, rulesets, excludeMarker);
    442
    443             executor.execute(r);
    444         }
    445         executor.shutdown();
    446
    447         try {
    448             executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
    449         } catch (InterruptedException e) {
    450         }
    451
    452         Report mainReport = ctx.getReport();
    453         Iterator i = factory.threadList.iterator();
    454         while (i.hasNext()) {
    455             PmdThread thread = (PmdThread) i.next();
    456             Report r = thread.context.getReport();
    457             mainReport.merge(r);
    458         }
    459     }
    460
    461     /***
    462      * Run PMD on a list of files.
    463      *
    464      * @param files             the List of DataSource instances.
    465      * @param ctx               the context in which PMD is operating. This contains the Report and
    466      *                          whatnot
    467      * @param rulesets          the RuleSets
    468      * @param debugEnabled
    469      * @param shortNamesEnabled
    470      * @param inputPath
    471      * @param encoding
    472      * @throws IOException If one of the files could not be read
    473      */
    474     public void processFiles(List files, RuleContext ctx, RuleSets rulesets,
    475                              boolean debugEnabled, boolean shortNamesEnabled, String inputPath,
    476                              String encoding) throws IOException {
    477         for (Iterator i = files.iterator(); i.hasNext();) {
    478             DataSource dataSource = (DataSource) i.next();
    479
    480             String niceFileName = dataSource.getNiceFileName(shortNamesEnabled,
    481                     inputPath);
    482             ctx.setSourceCodeFilename(niceFileName);
    483             if (debugEnabled) {
    484                 System.out.println("Processing " + ctx.getSourceCodeFilename());
    485             }
    486
    487             try {
    488                 InputStream stream = new BufferedInputStream(dataSource
    489                         .getInputStream());
    490                 processFile(stream, encoding, rulesets, ctx);
    491             } catch (PMDException pmde) {
    492                 if (debugEnabled) {
    493                     pmde.getReason().printStackTrace();
    494                 }
    495                 ctx.getReport().addError(new Report.ProcessingError(pmde.getMessage(), niceFileName));
    496             }
    497         }
    498     }
    499
    500     /***
    501      * If in debug modus, print the names of the rules.
    502      *
    503      * @param debugEnabled the boolean indicating if debug is enabled
    504      * @param rulesets     the RuleSets to print
    505      */
    506     private static void printRuleNamesInDebug(boolean debugEnabled, RuleSets rulesets) {
    507         if (debugEnabled) {
    508             for (Iterator i = rulesets.getAllRules().iterator(); i.hasNext();) {
    509                 Rule r = (Rule) i.next();
    510                 System.out.println("Loaded rule " + r.getName());
    511             }
    512         }
    513     }
    514
    515     /***
    516      * Collects the given file into a list.
    517      *
    518      * @param inputFileName a file name
    519      * @param fileSelector  Filtering of wanted source files
    520      * @return the list of files collected from the <code>inputFileName</code>
    521      * @see #collect(String, SourceFileSelector)
    522      */
    523     private static List collectFilesFromOneName(String inputFileName,
    524                                                 SourceFileSelector fileSelector) {
    525         return collect(inputFileName, fileSelector);
    526     }
    527
    528     /***
    529      * Collects the files from the given comma-separated list.
    530      *
    531      * @param fileList     comma-separated list of filenames
    532      * @param fileSelector Filtering of wanted source files
    533      * @return list of files collected from the <code>fileList</code>
    534      */
    535     private static List collectFromCommaDelimitedString(String fileList,
    536                                                         SourceFileSelector fileSelector) {
    537         List files = new ArrayList();
    538         for (StringTokenizer st = new StringTokenizer(fileList, ","); st
    539                 .hasMoreTokens();) {
    540             files.addAll(collect(st.nextToken(), fileSelector));
    541         }
    542         return files;
    543     }
    544
    545     /***
    546      * Collects the files from the given <code>filename</code>.
    547      *
    548      * @param filename     the source from which to collect files
    549      * @param fileSelector Filtering of wanted source files
    550      * @return a list of files found at the given <code>filename</code>
    551      * @throws RuntimeException if <code>filename</code> is not found
    552      */
    553     private static List collect(String filename, SourceFileSelector fileSelector) {
    554         File inputFile = new File(filename);
    555         if (!inputFile.exists()) {
    556             throw new RuntimeException("File " + inputFile.getName()
    557                     + " doesn't exist");
    558         }
    559         List dataSources = new ArrayList();
    560         if (!inputFile.isDirectory()) {
    561             if (filename.endsWith(".zip") || filename.endsWith(".jar")) {
    562                 ZipFile zipFile;
    563                 try {
    564                     zipFile = new ZipFile(inputFile);
    565                     Enumeration e = zipFile.entries();
    566                     while (e.hasMoreElements()) {
    567                         ZipEntry zipEntry = (ZipEntry) e.nextElement();
    568                         if (fileSelector.isWantedFile(zipEntry.getName())) {
    569                             dataSources.add(new ZipDataSource(zipFile, zipEntry));
    570                         }
    571                     }
    572                 } catch (IOException ze) {
    573                     throw new RuntimeException("Zip file " + inputFile.getName()
    574                             + " can't be opened");
    575                 }
    576             } else {
    577                 dataSources.add(new FileDataSource(inputFile));
    578             }
    579         } else {
    580             FileFinder finder = new FileFinder();
    581             List files = finder.findFilesFrom(inputFile.getAbsolutePath(),
    582                     new SourceFileOrDirectoryFilter(fileSelector), true);
    583             for (Iterator i = files.iterator(); i.hasNext();) {
    584                 dataSources.add(new FileDataSource((File) i.next()));
    585             }
    586         }
    587         return dataSources;
    588     }
    589
    590 }
    591
    592