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