diff --git a/pmd/regress/test/net/sourceforge/pmd/properties/AbstractPropertyDescriptorTester.java b/pmd/regress/test/net/sourceforge/pmd/properties/AbstractPropertyDescriptorTester.java index 103dd19866..b729b56026 100644 --- a/pmd/regress/test/net/sourceforge/pmd/properties/AbstractPropertyDescriptorTester.java +++ b/pmd/regress/test/net/sourceforge/pmd/properties/AbstractPropertyDescriptorTester.java @@ -6,8 +6,11 @@ import net.sourceforge.pmd.PropertyDescriptor; import net.sourceforge.pmd.util.CollectionUtil; import org.junit.Test; + /** - * + * Base functionality for all concrete subclasses that evaluate type-specific property descriptors. + * Checks for error conditions during construction, error value detection, serialization, etc. + * * @author Brian Remedios */ public abstract class AbstractPropertyDescriptorTester { @@ -225,8 +228,4 @@ public abstract class AbstractPropertyDescriptorTester { } return results; } - -// public static junit.framework.Test suite() { -// return new junit.framework.JUnit4TestAdapter(AbstractPropertyDescriptorTester.class); -// } } diff --git a/pmd/regress/test/net/sourceforge/pmd/properties/DoublePropertyTest.java b/pmd/regress/test/net/sourceforge/pmd/properties/DoublePropertyTest.java index 445037b96c..7e79c18f82 100644 --- a/pmd/regress/test/net/sourceforge/pmd/properties/DoublePropertyTest.java +++ b/pmd/regress/test/net/sourceforge/pmd/properties/DoublePropertyTest.java @@ -4,6 +4,9 @@ import net.sourceforge.pmd.PropertyDescriptor; import net.sourceforge.pmd.lang.rule.properties.DoubleProperty; /** + * Evaluates the functionality of the DoubleProperty descriptor by testing its ability to catch creation + * errors (illegal args), flag out-of-range test values, and serialize/deserialize groups of double values + * onto/from a string buffer. * * @author Brian Remedios */ diff --git a/pmd/regress/test/net/sourceforge/pmd/properties/FloatPropertyTest.java b/pmd/regress/test/net/sourceforge/pmd/properties/FloatPropertyTest.java index e3c179df57..c8650c6292 100644 --- a/pmd/regress/test/net/sourceforge/pmd/properties/FloatPropertyTest.java +++ b/pmd/regress/test/net/sourceforge/pmd/properties/FloatPropertyTest.java @@ -4,6 +4,9 @@ import net.sourceforge.pmd.PropertyDescriptor; import net.sourceforge.pmd.lang.rule.properties.FloatProperty; /** + * Evaluates the functionality of the FloatProperty descriptor by testing its ability to catch creation + * errors (illegal args), flag out-of-range test values, and serialize/deserialize groups of float values + * onto/from a string buffer. * * @author Brian Remedios */ diff --git a/pmd/regress/test/net/sourceforge/pmd/properties/IntegerPropertyTest.java b/pmd/regress/test/net/sourceforge/pmd/properties/IntegerPropertyTest.java index a02d405a81..9fdbd5b0b3 100644 --- a/pmd/regress/test/net/sourceforge/pmd/properties/IntegerPropertyTest.java +++ b/pmd/regress/test/net/sourceforge/pmd/properties/IntegerPropertyTest.java @@ -6,6 +6,9 @@ import net.sourceforge.pmd.PropertyDescriptor; import net.sourceforge.pmd.lang.rule.properties.IntegerProperty; /** + * Evaluates the functionality of the IntegerProperty descriptor by testing its ability to catch creation + * errors (illegal args), flag out-of-range test values, and serialize/deserialize groups of integers + * onto/from a string buffer. * * @author Brian Remedios */ @@ -14,16 +17,16 @@ public class IntegerPropertyTest extends AbstractPropertyDescriptorTester { private static final int MIN = 1; private static final int MAX = 12; private static final int SHIFT = 3; - + /** * Method createValue. * @param count int * @return Object */ protected Object createValue(int count) { - + if (count == 1) return Integer.valueOf((int)(System.currentTimeMillis() % 100)); - + Integer[] values = new Integer[count]; for (int i=0; i("enumType", "Property with a enumerated choices", new String[] {"String", "Object"}, new Class[] {String.class, Object.class}, 1, 5.0f); public static final PropertyDescriptor multiEnumType = new EnumeratedProperty("enumType", "Property with a enumerated choices", new String[] {"String", "Object"}, new Class[] {String.class, Object.class}, new int[] {0,1}, 5.0f); diff --git a/pmd/regress/test/net/sourceforge/pmd/properties/StringPropertyTest.java b/pmd/regress/test/net/sourceforge/pmd/properties/StringPropertyTest.java index 68342e3e03..7a03bc4914 100644 --- a/pmd/regress/test/net/sourceforge/pmd/properties/StringPropertyTest.java +++ b/pmd/regress/test/net/sourceforge/pmd/properties/StringPropertyTest.java @@ -4,6 +4,9 @@ import net.sourceforge.pmd.PropertyDescriptor; import net.sourceforge.pmd.lang.rule.properties.StringProperty; /** + * Evaluates the functionality of the StringProperty descriptor by testing its ability to catch creation + * errors (illegal args), flag invalid strings per any specified expressions, and serialize/deserialize + * groups of strings onto/from a string buffer. * * @author Brian Remedios */ diff --git a/pmd/regress/test/net/sourceforge/pmd/properties/TypePropertyTest.java b/pmd/regress/test/net/sourceforge/pmd/properties/TypePropertyTest.java index 91406a3533..b72ddde051 100644 --- a/pmd/regress/test/net/sourceforge/pmd/properties/TypePropertyTest.java +++ b/pmd/regress/test/net/sourceforge/pmd/properties/TypePropertyTest.java @@ -1,20 +1,28 @@ package test.net.sourceforge.pmd.properties; +import java.util.Comparator; import java.util.HashMap; import java.util.Map; - -import org.junit.Test; +import java.util.Observer; +import java.util.Set; import net.sourceforge.pmd.PropertyDescriptor; import net.sourceforge.pmd.lang.rule.properties.TypeProperty; /** + * Evaluates the functionality of the TypeProperty descriptor by testing its ability to catch creation + * errors (illegal args), flag invalid Type values per the allowable packages, and serialize/deserialize + * groups of types onto/from a string buffer. + * + * We're using java.lang classes for 'normal' constructors and applying java.util types as ones we expect + * to fail. * * @author Brian Remedios */ public class TypePropertyTest extends AbstractPropertyDescriptorTester { - public static final Class[] classes = new Class[] { String.class, Integer.class, int.class, HashMap.class, Map.class }; + private static final Class[] javaLangClasses = new Class[] { String.class, Integer.class, Thread.class, Object.class, Runtime.class }; + private static final Class[] javaUtilTypes = new Class[] { HashMap.class, Map.class, Comparator.class, Set.class, Observer.class }; public TypePropertyTest() { super(); @@ -27,7 +35,7 @@ public class TypePropertyTest extends AbstractPropertyDescriptorTester { */ protected Object createValue(int count) { - if (count == 1) return randomChoice(classes); + if (count == 1) return randomChoice(javaLangClasses); Object[] values = new Object[count]; for (int i=0; i nameSet = new HashSet(items.length); + String name = null; + + for (int i=0; i TYPE_SHORTCUTS = ClassUtil.getClassShortNames(); + + private static String shortestNameFor(Class cls) { + String compactName = TYPE_SHORTCUTS.get(cls); + return compactName == null ? cls.getName() : compactName; + } + + /** + * @param method + * @return + */ + public static String asStringFor(Method method) { + StringBuilder sb = new StringBuilder(); + asStringOn(method, sb); + return sb.toString(); + } + + /** + * Return the value as a string that can be easily recognized and parsed + * when we see it again. + * + * @param value Object + * @return String + */ + protected String asString(Object value) { + return value == null ? "" : asStringFor((Method)value); + } + + private static void serializedTypeIdOn(Class type, StringBuilder sb) { + + Class arrayType = type.getComponentType(); + if (arrayType == null) { + sb.append(shortestNameFor(type)); + return; + } + sb.append(shortestNameFor(arrayType)).append("[]"); + } + + /** + * Serializes the method signature onto the specified buffer. + * + * @param method Method + * @param sb StringBuilder + */ + public static void asStringOn(Method method, StringBuilder sb) { + + Class clazz = method.getDeclaringClass(); + + sb.append(shortestNameFor(clazz) ); + sb.append(CLASS_METHOD_DELIMITER); + sb.append(method.getName()); + + sb.append(METHOD_GROUP_DELIMITERS[0]); + + Class[] argTypes = method.getParameterTypes(); + if (argTypes.length == 0) { + sb.append(METHOD_GROUP_DELIMITERS[1]); + return; + } + + serializedTypeIdOn(argTypes[0], sb); + for (int i=1; i cls = classIn(propertyString); - String methodName = methodNameIn(propertyString); - Class[] parameterTypes = parameterTypesIn(propertyString); - - try { - return cls.getMethod(methodName, parameterTypes); - } catch (Exception e) { - throw new IllegalArgumentException("invalid method: " + propertyString); + if (!isMultiValue()) { + return methodFrom( + valueString, CLASS_METHOD_DELIMITER, METHOD_ARG_DELIMITER + ); } - } + + String[] values = StringUtil.substringsOf(valueString, multiValueDelimiter()); - private Class classIn(String propertyString) throws IllegalArgumentException { - - int dotPos = propertyString.lastIndexOf('.'); - String className = propertyString.substring(0, dotPos); - - try { - return Class.forName(className); - } catch (Exception ex) { - throw new IllegalArgumentException("class not found: " + className); + Method[] methods = new Method[values.length]; + for (int i=0; i[] parameterTypesIn(String propertyString) { - return null; + return methods; } } diff --git a/pmd/src/net/sourceforge/pmd/lang/rule/properties/StringProperty.java b/pmd/src/net/sourceforge/pmd/lang/rule/properties/StringProperty.java index ea1c7975b8..e1bd91f309 100644 --- a/pmd/src/net/sourceforge/pmd/lang/rule/properties/StringProperty.java +++ b/pmd/src/net/sourceforge/pmd/lang/rule/properties/StringProperty.java @@ -64,6 +64,12 @@ public class StringProperty extends AbstractProperty { checkDefaults(theDefaultValue, aMultiValueDelimiter); } + /** + * + * @param defaultValue + * @param delim + * @throws IllegalArgumentException + */ private static void checkDefaults(Object defaultValue, char delim) { if (defaultValue == null) { return; } diff --git a/pmd/src/net/sourceforge/pmd/lang/rule/properties/TypeProperty.java b/pmd/src/net/sourceforge/pmd/lang/rule/properties/TypeProperty.java index 5ed7878ccb..6aaa952232 100644 --- a/pmd/src/net/sourceforge/pmd/lang/rule/properties/TypeProperty.java +++ b/pmd/src/net/sourceforge/pmd/lang/rule/properties/TypeProperty.java @@ -4,13 +4,14 @@ package net.sourceforge.pmd.lang.rule.properties; import net.sourceforge.pmd.util.ClassUtil; +import net.sourceforge.pmd.util.StringUtil; /** * Defines a property that supports class types, even for primitive values! * * @author Brian Remedios */ -public class TypeProperty extends StringProperty { +public class TypeProperty extends AbstractPackagedProperty { private static final char DELIMITER = '|'; @@ -19,10 +20,11 @@ public class TypeProperty extends StringProperty { * @param theName String * @param theDescription String * @param theDefault Class + * @param legalPackageNames String[] * @param theUIOrder float */ - public TypeProperty(String theName, String theDescription, Class theDefault, float theUIOrder) { - super(theName, theDescription, theDefault, theUIOrder, DELIMITER); + public TypeProperty(String theName, String theDescription, Class theDefault, String[] legalPackageNames, float theUIOrder) { + super(theName, theDescription, theDefault, legalPackageNames, theUIOrder); isMultiValue(false); } @@ -32,14 +34,19 @@ public class TypeProperty extends StringProperty { * @param theName String * @param theDescription String * @param theDefaults Class[] + * @param legalPackageNames String[] * @param theUIOrder float */ - public TypeProperty(String theName, String theDescription, Class[] theDefaults, float theUIOrder) { - super(theName, theDescription, theDefaults, theUIOrder, DELIMITER); + public TypeProperty(String theName, String theDescription, Class[] theDefaults, String[] legalPackageNames, float theUIOrder) { + super(theName, theDescription, theDefaults, legalPackageNames, theUIOrder); isMultiValue(true); } + protected String packageNameOf(Object item) { + return ((Class)item).getName(); + } + /** * Method type. * @return Class @@ -49,6 +56,10 @@ public class TypeProperty extends StringProperty { public Class type() { return Class.class; } + + protected String itemTypeName() { + return "type"; + } /** * Method asString. @@ -64,6 +75,7 @@ public class TypeProperty extends StringProperty { * Method classFrom. * @param className String * @return Class + * @throws IllegalArgumentException */ private Class classFrom(String className) { @@ -92,24 +104,12 @@ public class TypeProperty extends StringProperty { return classFrom(valueString); } - String[] values = (String[]) super.valueFrom(valueString); + String[] values = StringUtil.substringsOf(valueString, DELIMITER); - Class[] classes = new Class[values.length]; + Class[] classes = new Class[values.length]; for (int i = 0; i < values.length; i++) { classes[i] = classFrom(values[i]); } return classes; } - - /** - * Neutralize unwanted superclass functionality that will result - * in a class cast exception. - * - * @param value Object - * @return String - */ - @Override - protected String valueErrorFor(Object value) { - return null; - } } diff --git a/pmd/src/net/sourceforge/pmd/util/ClassUtil.java b/pmd/src/net/sourceforge/pmd/util/ClassUtil.java index c67f145cdf..0709e02194 100644 --- a/pmd/src/net/sourceforge/pmd/util/ClassUtil.java +++ b/pmd/src/net/sourceforge/pmd/util/ClassUtil.java @@ -1,69 +1,112 @@ package net.sourceforge.pmd.util; +import java.lang.reflect.Method; import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; /** - * Various class-related utility methods + * Various class-related utility methods. * * @author Brian Remedios */ public final class ClassUtil { - private ClassUtil() { - }; + public static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; - @SuppressWarnings("PMD.AvoidUsingShortType") - private static final TypeMap PRIMITIVE_TYPE_NAMES = new TypeMap(new Class[] { int.class, byte.class, long.class, - short.class, float.class, double.class, char.class, boolean.class, }); + private ClassUtil() { + }; - private static final TypeMap TYPES_BY_NAME = new TypeMap(new Class[] { Integer.class, Byte.class, Long.class, - Short.class, Float.class, Double.class, Character.class, Boolean.class, BigDecimal.class, String.class, - Object.class, }); + @SuppressWarnings("PMD.AvoidUsingShortType") + private static final TypeMap PRIMITIVE_TYPE_NAMES = new TypeMap( + new Class[] { int.class, byte.class, long.class, short.class, + float.class, double.class, char.class, boolean.class, }); - /** - * Returns the type(class) for the name specified - * or null if not found. - * - * @param name String - * @return Class - */ - public static Class getPrimitiveTypeFor(String name) { - return PRIMITIVE_TYPE_NAMES.typeFor(name); - } + private static final TypeMap TYPES_BY_NAME = new TypeMap(new Class[] { + Integer.class, Byte.class, Long.class, Short.class, Float.class, + Double.class, Character.class, Boolean.class, BigDecimal.class, + String.class, Object.class, }); - /** - * Attempt to determine the actual class given the short name. - * - * @param shortName String - * @return Class - */ - public static Class getTypeFor(String shortName) { - - Class type = TYPES_BY_NAME.typeFor(shortName); - if (type != null) { - return type; + /** + * Returns the type(class) for the name specified or null if not found. + * + * @param name String + * @return Class + */ + public static Class getPrimitiveTypeFor(String name) { + return PRIMITIVE_TYPE_NAMES.typeFor(name); } - type = PRIMITIVE_TYPE_NAMES.typeFor(shortName); - if (type != null) { - return type; + /** + * Return a map of all the short names of classes we maintain mappings for. + * The names are keyed by the classes themselves. + * + * @return + */ + public static Map getClassShortNames() { + + Map map = new HashMap(); + map.putAll(PRIMITIVE_TYPE_NAMES.asInverseWithShortName()); + map.putAll(TYPES_BY_NAME.asInverseWithShortName()); + return map; } - return CollectionUtil.getCollectionTypeFor(shortName); - } + /** + * Attempt to determine the actual class given the short name. + * + * @param shortName String + * @return Class + */ + public static Class getTypeFor(String shortName) { - /** - * Returns the abbreviated name of the type, - * without the package name - * - * @param fullTypeName - * @return String - */ + Class type = TYPES_BY_NAME.typeFor(shortName); + if (type != null) { + return type; + } - public static String withoutPackageName(String fullTypeName) { + type = PRIMITIVE_TYPE_NAMES.typeFor(shortName); + if (type != null) { + return type; + } - int dotPos = fullTypeName.lastIndexOf('.'); + return CollectionUtil.getCollectionTypeFor(shortName); + } - return dotPos > 0 ? fullTypeName.substring(dotPos + 1) : fullTypeName; - } + /** + * Returns the abbreviated name of the type, without the package name + * + * @param fullTypeName + * @return String + */ + + public static String withoutPackageName(String fullTypeName) { + + int dotPos = fullTypeName.lastIndexOf('.'); + return dotPos > 0 ? fullTypeName.substring(dotPos + 1) : fullTypeName; + } + + /** + * Attempts to return the specified method from the class provided but will + * walk up its superclasses until it finds a match. Returns null if it + * doesn't. + * + * @param clasz Class + * @param methodName String + * @param paramTypes Class[] + * @return Method + */ + public static Method methodFor(Class clasz, String methodName, Class[] paramTypes) { + + Method method = null; + Class current = clasz; + while (current != Object.class) { + try { + method = current.getDeclaredMethod(methodName, paramTypes); + } catch (NoSuchMethodException ex) { + current = current.getSuperclass(); + } + if (method != null) { return method; } + } + return null; + } } diff --git a/pmd/src/net/sourceforge/pmd/util/TypeMap.java b/pmd/src/net/sourceforge/pmd/util/TypeMap.java index 7191d7a4bb..11f9344548 100644 --- a/pmd/src/net/sourceforge/pmd/util/TypeMap.java +++ b/pmd/src/net/sourceforge/pmd/util/TypeMap.java @@ -1,84 +1,158 @@ package net.sourceforge.pmd.util; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; /** - * A specialized map that stores classes by both their full and short names. + * A specialized map that stores types by both their full and short (without package prefixes) names. + * If an incoming type shares the same name (but different package/prefix) with a type already in the + * map then an IllegalArgumentException will be thrown since any subsequent retrievals by said short + * name could be in error. * * @author Brian Remedios */ public class TypeMap { - private Map> typesByName; + private Map> typesByName; - /** - * Constructor for TypeMap. - * @param initialSize int - */ - public TypeMap(int initialSize) { - typesByName = new HashMap>(initialSize); - } - - /** - * Constructor for TypeMap that takes in an initial set of types. - * - * @param types Class[] - */ - public TypeMap(Class... types) { - this(types.length); - add(types); - } - - /** - * Adds a type to the receiver and stores it keyed by both its full - * and short names. - * - * @param type Class - */ - public void add(Class type) { - typesByName.put(type.getName(), type); - typesByName.put(ClassUtil.withoutPackageName(type.getName()), type); - } - - /** - * Returns whether the type is known to the receiver. - * - * @param type Class - * @return boolean - */ - public boolean contains(Class type) { - return typesByName.containsValue(type); - } - - /** - * Returns whether the typeName is known to the receiver. - * - * @param typeName String - * @return boolean - */ - public boolean contains(String typeName) { - return typesByName.containsKey(typeName); - } - - /** - * Returns the type for the typeName specified. - * - * @param typeName String - * @return Class - */ - public Class typeFor(String typeName) { - return typesByName.get(typeName); - } - - /** - * Adds an array of types to the receiver at once. - * - * @param types Class[] - */ - public void add(Class... types) { - for (Class element : types) { - add(element); + /** + * Constructor for TypeMap. + * + * @param initialSize int + */ + public TypeMap(int initialSize) { + typesByName = new HashMap>(initialSize); + } + + /** + * Constructor for TypeMap that takes in an initial set of types. + * + * @param types Class[] + */ + public TypeMap(Class... types) { + this(types.length); + add(types); + } + + /** + * Adds a type to the receiver and stores it keyed by both its full and + * short names. Throws an exception if the short name of the argument + * matches an existing one already in the map for a different class. + * + * @param type Class + * @throws IllegalArgumentException + */ + @SuppressWarnings("PMD.CompareObjectsWithEquals") + public void add(Class type) { + + final String shortName = ClassUtil.withoutPackageName(type.getName()); + Class existingType = typesByName.get(shortName); + if (existingType == null) { + typesByName.put(type.getName(), type); + typesByName.put(shortName, type); + return; + } + + if (existingType != type) { + throw new IllegalArgumentException( + "Short name collision between existing " + + existingType + " and new " + type + ); + } + } + + /** + * Returns whether the type is known to the receiver. + * + * @param type Class + * @return boolean + */ + public boolean contains(Class type) { + return typesByName.containsValue(type); + } + + /** + * Returns whether the typeName is known to the receiver. + * + * @param typeName String + * @return boolean + */ + public boolean contains(String typeName) { + return typesByName.containsKey(typeName); + } + + /** + * Returns the type for the typeName specified. + * + * @param typeName String + * @return Class + */ + public Class typeFor(String typeName) { + return typesByName.get(typeName); + } + + /** + * Adds an array of types to the receiver at once. + * + * @param types Class[] + */ + public void add(Class... types) { + for (Class element : types) { + add(element); + } + } + + /** + * Creates and returns a map of short type names (without the package + * prefixes) keyed by the classes themselves. + * + * @return Map + */ + public Map, String> asInverseWithShortName() { + + Map, String> inverseMap = new HashMap, String>(typesByName.size() / 2); + + Iterator iter = typesByName.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry) iter.next(); + storeShortest(inverseMap, entry.getValue(), (String) entry.getKey()); + } + + return inverseMap; + } + + /** + * Returns the total number of entries in the receiver. This will be exactly + * twice the number of types added. + * + * @return + */ + public int size() { + return typesByName.size(); + } + + /** + * Store the shorter of the incoming value or the existing value in the map + * at the key specified. + * + * @param map + * @param key + * @param value + */ + private void storeShortest(Map map, Object key, String value) { + + String existingValue = (String) map.get(key); + + if (existingValue == null) { + map.put(key, value); + return; + } + + if (existingValue.length() < value.length()) { + return; + } + + map.put(key, value); } - } }