View Javadoc

1   /*
2    * Copyright 2001,2004 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.commons.jexl.util.introspection;
18  
19  import java.lang.reflect.Method;
20  import java.util.ArrayList;
21  import java.util.Hashtable;
22  import java.util.Iterator;
23  import java.util.LinkedList;
24  import java.util.List;
25  import java.util.Map;
26  
27  /***
28   * 
29   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
30   * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
31   * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
32   * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
33   * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
34   * @since 1.0
35   * @version $Id: MethodMap.java 398495 2006-05-01 01:33:43Z dion $
36   */
37  public class MethodMap {
38      /*** whether a method is more specific than a previously compared one. */
39      private static final int MORE_SPECIFIC = 0;
40      /*** whether a method is less specific than a previously compared one. */
41      private static final int LESS_SPECIFIC = 1;
42      /*** A method doesn't match a previously compared one. */
43      private static final int INCOMPARABLE = 2;
44  
45      /***
46       * Keep track of all methods with the same name.
47       */
48      protected Map methodByNameMap = new Hashtable();
49  
50      /***
51       * Add a method to a list of methods by name. For a particular class we are
52       * keeping track of all the methods with the same name.
53       * @param method the method.
54       */
55      public void add(Method method) {
56          String methodName = method.getName();
57  
58          List l = get(methodName);
59  
60          if (l == null) {
61              l = new ArrayList();
62              methodByNameMap.put(methodName, l);
63          }
64  
65          l.add(method);
66      }
67  
68      /***
69       * Return a list of methods with the same name.
70       * 
71       * @param key The method name.
72       * @return List list of methods
73       */
74      public List get(String key) {
75          return (List) methodByNameMap.get(key);
76      }
77  
78      /***
79       * <p>
80       * Find a method. Attempts to find the most specific applicable method using
81       * the algorithm described in the JLS section 15.12.2 (with the exception
82       * that it can't distinguish a primitive type argument from an object type
83       * argument, since in reflection primitive type arguments are represented by
84       * their object counterparts, so for an argument of type (say)
85       * java.lang.Integer, it will not be able to decide between a method that
86       * takes int and a method that takes java.lang.Integer as a parameter.
87       * </p>
88       * 
89       * <p>
90       * This turns out to be a relatively rare case where this is needed -
91       * however, functionality like this is needed.
92       * </p>
93       * 
94       * @param methodName name of method
95       * @param args the actual arguments with which the method is called
96       * @return the most specific applicable method, or null if no method is
97       *         applicable.
98       * @throws AmbiguousException if there is more than one maximally specific
99       *             applicable method
100      */
101     public Method find(String methodName, Object[] args) throws AmbiguousException {
102         List methodList = get(methodName);
103 
104         if (methodList == null) {
105             return null;
106         }
107 
108         int l = args.length;
109         Class[] classes = new Class[l];
110 
111         for (int i = 0; i < l; ++i) {
112             Object arg = args[i];
113 
114             /*
115              * if we are careful down below, a null argument goes in there so we
116              * can know that the null was passed to the method
117              */
118             classes[i] = arg == null ? null : arg.getClass();
119         }
120 
121         return getMostSpecific(methodList, classes);
122     }
123 
124     /***
125      * simple distinguishable exception, used when we run across ambiguous
126      * overloading.
127      */
128     public static class AmbiguousException extends Exception {
129         /*** serialization version id jdk13 generated. */
130         static final long serialVersionUID = 8758118091728717367L;
131     }
132 
133     /***
134      * Gets the most specific method from a list.
135      * @param methods list of {@link Method methods}
136      * @param classes argument types
137      * @return the most specific method, or null
138      * @throws AmbiguousException if there is more than one specific method
139      */
140     private static Method getMostSpecific(List methods, Class[] classes) throws AmbiguousException {
141         LinkedList applicables = getApplicables(methods, classes);
142 
143         if (applicables.isEmpty()) {
144             return null;
145         }
146 
147         if (applicables.size() == 1) {
148             return (Method) applicables.getFirst();
149         }
150 
151         /*
152          * This list will contain the maximally specific methods. Hopefully at
153          * the end of the below loop, the list will contain exactly one method,
154          * (the most specific method) otherwise we have ambiguity.
155          */
156 
157         LinkedList maximals = new LinkedList();
158 
159         for (Iterator applicable = applicables.iterator(); applicable.hasNext();) {
160             Method app = (Method) applicable.next();
161             Class[] appArgs = app.getParameterTypes();
162             boolean lessSpecific = false;
163 
164             for (Iterator maximal = maximals.iterator(); !lessSpecific && maximal.hasNext();) {
165                 Method max = (Method) maximal.next();
166 
167                 switch (moreSpecific(appArgs, max.getParameterTypes())) {
168                     case MORE_SPECIFIC:
169                         /*
170                          * This method is more specific than the previously
171                          * known maximally specific, so remove the old maximum.
172                          */
173                         maximal.remove();
174                         break;
175 
176                     case LESS_SPECIFIC:
177                         /*
178                          * This method is less specific than some of the
179                          * currently known maximally specific methods, so we
180                          * won't add it into the set of maximally specific
181                          * methods
182                          */
183                         lessSpecific = true;
184                         break;
185                 }
186             }
187 
188             if (!lessSpecific) {
189                 maximals.addLast(app);
190             }
191         }
192 
193         if (maximals.size() > 1) {
194             // We have more than one maximally specific method
195             throw new AmbiguousException();
196         }
197 
198         return (Method) maximals.getFirst();
199     }
200 
201     /***
202      * Determines which method signature (represented by a class array) is more
203      * specific. This defines a partial ordering on the method signatures.
204      * 
205      * @param c1 first signature to compare
206      * @param c2 second signature to compare
207      * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if c1
208      *         is less specific than c2, INCOMPARABLE if they are incomparable.
209      */
210     private static int moreSpecific(Class[] c1, Class[] c2) {
211         boolean c1MoreSpecific = false;
212         boolean c2MoreSpecific = false;
213 
214         for (int i = 0; i < c1.length; ++i) {
215             if (c1[i] != c2[i]) {
216                 c1MoreSpecific = c1MoreSpecific || isStrictMethodInvocationConvertible(c2[i], c1[i]);
217                 c2MoreSpecific = c2MoreSpecific || isStrictMethodInvocationConvertible(c1[i], c2[i]);
218             }
219         }
220 
221         if (c1MoreSpecific) {
222             if (c2MoreSpecific) {
223                 /*
224                  * Incomparable due to cross-assignable arguments (i.e.
225                  * foo(String, Object) vs. foo(Object, String))
226                  */
227 
228                 return INCOMPARABLE;
229             }
230 
231             return MORE_SPECIFIC;
232         }
233 
234         if (c2MoreSpecific) {
235             return LESS_SPECIFIC;
236         }
237 
238         /*
239          * Incomparable due to non-related arguments (i.e. foo(Runnable) vs.
240          * foo(Serializable))
241          */
242 
243         return INCOMPARABLE;
244     }
245 
246     /***
247      * Returns all methods that are applicable to actual argument types.
248      * 
249      * @param methods list of all candidate methods
250      * @param classes the actual types of the arguments
251      * @return a list that contains only applicable methods (number of formal
252      *         and actual arguments matches, and argument types are assignable
253      *         to formal types through a method invocation conversion).
254      */
255     private static LinkedList getApplicables(List methods, Class[] classes) {
256         LinkedList list = new LinkedList();
257 
258         for (Iterator imethod = methods.iterator(); imethod.hasNext();) {
259             Method method = (Method) imethod.next();
260 
261             if (isApplicable(method, classes)) {
262                 list.add(method);
263             }
264 
265         }
266         return list;
267     }
268 
269     /***
270      * Returns true if the supplied method is applicable to actual argument
271      * types.
272      * @param method the method to check
273      * @param classes possible argument types
274      * @return true if the arguments are applicable to the method.
275      */
276     private static boolean isApplicable(Method method, Class[] classes) {
277         Class[] methodArgs = method.getParameterTypes();
278 
279         if (methodArgs.length != classes.length) {
280             return false;
281         }
282 
283         for (int i = 0; i < classes.length; ++i) {
284             if (!isMethodInvocationConvertible(methodArgs[i], classes[i])) {
285                 return false;
286             }
287         }
288 
289         return true;
290     }
291 
292     /***
293      * Determines whether a type represented by a class object is convertible to
294      * another type represented by a class object using a method invocation
295      * conversion, treating object types of primitive types as if they were
296      * primitive types (that is, a Boolean actual parameter type matches boolean
297      * primitive formal type). This behavior is because this method is used to
298      * determine applicable methods for an actual parameter list, and primitive
299      * types are represented by their object duals in reflective method calls.
300      * 
301      * @param formal the formal parameter type to which the actual parameter
302      *            type should be convertible
303      * @param actual the actual parameter type.
304      * @return true if either formal type is assignable from actual type, or
305      *         formal is a primitive type and actual is its corresponding object
306      *         type or an object type of a primitive type that can be converted
307      *         to the formal type.
308      */
309     private static boolean isMethodInvocationConvertible(Class formal, Class actual) {
310         /*
311          * if it's a null, it means the arg was null
312          */
313         if (actual == null && !formal.isPrimitive()) {
314             return true;
315         }
316 
317         /*
318          * Check for identity or widening reference conversion
319          */
320 
321         if (actual != null && formal.isAssignableFrom(actual)) {
322             return true;
323         }
324 
325         /*
326          * Check for boxing with widening primitive conversion. Note that actual
327          * parameters are never primitives.
328          */
329 
330         if (formal.isPrimitive()) {
331             if (formal == Boolean.TYPE && actual == Boolean.class) {
332                 return true;
333             }
334             if (formal == Character.TYPE && actual == Character.class) {
335                 return true;
336             }
337             if (formal == Byte.TYPE && actual == Byte.class) {
338                 return true;
339             }
340             if (formal == Short.TYPE && (actual == Short.class || actual == Byte.class)) {
341                 return true;
342             }
343             if (formal == Integer.TYPE && (actual == Integer.class || actual == Short.class || actual == Byte.class)) {
344                 return true;
345             }
346             if (formal == Long.TYPE
347                 && (actual == Long.class || actual == Integer.class || actual == Short.class || actual == Byte.class)) {
348                 return true;
349             }
350             if (formal == Float.TYPE
351                 && (actual == Float.class || actual == Long.class || actual == Integer.class 
352                     || actual == Short.class || actual == Byte.class)) {
353                 return true;
354             }
355             if (formal == Double.TYPE
356                 && (actual == Double.class || actual == Float.class || actual == Long.class || actual == Integer.class
357                     || actual == Short.class || actual == Byte.class)) {
358                 return true;
359             }
360         }
361 
362         return false;
363     }
364 
365     /***
366      * Determines whether a type represented by a class object is convertible to
367      * another type represented by a class object using a method invocation
368      * conversion, without matching object and primitive types. This method is
369      * used to determine the more specific type when comparing signatures of
370      * methods.
371      * 
372      * @param formal the formal parameter type to which the actual parameter
373      *            type should be convertible
374      * @param actual the actual parameter type.
375      * @return true if either formal type is assignable from actual type, or
376      *         formal and actual are both primitive types and actual can be
377      *         subject to widening conversion to formal.
378      */
379     private static boolean isStrictMethodInvocationConvertible(Class formal, Class actual) {
380         /*
381          * we shouldn't get a null into, but if so
382          */
383         if (actual == null && !formal.isPrimitive()) {
384             return true;
385         }
386 
387         /*
388          * Check for identity or widening reference conversion
389          */
390 
391         if (formal.isAssignableFrom(actual)) {
392             return true;
393         }
394 
395         /*
396          * Check for widening primitive conversion.
397          */
398 
399         if (formal.isPrimitive()) {
400             if (formal == Short.TYPE && (actual == Byte.TYPE)) {
401                 return true;
402             }
403             if (formal == Integer.TYPE && (actual == Short.TYPE || actual == Byte.TYPE)) {
404                 return true;
405             }
406             if (formal == Long.TYPE && (actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE)) {
407                 return true;
408             }
409             if (formal == Float.TYPE
410                 && (actual == Long.TYPE || actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE)) {
411                 return true;
412             }
413             if (formal == Double.TYPE
414                 && (actual == Float.TYPE || actual == Long.TYPE || actual == Integer.TYPE || actual == Short.TYPE 
415                     || actual == Byte.TYPE)) {
416                 return true;
417             }
418         }
419         return false;
420     }
421 }