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
133
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
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