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<?> propertyDescriptor) throws 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 }
|