View Javadoc

1   /*
2    * This file is part of Domingo
3    * an Open Source Java-API to Lotus Notes/Domino
4    * hosted at http://domingo.sourceforge.net
5    *
6    * Copyright (c) 2003-2007 Beck et al. projects GmbH Munich, Germany (http://www.bea.de)
7    *
8    * This library is free software; you can redistribute it and/or
9    * modify it under the terms of the GNU Lesser General Public
10   * License as published by the Free Software Foundation; either
11   * version 2.1 of the License, or (at your option) any later version.
12   *
13   * This library is distributed in the hope that it will be useful,
14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   * Lesser General Public License for more details.
17   *
18   * You should have received a copy of the GNU Lesser General Public
19   * License along with this library; if not, write to the Free Software
20   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21   */
22  
23  /* Origin:
24   * From <http://www.adtmag.com/java/article.aspx?id=4276>
25   * Original license- public domain? code published in article
26   * with changes by Ronny Brandt, see: <http://sourceforge.net/projects/dresden-ocl>
27   * dresden-ocl is licensed GNU LGPL 2.1 or any later version
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                 // First guy in is the most specific so far.
180                 mostSpecificMembers.add(member);
181             } else {
182                 boolean moreSpecific = true;
183                 boolean lessSpecific = false;
184 
185                 // Is member more specific than everyone in the most-specific
186                 // set?
187                 for (Iterator specificIt = mostSpecificMembers.iterator(); specificIt.hasNext();) {
188                     Member moreSpecificMember = (Member) specificIt.next();
189 
190                     if (!memberIsMoreSpecific(member, moreSpecificMember)) {
191                         /*
192                          * Can't be more specific than the whole set. Bail out,
193                          * and mark whether member is less specific than the
194                          * member under consideration. If it is less specific,
195                          * it need not be added to the ambiguity set. This is no
196                          * guarantee of not getting added to the ambiguity
197                          * set...we're just not clever enough yet to make that
198                          * assessment.
199                          */
200 
201                         moreSpecific = false;
202                         lessSpecific = memberIsMoreSpecific(moreSpecificMember, member);
203                         break;
204                     }
205                 }
206 
207                 if (moreSpecific) {
208                     // Member is the most specific now.
209                     mostSpecificMembers.clear();
210                     mostSpecificMembers.add(member);
211                 } else if (!lessSpecific) {
212                     // Add to ambiguity set if mutually unspecific.
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         // Method[] methods = clazz.getMethods();
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 }