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();
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;
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 ?
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;
290 }
291
292 if (this == o) {
293 return true;
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 public static boolean importsPackage(ASTCompilationUnit 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 }