1 package org.apache.velocity.util.introspection;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.lang.reflect.Method;
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.LinkedList;
27 import java.util.List;
28 import java.util.Map;
29
30 /**
31 *
32 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
33 * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
34 * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
35 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
36 * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
37 * @version $Id: MethodMap.java 679922 2008-07-25 22:58:13Z nbubna $
38 */
39 public class MethodMap
40 {
41 private static final int MORE_SPECIFIC = 0;
42 private static final int LESS_SPECIFIC = 1;
43 private static final int INCOMPARABLE = 2;
44
45 /**
46 * Keep track of all methods with the same name.
47 */
48 Map methodByNameMap = new HashMap();
49
50 /**
51 * Add a method to a list of methods by name.
52 * For a particular class we are keeping track
53 * of all the methods with the same name.
54 * @param method
55 */
56 public void add(Method method)
57 {
58 String methodName = method.getName();
59
60 List l = get( methodName );
61
62 if ( l == null)
63 {
64 l = new ArrayList();
65 methodByNameMap.put(methodName, l);
66 }
67
68 l.add(method);
69 }
70
71 /**
72 * Return a list of methods with the same name.
73 *
74 * @param key
75 * @return List list of methods
76 */
77 public List get(String key)
78 {
79 return (List) methodByNameMap.get(key);
80 }
81
82 /**
83 * <p>
84 * Find a method. Attempts to find the
85 * most specific applicable method using the
86 * algorithm described in the JLS section
87 * 15.12.2 (with the exception that it can't
88 * distinguish a primitive type argument from
89 * an object type argument, since in reflection
90 * primitive type arguments are represented by
91 * their object counterparts, so for an argument of
92 * type (say) java.lang.Integer, it will not be able
93 * to decide between a method that takes int and a
94 * method that takes java.lang.Integer as a parameter.
95 * </p>
96 *
97 * <p>
98 * This turns out to be a relatively rare case
99 * where this is needed - however, functionality
100 * like this is needed.
101 * </p>
102 *
103 * @param methodName name of method
104 * @param args the actual arguments with which the method is called
105 * @return the most specific applicable method, or null if no
106 * method is applicable.
107 * @throws AmbiguousException if there is more than one maximally
108 * specific applicable method
109 */
110 public Method find(String methodName, Object[] args)
111 throws AmbiguousException
112 {
113 List methodList = get(methodName);
114
115 if (methodList == null)
116 {
117 return null;
118 }
119
120 int l = args.length;
121 Class[] classes = new Class[l];
122
123 for(int i = 0; i < l; ++i)
124 {
125 Object arg = args[i];
126
127 /*
128 * if we are careful down below, a null argument goes in there
129 * so we can know that the null was passed to the method
130 */
131 classes[i] =
132 arg == null ? null : arg.getClass();
133 }
134
135 return getBestMatch(methodList, classes);
136 }
137
138 private static Method getBestMatch(List methods, Class[] args)
139 {
140 List equivalentMatches = null;
141 Method bestMatch = null;
142 Class[] bestMatchTypes = null;
143 for (Iterator i = methods.iterator(); i.hasNext(); )
144 {
145 Method method = (Method)i.next();
146 if (isApplicable(method, args))
147 {
148 if (bestMatch == null)
149 {
150 bestMatch = method;
151 bestMatchTypes = method.getParameterTypes();
152 }
153 else
154 {
155 Class[] methodTypes = method.getParameterTypes();
156 switch (compare(methodTypes, bestMatchTypes))
157 {
158 case MORE_SPECIFIC:
159 if (equivalentMatches == null)
160 {
161 bestMatch = method;
162 bestMatchTypes = methodTypes;
163 }
164 else
165 {
166 // have to beat all other ambiguous ones...
167 int ambiguities = equivalentMatches.size();
168 for (int a=0; a < ambiguities; a++)
169 {
170 Method other = (Method)equivalentMatches.get(a);
171 switch (compare(methodTypes, other.getParameterTypes()))
172 {
173 case MORE_SPECIFIC:
174 // ...and thus replace them all...
175 bestMatch = method;
176 bestMatchTypes = methodTypes;
177 equivalentMatches = null;
178 ambiguities = 0;
179 break;
180
181 case INCOMPARABLE:
182 // ...join them...
183 equivalentMatches.add(method);
184 break;
185
186 case LESS_SPECIFIC:
187 // ...or just go away.
188 break;
189 }
190 }
191 }
192 break;
193
194 case INCOMPARABLE:
195 if (equivalentMatches == null)
196 {
197 equivalentMatches = new ArrayList(bestMatchTypes.length);
198 }
199 equivalentMatches.add(method);
200 break;
201
202 case LESS_SPECIFIC:
203 // do nothing
204 break;
205 }
206 }
207 }
208 }
209
210 if (equivalentMatches != null)
211 {
212 throw new AmbiguousException();
213 }
214 return bestMatch;
215 }
216
217 /**
218 * Simple distinguishable exception, used when
219 * we run across ambiguous overloading. Caught
220 * by the introspector.
221 */
222 public static class AmbiguousException extends RuntimeException
223 {
224 /**
225 * Version Id for serializable
226 */
227 private static final long serialVersionUID = -2314636505414551663L;
228 }
229
230 /**
231 * Determines which method signature (represented by a class array) is more
232 * specific. This defines a partial ordering on the method signatures.
233 * @param c1 first signature to compare
234 * @param c2 second signature to compare
235 * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
236 * c1 is less specific than c2, INCOMPARABLE if they are incomparable.
237 */
238 private static int compare(Class[] c1, Class[] c2)
239 {
240 boolean c1MoreSpecific = false;
241 boolean c2MoreSpecific = false;
242
243 // compare lengths to handle comparisons where the size of the arrays
244 // doesn't match, but the methods are both applicable due to the fact
245 // that one is a varargs method
246 if (c1.length > c2.length)
247 {
248 return MORE_SPECIFIC;
249 }
250 if (c2.length > c1.length)
251 {
252 return LESS_SPECIFIC;
253 }
254
255 // ok, move on and compare those of equal lengths
256 for(int i = 0; i < c1.length; ++i)
257 {
258 if(c1[i] != c2[i])
259 {
260 boolean last = (i == c1.length - 1);
261 c1MoreSpecific =
262 c1MoreSpecific ||
263 isStrictConvertible(c2[i], c1[i], last);
264 c2MoreSpecific =
265 c2MoreSpecific ||
266 isStrictConvertible(c1[i], c2[i], last);
267 }
268 }
269
270 if(c1MoreSpecific)
271 {
272 if(c2MoreSpecific)
273 {
274 /*
275 * If one method accepts varargs and the other does not,
276 * call the non-vararg one more specific.
277 */
278 boolean last1Array = c1[c1.length - 1].isArray();
279 boolean last2Array = c2[c2.length - 1].isArray();
280 if (last1Array && !last2Array)
281 {
282 return LESS_SPECIFIC;
283 }
284 if (!last1Array && last2Array)
285 {
286 return MORE_SPECIFIC;
287 }
288
289 /*
290 * Incomparable due to cross-assignable arguments (i.e.
291 * foo(String, Object) vs. foo(Object, String))
292 */
293 return INCOMPARABLE;
294 }
295
296 return MORE_SPECIFIC;
297 }
298
299 if(c2MoreSpecific)
300 {
301 return LESS_SPECIFIC;
302 }
303
304 /*
305 * Incomparable due to non-related arguments (i.e.
306 * foo(Runnable) vs. foo(Serializable))
307 */
308
309 return INCOMPARABLE;
310 }
311
312 /**
313 * Returns true if the supplied method is applicable to actual
314 * argument types.
315 *
316 * @param method method that will be called
317 * @param classes arguments to method
318 * @return true if method is applicable to arguments
319 */
320 private static boolean isApplicable(Method method, Class[] classes)
321 {
322 Class[] methodArgs = method.getParameterTypes();
323
324 if (methodArgs.length > classes.length)
325 {
326 // if there's just one more methodArg than class arg
327 // and the last methodArg is an array, then treat it as a vararg
328 if (methodArgs.length == classes.length + 1 &&
329 methodArgs[methodArgs.length - 1].isArray())
330 {
331 return true;
332 }
333 else
334 {
335 return false;
336 }
337 }
338 else if (methodArgs.length == classes.length)
339 {
340 // this will properly match when the last methodArg
341 // is an array/varargs and the last class is the type of array
342 // (e.g. String when the method is expecting String...)
343 for(int i = 0; i < classes.length; ++i)
344 {
345 if(!isConvertible(methodArgs[i], classes[i], false))
346 {
347 // if we're on the last arg and the method expects an array
348 if (i == classes.length - 1 && methodArgs[i].isArray())
349 {
350 // check to see if the last arg is convertible
351 // to the array's component type
352 return isConvertible(methodArgs[i], classes[i], true);
353 }
354 return false;
355 }
356 }
357 }
358 else if (methodArgs.length > 0) // more arguments given than the method accepts; check for varargs
359 {
360 // check that the last methodArg is an array
361 Class lastarg = methodArgs[methodArgs.length - 1];
362 if (!lastarg.isArray())
363 {
364 return false;
365 }
366
367 // check that they all match up to the last method arg
368 for (int i = 0; i < methodArgs.length - 1; ++i)
369 {
370 if (!isConvertible(methodArgs[i], classes[i], false))
371 {
372 return false;
373 }
374 }
375
376 // check that all remaining arguments are convertible to the vararg type
377 Class vararg = lastarg.getComponentType();
378 for (int i = methodArgs.length - 1; i < classes.length; ++i)
379 {
380 if (!isConvertible(vararg, classes[i], false))
381 {
382 return false;
383 }
384 }
385 }
386
387 return true;
388 }
389
390 private static boolean isConvertible(Class formal, Class actual,
391 boolean possibleVarArg)
392 {
393 return IntrospectionUtils.
394 isMethodInvocationConvertible(formal, actual, possibleVarArg);
395 }
396
397 private static boolean isStrictConvertible(Class formal, Class actual,
398 boolean possibleVarArg)
399 {
400 return IntrospectionUtils.
401 isStrictMethodInvocationConvertible(formal, actual, possibleVarArg);
402 }
403 }