1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package de.bea.domingo.map;
31
32 import java.lang.reflect.Constructor;
33 import java.lang.reflect.Member;
34 import java.lang.reflect.Method;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.HashMap;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.Map;
41
42 /***
43 * Finds methods and constructors that can be invoked by reflection.
44 * Attempts to address some of the limitations of the JDK's
45 * Class.getMethod() and Class.getConstructor(), and other JDK
46 * reflective facilities.
47 */
48 public final class MethodFinder {
49
50 /*** The target class to look for methods and constructors in. */
51 private final Class clazz;
52
53 /*** Mapping from method name to the Methods in the target class with that name. */
54 private final Map methodMap = new HashMap();
55
56 /*** List of the Constructors in the target class. */
57 private final List ctorList = new ArrayList();
58
59 /*** Mapping from a Constructor or Method object to the Class objects representing its formal parameters. */
60 private final Map paramMap = new HashMap();
61
62 /***
63 * @param clazz Class in which I will look for methods and constructors
64 * throws IllegalArgumentException if clazz is null, or represents a primitive, or represents an array type
65 */
66 public MethodFinder(final Class clazz) {
67 if (clazz == null) {
68 throw new IllegalArgumentException("null Class parameter");
69 }
70 if (clazz.isPrimitive()) {
71 throw new IllegalArgumentException("primitive Class parameter");
72 }
73 if (clazz.isArray()) {
74 throw new IllegalArgumentException("array Class parameter");
75 }
76 this.clazz = clazz;
77 loadMethods();
78 loadConstructors();
79 }
80
81 /***
82 * {@inheritDoc}
83 *
84 * @see java.lang.Object#equals(java.lang.Object)
85 */
86 public boolean equals(final Object o) {
87 if (this == o) {
88 return true;
89 } else if (o == null || getClass() != o.getClass()) {
90 return false;
91 } else {
92 MethodFinder other = (MethodFinder) o;
93 return clazz.equals(other.clazz);
94 }
95 }
96
97 /***
98 * Returns the most specific public constructor in my target class that
99 * accepts the number and type of parameters in the given Class array in a
100 * reflective invocation. <p> A null value or Void.TYPE in parameterTypes
101 * matches a corresponding Object or array reference in a constructor's
102 * formal parameter list, but not a primitive formal parameter.
103 *
104 * @param parameterTypes array representing the number and types of
105 * parameters to look for in the constructor's signature. A null
106 * array is treated as a zero-length array.
107 * @return Constructor object satisfying the conditions
108 * @throws NoSuchMethodException if no constructors match the criteria,
109 * or if the reflective call is ambiguous based on the
110 * parameter types
111 */
112 public Constructor findConstructor(final Class[] parameterTypes) throws NoSuchMethodException {
113 return (Constructor) findMemberIn(ctorList, parameterTypes == null ? new Class[0] : parameterTypes);
114 }
115
116 /***
117 * Basis of findConstructor() and findMethod(). The member list fed to this
118 * method will be either all Constructor objects or all Method objects.
119 */
120 private Member findMemberIn(final List memberList, final Class[] parameterTypes) throws NoSuchMethodException {
121 List matchingMembers = new ArrayList();
122 for (Iterator it = memberList.iterator(); it.hasNext();) {
123 Member member = (Member) it.next();
124 Class[] methodParamTypes = (Class[]) paramMap.get(member);
125
126 if (Arrays.equals(methodParamTypes, parameterTypes)) {
127 return member;
128 }
129 if (ClassUtilities.compatibleClasses(methodParamTypes, parameterTypes)) {
130 matchingMembers.add(member);
131 }
132 }
133 if (matchingMembers.isEmpty()) {
134 throw new NoSuchMethodException("no member in " + clazz.getName() + " matching given args");
135 }
136 if (matchingMembers.size() == 1) {
137 return (Member) matchingMembers.get(0);
138 }
139 return findMostSpecificMemberIn(matchingMembers);
140 }
141
142 /***
143 * Returns the most specific public method in my target class that has the
144 * given name and accepts the number and type of parameters in the given
145 * Class array in a reflective invocation. <p> A null value or Void.TYPE in
146 * parameterTypes will match a corresponding Object or array reference in a
147 * method's formal parameter list, but not a primitive formal parameter.
148 *
149 * @param methodName name of the method to search for
150 * @param parameterTypes array representing the number and types of
151 * parameters to look for in the method's signature. A null array
152 * is treated as a zero-length array.
153 * @return Method object satisfying the conditions
154 * @throws NoSuchMethodException if no methods match the criteria, or if
155 * the reflective call is ambiguous based on the parameter
156 * types, or if methodName is null
157 */
158 public Method findMethod(final String methodName, final Class[] parameterTypes) throws NoSuchMethodException {
159 List methodList = (List) methodMap.get(methodName);
160 if (methodList == null) {
161 throw new NoSuchMethodException("no method named " + clazz.getName() + "." + methodName);
162 }
163 return (Method) findMemberIn(methodList, parameterTypes == null ? new Class[0] : parameterTypes);
164 }
165
166 /***
167 * @param a List of Members (either all Constructors or all Methods)
168 * @return the most specific of all Members in the list
169 * @throws NoSuchMethodException if there is an ambiguity as to which is
170 * most specific
171 */
172 private Member findMostSpecificMemberIn(final List memberList) throws NoSuchMethodException {
173 List mostSpecificMembers = new ArrayList();
174
175 for (Iterator memberIt = memberList.iterator(); memberIt.hasNext();) {
176 Member member = (Member) memberIt.next();
177
178 if (mostSpecificMembers.isEmpty()) {
179
180 mostSpecificMembers.add(member);
181 } else {
182 boolean moreSpecific = true;
183 boolean lessSpecific = false;
184
185
186
187 for (Iterator specificIt = mostSpecificMembers.iterator(); specificIt.hasNext();) {
188 Member moreSpecificMember = (Member) specificIt.next();
189
190 if (!memberIsMoreSpecific(member, moreSpecificMember)) {
191
192
193
194
195
196
197
198
199
200
201 moreSpecific = false;
202 lessSpecific = memberIsMoreSpecific(moreSpecificMember, member);
203 break;
204 }
205 }
206
207 if (moreSpecific) {
208
209 mostSpecificMembers.clear();
210 mostSpecificMembers.add(member);
211 } else if (!lessSpecific) {
212
213 mostSpecificMembers.add(member);
214 }
215 }
216 }
217
218 if (mostSpecificMembers.size() > 1) {
219 throw new NoSuchMethodException("Ambiguous request for member in " + this.clazz.getName() + " matching given args");
220 }
221
222 return (Member) mostSpecificMembers.get(0);
223 }
224
225 /***
226 * @param args an Object array
227 * @return an array of Class objects representing the classes of the objects
228 * in the given Object array. If args is null, a zero-length Class
229 * array is returned. If an element in args is null, then Void.TYPE
230 * is the corresponding Class in the return array.
231 */
232 public static Class[] getParameterTypesFrom(final Object[] args) {
233 if (args == null) {
234 return new Class[0];
235 }
236 Class[] argTypes = new Class[args.length];
237 for (int i = 0; i < args.length; ++i) {
238 argTypes[i] = (args[i] == null) ? Void.TYPE : args[i].getClass();
239 }
240 return argTypes;
241 }
242
243 /***
244 * @param classNames String array of fully qualified names (FQNs) of classes
245 * or primitives. Represent an array type by using its JVM type
246 * descriptor, with dots instead of slashes (e.g. represent the
247 * type int[] with "[I", and Object[][] with
248 * "[[Ljava.lang.Object;").
249 * @return an array of Class objects representing the classes or primitives
250 * named by the FQNs in the given String array. If the String array
251 * is null, a zero-length Class array is returned. If an element in
252 * classNames is null, the empty string, "void", or "null", then
253 * Void.TYPE is the corresponding Class in the return array. If any
254 * classes require loading because of this operation, the loading is
255 * done by the ClassLoader that loaded this class. Such classes are
256 * not initialized, however.
257 * @throws ClassNotFoundException if any of the FQNs name an unknown
258 * class
259 */
260 public static Class[] getParameterTypesFrom(final String[] classNames) throws ClassNotFoundException {
261 return getParameterTypesFrom(classNames, MethodFinder.class.getClassLoader());
262 }
263
264 /***
265 * @param classNames String array of fully qualified names (FQNs) of classes
266 * or primitives. Represent an array type by using its JVM type
267 * descriptor, with dots instead of slashes (e.g. represent the
268 * type int[] with "[I", and Object[][] with
269 * "[[Ljava.lang.Object;").
270 * @param loader a ClassLoader
271 * @return an array of Class objects representing the classes or primitives
272 * named by the FQNs in the given String array. If the String array
273 * is null, a zero-length Class array is returned. If an element in
274 * classNames is null, the empty string, "void", or "null", then
275 * Void.TYPE is the corresponding Class in the return array. If any
276 * classes require loading because of this operation, the loading is
277 * done by the given ClassLoader. Such classes are not initialized,
278 * however.
279 * @throws ClassNotFoundException if any of the FQNs name an unknown
280 * class
281 */
282 public static Class[] getParameterTypesFrom(final String[] classNames, final ClassLoader loader)
283 throws ClassNotFoundException {
284 if (classNames == null) {
285 return new Class[0];
286 }
287 Class[] types = new Class[classNames.length];
288 for (int i = 0; i < classNames.length; ++i) {
289 types[i] = ClassUtilities.classForNameOrPrimitive(classNames[i], loader);
290 }
291 return types;
292 }
293
294 /***
295 * {@inheritDoc}
296 * @see java.lang.Object#hashCode()
297 */
298 public int hashCode() {
299 return clazz.hashCode();
300 }
301
302 /***
303 * Loads up the data structures for my target class's constructors.
304 */
305 private void loadConstructors() {
306 Constructor[] ctors = clazz.getConstructors();
307 for (int i = 0; i < ctors.length; ++i) {
308 ctorList.add(ctors[i]);
309 paramMap.put(ctors[i], ctors[i].getParameterTypes());
310 }
311 }
312
313 /***
314 * Loads up the data structures for my target class's methods.
315 */
316 private void loadMethods() {
317
318 List allMethods = getAllMethods();
319 Method[] methods = (Method[]) allMethods.toArray(new Method[allMethods.size()]);
320 for (int i = 0; i < methods.length; ++i) {
321 Method m = methods[i];
322 String methodName = m.getName();
323 Class[] paramTypes = m.getParameterTypes();
324 List list = (List) methodMap.get(methodName);
325 if (list == null) {
326 list = new ArrayList();
327 methodMap.put(methodName, list);
328 }
329 if (!ClassUtilities.classIsAccessible(clazz)) {
330 m = ClassUtilities.getAccessibleMethodFrom(clazz, methodName, paramTypes);
331 }
332 if (m != null) {
333 list.add(m);
334 paramMap.put(m, paramTypes);
335 }
336 }
337 }
338
339 private List getAllMethods() {
340 List allMethods = new ArrayList();
341 Class c = clazz;
342 while ((c != null)) {
343 Method[] methods = c.getDeclaredMethods();
344 List list = null;
345 if (methods != null) {
346 list = Arrays.asList(methods);
347 }
348 if (list != null) {
349 allMethods.addAll(list);
350 }
351 c = c.getSuperclass();
352 }
353 return allMethods;
354 }
355
356 /***
357 * @param first a Member
358 * @param second a Member
359 * @return true if the first Member is more specific than the second,
360 * false otherwise. Specificity is determined according to the
361 * procedure in the Java Language Specification, section 15.12.2.
362 */
363 private boolean memberIsMoreSpecific(final Member first, final Member second) {
364 Class[] firstParamTypes = (Class[]) paramMap.get(first);
365 Class[] secondParamTypes = (Class[]) paramMap.get(second);
366 return ClassUtilities.compatibleClasses(secondParamTypes, firstParamTypes);
367 }
368 }