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 }