Some piece of information handy to PMD developers. Clearly not everything, but some useful bits :)
The new PMD properties subsystem is intended to bring some rigor and expanded functionality to the wild world of rule properies. It defines a value type template that can be used by IDE plugins to enumerate the properties specified by individual rules and provides validation and serialization services for multi-value properties. It uses custom serialization routines to generate human-readable values that can be edited in the XML files
The subsystem implements the following property constructors with the leading name and description arguments not shown:
The delimiter character is used in the serialized string and cannot be part of the property value strings.
(PMD doesn’t currently support full type resolution at the moment)
The delimiter character is used in the serialized string and cannot be part of the property value characters.
The 2D value array holds the label-value tuples in the order that they should be presented in the UI widget. See usage below.
All rule properties need to be characterized via individual PropertyDescriptors so that they can be viewed and adjusted the IDE plugin users. Since the descriptors never change at runtime we only need one of each so we create them as static singletons within the rule class definition. The following rule usage example makes use of a pair of integer properties:
public MyVarNameLengthRule extends AbstractRule() { private static final PropertyDescriptor minVarNameLength = new IntegerProperty( “minVarNameLength”, “Minimum length for variable names”, 3, 1.0f ); private static final PropertyDescriptor maxVarNameLength = new IntegerProperty( “maxVarNameLength”, “Maximum length for variable names”, 30, 1.1f ); private static final Map propertyDescriptorsByName = asFixedMap( new PropertyDescriptor[] { minVarNameLength, maxVarNameLength } ); public MyVarNameLengthRule() { }; protected Map propertiesByName() { return propertyDescriptorsByName; }; // rule body methods... }
All property descriptors must be returned via the propertiesByName() method for each rule class.
Properties can also be multi-valued, that is, we can capture and define a set of them at once:
private static final PropertyDescriptor booleanPrefixes = new StringProperty( “booleanPrefixes”, “Legal prefixes to use for boolean field names”, new String[] { “is”, “has”, “can” }, 1.0f, ‘|’ // reserved as delimiter );
There are at least two constructors for each property type, one that limits the property to a single value and another that accepts more than one.
In addition to the regular Java types such as Boolean, Integer, Float, Character, String, and Class/Type values you can also allow your rule users to pick between complex mixed datatypes such as maps or graphs that you define at compilation time:
private static final Object[][] mixedItems = new Object[][] { {"map", new HashMap()}, {"emptyArray", new Object[0]}, {"list", new ArrayList()}, {"string", "Hello World!"}, }; private static final PropertyDescriptor sampleObjects = new EnumeratedProperty( "testEnumerations", "Test enumerations with complex types", mixedItems, 1.0f )
Note that Java values held by the EnumeratedProperty are not written out as property values themselves, we just write out the labels they are associated with. Specifying a label in the XML file for an object that doesn’t exist will result in an IllegalArgumentException.
Defining the property rules within the ruleset XML files is straightforward for single values:
<properties> <property name="maxMethodArgs" value="2"/> </properties>
When specifying multiple values you will need to separate them using the delimiter held by the property descriptor, most commonly a single pipe character, ‘|’:
<properties> <property name="legalListTypes" value="java.util.ArrayList|java.util.Vector|java.util.HashMap"/> </properties>
You can define your own datatypes by implementing a subclass of AbstractPMDProperty and implementing the serialization, and validation routines listed in the PMDProperty interface. Just ensure that you create a corresponding JUnit test in the test.net.sourceforge.pmd.properties package to go along with it.
One of the implementation goals in this system is to try and come up with property constructors sufficiently useful that we don’t need to assemble them within static blocks. A single statement should be enough to build a rule property descriptor.
In order to assemble an effective UI to manage the rule properties the following setup sequence is suggested:
Determine whether the value is open-ended or enumerated. For example, can the user type in their own values or should they pick them from a list? Invoke the choices() method that may return a 2D array of label-value pairs you can use to populate your list widget. If the method returns null then jump to step #2.
You may need to maintain a mapping of the label-value pairs to translate between them depending on the capabilities of your list widget. The first pair in the array represents the default property value.
All multi-value properties make use of a character to delimit the values in their serialized form so you will need to ensure that you prevent the user from entering values containing it. Retrive the delimiter via the multiValueDelimiter() method.
You can use the errorFor(value) method to validate the values entered by the user or check the values held by the rule configuration file. It returns null or an error message as appropriate. It would be best to flag and disable rules that have invalid property values.
Use the defaultValue() method to reset the rule properties to their default value.
The two serialization methods, valueFrom() and asDelimitedString(), are to be used to retrive and store property values respectively.
Widgets should be ordered vertically according to the values returned by the uiOrder() method with lower-valued properties appearing above the ones with higher values. The order of the property descriptors returned from the rule cannot be guaranteed to be the same as the presentation order. If the two or more widgets share the same integer value then you can use the fractional portions to place their widgets in a horizontal sequence (if possible).
For types that can have null values, such as strings, then use the isRequired() method to flag any possible missing values.
If a property field is multi-valued then the maximum number of values it can hold is set to largest possible int value unless set explicitly in a rule property constructor.