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