RuleReference.java
001 package net.sourceforge.pmd.lang.rule;
002 
003 import java.util.ArrayList;
004 import java.util.Arrays;
005 import java.util.Collection;
006 import java.util.HashMap;
007 import java.util.List;
008 import java.util.Map;
009 
010 import net.sourceforge.pmd.PropertyDescriptor;
011 import net.sourceforge.pmd.RulePriority;
012 import net.sourceforge.pmd.RuleSetReference;
013 import net.sourceforge.pmd.lang.Language;
014 import net.sourceforge.pmd.lang.LanguageVersion;
015 import net.sourceforge.pmd.util.StringUtil;
016 
017 /**
018  * This class represents a Rule which is a reference to Rule defined in another
019  * RuleSet. All details of the Rule are delegated to the underlying referenced
020  * Rule, but those operations which modify overridden aspects of the rule are
021  * explicitly tracked.  Modification operations which set a value to the
022  * current underlying value do not override.
023  */
024 public class RuleReference extends AbstractDelegateRule {
025     
026   private Language language;
027   private LanguageVersion minimumLanguageVersion;
028   private LanguageVersion maximumLanguageVersion;
029   private Boolean deprecated;
030   private String name;
031   private List<PropertyDescriptor<?>> propertyDescriptors;
032   private Map<PropertyDescriptor<?>, Object> propertyValues;
033   private String message;
034   private String description;
035   private List<String> examples;
036   private String externalInfoUrl;
037   private RulePriority priority;
038   private RuleSetReference ruleSetReference;
039 
040   private static final List<PropertyDescriptor<?>> EMPTY_DESCRIPTORS = new ArrayList<PropertyDescriptor<?>>(0);
041   
042   public Language getOverriddenLanguage() {
043     return language;
044   }
045 
046   @Override
047   public void setLanguage(Language language) {
048     // Only override if different than current value, or if already overridden.
049     if (!isSame(language, super.getLanguage()) || this.language != null) {
050       this.language = language;
051       super.setLanguage(language);
052     }
053   }
054 
055   public LanguageVersion getOverriddenMinimumLanguageVersion() {
056     return minimumLanguageVersion;
057   }
058 
059   @Override
060   public void setMinimumLanguageVersion(LanguageVersion minimumLanguageVersion) {
061     // Only override if different than current value, or if already overridden.
062     if (!isSame(minimumLanguageVersion, super.getMinimumLanguageVersion()) || this.minimumLanguageVersion != null) {
063       this.minimumLanguageVersion = minimumLanguageVersion;
064       super.setMinimumLanguageVersion(minimumLanguageVersion);
065     }
066   }
067 
068   public LanguageVersion getOverriddenMaximumLanguageVersion() {
069     return maximumLanguageVersion;
070   }
071 
072   @Override
073   public void setMaximumLanguageVersion(LanguageVersion maximumLanguageVersion) {
074     // Only override if different than current value, or if already overridden.
075     if (!isSame(maximumLanguageVersion, super.getMaximumLanguageVersion()) || this.maximumLanguageVersion != null) {
076       this.maximumLanguageVersion = maximumLanguageVersion;
077       super.setMaximumLanguageVersion(maximumLanguageVersion);
078     }
079   }
080 
081   public Boolean isOverriddenDeprecated() {
082     return deprecated;
083   }
084 
085   @Override
086   public boolean isDeprecated() {
087     return deprecated != null && deprecated.booleanValue();
088   }
089 
090   @Override
091   public void setDeprecated(boolean deprecated) {
092     // Deprecation does not propagate to the underlying Rule.  It is the
093     // Rule reference itself which is being deprecated.
094     this.deprecated = deprecated ? deprecated : null;
095   }
096 
097   public String getOverriddenName() {
098     return name;
099   }
100 
101   @Override
102   public void setName(String name) {
103     // Only override if different than current value, or if already overridden.
104     if (!isSame(name, super.getName()) || this.name != null) {
105       this.name = name;
106       super.setName(name);
107     }
108   }
109 
110   public String getOverriddenMessage() {
111     return message;
112   }
113 
114   @Override
115   public void setMessage(String message) {
116     // Only override if different than current value, or if already overridden.
117     if (!isSame(message, super.getMessage()) || this.message != null) {
118       this.message = message;
119       super.setMessage(message);
120     }
121   }
122 
123   public String getOverriddenDescription() {
124     return description;
125   }
126 
127   @Override
128   public void setDescription(String description) {
129     // Only override if different than current value, or if already overridden.
130     if (!isSame(description, super.getDescription()) || this.description != null) {
131       this.description = description;
132       super.setDescription(description);
133     }
134   }
135 
136   public List<String> getOverriddenExamples() {
137     return examples;
138   }
139 
140   @Override
141   public void addExample(String example) {
142     // TODO Meaningful override of examples is hard, because they are merely
143     // a list of strings.  How does one indicate override of a particular
144     // value?  Via index?  Rule.setExample(int, String)?  But the XML format
145     // does not provide a means of overriding by index, not unless you took
146     // the position in the XML file to indicate corresponding index to
147     // override.  But that means you have to override starting from index 0.
148     // This would be so much easier if examples had to have names, like
149     // properties.
150 
151     // Only override if different than current values.
152     if (!contains(super.getExamples(), example)) {
153       if (this.examples == null) {
154         this.examples = new ArrayList<String>(1);
155       }
156       // TODO Fix later. To keep example overrides from being unbounded, we're only going to keep track of the last one.
157       this.examples.clear();
158       this.examples.add(example);
159       super.addExample(example);
160     }
161   }
162 
163   public String getOverriddenExternalInfoUrl() {
164     return externalInfoUrl;
165   }
166 
167   @Override
168   public void setExternalInfoUrl(String externalInfoUrl) {
169     // Only override if different than current value, or if already overridden.
170     if (!isSame(externalInfoUrl, super.getExternalInfoUrl()) || this.externalInfoUrl != null) {
171       this.externalInfoUrl = externalInfoUrl;
172       super.setExternalInfoUrl(externalInfoUrl);
173     }
174   }
175 
176   public RulePriority getOverriddenPriority() {
177     return priority;
178   }
179 
180   @Override
181   public void setPriority(RulePriority priority) {
182     // Only override if different than current value, or if already overridden.
183     if (priority != super.getPriority() || this.priority != null) {
184       this.priority = priority;
185       super.setPriority(priority);
186     }
187   }
188   
189     public List<PropertyDescriptor<?>> getOverriddenPropertyDescriptors() {
190         
191      return propertyDescriptors == null 
192              EMPTY_DESCRIPTORS : 
193              propertyDescriptors;
194     }
195 
196     @Override
197     public void definePropertyDescriptor(PropertyDescriptor<?> propertyDescriptorthrows IllegalArgumentException {
198   // Define on the underlying Rule, where it is impossible to have two
199   // property descriptors with the same name.  Therefore, there is no need
200   // to check if the property is already overridden at this level.
201   super.definePropertyDescriptor(propertyDescriptor);
202   if (this.propertyDescriptors == null) {
203       this.propertyDescriptors = new ArrayList<PropertyDescriptor<?>>();
204   }
205   this.propertyDescriptors.add(propertyDescriptor);
206     }
207 
208     public Map<PropertyDescriptor<?>, Object> getOverriddenPropertiesByPropertyDescriptor() {
209   return propertyValues;
210     }
211 
212     @Override
213     public <T> void setProperty(PropertyDescriptor<T> propertyDescriptor, T value) {
214   // Only override if different than current value.
215   if (!isSame(super.getProperty(propertyDescriptor), value)) {
216       if (this.propertyValues == null) {
217     this.propertyValues = new HashMap<PropertyDescriptor<?>, Object>();
218       }
219       this.propertyValues.put(propertyDescriptor, value);
220       super.setProperty(propertyDescriptor, value);
221   }
222     }
223 
224   public RuleSetReference getRuleSetReference() {
225     return ruleSetReference;
226   }
227 
228   public void setRuleSetReference(RuleSetReference ruleSetReference) {
229     this.ruleSetReference = ruleSetReference;
230   }
231 
232   private static boolean isSame(String s1, String s2) {
233     return StringUtil.isSame(s1, s2, true, false, true);
234   }
235 
236   @SuppressWarnings("PMD.CompareObjectsWithEquals")
237   private static boolean isSame(Object o1, Object o2) {
238         if (o1 instanceof Object[] && o2 instanceof Object[]) {
239             return isSame((Object[])o1, (Object[])o2);
240         }
241     return o1 == o2 || (o1 != null && o2 != null && o1.equals(o2));
242   }
243   private static boolean isSame(Object[] a1, Object[] a2) {
244     return a1 == a2 || (a1 != null && a2 != null && Arrays.equals(a1, a2));
245   }
246 
247   private static boolean contains(Collection<String> collection, String s1) {
248     for (String s2 : collection) {
249       if (isSame(s1, s2)) {
250         return true;
251       }
252     }
253     return false;
254   }
255   
256   public boolean hasOverriddenProperty(PropertyDescriptor<?> descriptor) {
257     return propertyValues != null && propertyValues.containsKey(descriptor);
258   }
259   
260   public boolean usesDefaultValues() {
261       
262       if (!getRule().usesDefaultValues()) {
263           return false;
264       }
265       
266       List<PropertyDescriptor<?>> descriptors = getOverriddenPropertyDescriptors();
267       if (!descriptors.isEmpty()) {
268           return false;
269       }
270       
271       for (PropertyDescriptor<?> desc : descriptors) {
272           if (!isSame(desc.defaultValue(), getProperty(desc))) {
273               return false;
274           }
275       }
276       
277       return true;
278   }
279 }