View Javadoc

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