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 rule.
17   *
18   * Rule developers can limit the rules to those within designated packages per the
19   * 'legalPackages' argument in the constructor which can be an array of partial
20   * package names, i.e., ["java.lang", "com.mycompany" ].
21   *
22   * @author Brian Remedios
23   */
24  public class MethodProperty extends AbstractPackagedProperty<Method> {
25  
26      public static final char CLASS_METHOD_DELIMITER = '#';
27      public static final char METHOD_ARG_DELIMITER = ',';
28      public static final char[] METHOD_GROUP_DELIMITERS = new char[] { '(', ')' };
29  
30      private static final String ARRAY_FLAG = "[]";
31      private static final Map<Class<?>, String> TYPE_SHORTCUTS = ClassUtil.getClassShortNames();
32  
33  	public static final PropertyDescriptorFactory FACTORY = new BasicPropertyDescriptorFactory<MethodProperty>(Method.class, packagedFieldTypesByKey) {
34  
35  		public MethodProperty createWith(Map<String, String> valuesById) {
36  			return new MethodProperty(
37  					nameIn(valuesById),
38  					descriptionIn(valuesById),
39  					defaultValueIn(valuesById),
40  					legalPackageNamesIn(valuesById),
41  					0f);
42  		}
43  	};
44      
45      /**
46       * @param cls Class<?>
47       * @return String
48       */
49      private static String shortestNameFor(Class<?> cls) {
50          String compactName = TYPE_SHORTCUTS.get(cls);
51          return compactName == null ? cls.getName() : compactName;
52      }
53  
54      /**
55       * Return the value of `method' as a string that can be easily recognized
56       * and parsed when we see it again.
57       *
58       * @param method the method to convert
59       * @return the string value
60       */
61      public static String asStringFor(Method method) {
62          StringBuilder sb = new StringBuilder();
63          asStringOn(method, sb);
64          return sb.toString();
65      }
66  
67      /**
68       * @return String
69       */
70      protected String defaultAsString() {
71          return asStringFor(defaultValue());
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 better way to get an array type?
129         }
130 
131         type = ClassUtil.getTypeFor(typeName); // try shortcut first
132         if (type != null) {
133             return type;
134         }
135 
136         try {
137             return Class.forName(typeName);
138         } catch (Exception ex) {
139             return null;
140         }
141     }
142 
143     /**
144      * Returns the method specified within the string argument after parsing out its source class and
145      * any optional arguments. Callers need to specify the delimiters expected between the various
146      * elements.  I.e.:
147      *
148      * 	"String#isEmpty()"
149      *  "String#indexOf(int)"
150      *  "String#substring(int,int)"
151      *
152      *  If a method isn't part of the specified class we will walk up any superclasses to Object to try
153      *  and find it.
154      *
155      *  If the classes are listed in the ClassUtil class within in Typemaps then you likely can avoid
156      *  specifying fully-qualified class names per the above example.
157      *
158      *  Returns null if a matching method cannot be found.
159      *
160      * @param methodNameAndArgTypes
161      * @param classMethodDelimiter
162      * @param methodArgDelimiter
163      * @return Method
164      */
165     public static Method methodFrom(String methodNameAndArgTypes, char classMethodDelimiter, char methodArgDelimiter) {
166 
167         // classname#methodname(arg1,arg2)
168         //          0          1         2
169 
170         int delimPos0 = methodNameAndArgTypes.indexOf(classMethodDelimiter);
171         if (delimPos0 < 0) {
172             return null;
173         }
174 
175         String className = methodNameAndArgTypes.substring(0, delimPos0);
176         Class<?> type = ClassUtil.getTypeFor(className);
177         if (type == null) {
178             return null;
179         }
180 
181         int delimPos1 = methodNameAndArgTypes.indexOf(METHOD_GROUP_DELIMITERS[0]);
182         if (delimPos1 < 0) {
183             String methodName = methodNameAndArgTypes.substring(delimPos0 + 1);
184             return ClassUtil.methodFor(type, methodName, ClassUtil.EMPTY_CLASS_ARRAY);
185         }
186 
187         String methodName = methodNameAndArgTypes.substring(delimPos0 + 1, delimPos1);
188         if (StringUtil.isEmpty(methodName)) {
189             return null;
190         } // missing method name?
191 
192         int delimPos2 = methodNameAndArgTypes.indexOf(METHOD_GROUP_DELIMITERS[1]);
193         if (delimPos2 < 0) {
194             return null;
195         } // error!
196 
197         String argTypesStr = methodNameAndArgTypes.substring(delimPos1 + 1, delimPos2);
198         if (StringUtil.isEmpty(argTypesStr)) {
199             return ClassUtil.methodFor(type, methodName, ClassUtil.EMPTY_CLASS_ARRAY);
200         } // no arg(s)
201 
202         String[] argTypeNames = StringUtil.substringsOf(argTypesStr, methodArgDelimiter);
203         Class<?>[] argTypes = new Class[argTypeNames.length];
204         for (int i = 0; i < argTypes.length; i++) {
205             argTypes[i] = typeFor(argTypeNames[i]);
206         }
207 
208         return ClassUtil.methodFor(type, methodName, argTypes);
209     }
210 
211     /**
212      * @param methodStr String
213      * @return Method
214      */
215     public static Method methodFrom(String methodStr) {
216     	return methodFrom(methodStr, CLASS_METHOD_DELIMITER, METHOD_ARG_DELIMITER);
217     }
218     
219     /**
220      * Constructor for MethodProperty.
221      *
222      * @param theName        String
223      * @param theDescription String
224      * @param theDefault     Method
225      * @param legalPackageNames String[]
226      * @param theUIOrder     float
227      * @throws IllegalArgumentException
228      */
229     public MethodProperty(String theName, String theDescription, Method theDefault, String[] legalPackageNames, float theUIOrder) {
230         super(theName, theDescription, theDefault, legalPackageNames, theUIOrder);
231     }
232 
233     /**
234      * Constructor for MethodProperty.
235      *
236      * @param theName        String
237      * @param theDescription String
238      * @param defaultMethodStr String
239      * @param legalPackageNames String[]
240      * @param theUIOrder     float
241      * @throws IllegalArgumentException
242      */
243     public MethodProperty(String theName, String theDescription, String defaultMethodStr, String[] legalPackageNames, float theUIOrder) {
244         super(theName, theDescription, methodFrom(defaultMethodStr), legalPackageNames, theUIOrder);
245     }
246     
247     /**
248      * Constructor for MethodProperty.
249      *
250      * @param theName        String
251      * @param theDescription String
252      * @param defaultMethodStr String
253      * @param otherParams Map<String, String>
254      * @param theUIOrder     float
255      * @throws IllegalArgumentException
256      */
257     public MethodProperty(String theName, String theDescription, String defaultMethodStr, Map<String, String> otherParams, float theUIOrder) {
258         this(theName, theDescription, methodFrom(defaultMethodStr), packageNamesIn(otherParams), theUIOrder);
259     }
260     
261     /**
262      * Return the value as a string that can be easily recognized and parsed
263      * when we see it again.
264      *
265      * @param value Object
266      * @return String
267      */
268     @Override
269     protected String asString(Object value) {
270         return value == null ? "" : asStringFor((Method) value);
271     }
272 
273     /**
274      * @param item Object
275      * @return String
276      */
277     @Override
278     protected String packageNameOf(Object item) {
279 
280         final Method method = (Method) item;
281         return method.getDeclaringClass().getName() + '.' + method.getName();
282     }
283 
284     /**
285      * @return String
286      */
287     @Override
288     protected String itemTypeName() {
289         return "method";
290     }
291 
292     /**
293      * @return Class
294      * @see net.sourceforge.pmd.PropertyDescriptor#type()
295      */
296     public Class<Method> type() {
297         return Method.class;
298     }
299 
300     /**
301      * @param valueString  String
302      * @return Object
303      * @throws IllegalArgumentException
304      * @see net.sourceforge.pmd.PropertyDescriptor#valueFrom(String)
305      */
306     public Method valueFrom(String valueString) throws IllegalArgumentException {
307        return methodFrom(valueString);
308     }
309 }