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((Method) value);
243 }
244
245 /**
246 * @param item Object
247 * @return String
248 */
249 @Override
250 protected String packageNameOf(Object item) {
251
252 final Method method = (Method) item;
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 valueString) throws IllegalArgumentException {
279 return methodFrom(valueString);
280 }
281 }
|