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
26   * current 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<PropertyDescriptor<?>>(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 overridden.
61  		if (!isSame(language, super.getLanguage()) || this.language != null) {
62  			this.language = language;
63  			super.setLanguage(language);
64  		}
65  	}
66  
67  	public LanguageVersion getOverriddenMinimumLanguageVersion() {
68  		return minimumLanguageVersion;
69  	}
70  
71  	@Override
72  	public void setMinimumLanguageVersion(LanguageVersion minimumLanguageVersion) {
73  		// Only override if different than current value, or if already overridden.
74  		if (!isSame(minimumLanguageVersion, super.getMinimumLanguageVersion()) || this.minimumLanguageVersion != null) {
75  			this.minimumLanguageVersion = minimumLanguageVersion;
76  			super.setMinimumLanguageVersion(minimumLanguageVersion);
77  		}
78  	}
79  
80  	public LanguageVersion getOverriddenMaximumLanguageVersion() {
81  		return maximumLanguageVersion;
82  	}
83  
84  	@Override
85  	public void setMaximumLanguageVersion(LanguageVersion maximumLanguageVersion) {
86  		// Only override if different than current value, or if already overridden.
87  		if (!isSame(maximumLanguageVersion, super.getMaximumLanguageVersion()) || this.maximumLanguageVersion != null) {
88  			this.maximumLanguageVersion = maximumLanguageVersion;
89  			super.setMaximumLanguageVersion(maximumLanguageVersion);
90  		}
91  	}
92  
93  	public Boolean isOverriddenDeprecated() {
94  		return deprecated;
95  	}
96  
97  	@Override
98  	public boolean isDeprecated() {
99  		return deprecated != null && deprecated.booleanValue();
100 	}
101 
102 	@Override
103 	public void setDeprecated(boolean deprecated) {
104 		// Deprecation does not propagate to the underlying Rule.  It is the
105 		// Rule reference itself which is being deprecated.
106 		this.deprecated = deprecated ? deprecated : null;
107 	}
108 
109 	public String getOverriddenName() {
110 		return name;
111 	}
112 
113 	@Override
114 	public void setName(String name) {
115 		// Only override if different than current value, or if already overridden.
116 		if (!isSame(name, super.getName()) || this.name != null) {
117 			this.name = name;
118 			super.setName(name);
119 		}
120 	}
121 
122 	public String getOverriddenMessage() {
123 		return message;
124 	}
125 
126 	@Override
127 	public void setMessage(String message) {
128 		// Only override if different than current value, or if already overridden.
129 		if (!isSame(message, super.getMessage()) || this.message != null) {
130 			this.message = message;
131 			super.setMessage(message);
132 		}
133 	}
134 
135 	public String getOverriddenDescription() {
136 		return description;
137 	}
138 
139 	@Override
140 	public void setDescription(String description) {
141 		// Only override if different than current value, or if already overridden.
142 		if (!isSame(description, super.getDescription()) || this.description != null) {
143 			this.description = description;
144 			super.setDescription(description);
145 		}
146 	}
147 
148 	public List<String> getOverriddenExamples() {
149 		return examples;
150 	}
151 
152 	@Override
153 	public void addExample(String example) {
154 		// TODO Meaningful override of examples is hard, because they are merely
155 		// a list of strings.  How does one indicate override of a particular
156 		// value?  Via index?  Rule.setExample(int, String)?  But the XML format
157 		// does not provide a means of overriding by index, not unless you took
158 		// the position in the XML file to indicate corresponding index to
159 		// override.  But that means you have to override starting from index 0.
160 		// This would be so much easier if examples had to have names, like
161 		// properties.
162 
163 		// Only override if different than current values.
164 		if (!contains(super.getExamples(), example)) {
165 			if (examples == null) {
166 				examples = new ArrayList<String>(1);
167 			}
168 			// TODO Fix later. To keep example overrides from being unbounded, we're only going to keep track of the last one.
169 			examples.clear();
170 			examples.add(example);
171 			super.addExample(example);
172 		}
173 	}
174 
175 	public String getOverriddenExternalInfoUrl() {
176 		return externalInfoUrl;
177 	}
178 
179 	@Override
180 	public void setExternalInfoUrl(String externalInfoUrl) {
181 		// Only override if different than current value, or if already overridden.
182 		if (!isSame(externalInfoUrl, super.getExternalInfoUrl()) || this.externalInfoUrl != null) {
183 			this.externalInfoUrl = externalInfoUrl;
184 			super.setExternalInfoUrl(externalInfoUrl);
185 		}
186 	}
187 
188 	public RulePriority getOverriddenPriority() {
189 		return priority;
190 	}
191 
192 	@Override
193 	public void setPriority(RulePriority priority) {
194 		// Only override if different than current value, or if already overridden.
195 		if (priority != super.getPriority() || this.priority != null) {
196 			this.priority = priority;
197 			super.setPriority(priority);
198 		}
199 	}
200 
201 	public List<PropertyDescriptor<?>> getOverriddenPropertyDescriptors() {
202 
203 		return propertyDescriptors == null ? 
204 				EMPTY_DESCRIPTORS : 
205 					propertyDescriptors;
206 	}
207 
208 	@Override
209 	public void definePropertyDescriptor(PropertyDescriptor<?> propertyDescriptor) throws IllegalArgumentException {
210 		// Define on the underlying Rule, where it is impossible to have two
211 		// property descriptors with the same name.  Therefore, there is no need
212 		// to check if the property is already overridden at this level.
213 		super.definePropertyDescriptor(propertyDescriptor);
214 		if (propertyDescriptors == null) {
215 			propertyDescriptors = new ArrayList<PropertyDescriptor<?>>();
216 		}
217 		propertyDescriptors.add(propertyDescriptor);
218 	}
219 
220 	public Map<PropertyDescriptor<?>, Object> getOverriddenPropertiesByPropertyDescriptor() {
221 		return propertyValues;
222 	}
223 
224 	@Override
225 	public <T> void setProperty(PropertyDescriptor<T> propertyDescriptor, T value) {
226 		// Only override if different than current value.
227 		if (!isSame(super.getProperty(propertyDescriptor), value)) {
228 			if (propertyValues == null) {
229 				propertyValues = new HashMap<PropertyDescriptor<?>, Object>();
230 			}
231 			propertyValues.put(propertyDescriptor, value);
232 			super.setProperty(propertyDescriptor, value);
233 		}
234 	}
235 
236 	public RuleSetReference getRuleSetReference() {
237 		return ruleSetReference;
238 	}
239 
240 	public void setRuleSetReference(RuleSetReference ruleSetReference) {
241 		this.ruleSetReference = ruleSetReference;
242 	}
243 
244 	private static boolean isSame(String s1, String s2) {
245 		return StringUtil.isSame(s1, s2, true, false, true);
246 	}
247 
248 	@SuppressWarnings("PMD.CompareObjectsWithEquals")
249 	private static boolean isSame(Object o1, Object o2) {
250 		if (o1 instanceof Object[] && o2 instanceof Object[]) {
251 			return isSame((Object[])o1, (Object[])o2);
252 		}
253 		return o1 == o2 || (o1 != null && o2 != null && o1.equals(o2));
254 	}
255 	
256 	@SuppressWarnings("PMD.UnusedNullCheckInEquals") //TODO: fix UnusedNullCheckInEquals rule for Arrays
257 	private static boolean isSame(Object[] a1, Object[] a2) {
258 		return a1 == a2 || (a1 != null && a2 != null && Arrays.equals(a1, a2));
259 	}
260 
261 	private static boolean contains(Collection<String> collection, String s1) {
262 		for (String s2 : collection) {
263 			if (isSame(s1, s2)) {
264 				return true;
265 			}
266 		}
267 		return false;
268 	}
269 
270 	public boolean hasDescriptor(PropertyDescriptor<?> descriptor) {    	    	
271 		return (propertyDescriptors != null && propertyDescriptors.contains(descriptor)) || 
272 		super.hasDescriptor(descriptor);
273 	}
274 
275 	public boolean hasOverriddenProperty(PropertyDescriptor<?> descriptor) {
276 		return propertyValues != null && propertyValues.containsKey(descriptor);
277 	}
278 
279 	public boolean usesDefaultValues() {
280 
281 		List<PropertyDescriptor<?>> descriptors = getOverriddenPropertyDescriptors();
282 		if (!descriptors.isEmpty()) {
283 			return false;
284 		}
285 
286 		for (PropertyDescriptor<?> desc : descriptors) {
287 			if (!isSame(desc.defaultValue(), getProperty(desc))) {
288 				return false;
289 			}
290 		}
291 
292 		if (!getRule().usesDefaultValues()) {
293 			return false;
294 		}
295 
296 		return true;
297 	}
298 
299 	public void useDefaultValueFor(PropertyDescriptor<?> desc) {
300 
301 		// not sure if we should go all the way through to the real thing?
302 				getRule().useDefaultValueFor(desc);
303 
304 				if (propertyValues == null) return;
305 
306 				propertyValues.remove(desc);
307 
308 				if (propertyDescriptors != null) {
309 					propertyDescriptors.remove(desc);
310 				}
311 	}
312 }