View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.rule.properties;
5   
6   import java.lang.reflect.Array;
7   import java.lang.reflect.Method;
8   import java.util.Map;
9   
10  import net.sourceforge.pmd.PropertyDescriptorFactory;
11  import net.sourceforge.pmd.lang.rule.properties.factories.BasicPropertyDescriptorFactory;
12  import net.sourceforge.pmd.util.ClassUtil;
13  import net.sourceforge.pmd.util.StringUtil;
14  
15  /**
16   * Defines a property type that can specify a single method to use as part of a
17   * rule.
18   *
19   * Rule developers can limit the rules to those within designated packages per
20   * the 'legalPackages' argument in the constructor which can be an array of
21   * partial package names, i.e., ["java.lang", "com.mycompany" ].
22   *
23   * @author Brian Remedios
24   */
25  public class MethodProperty extends AbstractPackagedProperty<Method> {
26  
27      public static final char CLASS_METHOD_DELIMITER = '#';
28      public static final char METHOD_ARG_DELIMITER = ',';
29      public static final char[] METHOD_GROUP_DELIMITERS = new char[] { '(', ')' };
30  
31      private static final String ARRAY_FLAG = "[]";
32      private static final Map<Class<?>, String> TYPE_SHORTCUTS = ClassUtil.getClassShortNames();
33  
34      public static final PropertyDescriptorFactory FACTORY = new BasicPropertyDescriptorFactory<MethodProperty>(
35              Method.class, PACKAGED_FIELD_TYPES_BY_KEY) {
36  
37          public MethodProperty createWith(Map<String, String> valuesById) {
38              char delimiter = delimiterIn(valuesById);
39              return new MethodProperty(nameIn(valuesById), descriptionIn(valuesById), defaultValueIn(valuesById),
40                      legalPackageNamesIn(valuesById, delimiter), 0f);
41          }
42      };
43  
44      /**
45       * @param cls Class<?>
46       * @return String
47       */
48      private static String shortestNameFor(Class<?> cls) {
49          String compactName = TYPE_SHORTCUTS.get(cls);
50          return compactName == null ? cls.getName() : compactName;
51      }
52  
53      /**
54       * Return the value of `method' as a string that can be easily recognized
55       * and parsed when we see it again.
56       *
57       * @param method the method to convert
58       * @return the string value
59       */
60      public static String asStringFor(Method method) {
61          StringBuilder sb = new StringBuilder();
62          asStringOn(method, sb);
63          return sb.toString();
64      }
65  
66      /**
67       * @return String
68       */
69      protected String defaultAsString() {
70          return asStringFor(defaultValue());
71      }
72  
73      /**
74       * @param type Class<?>
75       * @param sb StringBuilder
76       */
77      private static void serializedTypeIdOn(Class<?> type, StringBuilder sb) {
78  
79          Class<?> arrayType = type.getComponentType();
80          if (arrayType == null) {
81              sb.append(shortestNameFor(type));
82              return;
83          }
84          sb.append(shortestNameFor(arrayType)).append(ARRAY_FLAG);
85      }
86  
87      /**
88       * Serializes the method signature onto the specified buffer.
89       *
90       * @param method Method
91       * @param sb StringBuilder
92       */
93      public static void asStringOn(Method method, StringBuilder sb) {
94  
95          Class<?> clazz = method.getDeclaringClass();
96  
97          sb.append(shortestNameFor(clazz));
98          sb.append(CLASS_METHOD_DELIMITER);
99          sb.append(method.getName());
100 
101         sb.append(METHOD_GROUP_DELIMITERS[0]);
102 
103         Class<?>[] argTypes = method.getParameterTypes();
104         if (argTypes.length == 0) {
105             sb.append(METHOD_GROUP_DELIMITERS[1]);
106             return;
107         }
108 
109         serializedTypeIdOn(argTypes[0], sb);
110         for (int i = 1; i < argTypes.length; i++) {
111             sb.append(METHOD_ARG_DELIMITER);
112             serializedTypeIdOn(argTypes[i], sb);
113         }
114         sb.append(METHOD_GROUP_DELIMITERS[1]);
115     }
116 
117     /**
118      * @param typeName String
119      * @return Class<?>
120      */
121     private static Class<?> typeFor(String typeName) {
122 
123         Class<?> type = null;
124 
125         if (typeName.endsWith(ARRAY_FLAG)) {
126             String arrayTypeName = typeName.substring(0, typeName.length() - ARRAY_FLAG.length());
127             type = typeFor(arrayTypeName); // recurse
128             return Array.newInstance(type, 0).getClass(); // TODO is there a
129                                                           // better way to get
130                                                           // an array type?
131         }
132 
133         type = ClassUtil.getTypeFor(typeName); // try shortcut first
134         if (type != null) {
135             return type;
136         }
137 
138         try {
139             return Class.forName(typeName);
140         } catch (Exception ex) {
141             return null;
142         }
143     }
144 
145     /**
146      * Returns the method specified within the string argument after parsing out
147      * its source class and any optional arguments. Callers need to specify the
148      * delimiters expected between the various elements. I.e.:
149      *
150      * "String#isEmpty()" "String#indexOf(int)" "String#substring(int,int)"
151      *
152      * If a method isn't part of the specified class we will walk up any
153      * superclasses to Object to try and find it.
154      *
155      * If the classes are listed in the ClassUtil class within in Typemaps then
156      * you likely can avoid specifying fully-qualified class names per the above
157      * example.
158      *
159      * Returns null if a matching method cannot be found.
160      *
161      * @param methodNameAndArgTypes
162      * @param classMethodDelimiter
163      * @param methodArgDelimiter
164      * @return Method
165      */
166     public static Method methodFrom(String methodNameAndArgTypes, char classMethodDelimiter, char methodArgDelimiter) {
167 
168         // classname#methodname(arg1,arg2)
169         // 0 1 2
170 
171         int delimPos0 = -1;
172         if (methodNameAndArgTypes != null) {
173             delimPos0 = methodNameAndArgTypes.indexOf(classMethodDelimiter);
174         } else {
175             return null;
176         }
177 
178         if (delimPos0 < 0) {
179             return null;
180         }
181 
182         String className = methodNameAndArgTypes.substring(0, delimPos0);
183         Class<?> type = ClassUtil.getTypeFor(className);
184         if (type == null) {
185             return null;
186         }
187 
188         int delimPos1 = methodNameAndArgTypes.indexOf(METHOD_GROUP_DELIMITERS[0]);
189         if (delimPos1 < 0) {
190             String methodName = methodNameAndArgTypes.substring(delimPos0 + 1);
191             return ClassUtil.methodFor(type, methodName, ClassUtil.EMPTY_CLASS_ARRAY);
192         }
193 
194         String methodName = methodNameAndArgTypes.substring(delimPos0 + 1, delimPos1);
195         if (StringUtil.isEmpty(methodName)) {
196             return null;
197         } // missing method name?
198 
199         int delimPos2 = methodNameAndArgTypes.indexOf(METHOD_GROUP_DELIMITERS[1]);
200         if (delimPos2 < 0) {
201             return null;
202         } // error!
203 
204         String argTypesStr = methodNameAndArgTypes.substring(delimPos1 + 1, delimPos2);
205         if (StringUtil.isEmpty(argTypesStr)) {
206             return ClassUtil.methodFor(type, methodName, ClassUtil.EMPTY_CLASS_ARRAY);
207         } // no arg(s)
208 
209         String[] argTypeNames = StringUtil.substringsOf(argTypesStr, methodArgDelimiter);
210         Class<?>[] argTypes = new Class[argTypeNames.length];
211         for (int i = 0; i < argTypes.length; i++) {
212             argTypes[i] = typeFor(argTypeNames[i]);
213         }
214 
215         return ClassUtil.methodFor(type, methodName, argTypes);
216     }
217 
218     /**
219      * @param methodStr String
220      * @return Method
221      */
222     public static Method methodFrom(String methodStr) {
223         return methodFrom(methodStr, CLASS_METHOD_DELIMITER, METHOD_ARG_DELIMITER);
224     }
225 
226     /**
227      * Constructor for MethodProperty.
228      *
229      * @param theName String
230      * @param theDescription String
231      * @param theDefault Method
232      * @param legalPackageNames String[]
233      * @param theUIOrder float
234      * @throws IllegalArgumentException
235      */
236     public MethodProperty(String theName, String theDescription, Method theDefault, String[] legalPackageNames,
237             float theUIOrder) {
238         super(theName, theDescription, theDefault, legalPackageNames, theUIOrder);
239     }
240 
241     /**
242      * Constructor for MethodProperty.
243      *
244      * @param theName String
245      * @param theDescription String
246      * @param defaultMethodStr String
247      * @param legalPackageNames String[]
248      * @param theUIOrder float
249      * @throws IllegalArgumentException
250      */
251     public MethodProperty(String theName, String theDescription, String defaultMethodStr, String[] legalPackageNames,
252             float theUIOrder) {
253         super(theName, theDescription, methodFrom(defaultMethodStr), legalPackageNames, theUIOrder);
254     }
255 
256     /**
257      * Constructor for MethodProperty.
258      *
259      * @param theName String
260      * @param theDescription String
261      * @param defaultMethodStr String
262      * @param otherParams Map<String, String>
263      * @param theUIOrder float
264      * @throws IllegalArgumentException
265      */
266     public MethodProperty(String theName, String theDescription, String defaultMethodStr,
267             Map<String, String> otherParams, float theUIOrder) {
268         this(theName, theDescription, methodFrom(defaultMethodStr), packageNamesIn(otherParams), theUIOrder);
269     }
270 
271     /**
272      * Return the value as a string that can be easily recognized and parsed
273      * when we see it again.
274      *
275      * @param value Object
276      * @return String
277      */
278     @Override
279     protected String asString(Object value) {
280         return value == null ? "" : asStringFor((Method) value);
281     }
282 
283     /**
284      * @param item Object
285      * @return String
286      */
287     @Override
288     protected String packageNameOf(Object item) {
289 
290         final Method method = (Method) item;
291         return method.getDeclaringClass().getName() + '.' + method.getName();
292     }
293 
294     /**
295      * @return String
296      */
297     @Override
298     protected String itemTypeName() {
299         return "method";
300     }
301 
302     /**
303      * @return Class
304      * @see net.sourceforge.pmd.PropertyDescriptor#type()
305      */
306     public Class<Method> type() {
307         return Method.class;
308     }
309 
310     /**
311      * @param valueString String
312      * @return Object
313      * @throws IllegalArgumentException
314      * @see net.sourceforge.pmd.PropertyDescriptor#valueFrom(String)
315      */
316     public Method valueFrom(String valueString) throws IllegalArgumentException {
317         return methodFrom(valueString);
318     }
319 }