View Javadoc

1   /***
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd;
5   
6   import java.text.MessageFormat;
7   import java.util.Collections;
8   import java.util.HashMap;
9   import java.util.Iterator;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Properties;
13  
14  import net.sourceforge.pmd.ast.ASTClassOrInterfaceDeclaration;
15  import net.sourceforge.pmd.ast.ASTCompilationUnit;
16  import net.sourceforge.pmd.ast.ASTImportDeclaration;
17  import net.sourceforge.pmd.ast.JavaParserVisitorAdapter;
18  import net.sourceforge.pmd.ast.Node;
19  import net.sourceforge.pmd.ast.SimpleNode;
20  
21  public abstract class AbstractRule extends JavaParserVisitorAdapter implements Rule {
22  
23      protected String name = getClass().getName();
24      protected Properties properties = new Properties();		// TODO - remove when ready
25      protected String message;
26      protected String description;
27      protected String example;
28      protected String ruleSetName;
29      protected boolean include;
30      protected boolean usesDFA;
31      protected boolean usesTypeResolution;
32      protected int priority = LOWEST_PRIORITY;
33      protected String externalInfoUrl;
34  
35      private static final boolean inOldPropertyMode = true;	// temporary flag during conversion
36      
37  	protected static Map asFixedMap(PropertyDescriptor[] descriptors) {
38  		
39  		Map descsById = new HashMap(descriptors.length);
40  		
41  		for (int i=0; i<descriptors.length; i++) {
42  			descsById.put(descriptors[i].name(), descriptors[i]);
43  		}
44  		return Collections.unmodifiableMap(descsById);
45  	}	
46  	
47  	protected static Map asFixedMap(PropertyDescriptor descriptor) {
48  		return asFixedMap(new PropertyDescriptor[] {descriptor});
49  	}
50      
51      public String getRuleSetName() {
52          return ruleSetName;
53      }
54  
55      public void setRuleSetName(String ruleSetName) {
56          this.ruleSetName = ruleSetName;
57      }
58  
59      public String getDescription() {
60          return description;
61      }
62  
63      public void setDescription(String description) {
64          this.description = description;
65      }
66  
67      public String getExample() {
68          return example;
69      }
70  
71      public void setExample(String example) {
72          this.example = example;
73      }
74  
75      /***
76       * @deprecated - property values will be guaranteed available via default values
77       */
78      public boolean hasProperty(String name) {
79      	
80          return inOldPropertyMode ?	// TODO -remove 
81          	properties.containsKey(name) :
82          	propertiesByName().containsKey(name);
83      }
84      
85      /***
86       * @deprecated 
87       */
88      public void addProperty(String name, String value) {
89          properties.setProperty(name, value);
90      }
91  
92      /***
93       * @deprecated 
94       */
95      public void addProperties(Properties properties) {
96          this.properties.putAll(properties);
97      }
98      
99      public double[] getDoubleProperties(PropertyDescriptor descriptor) {
100     	
101         Number[] values = (Number[])getProperties(descriptor);
102         
103         double[] doubles = new double[values.length];
104         for (int i=0; i<doubles.length; i++) doubles[i] = values[i].doubleValue();
105         return doubles;
106     }
107     
108     /***
109      * @deprecated - use getDoubleProperty(PropertyDescriptor) instead
110      */
111     public double getDoubleProperty(String name) {
112     	
113     	return Double.parseDouble(properties.getProperty(name));
114     }
115 
116     public double getDoubleProperty(PropertyDescriptor descriptor) {
117     	
118     	return ((Number)getProperty(descriptor)).doubleValue();
119     }
120     
121     public int[] getIntProperties(PropertyDescriptor descriptor) {
122     	
123         Number[] values = (Number[])getProperties(descriptor);
124         
125         int[] ints = new int[values.length];
126         for (int i=0; i<ints.length; i++) ints[i] = values[i].intValue();
127         return ints;
128     }
129     
130     /***
131      * @deprecated - use getIntProperty(PropertyDescriptor) instead
132      */
133     public int getIntProperty(String name) {
134     	
135     	return Integer.parseInt(properties.getProperty(name));
136     }
137 
138     public int getIntProperty(PropertyDescriptor descriptor) {
139     	
140     	return ((Number)getProperty(descriptor)).intValue();
141     }
142     
143     public Class[] getTypeProperties(PropertyDescriptor descriptor) {
144     	
145         return (Class[])getProperties(descriptor);
146     }
147     
148     public Class getTypeProperty(PropertyDescriptor descriptor) {
149     	
150     	return (Class)getProperty(descriptor);
151     }
152     
153     public boolean[] getBooleanProperties(PropertyDescriptor descriptor) {
154     	
155         Boolean[] values = (Boolean[])getProperties(descriptor);
156         
157         boolean[] bools = new boolean[values.length];
158         for (int i=0; i<bools.length; i++) bools[i] = values[i].booleanValue();
159         return bools;
160     }
161     
162     public boolean getBooleanProperty(PropertyDescriptor descriptor) {
163     	
164     	return ((Boolean)getProperty(descriptor)).booleanValue();  
165     }
166     
167     /***
168      * @deprecated - use getBooleanProperty(PropertyDescriptor) instead
169      */
170     public boolean getBooleanProperty(String name) {
171     	
172     	return Boolean.valueOf(properties.getProperty(name)).booleanValue();        
173     }
174     
175     /***
176      * @deprecated - use setProperty(PropertyDescriptor, Object) instead
177      * 
178      * @param name
179      * @param flag
180      */
181     public void setBooleanProperty(String name, boolean flag) {
182     	
183     	properties.setProperty(name, Boolean.toString(flag));
184     }
185     
186     public String[] getStringProperties(PropertyDescriptor descriptor) {
187     	
188         return (String[])getProperties(descriptor);
189     }
190     
191 
192     /***
193      * @deprecated - use getStringProperty(PropertyDescriptor) instead
194      * 
195      */
196     public String getStringProperty(String name) {    	    	
197     	return properties.getProperty(name);
198     }
199     
200     public String getStringProperty(PropertyDescriptor descriptor) {
201     	return (String)getProperty(descriptor);
202     }
203     
204     private Object getProperty(PropertyDescriptor descriptor) {
205     	    	
206     	if (descriptor.maxValueCount() > 1) propertyGetError(descriptor, true);
207     	
208     	String rawValue = properties.getProperty(descriptor.name());
209     	
210         return rawValue == null || rawValue.length() == 0 ?
211         	descriptor.defaultValue() :
212         	descriptor.valueFrom(rawValue);
213     }
214     
215     public void setProperty(PropertyDescriptor descriptor, Object value) {
216     	    	
217     	if (descriptor.maxValueCount() > 1) propertySetError(descriptor, true);
218     	
219     	properties.setProperty(descriptor.name(), descriptor.asDelimitedString(value));
220     }
221     
222     private Object[] getProperties(PropertyDescriptor descriptor) {
223     	    	
224     	if (descriptor.maxValueCount() == 1) propertyGetError(descriptor, false);
225     	
226     	String rawValue = properties.getProperty(descriptor.name());
227     	
228         return rawValue == null || rawValue.length() == 0 ?
229            	(Object[])descriptor.defaultValue() :
230            	(Object[])descriptor.valueFrom(rawValue);
231     }
232     
233     public void setProperties(PropertyDescriptor descriptor, Object[] values) {
234     	    	
235     	if (descriptor.maxValueCount() == 1) propertySetError(descriptor, false);
236     	
237     	properties.setProperty(descriptor.name(), descriptor.asDelimitedString(values));
238     }
239     
240     private void propertyGetError(PropertyDescriptor descriptor, boolean requestedSingleValue) {
241     	
242     	if (requestedSingleValue) {
243     		throw new RuntimeException("Cannot retrieve a single value from a multi-value property field");
244     		}
245     	throw new RuntimeException("Cannot retrieve multiple values from a single-value property field");
246     }
247     
248     private void propertySetError(PropertyDescriptor descriptor, boolean setSingleValue) {
249     	
250     	if (setSingleValue) {
251     		throw new RuntimeException("Cannot set a single value within a multi-value property field");
252     		}
253     	throw new RuntimeException("Cannot set multiple values within a single-value property field");
254     }
255     
256     public String getName() {
257         return name;
258     }
259 
260     public void setName(String name) {
261         this.name = name;
262     }
263 
264     public String getMessage() {
265         return message;
266     }
267 
268     public void setMessage(String message) {
269         this.message = message;
270     }
271 
272     public String getExternalInfoUrl() {
273         return externalInfoUrl;
274     }
275 
276     public void setExternalInfoUrl(String url) {
277         this.externalInfoUrl = url;
278     }
279 
280     /***
281      * Test if rules are equals. Rules are equals if
282      * 1. they have the same implementation class
283      * 2. they have the same name
284      * 3. they have the same priority
285      * 4. they share the same properties/values
286      */
287     public boolean equals(Object o) {
288         if (o == null) {
289             return false; // trivial
290         }
291 
292         if (this == o) {
293             return true;  // trivial
294         }
295 
296         Rule rule = null;
297         boolean equality = this.getClass().getName().equals(o.getClass().getName());
298 
299         if (equality) {
300             rule = (Rule) o;
301             equality = this.getName().equals(rule.getName())
302                     && this.getPriority() == rule.getPriority()
303                     && this.getProperties().equals(rule.getProperties());
304         }
305 
306         return equality;
307     }
308 
309     /***
310      * Return a hash code to conform to equality. Try with a string.
311      */
312     public int hashCode() {
313         String s = getClass().getName() + getName() + getPriority() + getProperties().toString();
314         return s.hashCode();
315     }
316 
317     public void apply(List acus, RuleContext ctx) {
318         visitAll(acus, ctx);
319     }
320 
321     /***
322      * @deprecated - retrieve by name using get<type>Property or get<type>Properties
323      */
324     public Properties getProperties() {
325         return properties;
326     }
327 
328     public boolean include() {
329         return include;
330     }
331 
332     public void setInclude(boolean include) {
333         this.include = include;
334     }
335 
336     public int getPriority() {
337         return priority;
338     }
339 
340     public String getPriorityName() {
341         return PRIORITIES[getPriority() - 1];
342     }
343 
344     public void setPriority(int priority) {
345         this.priority = priority;
346     }
347 
348     public void setUsesDFA() {
349         this.usesDFA = true;
350     }
351 
352     public boolean usesDFA() {
353         return this.usesDFA;
354     }
355 
356     public void setUsesTypeResolution() {
357         this.usesTypeResolution = true;
358     }
359 
360     public boolean usesTypeResolution() {
361         return this.usesTypeResolution;
362     }
363 
364     protected void visitAll(List acus, RuleContext ctx) {
365         for (Iterator i = acus.iterator(); i.hasNext();) {
366             ASTCompilationUnit node = (ASTCompilationUnit) i.next();
367             visit(node, ctx);
368         }
369     }
370 
371     /***
372      * Adds a violation to the report.
373      *
374      * @param ctx  the RuleContext
375      * @param node the node that produces the violation
376      */
377     protected final void addViolation(Object data, SimpleNode node) {
378         RuleContext ctx = (RuleContext) data;
379         ctx.getReport().addRuleViolation(new RuleViolation(this, ctx, node));
380     }
381 
382     /***
383      * Adds a violation to the report.
384      *
385      * @param ctx  the RuleContext
386      * @param node the node that produces the violation
387      * @param msg  specific message to put in the report
388      */
389     protected final void addViolationWithMessage(Object data, SimpleNode node, String msg) {
390         RuleContext ctx = (RuleContext) data;
391         ctx.getReport().addRuleViolation(new RuleViolation(this, ctx, node, msg));
392     }
393 
394     /***
395      * Adds a violation to the report.
396      *
397      * @param ctx   the RuleContext
398      * @param node  the node that produces the violation
399      * @param embed a variable to embed in the rule violation message
400      */
401     protected final void addViolation(Object data, SimpleNode node, String embed) {
402         RuleContext ctx = (RuleContext) data;
403         ctx.getReport().addRuleViolation(new RuleViolation(this, ctx, node, MessageFormat.format(getMessage(), new Object[]{embed})));
404     }
405 
406     /***
407      * Adds a violation to the report.
408      *
409      * @param ctx  the RuleContext
410      * @param node the node that produces the violation, may be null, in which case all line and column info will be set to zero
411      * @param args objects to embed in the rule violation message
412      */
413     protected final void addViolation(Object data, Node node, Object[] args) {
414         RuleContext ctx = (RuleContext) data;
415         ctx.getReport().addRuleViolation(new RuleViolation(this, ctx, (SimpleNode) node, MessageFormat.format(getMessage(), args)));
416     }
417 
418     /***
419      * Gets the Image of the first parent node of type ASTClassOrInterfaceDeclaration or <code>null</code>
420      *
421      * @param node the node which will be searched
422      */
423     protected final String getDeclaringType(SimpleNode node) {
424         ASTClassOrInterfaceDeclaration c = (ASTClassOrInterfaceDeclaration) node.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class);
425         if (c != null)
426             return c.getImage();
427         return null;
428     }
429     
430     public static boolean isQualifiedName(SimpleNode node) {
431     	return node.getImage().indexOf('.') != -1;
432     }
433     
434     publicASTCompilationUnit node, String packageName) {/package-summary.html">ong> static boolean importsPackage(ASTCompilationUnit node, String packageName) {
435     	
436         List nodes = node.findChildrenOfType(ASTImportDeclaration.class);
437         for (Iterator i = nodes.iterator(); i.hasNext();) {
438             ASTImportDeclaration n = (ASTImportDeclaration) i.next();
439             >if (n.getPackageName().startsWith(packageName)) {
440                 return true;
441             }
442         }
443         return false;
444     }
445     
446     /***
447      * Return all the relevant properties for the receiver by
448      * overriding in subclasses as necessary.
449      * 
450      * @return Map
451      */
452     protected Map propertiesByName() {
453     	return Collections.EMPTY_MAP;
454     }
455     
456     /***
457      * Return the indicated property descriptor or null if not found.
458      * 
459      * @param propertyName String
460      * @return PropertyDescriptor
461      */
462     public PropertyDescriptor propertyDescriptorFor(String propertyName) {
463     	PropertyDescriptor desc = (PropertyDescriptor)propertiesByName().get(propertyName);
464     	if (desc == null) throw new IllegalArgumentException("unknown property: " + propertyName);
465     	return desc;
466     }
467 }