001 /**
002 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003 */
004 package net.sourceforge.pmd.lang.rule;
005
006 import java.util.ArrayList;
007 import java.util.Collections;
008 import java.util.HashMap;
009 import java.util.Iterator;
010 import java.util.List;
011 import java.util.Map;
012
013 import net.sourceforge.pmd.PropertyDescriptor;
014 import net.sourceforge.pmd.Rule;
015 import net.sourceforge.pmd.RuleContext;
016 import net.sourceforge.pmd.RulePriority;
017 import net.sourceforge.pmd.lang.Language;
018 import net.sourceforge.pmd.lang.LanguageVersion;
019 import net.sourceforge.pmd.lang.ast.Node;
020 import net.sourceforge.pmd.util.CollectionUtil;
021
022 /**
023 * Basic abstract implementation of all parser-independent methods of the Rule
024 * interface.
025 *
026 * @author pieter_van_raemdonck - Application Engineers NV/SA - www.ae.be
027 */
028 // FUTURE Implement Cloneable and clone()?
029 public abstract class AbstractRule implements Rule {
030 private Language language;
031 private LanguageVersion minimumLanguageVersion;
032 private LanguageVersion maximumLanguageVersion;
033 private boolean deprecated;
034 private String name = getClass().getName();
035 private String since;
036 private String ruleClass = getClass().getName();
037 private String ruleSetName;
038 private String message;
039 private String description;
040 private List<String> examples = new ArrayList<String>();
041 private String externalInfoUrl;
042 private RulePriority priority = RulePriority.LOW;
043 private List<PropertyDescriptor<?>> propertyDescriptors = new ArrayList<PropertyDescriptor<?>>();
044 // Map of explicitly set property values.
045 private Map<PropertyDescriptor<?>, Object> propertyValuesByDescriptor = new HashMap<PropertyDescriptor<?>, Object>();
046 private boolean usesDFA;
047 private boolean usesTypeResolution;
048 private List<String> ruleChainVisits = new ArrayList<String>();
049
050 public AbstractRule() {
051 definePropertyDescriptor(Rule.VIOLATION_SUPPRESS_REGEX_DESCRIPTOR);
052 definePropertyDescriptor(Rule.VIOLATION_SUPPRESS_XPATH_DESCRIPTOR);
053 }
054
055 /**
056 * @see Rule#getLanguage()
057 */
058 public Language getLanguage() {
059 return language;
060 }
061
062 /**
063 * @see Rule#setLanguage(Language)
064 */
065 public void setLanguage(Language language) {
066 if (this.language != null && this instanceof ImmutableLanguage && !this.language.equals(language)) {
067 throw new UnsupportedOperationException("The Language for Rule class " + this.getClass().getName()
068 + " is immutable and cannot be changed.");
069 }
070 this.language = language;
071 }
072
073 /**
074 * @see Rule#getMinimumLanguageVersion()
075 */
076 public LanguageVersion getMinimumLanguageVersion() {
077 return minimumLanguageVersion;
078 }
079
080 /**
081 * @see Rule#setMinimumLanguageVersion(LanguageVersion)
082 */
083 public void setMinimumLanguageVersion(LanguageVersion minimumLanguageVersion) {
084 this.minimumLanguageVersion = minimumLanguageVersion;
085 }
086
087 /**
088 * @see Rule#getMaximumLanguageVersion()
089 */
090 public LanguageVersion getMaximumLanguageVersion() {
091 return maximumLanguageVersion;
092 }
093
094 /**
095 * @see Rule#setMaximumLanguageVersion(LanguageVersion)
096 */
097 public void setMaximumLanguageVersion(LanguageVersion maximumLanguageVersion) {
098 this.maximumLanguageVersion = maximumLanguageVersion;
099 }
100
101 /**
102 * @see Rule#isDeprecated()
103 */
104 public boolean isDeprecated() {
105 return deprecated;
106 }
107
108 /**
109 * @see Rule#setDeprecated(boolean)
110 */
111 public void setDeprecated(boolean deprecated) {
112 this.deprecated = deprecated;
113 }
114
115 /**
116 * @see Rule#getName()
117 */
118 public String getName() {
119 return name;
120 }
121
122 /**
123 * @see Rule#setName(String)
124 */
125 public void setName(String name) {
126 this.name = name;
127 }
128
129 /**
130 * @see Rule#getSince()
131 */
132 public String getSince() {
133 return since;
134 }
135
136 /**
137 * @see Rule#setSince(String)
138 */
139 public void setSince(String since) {
140 this.since = since;
141 }
142
143 /**
144 * @see Rule#getRuleClass()
145 */
146 public String getRuleClass() {
147 return ruleClass;
148 }
149
150 /**
151 * @see Rule#setRuleClass(String)
152 */
153 public void setRuleClass(String ruleClass) {
154 this.ruleClass = ruleClass;
155 }
156
157 /**
158 * @see Rule#getRuleSetName()
159 */
160 public String getRuleSetName() {
161 return ruleSetName;
162 }
163
164 /**
165 * @see Rule#setRuleSetName(String)
166 */
167 public void setRuleSetName(String ruleSetName) {
168 this.ruleSetName = ruleSetName;
169 }
170
171 /**
172 * @see Rule#getMessage()
173 */
174 public String getMessage() {
175 return message;
176 }
177
178 /**
179 * @see Rule#setMessage(String)
180 */
181 public void setMessage(String message) {
182 this.message = message;
183 }
184
185 /**
186 * @see Rule#getDescription()
187 */
188 public String getDescription() {
189 return description;
190 }
191
192 /**
193 * @see Rule#setDescription(String)
194 */
195 public void setDescription(String description) {
196 this.description = description;
197 }
198
199 /**
200 * @see Rule#getExamples()
201 */
202 public List<String> getExamples() {
203 // TODO Needs to be externally immutable
204 return examples;
205 }
206
207 /**
208 * @see Rule#addExample(String)
209 */
210 public void addExample(String example) {
211 examples.add(example);
212 }
213
214 /**
215 * @see Rule#getExternalInfoUrl()
216 */
217 public String getExternalInfoUrl() {
218 return externalInfoUrl;
219 }
220
221 /**
222 * @see Rule#setExternalInfoUrl(String)
223 */
224 public void setExternalInfoUrl(String externalInfoUrl) {
225 this.externalInfoUrl = externalInfoUrl;
226 }
227
228 /**
229 * @see Rule#getPriority()
230 */
231 public RulePriority getPriority() {
232 return priority;
233 }
234
235 /**
236 * @see Rule#setPriority(RulePriority)
237 */
238 public void setPriority(RulePriority priority) {
239 this.priority = priority;
240 }
241
242 /**
243 * @see Rule#definePropertyDescriptor(PropertyDescriptor)
244 */
245 public void definePropertyDescriptor(PropertyDescriptor<?> propertyDescriptor) {
246 // Check to ensure the property does not already exist.
247 for (PropertyDescriptor<?> descriptor : propertyDescriptors) {
248 if (descriptor.name().equals(propertyDescriptor.name())) {
249 throw new IllegalArgumentException("There is already a PropertyDescriptor with name '"
250 + propertyDescriptor.name() + "' defined on Rule " + this.getName() + ".");
251 }
252 }
253 propertyDescriptors.add(propertyDescriptor);
254 // Sort in UI order
255 Collections.sort(propertyDescriptors);
256 }
257
258 /**
259 * @see Rule#getPropertyDescriptor(String)
260 */
261 public PropertyDescriptor<?> getPropertyDescriptor(String name) {
262 for (PropertyDescriptor<?> propertyDescriptor : propertyDescriptors) {
263 if (name.equals(propertyDescriptor.name())) {
264 return propertyDescriptor;
265 }
266 }
267 return null;
268 }
269
270 /**
271 * @see Rule#hasDescriptor(PropertyDescriptor)
272 */
273 public boolean hasDescriptor(PropertyDescriptor<?> descriptor) {
274
275 if (propertyValuesByDescriptor.isEmpty()) {
276 getPropertiesByPropertyDescriptor(); // compute it
277 }
278
279 return propertyValuesByDescriptor.containsKey(descriptor);
280 }
281
282 /**
283 * @see Rule#getPropertyDescriptors()
284 */
285 public List<PropertyDescriptor<?>> getPropertyDescriptors() {
286 return propertyDescriptors;
287 }
288
289 /**
290 * @see Rule#getProperty(PropertyDescriptor)
291 */
292 @SuppressWarnings("unchecked")
293 public <T> T getProperty(PropertyDescriptor<T> propertyDescriptor) {
294 checkValidPropertyDescriptor(propertyDescriptor);
295 T value;
296 if (propertyValuesByDescriptor.containsKey(propertyDescriptor)) {
297 value = (T) propertyValuesByDescriptor.get(propertyDescriptor);
298 } else {
299 value = propertyDescriptor.defaultValue();
300 }
301 return value;
302 }
303
304 /**
305 * @see Rule#setProperty(PropertyDescriptor, Object)
306 */
307 public <T> void setProperty(PropertyDescriptor<T> propertyDescriptor, T value) {
308 checkValidPropertyDescriptor(propertyDescriptor);
309 propertyValuesByDescriptor.put(propertyDescriptor, value);
310 }
311
312 private void checkValidPropertyDescriptor(PropertyDescriptor<?> propertyDescriptor) {
313 if (!propertyDescriptors.contains(propertyDescriptor)) {
314 throw new IllegalArgumentException("Property descriptor not defined for Rule " + this.getName() + ": "
315 + propertyDescriptor);
316 }
317 }
318
319 /**
320 * @see Rule#getPropertiesByPropertyDescriptor()
321 */
322 public Map<PropertyDescriptor<?>, Object> getPropertiesByPropertyDescriptor() {
323 if (propertyDescriptors.isEmpty()) {
324 return Collections.emptyMap();
325 }
326
327 Map<PropertyDescriptor<?>, Object> propertiesByPropertyDescriptor = new HashMap<PropertyDescriptor<?>, Object>(
328 propertyDescriptors.size());
329 // Fill with existing explicitly values
330 propertiesByPropertyDescriptor.putAll(this.propertyValuesByDescriptor);
331
332 // Add default values for anything not yet set
333 for (PropertyDescriptor<?> propertyDescriptor : this.propertyDescriptors) {
334 if (!propertiesByPropertyDescriptor.containsKey(propertyDescriptor)) {
335 propertiesByPropertyDescriptor.put(propertyDescriptor, propertyDescriptor.defaultValue());
336 }
337 }
338
339 return propertiesByPropertyDescriptor;
340 }
341
342 /**
343 * @see Rule#usesDefaultValues()
344 */
345 public boolean usesDefaultValues() {
346
347 Map<PropertyDescriptor<?>, Object> valuesByProperty = getPropertiesByPropertyDescriptor();
348 if (valuesByProperty.isEmpty()) {
349 return true;
350 }
351
352 Iterator<Map.Entry<PropertyDescriptor<?>, Object>> iter = valuesByProperty.entrySet().iterator();
353 while (iter.hasNext()) {
354 Map.Entry<PropertyDescriptor<?>, Object> entry = iter.next();
355 if (!CollectionUtil.areEqual(entry.getKey().defaultValue(), entry.getValue())) {
356 return false;
357 }
358 }
359
360 return true;
361 }
362
363 /**
364 * @see Rule#setUsesDFA()
365 */
366 public void setUsesDFA() {
367 this.usesDFA = true;
368 }
369
370 /**
371 * @see Rule#usesDFA()
372 */
373 public boolean usesDFA() {
374 return this.usesDFA;
375 }
376
377 /**
378 * @see Rule#setUsesTypeResolution()
379 */
380 public void setUsesTypeResolution() {
381 this.usesTypeResolution = true;
382 }
383
384 /**
385 * @see Rule#usesTypeResolution()
386 */
387 public boolean usesTypeResolution() {
388 return this.usesTypeResolution;
389 }
390
391 /**
392 * @see Rule#usesRuleChain()
393 */
394 public boolean usesRuleChain() {
395 return !getRuleChainVisits().isEmpty();
396 }
397
398 /**
399 * @see Rule#getRuleChainVisits()
400 */
401 public List<String> getRuleChainVisits() {
402 return ruleChainVisits;
403 }
404
405 /**
406 * @see Rule#addRuleChainVisit(Class)
407 */
408 public void addRuleChainVisit(Class<? extends Node> nodeClass) {
409 if (!nodeClass.getSimpleName().startsWith("AST")) {
410 throw new IllegalArgumentException("Node class does not start with 'AST' prefix: " + nodeClass);
411 }
412 addRuleChainVisit(nodeClass.getSimpleName().substring("AST".length()));
413 }
414
415 /**
416 * @see Rule#addRuleChainVisit(String)
417 */
418 public void addRuleChainVisit(String astNodeName) {
419 if (!ruleChainVisits.contains(astNodeName)) {
420 ruleChainVisits.add(astNodeName);
421 }
422 }
423
424 /**
425 * @see Rule#start(RuleContext)
426 */
427 public void start(RuleContext ctx) {
428 // Override as needed
429 }
430
431 /**
432 * @see Rule#end(RuleContext)
433 */
434 public void end(RuleContext ctx) {
435 // Override as needed
436 }
437
438 /**
439 * @see RuleViolationFactory#addViolation(RuleContext, Rule, Node, String, Object[])
440 */
441 public void addViolation(Object data, Node node) {
442 RuleContext ruleContext = (RuleContext) data;
443 ruleContext.getLanguageVersion().getLanguageVersionHandler().getRuleViolationFactory().addViolation(
444 ruleContext, this, node, this.getMessage(), null);
445 }
446
447 /**
448 * @see RuleViolationFactory#addViolation(RuleContext, Rule, Node, String, Object[])
449 */
450 public void addViolation(Object data, Node node, String arg) {
451 RuleContext ruleContext = (RuleContext) data;
452 ruleContext.getLanguageVersion().getLanguageVersionHandler().getRuleViolationFactory().addViolation(
453 ruleContext, this, node, this.getMessage(), new Object[] { arg });
454 }
455
456 /**
457 * @see RuleViolationFactory#addViolation(RuleContext, Rule, Node, String, Object[])
458 */
459 public void addViolation(Object data, Node node, Object[] args) {
460 RuleContext ruleContext = (RuleContext) data;
461 ruleContext.getLanguageVersion().getLanguageVersionHandler().getRuleViolationFactory().addViolation(
462 ruleContext, this, node, this.getMessage(), args);
463 }
464
465 /**
466 * @see RuleViolationFactory#addViolation(RuleContext, Rule, Node, String, Object[])
467 */
468 public void addViolationWithMessage(Object data, Node node, String message) {
469 RuleContext ruleContext = (RuleContext) data;
470 ruleContext.getLanguageVersion().getLanguageVersionHandler().getRuleViolationFactory().addViolation(
471 ruleContext, this, node, message, null);
472 }
473
474 /**
475 * @see RuleViolationFactory#addViolation(RuleContext, Rule, Node, String, Object[])
476 */
477 public void addViolationWithMessage(Object data, Node node, String message, Object[] args) {
478 RuleContext ruleContext = (RuleContext) data;
479 ruleContext.getLanguageVersion().getLanguageVersionHandler().getRuleViolationFactory().addViolation(
480 ruleContext, this, node, message, args);
481 }
482
483 /**
484 * Rules are equal if:
485 * <ol>
486 * <li>They have the same implementation class.</li>
487 * <li>They have the same name.</li>
488 * <li>They have the same priority.</li>
489 * <li>They share the same properties.</li>
490 * </ol>
491 */
492 @Override
493 public boolean equals(Object o) {
494 if (o == null) {
495 return false; // trivial
496 }
497
498 if (this == o) {
499 return true; // trivial
500 }
501
502 boolean equality = this.getClass().getName().equals(o.getClass().getName());
503
504 if (equality) {
505 Rule that = (Rule) o;
506 equality = this.getName().equals(that.getName()) && this.getPriority().equals(that.getPriority())
507 && this.getPropertiesByPropertyDescriptor().equals(that.getPropertiesByPropertyDescriptor());
508 }
509
510 return equality;
511 }
512
513 /**
514 * @see #equals(Object)
515 */
516 @Override
517 public int hashCode() {
518 Object propertyValues = this.getPropertiesByPropertyDescriptor();
519 return this.getClass().getName().hashCode() + (this.getName() != null ? this.getName().hashCode() : 0)
520 + this.getPriority().hashCode() + (propertyValues != null ? propertyValues.hashCode() : 0);
521 }
522 }
|