MethodProperty.java
001 /**
002  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003  */
004 package net.sourceforge.pmd.lang.rule.properties;
005 
006 import java.lang.reflect.Array;
007 import java.lang.reflect.Method;
008 import java.util.Map;
009 
010 import net.sourceforge.pmd.util.ClassUtil;
011 import net.sourceforge.pmd.util.StringUtil;
012 
013 /**
014  * Defines a property type that can specify a single method to use as part of a rule.
015  *
016  * Rule developers can limit the rules to those within designated packages per the
017  * 'legalPackages' argument in the constructor which can be an array of partial
018  * package names, i.e., ["java.lang", "com.mycompany" ].
019  *
020  @author Brian Remedios
021  */
022 public class MethodProperty extends AbstractPackagedProperty<Method> {
023 
024     public static final char CLASS_METHOD_DELIMITER = '#';
025     public static final char METHOD_ARG_DELIMITER = ',';
026     public static final char[] METHOD_GROUP_DELIMITERS = new char[] { '('')' };
027 
028     private static final String ARRAY_FLAG = "[]";
029     private static final Map<Class, String> TYPE_SHORTCUTS = ClassUtil.getClassShortNames();
030 
031     /**
032      @param cls Class<?>
033      @return String
034      */
035     private static String shortestNameFor(Class<?> cls) {
036         String compactName = TYPE_SHORTCUTS.get(cls);
037         return compactName == null ? cls.getName() : compactName;
038     }
039 
040     /**
041      * Return the value of `method' as a string that can be easily recognized
042      * and parsed when we see it again.
043      *
044      @param method the method to convert
045      @return the string value
046      */
047     public static String asStringFor(Method method) {
048         StringBuilder sb = new StringBuilder();
049         asStringOn(method, sb);
050         return sb.toString();
051     }
052 
053     /**
054      @return String
055      */
056     protected String defaultAsString() {
057         return asStringFor(defaultValue());
058     }
059     /**
060      @param type Class<?>
061      @param sb StringBuilder
062      */
063     private static void serializedTypeIdOn(Class<?> type, StringBuilder sb) {
064 
065         Class<?> arrayType = type.getComponentType();
066         if (arrayType == null) {
067             sb.append(shortestNameFor(type));
068             return;
069         }
070         sb.append(shortestNameFor(arrayType)).append(ARRAY_FLAG);
071     }
072 
073     /**
074      * Serializes the method signature onto the specified buffer.
075      *
076      @param method Method
077      @param sb StringBuilder
078      */
079     public static void asStringOn(Method method, StringBuilder sb) {
080 
081         Class<?> clazz = method.getDeclaringClass();
082 
083         sb.append(shortestNameFor(clazz));
084         sb.append(CLASS_METHOD_DELIMITER);
085         sb.append(method.getName());
086 
087         sb.append(METHOD_GROUP_DELIMITERS[0]);
088 
089         Class<?>[] argTypes = method.getParameterTypes();
090         if (argTypes.length == 0) {
091             sb.append(METHOD_GROUP_DELIMITERS[1]);
092             return;
093         }
094 
095         serializedTypeIdOn(argTypes[0], sb);
096         for (int i = 1; i < argTypes.length; i++) {
097             sb.append(METHOD_ARG_DELIMITER);
098             serializedTypeIdOn(argTypes[i], sb);
099         }
100         sb.append(METHOD_GROUP_DELIMITERS[1]);
101     }
102 
103     /**
104      @param typeName String
105      @return Class<?>
106      */
107     private static Class<?> typeFor(String typeName) {
108 
109         Class<?> type = null;
110 
111         if (typeName.endsWith(ARRAY_FLAG)) {
112             String arrayTypeName = typeName.substring(0, typeName.length() - ARRAY_FLAG.length());
113             type = typeFor(arrayTypeName)// recurse
114             return Array.newInstance(type, 0).getClass()// TODO is there a better way to get an array type?
115         }
116 
117         type = ClassUtil.getTypeFor(typeName)// try shortcut first
118         if (type != null) {
119             return type;
120         }
121 
122         try {
123             return Class.forName(typeName);
124         catch (Exception ex) {
125             return null;
126         }
127     }
128 
129     /**
130      * Returns the method specified within the string argument after parsing out its source class and
131      * any optional arguments. Callers need to specify the delimiters expected between the various
132      * elements.  I.e.:
133      *
134      *   "String#isEmpty()"
135      *  "String#indexOf(int)"
136      *  "String#substring(int,int)"
137      *
138      *  If a method isn't part of the specified class we will walk up any superclasses to Object to try
139      *  and find it.
140      *
141      *  If the classes are listed in the ClassUtil class within in Typemaps then you likely can avoid
142      *  specifying fully-qualified class names per the above example.
143      *
144      *  Returns null if a matching method cannot be found.
145      *
146      @param methodNameAndArgTypes
147      @param classMethodDelimiter
148      @param methodArgDelimiter
149      @return Method
150      */
151     public static Method methodFrom(String methodNameAndArgTypes, char classMethodDelimiter, char methodArgDelimiter) {
152 
153         // classname#methodname(arg1,arg2)
154         //          0          1         2
155 
156         int delimPos0 = methodNameAndArgTypes.indexOf(classMethodDelimiter);
157         if (delimPos0 < 0) {
158             return null;
159         }
160 
161         String className = methodNameAndArgTypes.substring(0, delimPos0);
162         Class<?> type = ClassUtil.getTypeFor(className);
163         if (type == null) {
164             return null;
165         }
166 
167         int delimPos1 = methodNameAndArgTypes.indexOf(METHOD_GROUP_DELIMITERS[0]);
168         if (delimPos1 < 0) {
169             String methodName = methodNameAndArgTypes.substring(delimPos0 + 1);
170             return ClassUtil.methodFor(type, methodName, ClassUtil.EMPTY_CLASS_ARRAY);
171         }
172 
173         String methodName = methodNameAndArgTypes.substring(delimPos0 + 1, delimPos1);
174         if (StringUtil.isEmpty(methodName)) {
175             return null;
176         // missing method name?
177 
178         int delimPos2 = methodNameAndArgTypes.indexOf(METHOD_GROUP_DELIMITERS[1]);
179         if (delimPos2 < 0) {
180             return null;
181         // error!
182 
183         String argTypesStr = methodNameAndArgTypes.substring(delimPos1 + 1, delimPos2);
184         if (StringUtil.isEmpty(argTypesStr)) {
185             return ClassUtil.methodFor(type, methodName, ClassUtil.EMPTY_CLASS_ARRAY);
186         // no arg(s)
187 
188         String[] argTypeNames = StringUtil.substringsOf(argTypesStr, methodArgDelimiter);
189         Class<?>[] argTypes = new Class[argTypeNames.length];
190         for (int i = 0; i < argTypes.length; i++) {
191             argTypes[i= typeFor(argTypeNames[i]);
192         }
193 
194         return ClassUtil.methodFor(type, methodName, argTypes);
195     }
196 
197     /**
198      @param methodStr String
199      @return Method
200      */
201     public static Method methodFrom(String methodStr) {
202       return methodFrom(methodStr, CLASS_METHOD_DELIMITER, METHOD_ARG_DELIMITER);
203     }
204     
205     /**
206      * Constructor for MethodProperty.
207      *
208      @param theName        String
209      @param theDescription String
210      @param theDefault     Method
211      @param legalPackageNames String[]
212      @param theUIOrder     float
213      @throws IllegalArgumentException
214      */
215     public MethodProperty(String theName, String theDescription, Method theDefault, String[] legalPackageNames, float theUIOrder) {
216         super(theName, theDescription, theDefault, legalPackageNames, theUIOrder);
217     }
218 
219     /**
220      * Constructor for MethodProperty.
221      *
222      @param theName        String
223      @param theDescription String
224      @param defaultStr     String
225      @param legalPackageNames String[]
226      @param theUIOrder     float
227      @throws IllegalArgumentException
228      */
229     public MethodProperty(String theName, String theDescription, String defaultStr, String[] legalPackageNames, float theUIOrder) {
230         super(theName, theDescription, methodFrom(defaultStr), legalPackageNames, theUIOrder);
231     }
232     
233     /**
234      * Return the value as a string that can be easily recognized and parsed
235      * when we see it again.
236      *
237      @param value Object
238      @return String
239      */
240     @Override
241     protected String asString(Object value) {
242         return value == null "" : asStringFor((Methodvalue);
243     }
244 
245     /**
246      @param item Object
247      @return String
248      */
249     @Override
250     protected String packageNameOf(Object item) {
251 
252         final Method method = (Methoditem;
253         return method.getDeclaringClass().getName() '.' + method.getName();
254     }
255 
256     /**
257      @return String
258      */
259     @Override
260     protected String itemTypeName() {
261         return "method";
262     }
263 
264     /**
265      @return Class
266      @see net.sourceforge.pmd.PropertyDescriptor#type()
267      */
268     public Class<Method> type() {
269         return Method.class;
270     }
271 
272     /**
273      @param valueString  String
274      @return Object
275      @throws IllegalArgumentException
276      @see net.sourceforge.pmd.PropertyDescriptor#valueFrom(String)
277      */
278     public Method valueFrom(String valueStringthrows IllegalArgumentException {
279        return methodFrom(valueString);
280     }
281 }