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 }