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
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
135
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 }