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.Iterator;
25 import java.util.List;
26 import java.util.Map;
27
28 import org.apache.velocity.util.MapFactory;
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 935975 2010-04-20 16:04:55Z 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 = MapFactory.create(false);
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 //System.out.println("ambiguous: "+equivalentMatches);//for debugging only
213 throw new AmbiguousException();
214 }
215 return bestMatch;
216 }
217
218 /**
219 * Simple distinguishable exception, used when
220 * we run across ambiguous overloading. Caught
221 * by the introspector.
222 */
223 public static class AmbiguousException extends RuntimeException
224 {
225 /**
226 * Version Id for serializable
227 */
228 private static final long serialVersionUID = -2314636505414551663L;
229 }
230
231 /**
232 * Determines which method signature (represented by a class array) is more
233 * specific. This defines a partial ordering on the method signatures.
234 * @param c1 first signature to compare
235 * @param c2 second signature to compare
236 * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
237 * c1 is less specific than c2, INCOMPARABLE if they are incomparable.
238 */
239 private static int compare(Class[] c1, Class[] c2)
240 {
241 boolean c1MoreSpecific = false;
242 boolean c2MoreSpecific = false;
243
244 // compare lengths to handle comparisons where the size of the arrays
245 // doesn't match, but the methods are both applicable due to the fact
246 // that one is a varargs method
247 if (c1.length > c2.length)
248 {
249 return MORE_SPECIFIC;
250 }
251 if (c2.length > c1.length)
252 {
253 return LESS_SPECIFIC;
254 }
255
256 // ok, move on and compare those of equal lengths
257 for(int i = 0; i < c1.length; ++i)
258 {
259 if(c1[i] != c2[i])
260 {
261 boolean last = (i == c1.length - 1);
262 c1MoreSpecific =
263 c1MoreSpecific ||
264 isStrictConvertible(c2[i], c1[i], last) ||
265 c2[i] == Object.class;//Object is always least-specific
266 c2MoreSpecific =
267 c2MoreSpecific ||
268 isStrictConvertible(c1[i], c2[i], last) ||
269 c1[i] == Object.class;//Object is always least-specific
270 }
271 }
272
273 if(c1MoreSpecific)
274 {
275 if(c2MoreSpecific)
276 {
277 /*
278 * If one method accepts varargs and the other does not,
279 * call the non-vararg one more specific.
280 */
281 boolean last1Array = c1[c1.length - 1].isArray();
282 boolean last2Array = c2[c2.length - 1].isArray();
283 if (last1Array && !last2Array)
284 {
285 return LESS_SPECIFIC;
286 }
287 if (!last1Array && last2Array)
288 {
289 return MORE_SPECIFIC;
290 }
291
292 /*
293 * Incomparable due to cross-assignable arguments (i.e.
294 * foo(String, Object) vs. foo(Object, String))
295 */
296 return INCOMPARABLE;
297 }
298
299 return MORE_SPECIFIC;
300 }
301
302 if(c2MoreSpecific)
303 {
304 return LESS_SPECIFIC;
305 }
306
307 /*
308 * Incomparable due to non-related arguments (i.e.
309 * foo(Runnable) vs. foo(Serializable))
310 */
311
312 return INCOMPARABLE;
313 }
314
315 /**
316 * Returns true if the supplied method is applicable to actual
317 * argument types.
318 *
319 * @param method method that will be called
320 * @param classes arguments to method
321 * @return true if method is applicable to arguments
322 */
323 private static boolean isApplicable(Method method, Class[] classes)
324 {
325 Class[] methodArgs = method.getParameterTypes();
326
327 if (methodArgs.length > classes.length)
328 {
329 // if there's just one more methodArg than class arg
330 // and the last methodArg is an array, then treat it as a vararg
331 if (methodArgs.length == classes.length + 1 &&
332 methodArgs[methodArgs.length - 1].isArray())
333 {
334 // all the args preceding the vararg must match
335 for (int i = 0; i < classes.length; i++)
336 {
337 if (!isConvertible(methodArgs[i], classes[i], false))
338 {
339 return false;
340 }
341 }
342 return true;
343 }
344 else
345 {
346 return false;
347 }
348 }
349 else if (methodArgs.length == classes.length)
350 {
351 // this will properly match when the last methodArg
352 // is an array/varargs and the last class is the type of array
353 // (e.g. String when the method is expecting String...)
354 for(int i = 0; i < classes.length; ++i)
355 {
356 if(!isConvertible(methodArgs[i], classes[i], false))
357 {
358 // if we're on the last arg and the method expects an array
359 if (i == classes.length - 1 && methodArgs[i].isArray())
360 {
361 // check to see if the last arg is convertible
362 // to the array's component type
363 return isConvertible(methodArgs[i], classes[i], true);
364 }
365 return false;
366 }
367 }
368 }
369 else if (methodArgs.length > 0) // more arguments given than the method accepts; check for varargs
370 {
371 // check that the last methodArg is an array
372 Class lastarg = methodArgs[methodArgs.length - 1];
373 if (!lastarg.isArray())
374 {
375 return false;
376 }
377
378 // check that they all match up to the last method arg
379 for (int i = 0; i < methodArgs.length - 1; ++i)
380 {
381 if (!isConvertible(methodArgs[i], classes[i], false))
382 {
383 return false;
384 }
385 }
386
387 // check that all remaining arguments are convertible to the vararg type
388 Class vararg = lastarg.getComponentType();
389 for (int i = methodArgs.length - 1; i < classes.length; ++i)
390 {
391 if (!isConvertible(vararg, classes[i], false))
392 {
393 return false;
394 }
395 }
396 }
397
398 return true;
399 }
400
401 private static boolean isConvertible(Class formal, Class actual,
402 boolean possibleVarArg)
403 {
404 return IntrospectionUtils.
405 isMethodInvocationConvertible(formal, actual, possibleVarArg);
406 }
407
408 private static boolean isStrictConvertible(Class formal, Class actual,
409 boolean possibleVarArg)
410 {
411 return IntrospectionUtils.
412 isStrictMethodInvocationConvertible(formal, actual, possibleVarArg);
413 }
414 }