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