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.util.ArrayList;
    8   import java.util.Arrays;
    9   import java.util.Collection;
    10  import java.util.HashSet;
    11  import java.util.Iterator;
    12  import java.util.List;
    13  import java.util.logging.Level;
    14  import java.util.logging.Logger;
    15
    16  import net.sourceforge.pmd.benchmark.Benchmark;
    17  import net.sourceforge.pmd.benchmark.Benchmarker;
    18  import net.sourceforge.pmd.lang.Language;
    19  import net.sourceforge.pmd.lang.LanguageVersion;
    20  import net.sourceforge.pmd.lang.ast.Node;
    21  import net.sourceforge.pmd.lang.rule.RuleReference;
    22  import net.sourceforge.pmd.util.CollectionUtil;
    23  import net.sourceforge.pmd.util.StringUtil;
    24  import net.sourceforge.pmd.util.filter.Filter;
    25  import net.sourceforge.pmd.util.filter.Filters;
    26
    27  /**
    28   * This class represents a collection of rules along with some optional filter
    29   * patterns that can preclude their application on specific files.
    30   *
    31   * @see Rule
    32   */
    33  //FUTURE Implement Cloneable and clone()
    34  public class RuleSet {
    35
    36      private static final Logger LOG = Logger.getLogger(RuleSet.class.getName());
    37
    38  	private List<Rule> rules = new ArrayList<Rule>();
    39  	private String fileName;
    40  	private String name = "";
    41  	private String description = "";
    42
    43  	// TODO should these not be Sets or is their order important?
    44  	private List<String> excludePatterns = new ArrayList<String>(0);
    45  	private List<String> includePatterns = new ArrayList<String>(0);
    46
    47  	private Filter<File> filter;
    48
    49  	/**
    50  	 * A convenience constructor
    51  	 *
    52  	 * @param name
    53  	 * @param theRules
    54  	 * @return
    55  	 */
    56  	public static RuleSet createFor(String name, Rule... theRules) {
    57
    58  		RuleSet rs = new RuleSet();
    59  		rs.setName(name);
    60  		for (Rule rule : theRules) {
    61  			rs.addRule(rule);
    62  		}
    63  		return rs;
    64  	}
    65
    66  	/**
    67  	 * Returns the number of rules in this ruleset
    68  	 *
    69  	 * @return an int representing the number of rules
    70  	 */
    71  	public int size() {
    72  		return rules.size();
    73  	}
    74
    75  	/**
    76  	 * Add a new rule to this ruleset. Note that this method does not check for duplicates.
    77  	 *
    78  	 * @param rule the rule to be added
    79  	 */
    80  	public void addRule(Rule rule) {
    81  		if (rule == null) {
    82  			throw new IllegalArgumentException("Missing rule");
    83  		}
    84  		rules.add(rule);
    85  	}
    86
    87  	/**
    88  	 * Adds a rule. If a rule with the same name and language already existed before in the ruleset,
    89  	 * then the new rule will replace it. This makes sure that the rule configured is overridden.
    90  	 * @param rule
    91  	 * @return <code>true</code> if the new rule replaced an existing one, otherwise <code>false</code>.
    92  	 */
    93  	public boolean addRuleReplaceIfExists(Rule rule) {
    94          if (rule == null) {
    95              throw new IllegalArgumentException("Missing rule");
    96          }
    97
    98          boolean replaced = false;
    99          for (Iterator<Rule> it = rules.iterator(); it.hasNext(); ) {
    100             Rule r = it.next();
    101             if (r.getName().equals(rule.getName()) && r.getLanguage() == rule.getLanguage()) {
    102                 it.remove();
    103                 replaced = true;
    104             }
    105         }
    106         addRule(rule);
    107         return replaced;
    108 	}
    109
    110 	/**
    111 	 * Only adds a rule to the ruleset if no rule with the same name for the same language was added
    112 	 * before, so that the existent rule configuration won't be overridden.
    113 	 * @param rule
    114 	 * @return <code>true</code> if the rule was added, <code>false</code> otherwise
    115 	 */
    116 	public boolean addRuleIfNotExists(Rule rule) {
    117         if (rule == null) {
    118             throw new IllegalArgumentException("Missing rule");
    119         }
    120
    121         boolean exists = false;
    122         for (Rule r : rules) {
    123             if (r.getName().equals(rule.getName()) && r.getLanguage() == rule.getLanguage()) {
    124                 exists = true;
    125                 break;
    126             }
    127         }
    128         if (!exists) {
    129             addRule(rule);
    130         }
    131         return !exists;
    132 	}
    133
    134 	/**
    135 	 * Add a new rule by reference to this ruleset.
    136 	 *
    137 	 * @param ruleSetFileName the ruleset which contains the rule
    138 	 * @param rule the rule to be added
    139 	 */
    140 	public void addRuleByReference(String ruleSetFileName, Rule rule) {
    141 		if (StringUtil.isEmpty(ruleSetFileName)) {
    142 			throw new RuntimeException("Adding a rule by reference is not allowed with an empty rule set file name.");
    143 		}
    144 		if (rule == null) {
    145 			throw new IllegalArgumentException("Cannot add a null rule reference to a RuleSet");
    146 		}
    147 		if (!(rule instanceof RuleReference)) {
    148 			RuleSetReference ruleSetReference = new RuleSetReference();
    149 			ruleSetReference.setRuleSetFileName(ruleSetFileName);
    150 			RuleReference ruleReference = new RuleReference();
    151 			ruleReference.setRule(rule);
    152 			ruleReference.setRuleSetReference(ruleSetReference);
    153 			rule = ruleReference;
    154 		}
    155 		rules.add(rule);
    156 	}
    157
    158 	/**
    159 	 * Returns the actual Collection of rules in this ruleset
    160 	 *
    161 	 * @return a Collection with the rules. All objects are of type {@link Rule}
    162 	 */
    163 	public Collection<Rule> getRules() {
    164 		return rules;
    165 	}
    166
    167 	/**
    168 	 * Does any Rule for the given Language use the DFA layer?
    169 	 * @param language The Language.
    170 	 * @return <code>true</code> if a Rule for the Language uses the DFA layer,
    171 	 * <code>false</code> otherwise.
    172 	 */
    173 	public boolean usesDFA(Language language) {
    174 		for (Rule r : rules) {
    175 			if (r.getLanguage().equals(language)) {
    176 				if (r.usesDFA()) {
    177 					return true;
    178 				}
    179 			}
    180 		}
    181 		return false;
    182 	}
    183
    184 	/**
    185 	 * Returns the first Rule found with the given name (case-sensitive).
    186 	 *
    187 	 * Note: Since we support multiple languages, rule names
    188 	 * are not expected to be unique within any specific
    189 	 * ruleset.
    190 	 *
    191 	 * @param ruleName the exact name of the rule to find
    192 	 * @return the rule or null if not found
    193 	 */
    194 	public Rule getRuleByName(String ruleName) {
    195
    196 		for (Rule r : rules) {
    197 			if (r.getName().equals(ruleName)) {
    198 				return r;
    199 			}
    200 		}
    201 		return null;
    202 	}
    203
    204 	/**
    205 	 * Add a whole RuleSet to this RuleSet
    206 	 *
    207 	 * @param ruleSet the RuleSet to add
    208 	 */
    209 	public void addRuleSet(RuleSet ruleSet) {
    210 		rules.addAll(rules.size(), ruleSet.getRules());
    211 	}
    212
    213    /**
    214      * Add all rules by reference from one RuleSet to this RuleSet.  The rules
    215      * can be added as individual references, or collectively as an all rule
    216      * reference.
    217      *
    218      * @param ruleSet the RuleSet to add
    219      * @param allRules
    220      */
    221     public void addRuleSetByReference(RuleSet ruleSet, boolean allRules) {
    222         addRuleSetByReference(ruleSet, allRules, (String[])null);
    223     }
    224
    225 	/**
    226 	 * Add all rules by reference from one RuleSet to this RuleSet.  The rules
    227 	 * can be added as individual references, or collectively as an all rule
    228 	 * reference.
    229 	 *
    230 	 * @param ruleSet the RuleSet to add
    231 	 * @param allRules
    232 	 * @param excludes names of the rules that should be excluded.
    233 	 */
    234 	public void addRuleSetByReference(RuleSet ruleSet, boolean allRules, String ... excludes) {
    235 		if (StringUtil.isEmpty(ruleSet.getFileName())) {
    236 			throw new RuntimeException("Adding a rule by reference is not allowed with an empty rule set file name.");
    237 		}
    238 		RuleSetReference ruleSetReference = new RuleSetReference(ruleSet.getFileName());
    239 		ruleSetReference.setAllRules(allRules);
    240 		if (excludes != null) {
    241 		    ruleSetReference.setExcludes(new HashSet<String>(Arrays.asList(excludes)));
    242 		}
    243 		for (Rule rule : ruleSet.getRules()) {
    244 			RuleReference ruleReference = new RuleReference(rule, ruleSetReference);
    245 			rules.add(ruleReference);
    246 		}
    247 	}
    248
    249 	/**
    250 	 * Check if a given source file should be checked by rules in this RuleSet.  A file
    251 	 * should not be checked if there is an <code>exclude</code> pattern which matches
    252 	 * the file, unless there is an <code>include</code> pattern which also matches
    253 	 * the file.  In other words, <code>include</code> patterns override <code>exclude</code>
    254 	 * patterns.
    255 	 *
    256 	 * @param file the source file to check
    257 	 * @return <code>true</code> if the file should be checked, <code>false</code> otherwise
    258 	 */
    259 	public boolean applies(File file) {
    260 		// Initialize filter based on patterns
    261 		if (filter == null) {
    262 			Filter<String> regexFilter = Filters.buildRegexFilterIncludeOverExclude(includePatterns, excludePatterns);
    263 			filter = Filters.toNormalizedFileFilter(regexFilter);
    264 		}
    265
    266 		return file != null ? filter.filter(file) : true;
    267 	}
    268
    269 	public void start(RuleContext ctx) {
    270 		for (Rule rule : rules) {
    271 			rule.start(ctx);
    272 		}
    273 	}
    274
    275 	public void apply(List<? extends Node> acuList, RuleContext ctx) {
    276 		long start = System.nanoTime();
    277 		for (Rule rule : rules) {
    278             try {
    279                 if (!rule.usesRuleChain() && applies(rule, ctx.getLanguageVersion())) {
    280                     rule.apply(acuList, ctx);
    281                     long end = System.nanoTime();
    282                     Benchmarker.mark(Benchmark.Rule, rule.getName(), end - start, 1);
    283                     start = end;
    284                 }
    285             } catch (ThreadDeath td) {
    286                 throw td;
    287             } catch (Throwable t) {
    288                 LOG.log(Level.WARNING, "Exception applying rule " + rule.getName() + ", continuing with next rule", t);
    289             }
    290 		}
    291 	}
    292
    293 	/**
    294 	 * Does the given Rule apply to the given LanguageVersion?  If so, the
    295 	 * Language must be the same and be between the minimum and maximums
    296 	 * versions on the Rule.
    297 	 *
    298 	 * @param rule The rule.
    299 	 * @param languageVersion The language version.
    300 	 */
    301 	public static boolean applies(Rule rule, LanguageVersion languageVersion) {
    302 		final LanguageVersion min = rule.getMinimumLanguageVersion();
    303 		final LanguageVersion max = rule.getMinimumLanguageVersion();
    304 		return rule.getLanguage().equals(languageVersion.getLanguage())
    305 		&& (min == null || min.compareTo(languageVersion) <= 0)
    306 		&& (max == null || max.compareTo(languageVersion) >= 0);
    307 	}
    308
    309 	public void end(RuleContext ctx) {
    310 		for (Rule rule : rules) {
    311 			rule.end(ctx);
    312 		}
    313 	}
    314
    315 	/**
    316 	 * @see java.lang.Object#equals(java.lang.Object)
    317 	 */
    318 	@Override
    319 	public boolean equals(Object o) {
    320 		if (!(o instanceof RuleSet)) {
    321 			return false; // Trivial
    322 		}
    323
    324 		if (this == o) {
    325 			return true; // Basic equality
    326 		}
    327
    328 		RuleSet ruleSet = (RuleSet) o;
    329 		return getName().equals(ruleSet.getName()) && getRules().equals(ruleSet.getRules());
    330 	}
    331
    332 	/**
    333 	 * @see java.lang.Object#hashCode()
    334 	 */
    335 	@Override
    336 	public int hashCode() {
    337 		return getName().hashCode() + 13 * getRules().hashCode();
    338 	}
    339
    340 	public String getFileName() {
    341 		return fileName;
    342 	}
    343
    344 	public void setFileName(String fileName) {
    345 		this.fileName = fileName;
    346 	}
    347
    348 	public String getName() {
    349 		return name;
    350 	}
    351
    352 	public void setName(String name) {
    353 		this.name = name;
    354 	}
    355
    356 	public String getDescription() {
    357 		return description;
    358 	}
    359
    360 	public void setDescription(String description) {
    361 		this.description = description;
    362 	}
    363
    364 	public List<String> getExcludePatterns() {
    365 		return excludePatterns;
    366 	}
    367
    368 	public void addExcludePattern(String aPattern) {
    369
    370 		if (excludePatterns.contains(aPattern)) return;
    371
    372 		excludePatterns.add(aPattern);
    373 		patternsChanged();
    374 	}
    375
    376 	public void addExcludePatterns(Collection<String> someExcludePatterns) {
    377
    378 		int added = CollectionUtil.addWithoutDuplicates(someExcludePatterns, excludePatterns);
    379 		if (added > 0) patternsChanged();
    380 	}
    381
    382 	public void setExcludePatterns(Collection<String> theExcludePatterns) {
    383
    384 		if (excludePatterns.equals(theExcludePatterns)) return;
    385
    386 		excludePatterns.clear();
    387 		CollectionUtil.addWithoutDuplicates(theExcludePatterns, excludePatterns);
    388 		patternsChanged();
    389 	}
    390
    391 	public List<String> getIncludePatterns() {
    392 		return includePatterns;
    393 	}
    394
    395 	public void addIncludePattern(String aPattern) {
    396
    397 		if (includePatterns.contains(aPattern)) return;
    398
    399 		includePatterns.add(aPattern);
    400 		patternsChanged();
    401 	}
    402
    403 	public void addIncludePatterns(Collection<String> someIncludePatterns) {
    404
    405 		int added = CollectionUtil.addWithoutDuplicates(someIncludePatterns, includePatterns);
    406 		if (added > 0) patternsChanged();
    407 	}
    408
    409 	public void setIncludePatterns(Collection<String> theIncludePatterns) {
    410
    411 		if (includePatterns.equals(theIncludePatterns)) return;
    412
    413 		includePatterns.clear();
    414 		CollectionUtil.addWithoutDuplicates(theIncludePatterns, includePatterns);
    415 		patternsChanged();
    416 	}
    417
    418 	private void patternsChanged() {
    419 		filter = null;	// ensure we start with one that reflects the current patterns
    420 	}
    421
    422 	/**
    423 	 * Does any Rule for the given Language use Type Resolution?
    424 	 * @param language The Language.
    425 	 * @return <code>true</code> if a Rule for the Language uses Type Resolution,
    426 	 * <code>false</code> otherwise.
    427 	 */
    428 	public boolean usesTypeResolution(Language language) {
    429 		for (Rule r : rules) {
    430 			if (r.getLanguage().equals(language)) {
    431 				if (r.usesTypeResolution()) {
    432 					return true;
    433 				}
    434 			}
    435 		}
    436 		return false;
    437 	}
    438
    439 	/**
    440 	 * Remove and collect any misconfigured rules.
    441 	 *
    442 	 * @param collector
    443 	 */
    444 	public void removeDysfunctionalRules(Collection<Rule> collector) {
    445
    446 		Iterator<Rule> iter = rules.iterator();
    447
    448 		while (iter.hasNext()) {
    449 			Rule rule = iter.next();
    450 			if (rule.dysfunctionReason() != null) {
    451 				iter.remove();
    452 				collector.add(rule);
    453 			}
    454 		}
    455 	}
    456
    457 }