PMD xref

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 java.io.File;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.net.URISyntaxException;
10 import java.sql.SQLException;
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.Comparator;
15 import java.util.HashSet;
16 import java.util.LinkedList;
17 import java.util.List;
18 import java.util.Properties;
19 import java.util.Set;
20 import java.util.logging.Handler;
21 import java.util.logging.Level;
22 import java.util.logging.Logger;
23 
24 import net.sourceforge.pmd.benchmark.Benchmark;
25 import net.sourceforge.pmd.benchmark.Benchmarker;
26 import net.sourceforge.pmd.benchmark.TextReport;
27 import net.sourceforge.pmd.cli.PMDCommandLineInterface;
28 import net.sourceforge.pmd.cli.PMDParameters;
29 import net.sourceforge.pmd.lang.Language;
30 import net.sourceforge.pmd.lang.LanguageFilenameFilter;
31 import net.sourceforge.pmd.lang.LanguageVersion;
32 import net.sourceforge.pmd.lang.LanguageVersionDiscoverer;
33 import net.sourceforge.pmd.lang.LanguageVersionHandler;
34 import net.sourceforge.pmd.lang.Parser;
35 import net.sourceforge.pmd.lang.ParserOptions;
36 import net.sourceforge.pmd.processor.MonoThreadProcessor;
37 import net.sourceforge.pmd.processor.MultiThreadProcessor;
38 import net.sourceforge.pmd.renderers.Renderer;
39 import net.sourceforge.pmd.util.FileUtil;
40 import net.sourceforge.pmd.util.IOUtil;
41 import net.sourceforge.pmd.util.SystemUtils;
42 import net.sourceforge.pmd.util.database.DBMSMetadata;
43 import net.sourceforge.pmd.util.database.DBURI;
44 import net.sourceforge.pmd.util.database.SourceObject;
45 import net.sourceforge.pmd.util.datasource.DataSource;
46 import net.sourceforge.pmd.util.datasource.ReaderDataSource;
47 import net.sourceforge.pmd.util.log.ConsoleLogHandler;
48 import net.sourceforge.pmd.util.log.ScopedLogHandlersManager;
49 
50 /**
51  * This is the main class for interacting with PMD. The primary flow of all Rule
52  * process is controlled via interactions with this class. A command line
53  * interface is supported, as well as a programmatic API for integrating PMD
54  * with other software such as IDEs and Ant.
55  */
56 public class PMD {
57 
58 private static final Logger LOG = Logger.getLogger(PMD.class.getName());
59 
60 /** The line delimiter used by PMD in outputs. Usually the platform specific line separator. */
61 public static final String EOL = System.getProperty("line.separator", "\n");
62 
63 /** The default suppress marker string. */
64 public static final String SUPPRESS_MARKER = "NOPMD";
65 
66 /**
67  * Parses the given string as a database uri and returns a list of datasources.
68  * @param uriString the URI to parse
69  * @return list of data sources
70  * @throws PMDException if the URI couldn't be parsed
71  * @see DBURI
72  */
73 public static List<DataSource> getURIDataSources(String uriString) throws PMDException {
74 List<DataSource> dataSources = new ArrayList<DataSource>();
75 
76 try {
77 DBURI dbUri = new DBURI(uriString);
78 DBMSMetadata dbmsMetadata = new DBMSMetadata(dbUri);
79 LOG.log(Level.FINE, "DBMSMetadata retrieved");
80 List<SourceObject> sourceObjectList = dbmsMetadata.getSourceObjectList();
81 LOG.log(Level.FINE, "Located {0} database source objects", sourceObjectList.size());
82 for (SourceObject sourceObject : sourceObjectList) {
83 String falseFilePath = sourceObject.getPseudoFileName();
84 LOG.log(Level.FINEST, "Adding database source object {0}", falseFilePath);
85 
86 try {
87 dataSources.add(new ReaderDataSource(dbmsMetadata.getSourceCode(sourceObject), falseFilePath));
88 } catch (SQLException ex) {
89 LOG.log(Level.WARNING, "Cannot get SourceCode for " + falseFilePath + " - skipping ...", ex);
90 }
91 }
92 } catch (URISyntaxException e) {
93 throw new PMDException("Cannot get DataSources from DBURI - \"" + uriString + "\"", e);
94 } catch (SQLException e) {
95 throw new PMDException("Cannot get DataSources from DBURI, couldn't access the database - \"" + uriString
96 + "\"", e);
97 } catch (ClassNotFoundException e) {
98 throw new PMDException("Cannot get DataSources from DBURI, probably missing database jdbc driver - \""
99 + uriString + "\"", e);
100 } catch (Exception e) {
101 throw new PMDException("Encountered unexpected problem with URI \""
102 + uriString + "\"", e);
103 }
104 return dataSources;
105 }
106 
107 /** Contains the configuration with which this PMD instance has been created. */
108 protected final PMDConfiguration configuration;
109 
110 private final SourceCodeProcessor rulesetsFileProcessor;
111 
112 /**
113  * Helper method to get a configured parser for the requested language. The parser is
114  * configured based on the given {@link PMDConfiguration}.
115  * @param languageVersion the requested language
116  * @param configuration the given configuration
117  * @return the pre-configured parser
118  */
119 public static Parser parserFor(LanguageVersion languageVersion, PMDConfiguration configuration) {
120 
121 // TODO Handle Rules having different parser options.
122 LanguageVersionHandler languageVersionHandler = languageVersion.getLanguageVersionHandler();
123 ParserOptions options = languageVersionHandler.getDefaultParserOptions();
124 if (configuration != null)
125 options.setSuppressMarker(configuration.getSuppressMarker());
126 return languageVersionHandler.getParser(options);
127 }
128 
129 /**
130  * Create a report, filter out any defective rules, and keep a record of
131  * them.
132  * 
133  * @param rs the rules
134  * @param ctx the rule context
135  * @param fileName the filename of the source file, which should appear in the report
136  * @return the Report
137  */
138 public static Report setupReport(RuleSets rs, RuleContext ctx, String fileName) {
139 
140 Set<Rule> brokenRules = removeBrokenRules(rs);
141 Report report = Report.createReport(ctx, fileName);
142 
143 for (Rule rule : brokenRules) {
144 report.addConfigError(new Report.RuleConfigurationError(rule, rule.dysfunctionReason()));
145 }
146 
147 return report;
148 }
149 
150 /**
151  * Remove and return the misconfigured rules from the rulesets and log them
152  * for good measure.
153  * 
154  * @param ruleSets
155  * RuleSets
156  * @return Set<Rule>
157  */
158 private static Set<Rule> removeBrokenRules(RuleSets ruleSets) {
159 
160 Set<Rule> brokenRules = new HashSet<Rule>();
161 ruleSets.removeDysfunctionalRules(brokenRules);
162 
163 for (Rule rule : brokenRules) {
164 LOG.log(Level.WARNING,
165 "Removed misconfigured rule: " + rule.getName() + " cause: " + rule.dysfunctionReason());
166 }
167 
168 return brokenRules;
169 }
170 
171 /**
172  * Create a PMD instance using a default Configuration. Changes to the
173  * configuration may be required.
174  */
175 public PMD() {
176 this(new PMDConfiguration());
177 }
178 
179 /**
180  * Create a PMD instance using the specified Configuration.
181  * 
182  * @param configuration
183  * The runtime Configuration of PMD to use.
184  */
185 public PMD(PMDConfiguration configuration) {
186 this.configuration = configuration;
187 this.rulesetsFileProcessor = new SourceCodeProcessor(configuration);
188 }
189 
190 /**
191  * Get the runtime configuration. The configuration can be modified to
192  * affect how PMD behaves.
193  * 
194  * @return The configuration.
195  * @see PMDConfiguration
196  */
197 public PMDConfiguration getConfiguration() {
198 return configuration;
199 }
200 
201 /**
202  * Gets the source code processor.
203  * @return SourceCodeProcessor
204  */
205 public SourceCodeProcessor getSourceCodeProcessor() {
206 return rulesetsFileProcessor;
207 }
208 
209 /**
210  * This method is the main entry point for command line usage.
211  * 
212  * @param configuration the configure to use
213  */
214 public static void doPMD(PMDConfiguration configuration) {
215 
216 // Load the RuleSets
217 long startLoadRules = System.nanoTime();
218 RuleSetFactory ruleSetFactory = RulesetsFactoryUtils.getRulesetFactory(configuration);
219 
220 RuleSets ruleSets = RulesetsFactoryUtils.getRuleSets(configuration.getRuleSets(), ruleSetFactory,
221 startLoadRules);
222 if (ruleSets == null)
223 return;
224 
225 Set<Language> languages = getApplicableLanguages(configuration, ruleSets);
226 List<DataSource> files = getApplicableFiles(configuration, languages);
227 
228 long reportStart = System.nanoTime();
229 try {
230 Renderer renderer = configuration.createRenderer();
231 List<Renderer> renderers = new LinkedList<Renderer>();
232 renderers.add(renderer);
233 
234 renderer.setWriter(IOUtil.createWriter(configuration.getReportFile()));
235 renderer.start();
236 
237 Benchmarker.mark(Benchmark.Reporting, System.nanoTime() - reportStart, 0);
238 
239 RuleContext ctx = new RuleContext();
240 
241 processFiles(configuration, ruleSetFactory, files, ctx, renderers);
242 
243 reportStart = System.nanoTime();
244 renderer.end();
245 renderer.flush();
246 } catch (Exception e) {
247 String message = e.getMessage();
248 if (message != null) {
249 LOG.severe(message);
250 } else {
251 LOG.log(Level.SEVERE, "Exception during processing", e);
252 }
253 LOG.log(Level.FINE, "Exception during processing", e);
254 LOG.info(PMDCommandLineInterface.buildUsageText());
255 } finally {
256 Benchmarker.mark(Benchmark.Reporting, System.nanoTime() - reportStart, 0);
257 }
258 }
259 
260 /**
261  * Creates a new rule context, initialized with a new, empty report.
262  *
263  * @param sourceCodeFilename the source code filename
264  * @param sourceCodeFile the source code file
265  * @return the rule context
266  */
267 public static RuleContext newRuleContext(String sourceCodeFilename, File sourceCodeFile) {
268 
269 RuleContext context = new RuleContext();
270 context.setSourceCodeFile(sourceCodeFile);
271 context.setSourceCodeFilename(sourceCodeFilename);
272 context.setReport(new Report());
273 return context;
274 }
275 
276 /**
277  * A callback that would be implemented by IDEs keeping track of PMD's
278  * progress as it evaluates a set of files.
279  * 
280  * @author Brian Remedios
281  */
282 public interface ProgressMonitor {
283 /**
284  * A status update reporting on current progress. Implementers will
285  * return true if it is to continue, false otherwise.
286  * 
287  * @param total total number of files to be analyzed
288  * @param totalDone number of files, that have been done analyzing.
289  * @return <code>true</code> if the execution of PMD should continue, <code>false</code> if the execution
290  * should be cancelled/terminated.
291  */
292 boolean status(int total, int totalDone);
293 }
294 
295 /**
296  * An entry point that would typically be used by IDEs intent on providing
297  * ongoing feedback and the ability to terminate it at will.
298  * 
299  * @param configuration the PMD configuration to use
300  * @param ruleSetFactory ruleset factory
301  * @param files the files to analyze
302  * @param ctx the rule context to use for the execution
303  * @param monitor PMD informs about the progress through this progress monitor. It provides also
304  * the ability to terminate/cancel the execution.
305  */
306 public static void processFiles(PMDConfiguration configuration, RuleSetFactory ruleSetFactory,
307 Collection<File> files, RuleContext ctx, ProgressMonitor monitor) {
308 
309 // TODO
310 // call the main processFiles with just the new monitor and a single
311 // logRenderer
312 }
313 
314 /**
315  * Run PMD on a list of files using multiple threads - if more than one is
316  * available
317  * 
318  * @param configuration
319  * Configuration
320  * @param ruleSetFactory
321  * RuleSetFactory
322  * @param files
323  * List<DataSource>
324  * @param ctx
325  * RuleContext
326  * @param renderers
327  * List<Renderer>
328  */
329 public static void processFiles(final PMDConfiguration configuration, final RuleSetFactory ruleSetFactory,
330 final List<DataSource> files, final RuleContext ctx, final List<Renderer> renderers) {
331 
332 sortFiles(configuration, files);
333 
334 /*
335  * Check if multithreaded support is available. ExecutorService can also
336  * be disabled if threadCount is not positive, e.g. using the
337  * "-threads 0" command line option.
338  */
339 if (SystemUtils.MT_SUPPORTED && configuration.getThreads() > 0) {
340 new MultiThreadProcessor(configuration).processFiles(ruleSetFactory, files, ctx, renderers);
341 } else {
342 new MonoThreadProcessor(configuration).processFiles(ruleSetFactory, files, ctx, renderers);
343 }
344 }
345 
346 private static void sortFiles(final PMDConfiguration configuration, final List<DataSource> files) {
347 if (configuration.isStressTest()) {
348 // randomize processing order
349 Collections.shuffle(files);
350 } else {
351 final boolean useShortNames = configuration.isReportShortNames();
352 final String inputPaths = configuration.getInputPaths();
353 Collections.sort(files, new Comparator<DataSource>() {
354 public int compare(DataSource left, DataSource right) {
355 String leftString = left.getNiceFileName(useShortNames, inputPaths);
356 String rightString = right.getNiceFileName(useShortNames, inputPaths);
357 return leftString.compareTo(rightString);
358 }
359 });
360 }
361 }
362 
363 /**
364  * Determines all the files, that should be analyzed by PMD.
365  * @param configuration contains either the file path or the DB URI, from where to load the files
366  * @param languages used to filter by file extension
367  * @return List<DataSource> of files
368  */
369 public static List<DataSource> getApplicableFiles(PMDConfiguration configuration, Set<Language> languages) {
370 long startFiles = System.nanoTime();
371 LanguageFilenameFilter fileSelector = new LanguageFilenameFilter(languages);
372 List<DataSource> files = new ArrayList<DataSource>();
373 
374 if (null != configuration.getInputPaths()) {
375 files.addAll(FileUtil.collectFiles(configuration.getInputPaths(), fileSelector));
376 }
377 
378 if (null != configuration.getInputUri()) {
379 String uriString = configuration.getInputUri();
380 try {
381 List<DataSource> dataSources = getURIDataSources(uriString);
382 
383 files.addAll(dataSources);
384 } catch (PMDException ex) {
385 LOG.log(Level.SEVERE, "Problem with Input URI", ex);
386 throw new RuntimeException("Problem with DBURI: " + uriString, ex);
387 }
388 }
389 long endFiles = System.nanoTime();
390 Benchmarker.mark(Benchmark.CollectFiles, endFiles - startFiles, 0);
391 return files;
392 }
393 
394 private static Set<Language> getApplicableLanguages(PMDConfiguration configuration, RuleSets ruleSets) {
395 Set<Language> languages = new HashSet<Language>();
396 LanguageVersionDiscoverer discoverer = configuration.getLanguageVersionDiscoverer();
397 
398 for (Rule rule : ruleSets.getAllRules()) {
399 Language language = rule.getLanguage();
400 if (languages.contains(language))
401 continue;
402 LanguageVersion version = discoverer.getDefaultLanguageVersion(language);
403 if (RuleSet.applies(rule, version)) {
404 languages.add(language);
405 LOG.fine("Using " + language.getShortName() + " version: " + version.getShortName());
406 }
407 }
408 return languages;
409 }
410 
411 /**
412  * Entry to invoke PMD as command line tool
413  * 
414  * @param args command line arguments
415  */
416 public static void main(String[] args) {
417 PMDCommandLineInterface.run(args);
418 }
419 
420 /**
421  * Parses the command line arguments and executes PMD.
422  * @param args command line arguments
423  * @return the exit code, where <code>0</code> means successful execution.
424  */
425 public static int run(String[] args) {
426 int status = 0;
427 long start = System.nanoTime();
428 final PMDParameters params = PMDCommandLineInterface.extractParameters(new PMDParameters(), args, "pmd");
429 final PMDConfiguration configuration = PMDParameters.transformParametersIntoConfiguration(params);
430 
431 final Level logLevel = params.isDebug() ? Level.FINER : Level.INFO;
432 final Handler logHandler = new ConsoleLogHandler();
433 final ScopedLogHandlersManager logHandlerManager = new ScopedLogHandlersManager(logLevel, logHandler);
434 final Level oldLogLevel = LOG.getLevel();
435 LOG.setLevel(logLevel); // Need to do this, since the static logger has
436 // already been initialized at this point
437 try {
438 PMD.doPMD(configuration);
439 } catch (Exception e) {
440 PMDCommandLineInterface.buildUsageText();
441 System.out.println(e.getMessage());
442 status = PMDCommandLineInterface.ERROR_STATUS;
443 } finally {
444 logHandlerManager.close();
445 LOG.setLevel(oldLogLevel);
446 if (params.isBenchmark()) {
447 long end = System.nanoTime();
448 Benchmarker.mark(Benchmark.TotalPMD, end - start, 0);
449 
450 TextReport report = new TextReport(); // TODO get specified
451 // report format from
452 // config
453 report.generate(Benchmarker.values(), System.err);
454 }
455 }
456 return status;
457 }
458 
459 /**
460  * Constant that contains always the current version of PMD.
461  */
462 public static final String VERSION;
463 /**
464  * Determines the version from maven's generated pom.properties file.
465  */
466 static {
467 String pmdVersion = null;
468 InputStream stream = PMD.class.getResourceAsStream("/META-INF/maven/net.sourceforge.pmd/pmd/pom.properties");
469 if (stream != null) {
470 try {
471 Properties properties = new Properties();
472 properties.load(stream);
473 pmdVersion = properties.getProperty("version");
474 } catch (IOException e) {
475 LOG.log(Level.FINE, "Couldn't determine version of PMD", e);
476 }
477 }
478 if (pmdVersion == null) {
479 pmdVersion = "unknown";
480 }
481 VERSION = pmdVersion;
482 }
483 }

AltStyle によって変換されたページ (->オリジナル) /