AbstractRule.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd.lang.rule;
005 
006 import java.util.ArrayList;
007 import java.util.Collections;
008 import java.util.HashMap;
009 import java.util.Iterator;
010 import java.util.List;
011 import java.util.Map;
012 
013 import net.sourceforge.pmd.PropertyDescriptor;
014 import net.sourceforge.pmd.Rule;
015 import net.sourceforge.pmd.RuleContext;
016 import net.sourceforge.pmd.RulePriority;
017 import net.sourceforge.pmd.lang.Language;
018 import net.sourceforge.pmd.lang.LanguageVersion;
019 import net.sourceforge.pmd.lang.ast.Node;
020 import net.sourceforge.pmd.util.CollectionUtil;
021 
022 /**
023  * Basic abstract implementation of all parser-independent methods of the Rule
024  * interface.
025  *
026  @author pieter_van_raemdonck - Application Engineers NV/SA - www.ae.be
027  */
028 // FUTURE Implement Cloneable and clone()?
029 public abstract class AbstractRule implements Rule {
030     private Language language;
031     private LanguageVersion minimumLanguageVersion;
032     private LanguageVersion maximumLanguageVersion;
033     private boolean deprecated;
034     private String name = getClass().getName();
035     private String since;
036     private String ruleClass = getClass().getName();
037     private String ruleSetName;
038     private String message;
039     private String description;
040     private List<String> examples = new ArrayList<String>();
041     private String externalInfoUrl;
042     private RulePriority priority = RulePriority.LOW;
043     private List<PropertyDescriptor<?>> propertyDescriptors = new ArrayList<PropertyDescriptor<?>>();
044     // Map of explicitly set property values.
045     private Map<PropertyDescriptor<?>, Object> propertyValuesByDescriptor = new HashMap<PropertyDescriptor<?>, Object>();
046     private boolean usesDFA;
047     private boolean usesTypeResolution;
048     private List<String> ruleChainVisits = new ArrayList<String>();
049 
050     public AbstractRule() {
051   definePropertyDescriptor(Rule.VIOLATION_SUPPRESS_REGEX_DESCRIPTOR);
052   definePropertyDescriptor(Rule.VIOLATION_SUPPRESS_XPATH_DESCRIPTOR);
053     }
054 
055     /**
056      @see Rule#getLanguage()
057      */
058     public Language getLanguage() {
059   return language;
060     }
061 
062     /**
063      @see Rule#setLanguage(Language)
064      */
065     public void setLanguage(Language language) {
066   if (this.language != null && this instanceof ImmutableLanguage && !this.language.equals(language)) {
067       throw new UnsupportedOperationException("The Language for Rule class " this.getClass().getName()
068         " is immutable and cannot be changed.");
069   }
070   this.language = language;
071     }
072 
073     /**
074      @see Rule#getMinimumLanguageVersion()
075      */
076     public LanguageVersion getMinimumLanguageVersion() {
077   return minimumLanguageVersion;
078     }
079 
080     /**
081      @see Rule#setMinimumLanguageVersion(LanguageVersion)
082      */
083     public void setMinimumLanguageVersion(LanguageVersion minimumLanguageVersion) {
084   this.minimumLanguageVersion = minimumLanguageVersion;
085     }
086 
087     /**
088      @see Rule#getMaximumLanguageVersion()
089      */
090     public LanguageVersion getMaximumLanguageVersion() {
091   return maximumLanguageVersion;
092     }
093 
094     /**
095      @see Rule#setMaximumLanguageVersion(LanguageVersion)
096      */
097     public void setMaximumLanguageVersion(LanguageVersion maximumLanguageVersion) {
098   this.maximumLanguageVersion = maximumLanguageVersion;
099     }
100 
101     /**
102      @see Rule#isDeprecated()
103      */
104     public boolean isDeprecated() {
105   return deprecated;
106     }
107 
108     /**
109      @see Rule#setDeprecated(boolean)
110      */
111     public void setDeprecated(boolean deprecated) {
112   this.deprecated = deprecated;
113     }
114 
115     /**
116      @see Rule#getName()
117      */
118     public String getName() {
119   return name;
120     }
121 
122     /**
123      @see Rule#setName(String)
124      */
125     public void setName(String name) {
126   this.name = name;
127     }
128 
129     /**
130      @see Rule#getSince()
131      */
132     public String getSince() {
133   return since;
134     }
135 
136     /**
137      @see Rule#setSince(String)
138      */
139     public void setSince(String since) {
140   this.since = since;
141     }
142 
143     /**
144      @see Rule#getRuleClass()
145      */
146     public String getRuleClass() {
147   return ruleClass;
148     }
149 
150     /**
151      @see Rule#setRuleClass(String)
152      */
153     public void setRuleClass(String ruleClass) {
154   this.ruleClass = ruleClass;
155     }
156 
157     /**
158      @see Rule#getRuleSetName()
159      */
160     public String getRuleSetName() {
161   return ruleSetName;
162     }
163 
164     /**
165      @see Rule#setRuleSetName(String)
166      */
167     public void setRuleSetName(String ruleSetName) {
168   this.ruleSetName = ruleSetName;
169     }
170 
171     /**
172      @see Rule#getMessage()
173      */
174     public String getMessage() {
175   return message;
176     }
177 
178     /**
179      @see Rule#setMessage(String)
180      */
181     public void setMessage(String message) {
182   this.message = message;
183     }
184 
185     /**
186      @see Rule#getDescription()
187      */
188     public String getDescription() {
189   return description;
190     }
191 
192     /**
193      @see Rule#setDescription(String)
194      */
195     public void setDescription(String description) {
196   this.description = description;
197     }
198 
199     /**
200      @see Rule#getExamples()
201      */
202     public List<String> getExamples() {
203   // TODO Needs to be externally immutable
204   return examples;
205     }
206 
207     /**
208      @see Rule#addExample(String)
209      */
210     public void addExample(String example) {
211   examples.add(example);
212     }
213 
214     /**
215      @see Rule#getExternalInfoUrl()
216      */
217     public String getExternalInfoUrl() {
218   return externalInfoUrl;
219     }
220 
221     /**
222      @see Rule#setExternalInfoUrl(String)
223      */
224     public void setExternalInfoUrl(String externalInfoUrl) {
225   this.externalInfoUrl = externalInfoUrl;
226     }
227 
228     /**
229      @see Rule#getPriority()
230      */
231     public RulePriority getPriority() {
232   return priority;
233     }
234 
235     /**
236      @see Rule#setPriority(RulePriority)
237      */
238     public void setPriority(RulePriority priority) {
239   this.priority = priority;
240     }
241 
242     /**
243      @see Rule#definePropertyDescriptor(PropertyDescriptor)
244      */
245     public void definePropertyDescriptor(PropertyDescriptor<?> propertyDescriptor) {
246   // Check to ensure the property does not already exist.
247   for (PropertyDescriptor<?> descriptor : propertyDescriptors) {
248       if (descriptor.name().equals(propertyDescriptor.name())) {
249     throw new IllegalArgumentException("There is already a PropertyDescriptor with name '"
250       + propertyDescriptor.name() "' defined on Rule " this.getName() ".");
251       }
252   }
253   propertyDescriptors.add(propertyDescriptor);
254   // Sort in UI order
255   Collections.sort(propertyDescriptors);
256     }
257 
258     /**
259      @see Rule#getPropertyDescriptor(String)
260      */
261     public PropertyDescriptor<?> getPropertyDescriptor(String name) {
262   for (PropertyDescriptor<?> propertyDescriptor : propertyDescriptors) {
263       if (name.equals(propertyDescriptor.name())) {
264     return propertyDescriptor;
265       }
266   }
267   return null;
268     }
269 
270     /**
271      @see Rule#hasDescriptor(PropertyDescriptor)
272      */
273     public boolean hasDescriptor(PropertyDescriptor<?> descriptor) {
274 
275   if (propertyValuesByDescriptor.isEmpty()) {
276       getPropertiesByPropertyDescriptor()// compute it
277   }
278 
279   return propertyValuesByDescriptor.containsKey(descriptor);
280     }
281 
282     /**
283      @see Rule#getPropertyDescriptors()
284      */
285     public List<PropertyDescriptor<?>> getPropertyDescriptors() {
286   return propertyDescriptors;
287     }
288 
289     /**
290      @see Rule#getProperty(PropertyDescriptor)
291      */
292     @SuppressWarnings("unchecked")
293     public <T> T getProperty(PropertyDescriptor<T> propertyDescriptor) {
294   checkValidPropertyDescriptor(propertyDescriptor);
295   T value;
296   if (propertyValuesByDescriptor.containsKey(propertyDescriptor)) {
297       value = (TpropertyValuesByDescriptor.get(propertyDescriptor);
298   else {
299       value = propertyDescriptor.defaultValue();
300   }
301   return value;
302     }
303 
304     /**
305      @see Rule#setProperty(PropertyDescriptor, Object)
306      */
307     public <T> void setProperty(PropertyDescriptor<T> propertyDescriptor, T value) {
308   checkValidPropertyDescriptor(propertyDescriptor);
309   propertyValuesByDescriptor.put(propertyDescriptor, value);
310     }
311 
312     private void checkValidPropertyDescriptor(PropertyDescriptor<?> propertyDescriptor) {
313   if (!propertyDescriptors.contains(propertyDescriptor)) {
314       throw new IllegalArgumentException("Property descriptor not defined for Rule " this.getName() ": "
315         + propertyDescriptor);
316   }
317     }
318 
319     /**
320      @see Rule#getPropertiesByPropertyDescriptor()
321      */
322     public Map<PropertyDescriptor<?>, Object> getPropertiesByPropertyDescriptor() {
323   if (propertyDescriptors.isEmpty()) {
324       return Collections.emptyMap();
325   }
326 
327   Map<PropertyDescriptor<?>, Object> propertiesByPropertyDescriptor = new HashMap<PropertyDescriptor<?>, Object>(
328     propertyDescriptors.size());
329   // Fill with existing explicitly values
330   propertiesByPropertyDescriptor.putAll(this.propertyValuesByDescriptor);
331 
332   // Add default values for anything not yet set
333   for (PropertyDescriptor<?> propertyDescriptor : this.propertyDescriptors) {
334       if (!propertiesByPropertyDescriptor.containsKey(propertyDescriptor)) {
335     propertiesByPropertyDescriptor.put(propertyDescriptor, propertyDescriptor.defaultValue());
336       }
337   }
338 
339   return propertiesByPropertyDescriptor;
340     }
341 
342     /**
343      @see Rule#usesDefaultValues()
344      */
345     public boolean usesDefaultValues() {
346 
347   Map<PropertyDescriptor<?>, Object> valuesByProperty = getPropertiesByPropertyDescriptor();
348   if (valuesByProperty.isEmpty()) {
349       return true;
350   }
351 
352   Iterator<Map.Entry<PropertyDescriptor<?>, Object>> iter = valuesByProperty.entrySet().iterator();
353   while (iter.hasNext()) {
354       Map.Entry<PropertyDescriptor<?>, Object> entry = iter.next();
355       if (!CollectionUtil.areEqual(entry.getKey().defaultValue(), entry.getValue())) {
356     return false;
357       }
358   }
359 
360   return true;
361     }
362 
363     /**
364      @see Rule#setUsesDFA()
365      */
366     public void setUsesDFA() {
367   this.usesDFA = true;
368     }
369 
370     /**
371      @see Rule#usesDFA()
372      */
373     public boolean usesDFA() {
374   return this.usesDFA;
375     }
376 
377     /**
378      @see Rule#setUsesTypeResolution()
379      */
380     public void setUsesTypeResolution() {
381   this.usesTypeResolution = true;
382     }
383 
384     /**
385      @see Rule#usesTypeResolution()
386      */
387     public boolean usesTypeResolution() {
388   return this.usesTypeResolution;
389     }
390 
391     /**
392      @see Rule#usesRuleChain()
393      */
394     public boolean usesRuleChain() {
395   return !getRuleChainVisits().isEmpty();
396     }
397 
398     /**
399      @see Rule#getRuleChainVisits()
400      */
401     public List<String> getRuleChainVisits() {
402   return ruleChainVisits;
403     }
404 
405     /**
406      @see Rule#addRuleChainVisit(Class)
407      */
408     public void addRuleChainVisit(Class<? extends Node> nodeClass) {
409   if (!nodeClass.getSimpleName().startsWith("AST")) {
410       throw new IllegalArgumentException("Node class does not start with 'AST' prefix: " + nodeClass);
411   }
412   addRuleChainVisit(nodeClass.getSimpleName().substring("AST".length()));
413     }
414 
415     /**
416      @see Rule#addRuleChainVisit(String)
417      */
418     public void addRuleChainVisit(String astNodeName) {
419   if (!ruleChainVisits.contains(astNodeName)) {
420       ruleChainVisits.add(astNodeName);
421   }
422     }
423 
424     /**
425      @see Rule#start(RuleContext)
426      */
427     public void start(RuleContext ctx) {
428   // Override as needed
429     }
430 
431     /**
432      @see Rule#end(RuleContext)
433      */
434     public void end(RuleContext ctx) {
435   // Override as needed
436     }
437 
438     /**
439      @see RuleViolationFactory#addViolation(RuleContext, Rule, Node, String, Object[])
440      */
441     public void addViolation(Object data, Node node) {
442   RuleContext ruleContext = (RuleContextdata;
443   ruleContext.getLanguageVersion().getLanguageVersionHandler().getRuleViolationFactory().addViolation(
444     ruleContext, this, node, this.getMessage()null);
445     }
446 
447     /**
448      @see RuleViolationFactory#addViolation(RuleContext, Rule, Node, String, Object[])
449      */
450     public void addViolation(Object data, Node node, String arg) {
451   RuleContext ruleContext = (RuleContextdata;
452   ruleContext.getLanguageVersion().getLanguageVersionHandler().getRuleViolationFactory().addViolation(
453     ruleContext, this, node, this.getMessage()new Object[] { arg });
454     }
455 
456     /**
457      @see RuleViolationFactory#addViolation(RuleContext, Rule, Node, String, Object[])
458      */
459     public void addViolation(Object data, Node node, Object[] args) {
460   RuleContext ruleContext = (RuleContextdata;
461   ruleContext.getLanguageVersion().getLanguageVersionHandler().getRuleViolationFactory().addViolation(
462     ruleContext, this, node, this.getMessage(), args);
463     }
464 
465     /**
466      @see RuleViolationFactory#addViolation(RuleContext, Rule, Node, String, Object[])
467      */
468     public void addViolationWithMessage(Object data, Node node, String message) {
469   RuleContext ruleContext = (RuleContextdata;
470   ruleContext.getLanguageVersion().getLanguageVersionHandler().getRuleViolationFactory().addViolation(
471     ruleContext, this, node, message, null);
472     }
473 
474     /**
475      @see RuleViolationFactory#addViolation(RuleContext, Rule, Node, String, Object[])
476      */
477     public void addViolationWithMessage(Object data, Node node, String message, Object[] args) {
478   RuleContext ruleContext = (RuleContextdata;
479   ruleContext.getLanguageVersion().getLanguageVersionHandler().getRuleViolationFactory().addViolation(
480     ruleContext, this, node, message, args);
481     }
482 
483     /**
484      * Rules are equal if:
485      <ol>
486      <li>They have the same implementation class.</li>
487      <li>They have the same name.</li>
488      <li>They have the same priority.</li>
489      <li>They share the same properties.</li>
490      </ol>
491      */
492     @Override
493     public boolean equals(Object o) {
494   if (o == null) {
495       return false// trivial
496   }
497 
498   if (this == o) {
499       return true// trivial
500   }
501 
502   boolean equality = this.getClass().getName().equals(o.getClass().getName());
503 
504   if (equality) {
505       Rule that = (Ruleo;
506       equality = this.getName().equals(that.getName()) && this.getPriority().equals(that.getPriority())
507         && this.getPropertiesByPropertyDescriptor().equals(that.getPropertiesByPropertyDescriptor());
508   }
509 
510   return equality;
511     }
512 
513     /**
514      @see #equals(Object)
515      */
516     @Override
517     public int hashCode() {
518   Object propertyValues = this.getPropertiesByPropertyDescriptor();
519   return this.getClass().getName().hashCode() (this.getName() != null this.getName().hashCode() 0)
520     this.getPriority().hashCode() (propertyValues != null ? propertyValues.hashCode() 0);
521     }
522 }