/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.rule.properties;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Map;
import net.sourceforge.pmd.PropertyDescriptorFactory;
import net.sourceforge.pmd.lang.rule.properties.factories.BasicPropertyDescriptorFactory;
import net.sourceforge.pmd.util.ClassUtil;
import net.sourceforge.pmd.util.StringUtil;
/**
* Defines a property type that can specify a single method to use as part of a
* rule.
*
* <p>Rule developers can limit the rules to those within designated packages per
* the 'legalPackages' argument in the constructor which can be an array of
* partial package names, i.e., ["java.lang", "com.mycompany" ].</p>
*
* @author Brian Remedios
*/
public class MethodProperty extends AbstractPackagedProperty<Method> {
public static final char CLASS_METHOD_DELIMITER = '#';
public static final char METHOD_ARG_DELIMITER = ',';
public static final char[] METHOD_GROUP_DELIMITERS = new char[] { '(', ')' };
private static final String ARRAY_FLAG = "[]";
private static final Map<Class<?>, String> TYPE_SHORTCUTS = ClassUtil.getClassShortNames();
public static final PropertyDescriptorFactory FACTORY = new BasicPropertyDescriptorFactory<MethodProperty>(
Method.class, PACKAGED_FIELD_TYPES_BY_KEY) {
@Override
public MethodProperty createWith(Map<String, String> valuesById) {
char delimiter = delimiterIn(valuesById);
return new MethodProperty(nameIn(valuesById), descriptionIn(valuesById), defaultValueIn(valuesById),
legalPackageNamesIn(valuesById, delimiter), 0f);
}
};
/**
* Constructor for MethodProperty.
*
* @param theName
* String
* @param theDescription
* String
* @param theDefault
* Method
* @param legalPackageNames
* String[]
* @param theUIOrder
* float
* @throws IllegalArgumentException
*/
public MethodProperty(String theName, String theDescription, Method theDefault, String[] legalPackageNames,
float theUIOrder) {
super(theName, theDescription, theDefault, legalPackageNames, theUIOrder);
}
/**
* Constructor for MethodProperty.
*
* @param theName
* String
* @param theDescription
* String
* @param defaultMethodStr
* String
* @param legalPackageNames
* String[]
* @param theUIOrder
* float
* @throws IllegalArgumentException
*/
public MethodProperty(String theName, String theDescription, String defaultMethodStr, String[] legalPackageNames,
float theUIOrder) {
super(theName, theDescription, methodFrom(defaultMethodStr), legalPackageNames, theUIOrder);
}
public MethodProperty(String theName, String theDescription, String defaultMethodStr,
Map<String, String> otherParams, float theUIOrder) {
this(theName, theDescription, methodFrom(defaultMethodStr), packageNamesIn(otherParams), theUIOrder);
}
private static String shortestNameFor(Class<?> cls) {
String compactName = TYPE_SHORTCUTS.get(cls);
return compactName == null ? cls.getName() : compactName;
}
/**
* Return the value of `method' as a string that can be easily recognized
* and parsed when we see it again.
*
* @param method
* the method to convert
* @return the string value
*/
public static String asStringFor(Method method) {
StringBuilder sb = new StringBuilder();
asStringOn(method, sb);
return sb.toString();
}
/**
* @return String
*/
@Override
protected String defaultAsString() {
return asStringFor(defaultValue());
}
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(ARRAY_FLAG);
}
/**
* 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 < argTypes.length; i++) {
sb.append(METHOD_ARG_DELIMITER);
serializedTypeIdOn(argTypes[i], sb);
}
sb.append(METHOD_GROUP_DELIMITERS[1]);
}
private static Class<?> typeFor(String typeName) {
Class<?> type = null;
if (typeName.endsWith(ARRAY_FLAG)) {
String arrayTypeName = typeName.substring(0, typeName.length() - ARRAY_FLAG.length());
type = typeFor(arrayTypeName); // recurse
return Array.newInstance(type, 0).getClass(); // TODO is there a
// better way to get
// an array type?
}
type = ClassUtil.getTypeFor(typeName); // try shortcut first
if (type != null) {
return type;
}
try {
return Class.forName(typeName);
} catch (Exception ex) {
return null;
}
}
/**
* Returns the method specified within the string argument after parsing out
* its source class and any optional arguments. Callers need to specify the
* delimiters expected between the various elements. I.e.:
*
* "String#isEmpty()" "String#indexOf(int)" "String#substring(int,int)"
*
* If a method isn't part of the specified class we will walk up any
* superclasses to Object to try and find it.
*
* If the classes are listed in the ClassUtil class within in Typemaps then
* you likely can avoid specifying fully-qualified class names per the above
* example.
*
* Returns null if a matching method cannot be found.
*
* @param methodNameAndArgTypes
* @param classMethodDelimiter
* @param methodArgDelimiter
* @return Method
*/
public static Method methodFrom(String methodNameAndArgTypes, char classMethodDelimiter, char methodArgDelimiter) {
// classname#methodname(arg1,arg2)
// 0 1 2
int delimPos0 = -1;
if (methodNameAndArgTypes != null) {
delimPos0 = methodNameAndArgTypes.indexOf(classMethodDelimiter);
} else {
return null;
}
if (delimPos0 < 0) {
return null;
}
String className = methodNameAndArgTypes.substring(0, delimPos0);
Class<?> type = ClassUtil.getTypeFor(className);
if (type == null) {
return null;
}
int delimPos1 = methodNameAndArgTypes.indexOf(METHOD_GROUP_DELIMITERS[0]);
if (delimPos1 < 0) {
String methodName = methodNameAndArgTypes.substring(delimPos0 + 1);
return ClassUtil.methodFor(type, methodName, ClassUtil.EMPTY_CLASS_ARRAY);
}
String methodName = methodNameAndArgTypes.substring(delimPos0 + 1, delimPos1);
if (StringUtil.isEmpty(methodName)) {
return null;
} // missing method name?
int delimPos2 = methodNameAndArgTypes.indexOf(METHOD_GROUP_DELIMITERS[1]);
if (delimPos2 < 0) {
return null;
} // error!
String argTypesStr = methodNameAndArgTypes.substring(delimPos1 + 1, delimPos2);
if (StringUtil.isEmpty(argTypesStr)) {
return ClassUtil.methodFor(type, methodName, ClassUtil.EMPTY_CLASS_ARRAY);
} // no arg(s)
String[] argTypeNames = StringUtil.substringsOf(argTypesStr, methodArgDelimiter);
Class<?>[] argTypes = new Class[argTypeNames.length];
for (int i = 0; i < argTypes.length; i++) {
argTypes[i] = typeFor(argTypeNames[i]);
}
return ClassUtil.methodFor(type, methodName, argTypes);
}
/**
* @param methodStr
* String
* @return Method
*/
public static Method methodFrom(String methodStr) {
return methodFrom(methodStr, CLASS_METHOD_DELIMITER, METHOD_ARG_DELIMITER);
}
/**
* Return the value as a string that can be easily recognized and parsed
* when we see it again.
*
* @param value
* Object
* @return String
*/
@Override
protected String asString(Object value) {
return value == null ? "" : asStringFor((Method) value);
}
/**
* @param item
* Object
* @return String
*/
@Override
protected String packageNameOf(Object item) {
final Method method = (Method) item;
return method.getDeclaringClass().getName() + '.' + method.getName();
}
/**
* @return String
*/
@Override
protected String itemTypeName() {
return "method";
}
/**
* @return Class
* @see net.sourceforge.pmd.PropertyDescriptor#type()
*/
@Override
public Class<Method> type() {
return Method.class;
}
/**
* @param valueString
* String
* @return Object
* @throws IllegalArgumentException
* @see net.sourceforge.pmd.PropertyDescriptor#valueFrom(String)
*/
@Override
public Method valueFrom(String valueString) throws IllegalArgumentException {
return methodFrom(valueString);
}
}