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.File;
16  import java.io.FileNotFoundException;
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.io.InputStreamReader;
20  import java.io.Reader;
21  import java.io.UnsupportedEncodingException;
22  import java.util.ArrayList;
23  import java.util.Enumeration;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.StringTokenizer;
27  import java.util.zip.ZipEntry;
28  import java.util.zip.ZipFile;
29  
30  public class PMD {
31      public static final String EOL = System.getProperty("line.separator", "\n");
32      public static final String VERSION = "3.7";
33  
34      private String excludeMarker = ExcludeLines.EXCLUDE_MARKER;
35      private SourceTypeDiscoverer sourceTypeDiscoverer = new SourceTypeDiscoverer();
36      private SourceTypeHandlerBroker sourceTypeHandlerBroker = new SourceTypeHandlerBroker();
37  
38      public PMD() {
39      }
40  
41      /***
42       * @param targetJDKVersion
43       * @deprecated Use the no-args constructor and the setJavaVersion method instead
44       */
45      public PMD(TargetJDKVersion targetJDKVersion) {
46          if (targetJDKVersion instanceof TargetJDK1_3) {
47              setJavaVersion(SourceType.JAVA_13);
48          } else if (targetJDKVersion instanceof TargetJDK1_5) {
49              setJavaVersion(SourceType.JAVA_15);
50          }
51      }
52  
53      /***
54       * Processes the file read by the reader agains the rule set.
55       *
56       * @param reader   input stream reader
57       * @param ruleSets set of rules to process against the file
58       * @param ctx      context in which PMD is operating. This contains the Renderer and
59       *                 whatnot
60       * @throws PMDException if the input could not be parsed or processed
61       */
62      public void processFile(Reader reader, RuleSets ruleSets, RuleContext ctx)
63              throws PMDException {
64          SourceType sourceType = getSourceTypeOfFile(ctx.getSourceCodeFilename());
65  
66          processFile(reader, ruleSets, ctx, sourceType);
67      }
68  
69      /***
70       * Processes the file read by the reader agains the rule set.
71       *
72       * @param reader     input stream reader
73       * @param ruleSets   set of rules to process against the file
74       * @param ctx        context in which PMD is operating. This contains the Renderer and
75       *                   whatnot
76       * @param sourceType the SourceType of the source
77       * @throws PMDException if the input could not be parsed or processed
78       */
79      public void processFile(Reader reader, RuleSets ruleSets, RuleContext ctx,
80                              SourceType sourceType) throws PMDException {
81          try {
82              SourceTypeHandler sourceTypeHandler = sourceTypeHandlerBroker
83                      .getVisitorsFactoryForSourceType(sourceType);
84  
85              ExcludeLines excluder = new ExcludeLines(reader, excludeMarker);
86              ctx.excludeLines(excluder.getLinesToExclude());
87  
88              Parser parser = sourceTypeHandler.getParser();
89              Object rootNode = parser.parse(excluder.getCopyReader());
90              Thread.yield();
91  
92              // TODO - move SymbolFacade traversal inside JavaParser.CompilationUnit()
93              sourceTypeHandler.getSymbolFacade().start(rootNode);
94  
95              Language language = SourceTypeToRuleLanguageMapper.getMappedLanguage(sourceType);
96  
97              if (ruleSets.usesDFA(language)) {
98                  sourceTypeHandler.getDataFlowFacade().start(rootNode);
99              }
100 
101             List acus = new ArrayList();
102             acus.add(rootNode);
103 
104             ruleSets.apply(acus, ctx, language);
105         } catch (ParseException pe) {
106             throw new PMDException("Error while parsing "
107                     + ctx.getSourceCodeFilename(), pe);
108         } catch (Exception e) {
109             throw new PMDException("Error while processing "
110                     + ctx.getSourceCodeFilename(), e);
111         } finally {
112             try {
113                 reader.close();
114             } catch (IOException e) {
115                 throw new PMDException("Error while closing "
116                         + ctx.getSourceCodeFilename(), e);
117             }
118         }
119     }
120 
121     /***
122      * Get the SourceType of the source file with given name. This depends on the fileName
123      * extension, and the java version.
124      * <p/>
125      * For compatibility with older code that does not always pass in a correct filename,
126      * unrecognized files are assumed to be java files.
127      *
128      * @param fileName Name of the file, can be absolute, or simple.
129      * @return the SourceType
130      */
131     private SourceType getSourceTypeOfFile(String fileName) {
132         SourceType sourceType = sourceTypeDiscoverer.getSourceTypeOfFile(fileName);
133         if (sourceType == null) {
134             // For compatibility with older code that does not always pass in
135             // a correct filename.
136             sourceType = sourceTypeDiscoverer.getSourceTypeOfJavaFiles();
137         }
138         return sourceType;
139     }
140 
141     /***
142      * Processes the file read by the reader agains the rule set.
143      *
144      * @param reader  input stream reader
145      * @param ruleSet set of rules to process against the file
146      * @param ctx     context in which PMD is operating. This contains the Renderer and
147      *                whatnot
148      * @throws PMDException if the input could not be parsed or processed
149      */
150     public void processFile(Reader reader, RuleSet ruleSet, RuleContext ctx)
151             throws PMDException {
152         processFile(reader, new RuleSets(ruleSet), ctx);
153     }
154 
155     /***
156      * Processes the input stream agains a rule set using the given input encoding.
157      *
158      * @param fileContents an input stream to analyze
159      * @param encoding     input stream's encoding
160      * @param ruleSet      set of rules to process against the file
161      * @param ctx          context in which PMD is operating. This contains the Report and whatnot
162      * @throws PMDException if the input encoding is unsupported or the input stream could
163      *                      not be parsed
164      * @see #processFile(Reader, RuleSet, RuleContext)
165      */
166     public void processFile(InputStream fileContents, String encoding,
167                             RuleSet ruleSet, RuleContext ctx) throws PMDException {
168         try {
169             processFile(new InputStreamReader(fileContents, encoding), ruleSet, ctx);
170         } catch (UnsupportedEncodingException uee) {
171             throw new PMDException("Unsupported encoding exception: "
172                     + uee.getMessage());
173         }
174     }
175 
176     /***
177      * Processes the input stream agains a rule set using the given input encoding.
178      *
179      * @param fileContents an input stream to analyze
180      * @param encoding     input stream's encoding
181      * @param ruleSets     set of rules to process against the file
182      * @param ctx          context in which PMD is operating. This contains the Report and whatnot
183      * @throws PMDException if the input encoding is unsupported or the input stream could
184      *                      not be parsed
185      * @see #processFile(Reader, RuleSet, RuleContext)
186      */
187     public void processFile(InputStream fileContents, String encoding,
188                             RuleSets ruleSets, RuleContext ctx) throws PMDException {
189         try {
190             processFile(new InputStreamReader(fileContents, encoding), ruleSets, ctx);
191         } catch (UnsupportedEncodingException uee) {
192             throw new PMDException("Unsupported encoding exception: "
193                     + uee.getMessage());
194         }
195     }
196 
197     /***
198      * Processes the input stream against a rule set assuming the platform character set.
199      *
200      * @param fileContents input stream to check
201      * @param ruleSet      the set of rules to process against the source code
202      * @param ctx          the context in which PMD is operating. This contains the Report and
203      *                     whatnot
204      * @throws PMDException if the input encoding is unsupported or the input input stream
205      *                      could not be parsed
206      * @see #processFile(InputStream, String, RuleSet, RuleContext)
207      */
208     public void processFile(InputStream fileContents, RuleSet ruleSet,
209                             RuleContext ctx) throws PMDException {
210         processFile(fileContents, System.getProperty("file.encoding"), ruleSet, ctx);
211     }
212 
213     public void setExcludeMarker(String marker) {
214         this.excludeMarker = marker;
215     }
216 
217     /***
218      * Set the SourceType to be used for ".java" files.
219      *
220      * @param javaVersion the SourceType that indicates the java version
221      */
222     public void setJavaVersion(SourceType javaVersion) {
223         sourceTypeDiscoverer.setSourceTypeOfJavaFiles(javaVersion);
224     }
225 
226     public static void main(String[] args) {
227         CommandLineOptions opts = new CommandLineOptions(args);
228 
229         SourceFileSelector fileSelector = new SourceFileSelector();
230 
231         fileSelector.setSelectJavaFiles(opts.isCheckJavaFiles());
232         fileSelector.setSelectJspFiles(opts.isCheckJspFiles());
233 
234         List files;
235         if (opts.containsCommaSeparatedFileList()) {
236             files = collectFromCommaDelimitedString(opts.getInputPath(),
237                     fileSelector);
238         } else {
239             files = collectFilesFromOneName(opts.getInputPath(), fileSelector);
240         }
241 
242         PMD pmd = new PMD();
243         if (opts.getTargetJDK().equals("1.3")) {
244             if (opts.debugEnabled())
245                 System.out.println("In JDK 1.3 mode");
246             pmd.setJavaVersion(SourceType.JAVA_13);
247         } else if (opts.getTargetJDK().equals("1.5")) {
248             if (opts.debugEnabled())
249                 System.out.println("In JDK 1.5 mode");
250             pmd.setJavaVersion(SourceType.JAVA_15);
251         } else {
252             if (opts.debugEnabled())
253                 System.out.println("In JDK 1.4 mode");
254             pmd.setJavaVersion(SourceType.JAVA_14);
255         }
256         pmd.setExcludeMarker(opts.getExcludeMarker());
257 
258         RuleContext ctx = new RuleContext();
259         Report report = new Report();
260         ctx.setReport(report);
261         report.start();
262 
263         try {
264             RuleSetFactory ruleSetFactory = new RuleSetFactory();
265             RuleSets rulesets = ruleSetFactory.createRuleSets(opts.getRulesets());
266             printRuleNamesInDebug(opts.debugEnabled(), rulesets);
267 
268             pmd.processFiles(files, ctx, rulesets, opts.debugEnabled(), opts
269                     .shortNamesEnabled(), opts.getInputPath(), opts.getEncoding());
270         } catch (FileNotFoundException fnfe) {
271             System.out.println(opts.usage());
272             fnfe.printStackTrace();
273         } catch (RuleSetNotFoundException rsnfe) {
274             System.out.println(opts.usage());
275             rsnfe.printStackTrace();
276         } catch (IOException ioe) {
277             System.out.println(opts.usage());
278             ioe.printStackTrace();
279         }
280         report.end();
281 
282         try {
283             Renderer r = opts.createRenderer();
284             System.out.println(r.render(ctx.getReport()));
285         } catch (Exception e) {
286             System.out.println(e.getMessage());
287             System.out.println(opts.usage());
288             if (opts.debugEnabled()) {
289                 e.printStackTrace();
290             }
291         }
292     }
293 
294     /***
295      * Run PMD on a list of files.
296      *
297      * @param files             the List of DataSource instances.
298      * @param ctx               the context in which PMD is operating. This contains the Report and
299      *                          whatnot
300      * @param rulesets          the RuleSets
301      * @param debugEnabled
302      * @param shortNamesEnabled
303      * @param inputPath
304      * @param encoding
305      * @throws IOException If one of the files could not be read
306      */
307     public void processFiles(List files, RuleContext ctx, RuleSets rulesets,
308                              boolean debugEnabled, boolean shortNamesEnabled, String inputPath,
309                              String encoding) throws IOException {
310         for (Iterator i = files.iterator(); i.hasNext();) {
311             DataSource dataSource = (DataSource) i.next();
312 
313             String niceFileName = dataSource.getNiceFileName(shortNamesEnabled,
314                     inputPath);
315             ctx.setSourceCodeFilename(niceFileName);
316             if (debugEnabled) {
317                 System.out.println("Processing " + ctx.getSourceCodeFilename());
318             }
319 
320             try {
321                 InputStream stream = new BufferedInputStream(dataSource
322                         .getInputStream());
323                 processFile(stream, encoding, rulesets, ctx);
324             } catch (PMDException pmde) {
325                 if (debugEnabled) {
326                     pmde.getReason().printStackTrace();
327                 }
328                 ctx.getReport().addError(new Report.ProcessingError(pmde.getMessage(), niceFileName));
329             }
330         }
331     }
332 
333     /***
334      * If in debug modus, print the names of the rules.
335      *
336      * @param debugEnabled the boolean indicating if debug is enabled
337      * @param rulesets     the RuleSets to print
338      */
339     private static void printRuleNamesInDebug(boolean debugEnabled, RuleSets rulesets) {
340         if (debugEnabled) {
341             for (Iterator i = rulesets.getAllRules().iterator(); i.hasNext();) {
342                 Rule r = (Rule) i.next();
343                 System.out.println("Loaded rule " + r.getName());
344             }
345         }
346     }
347 
348     /***
349      * Collects the given file into a list.
350      *
351      * @param inputFileName a file name
352      * @param fileSelector  Filtering of wanted source files
353      * @return the list of files collected from the <code>inputFileName</code>
354      * @see #collect(String)
355      */
356     private static List collectFilesFromOneName(String inputFileName,
357                                                 SourceFileSelector fileSelector) {
358         return collect(inputFileName, fileSelector);
359     }
360 
361     /***
362      * Collects the files from the given comma-separated list.
363      *
364      * @param fileList     comma-separated list of filenames
365      * @param fileSelector Filtering of wanted source files
366      * @return list of files collected from the <code>fileList</code>
367      */
368     private static List collectFromCommaDelimitedString(String fileList,
369                                                         SourceFileSelector fileSelector) {
370         List files = new ArrayList();
371         for (StringTokenizer st = new StringTokenizer(fileList, ","); st
372                 .hasMoreTokens();) {
373             files.addAll(collect(st.nextToken(), fileSelector));
374         }
375         return files;
376     }
377 
378     /***
379      * Collects the files from the given <code>filename</code>.
380      *
381      * @param filename     the source from which to collect files
382      * @param fileSelector Filtering of wanted source files
383      * @return a list of files found at the given <code>filename</code>
384      * @throws RuntimeException if <code>filename</code> is not found
385      */
386     private static List collect(String filename, SourceFileSelector fileSelector) {
387         File inputFile = new File(filename);
388         if (!inputFile.exists()) {
389             throw new RuntimeException("File " + inputFile.getName()
390                     + " doesn't exist");
391         }
392         List dataSources = new ArrayList();
393         if (!inputFile.isDirectory()) {
394             if (filename.endsWith(".zip") || filename.endsWith(".jar")) {
395                 ZipFile zipFile;
396                 try {
397                     zipFile = new ZipFile(inputFile);
398                     Enumeration e = zipFile.entries();
399                     while (e.hasMoreElements()) {
400                         ZipEntry zipEntry = (ZipEntry) e.nextElement();
401                         if (fileSelector.isWantedFile(zipEntry.getName())) {
402                             dataSources.add(new ZipDataSource(zipFile, zipEntry));
403                         }
404                     }
405                 } catch (IOException ze) {
406                     throw new RuntimeException("Zip file " + inputFile.getName()
407                             + " can't be opened");
408                 }
409             } else {
410                 dataSources.add(new FileDataSource(inputFile));
411             }
412         } else {
413             FileFinder finder = new FileFinder();
414             List files = finder.findFilesFrom(inputFile.getAbsolutePath(),
415                     new SourceFileOrDirectoryFilter(fileSelector), true);
416             for (Iterator i = files.iterator(); i.hasNext();) {
417                 dataSources.add(new FileDataSource((File) i.next()));
418             }
419         }
420         return dataSources;
421     }
422 
423 }