View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.rule;
5   
6   import java.util.ArrayList;
7   import java.util.Arrays;
8   import java.util.Collection;
9   import java.util.HashMap;
10  import java.util.List;
11  import java.util.Map;
12  
13  import net.sourceforge.pmd.PropertyDescriptor;
14  import net.sourceforge.pmd.Rule;
15  import net.sourceforge.pmd.RulePriority;
16  import net.sourceforge.pmd.RuleSetReference;
17  import net.sourceforge.pmd.lang.Language;
18  import net.sourceforge.pmd.lang.LanguageVersion;
19  import net.sourceforge.pmd.util.StringUtil;
20  
21  /**
22   * This class represents a Rule which is a reference to Rule defined in another
23   * RuleSet. All details of the Rule are delegated to the underlying referenced
24   * Rule, but those operations which modify overridden aspects of the rule are
25   * explicitly tracked. Modification operations which set a value to the current
26   * underlying value do not override.
27   */
28  public class RuleReference extends AbstractDelegateRule {
29  
30      private Language language;
31      private LanguageVersion minimumLanguageVersion;
32      private LanguageVersion maximumLanguageVersion;
33      private Boolean deprecated;
34      private String name;
35      private List<PropertyDescriptor<?>> propertyDescriptors;
36      private Map<PropertyDescriptor<?>, Object> propertyValues;
37      private String message;
38      private String description;
39      private List<String> examples;
40      private String externalInfoUrl;
41      private RulePriority priority;
42      private RuleSetReference ruleSetReference;
43  
44      private static final List<PropertyDescriptor<?>> EMPTY_DESCRIPTORS = new ArrayList<>(0);
45  
46      public Language getOverriddenLanguage() {
47          return language;
48      }
49  
50      public RuleReference() {
51      }
52  
53      public RuleReference(Rule theRule, RuleSetReference theRuleSetReference) {
54          setRule(theRule);
55          ruleSetReference = theRuleSetReference;
56      }
57  
58      @Override
59      public void setLanguage(Language language) {
60          // Only override if different than current value, or if already
61          // overridden.
62          if (!isSame(language, super.getLanguage()) || this.language != null) {
63              this.language = language;
64              super.setLanguage(language);
65          }
66      }
67  
68      public LanguageVersion getOverriddenMinimumLanguageVersion() {
69          return minimumLanguageVersion;
70      }
71  
72      @Override
73      public void setMinimumLanguageVersion(LanguageVersion minimumLanguageVersion) {
74          // Only override if different than current value, or if already
75          // overridden.
76          if (!isSame(minimumLanguageVersion, super.getMinimumLanguageVersion()) || this.minimumLanguageVersion != null) {
77              this.minimumLanguageVersion = minimumLanguageVersion;
78              super.setMinimumLanguageVersion(minimumLanguageVersion);
79          }
80      }
81  
82      public LanguageVersion getOverriddenMaximumLanguageVersion() {
83          return maximumLanguageVersion;
84      }
85  
86      @Override
87      public void setMaximumLanguageVersion(LanguageVersion maximumLanguageVersion) {
88          // Only override if different than current value, or if already
89          // overridden.
90          if (!isSame(maximumLanguageVersion, super.getMaximumLanguageVersion()) || this.maximumLanguageVersion != null) {
91              this.maximumLanguageVersion = maximumLanguageVersion;
92              super.setMaximumLanguageVersion(maximumLanguageVersion);
93          }
94      }
95  
96      public Boolean isOverriddenDeprecated() {
97          return deprecated;
98      }
99  
100     @Override
101     public boolean isDeprecated() {
102         return deprecated != null && deprecated.booleanValue();
103     }
104 
105     @Override
106     public void setDeprecated(boolean deprecated) {
107         // Deprecation does not propagate to the underlying Rule. It is the
108         // Rule reference itself which is being deprecated.
109         this.deprecated = deprecated ? deprecated : null;
110     }
111 
112     public String getOverriddenName() {
113         return name;
114     }
115 
116     public String getOriginalName() {
117         return super.getName();
118     }
119 
120     @Override
121     public void setName(String name) {
122         // Only override if different than current value, or if already
123         // overridden.
124         if (!isSame(name, super.getName()) || this.name != null) {
125             this.name = name;
126         }
127     }
128 
129     @Override
130     public String getName() {
131         if (this.name != null) {
132             return this.name;
133         }
134         return super.getName();
135     }
136 
137     public String getOverriddenMessage() {
138         return message;
139     }
140 
141     @Override
142     public void setMessage(String message) {
143         // Only override if different than current value, or if already
144         // overridden.
145         if (!isSame(message, super.getMessage()) || this.message != null) {
146             this.message = message;
147             super.setMessage(message);
148         }
149     }
150 
151     public String getOverriddenDescription() {
152         return description;
153     }
154 
155     @Override
156     public void setDescription(String description) {
157         // Only override if different than current value, or if already
158         // overridden.
159         if (!isSame(description, super.getDescription()) || this.description != null) {
160             this.description = description;
161             super.setDescription(description);
162         }
163     }
164 
165     public List<String> getOverriddenExamples() {
166         return examples;
167     }
168 
169     @Override
170     public void addExample(String example) {
171         // TODO Meaningful override of examples is hard, because they are merely
172         // a list of strings. How does one indicate override of a particular
173         // value? Via index? Rule.setExample(int, String)? But the XML format
174         // does not provide a means of overriding by index, not unless you took
175         // the position in the XML file to indicate corresponding index to
176         // override. But that means you have to override starting from index 0.
177         // This would be so much easier if examples had to have names, like
178         // properties.
179 
180         // Only override if different than current values.
181         if (!contains(super.getExamples(), example)) {
182             if (examples == null) {
183                 examples = new ArrayList<>(1);
184             }
185             // TODO Fix later. To keep example overrides from being unbounded,
186             // we're only going to keep track of the last one.
187             examples.clear();
188             examples.add(example);
189             super.addExample(example);
190         }
191     }
192 
193     public String getOverriddenExternalInfoUrl() {
194         return externalInfoUrl;
195     }
196 
197     @Override
198     public void setExternalInfoUrl(String externalInfoUrl) {
199         // Only override if different than current value, or if already
200         // overridden.
201         if (!isSame(externalInfoUrl, super.getExternalInfoUrl()) || this.externalInfoUrl != null) {
202             this.externalInfoUrl = externalInfoUrl;
203             super.setExternalInfoUrl(externalInfoUrl);
204         }
205     }
206 
207     public RulePriority getOverriddenPriority() {
208         return priority;
209     }
210 
211     @Override
212     public void setPriority(RulePriority priority) {
213         // Only override if different than current value, or if already
214         // overridden.
215         if (priority != super.getPriority() || this.priority != null) {
216             this.priority = priority;
217             super.setPriority(priority);
218         }
219     }
220 
221     public List<PropertyDescriptor<?>> getOverriddenPropertyDescriptors() {
222 
223         return propertyDescriptors == null ? EMPTY_DESCRIPTORS : propertyDescriptors;
224     }
225 
226     @Override
227     public void definePropertyDescriptor(PropertyDescriptor<?> propertyDescriptor) throws IllegalArgumentException {
228         // Define on the underlying Rule, where it is impossible to have two
229         // property descriptors with the same name. Therefore, there is no need
230         // to check if the property is already overridden at this level.
231         super.definePropertyDescriptor(propertyDescriptor);
232         if (propertyDescriptors == null) {
233             propertyDescriptors = new ArrayList<>();
234         }
235         propertyDescriptors.add(propertyDescriptor);
236     }
237 
238     public Map<PropertyDescriptor<?>, Object> getOverriddenPropertiesByPropertyDescriptor() {
239         return propertyValues;
240     }
241 
242     @Override
243     public <T> void setProperty(PropertyDescriptor<T> propertyDescriptor, T value) {
244         // Only override if different than current value.
245         if (!isSame(super.getProperty(propertyDescriptor), value)) {
246             if (propertyValues == null) {
247                 propertyValues = new HashMap<>();
248             }
249             propertyValues.put(propertyDescriptor, value);
250             super.setProperty(propertyDescriptor, value);
251         }
252     }
253 
254     public RuleSetReference getRuleSetReference() {
255         return ruleSetReference;
256     }
257 
258     public void setRuleSetReference(RuleSetReference ruleSetReference) {
259         this.ruleSetReference = ruleSetReference;
260     }
261 
262     private static boolean isSame(String s1, String s2) {
263         return StringUtil.isSame(s1, s2, true, false, true);
264     }
265 
266     @SuppressWarnings("PMD.CompareObjectsWithEquals")
267     private static boolean isSame(Object o1, Object o2) {
268         if (o1 instanceof Object[] && o2 instanceof Object[]) {
269             return isSame((Object[]) o1, (Object[]) o2);
270         }
271         return o1 == o2 || o1 != null && o2 != null && o1.equals(o2);
272     }
273 
274     @SuppressWarnings("PMD.UnusedNullCheckInEquals")
275     // TODO: fix UnusedNullCheckInEquals rule for Arrays
276     private static boolean isSame(Object[] a1, Object[] a2) {
277         return a1 == a2 || a1 != null && a2 != null && Arrays.equals(a1, a2);
278     }
279 
280     private static boolean contains(Collection<String> collection, String s1) {
281         for (String s2 : collection) {
282             if (isSame(s1, s2)) {
283                 return true;
284             }
285         }
286         return false;
287     }
288 
289     public boolean hasDescriptor(PropertyDescriptor<?> descriptor) {
290         return propertyDescriptors != null && propertyDescriptors.contains(descriptor)
291                 || super.hasDescriptor(descriptor);
292     }
293 
294     public boolean hasOverriddenProperty(PropertyDescriptor<?> descriptor) {
295         return propertyValues != null && propertyValues.containsKey(descriptor);
296     }
297 
298     public boolean usesDefaultValues() {
299 
300         List<PropertyDescriptor<?>> descriptors = getOverriddenPropertyDescriptors();
301         if (!descriptors.isEmpty()) {
302             return false;
303         }
304 
305         for (PropertyDescriptor<?> desc : descriptors) {
306             if (!isSame(desc.defaultValue(), getProperty(desc))) {
307                 return false;
308             }
309         }
310 
311         if (!getRule().usesDefaultValues()) {
312             return false;
313         }
314 
315         return true;
316     }
317 
318     public void useDefaultValueFor(PropertyDescriptor<?> desc) {
319 
320         // not sure if we should go all the way through to the real thing?
321         getRule().useDefaultValueFor(desc);
322 
323         if (propertyValues == null) {
324             return;
325         }
326 
327         propertyValues.remove(desc);
328 
329         if (propertyDescriptors != null) {
330             propertyDescriptors.remove(desc);
331         }
332     }
333 }