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 import java.util.concurrent.ConcurrentHashMap;
28
29 /**
30 *
31 * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
32 * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
33 * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
34 * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
35 * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
36 * @version $Id: MethodMap.java 928998 2010-03-30 05:37:17Z nbubna $
37 */
38 public class MethodMap
39 {
40 private static final int MORE_SPECIFIC = 0;
41 private static final int LESS_SPECIFIC = 1;
42 private static final int INCOMPARABLE = 2;
43
44 /**
45 * Keep track of all methods with the same name.
46 */
47 Map methodByNameMap = new ConcurrentHashMap();
48
49 /**
50 * Add a method to a list of methods by name.
51 * For a particular class we are keeping track
52 * of all the methods with the same name.
53 * @param method
54 */
55 public void add(Method method)
56 {
57 String methodName = method.getName();
58
59 List l = get( methodName );
60
61 if ( l == null)
62 {
63 l = new ArrayList();
64 methodByNameMap.put(methodName, l);
65 }
66
67 l.add(method);
68 }
69
70 /**
71 * Return a list of methods with the same name.
72 *
73 * @param key
74 * @return List list of methods
75 */
76 public List get(String key)
77 {
78 return (List) methodByNameMap.get(key);
79 }
80
81 /**
82 * <p>
83 * Find a method. Attempts to find the
84 * most specific applicable method using the
85 * algorithm described in the JLS section
86 * 15.12.2 (with the exception that it can't
87 * distinguish a primitive type argument from
88 * an object type argument, since in reflection
89 * primitive type arguments are represented by
90 * their object counterparts, so for an argument of
91 * type (say) java.lang.Integer, it will not be able
92 * to decide between a method that takes int and a
93 * method that takes java.lang.Integer as a parameter.
94 * </p>
95 *
96 * <p>
97 * This turns out to be a relatively rare case
98 * where this is needed - however, functionality
99 * like this is needed.
100 * </p>
101 *
102 * @param methodName name of method
103 * @param args the actual arguments with which the method is called
104 * @return the most specific applicable method, or null if no
105 * method is applicable.
106 * @throws AmbiguousException if there is more than one maximally
107 * specific applicable method
108 */
109 public Method find(String methodName, Object[] args)
110 throws AmbiguousException
111 {
112 List methodList = get(methodName);
113
114 if (methodList == null)
115 {
116 return null;
117 }
118
119 int l = args.length;
120 Class[] classes = new Class[l];
121
122 for(int i = 0; i < l; ++i)
123 {
124 Object arg = args[i];
125
126 /*
127 * if we are careful down below, a null argument goes in there
128 * so we can know that the null was passed to the method
129 */
130 classes[i] =
131 arg == null ? null : arg.getClass();
132 }
133
134 return getBestMatch(methodList, classes);
135 }
136
137 private static Method getBestMatch(List methods, Class[] args)
138 {
139 List equivalentMatches = null;
140 Method bestMatch = null;
141 Class[] bestMatchTypes = null;
142 for (Iterator i = methods.iterator(); i.hasNext(); )
143 {
144 Method method = (Method)i.next();
145 if (isApplicable(method, args))
146 {
147 if (bestMatch == null)
148 {
149 bestMatch = method;
150 bestMatchTypes = method.getParameterTypes();
151 }
152 else
153 {
154 Class[] methodTypes = method.getParameterTypes();
155 switch (compare(methodTypes, bestMatchTypes))
156 {
157 case MORE_SPECIFIC:
158 if (equivalentMatches == null)
159 {
160 bestMatch = method;
161 bestMatchTypes = methodTypes;
162 }
163 else
164 {
165 // have to beat all other ambiguous ones...
166 int ambiguities = equivalentMatches.size();
167 for (int a=0; a < ambiguities; a++)
168 {
169 Method other = (Method)equivalentMatches.get(a);
170 switch (compare(methodTypes, other.getParameterTypes()))
171 {
172 case MORE_SPECIFIC:
173 // ...and thus replace them all...
174 bestMatch = method;
175 bestMatchTypes = methodTypes;
176 equivalentMatches = null;
177 ambiguities = 0;
178 break;
179
180 case INCOMPARABLE:
181 // ...join them...
182 equivalentMatches.add(method);
183 break;
184
185 case LESS_SPECIFIC:
186 // ...or just go away.
187 break;
188 }
189 }
190 }
191 break;
192
193 case INCOMPARABLE:
194 if (equivalentMatches == null)
195 {
196 equivalentMatches = new ArrayList(bestMatchTypes.length);
197 }
198 equivalentMatches.add(method);
199 break;
200
201 case LESS_SPECIFIC:
202 // do nothing
203 break;
204 }
205 }
206 }
207 }
208
209 if (equivalentMatches != null)
210 {
211 System.out.println("ambiguous: "+equivalentMatches);
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 c2[i] == Object.class;//Object is always least-specific
265 c2MoreSpecific =
266 c2MoreSpecific ||
267 isStrictConvertible(c1[i], c2[i], last) ||
268 c1[i] == Object.class;//Object is always least-specific
269 }
270 }
271
272 if(c1MoreSpecific)
273 {
274 if(c2MoreSpecific)
275 {
276 /*
277 * If one method accepts varargs and the other does not,
278 * call the non-vararg one more specific.
279 */
280 boolean last1Array = c1[c1.length - 1].isArray();
281 boolean last2Array = c2[c2.length - 1].isArray();
282 if (last1Array && !last2Array)
283 {
284 return LESS_SPECIFIC;
285 }
286 if (!last1Array && last2Array)
287 {
288 return MORE_SPECIFIC;
289 }
290
291 /*
292 * Incomparable due to cross-assignable arguments (i.e.
293 * foo(String, Object) vs. foo(Object, String))
294 */
295 return INCOMPARABLE;
296 }
297
298 return MORE_SPECIFIC;
299 }
300
301 if(c2MoreSpecific)
302 {
303 return LESS_SPECIFIC;
304 }
305
306 /*
307 * Incomparable due to non-related arguments (i.e.
308 * foo(Runnable) vs. foo(Serializable))
309 */
310
311 return INCOMPARABLE;
312 }
313
314 /**
315 * Returns true if the supplied method is applicable to actual
316 * argument types.
317 *
318 * @param method method that will be called
319 * @param classes arguments to method
320 * @return true if method is applicable to arguments
321 */
322 private static boolean isApplicable(Method method, Class[] classes)
323 {
324 Class[] methodArgs = method.getParameterTypes();
325
326 if (methodArgs.length > classes.length)
327 {
328 // if there's just one more methodArg than class arg
329 // and the last methodArg is an array, then treat it as a vararg
330 if (methodArgs.length == classes.length + 1 &&
331 methodArgs[methodArgs.length - 1].isArray())
332 {
333 // all the args preceding the vararg must match
334 for (int i = 0; i < classes.length; i++)
335 {
336 if (!isConvertible(methodArgs[i], classes[i], false))
337 {
338 return false;
339 }
340 }
341 return true;
342 }
343 else
344 {
345 return false;
346 }
347 }
348 else if (methodArgs.length == classes.length)
349 {
350 // this will properly match when the last methodArg
351 // is an array/varargs and the last class is the type of array
352 // (e.g. String when the method is expecting String...)
353 for(int i = 0; i < classes.length; ++i)
354 {
355 if(!isConvertible(methodArgs[i], classes[i], false))
356 {
357 // if we're on the last arg and the method expects an array
358 if (i == classes.length - 1 && methodArgs[i].isArray())
359 {
360 // check to see if the last arg is convertible
361 // to the array's component type
362 return isConvertible(methodArgs[i], classes[i], true);
363 }
364 return false;
365 }
366 }
367 }
368 else if (methodArgs.length > 0) // more arguments given than the method accepts; check for varargs
369 {
370 // check that the last methodArg is an array
371 Class lastarg = methodArgs[methodArgs.length - 1];
372 if (!lastarg.isArray())
373 {
374 return false;
375 }
376
377 // check that they all match up to the last method arg
378 for (int i = 0; i < methodArgs.length - 1; ++i)
379 {
380 if (!isConvertible(methodArgs[i], classes[i], false))
381 {
382 return false;
383 }
384 }
385
386 // check that all remaining arguments are convertible to the vararg type
387 Class vararg = lastarg.getComponentType();
388 for (int i = methodArgs.length - 1; i < classes.length; ++i)
389 {
390 if (!isConvertible(vararg, classes[i], false))
391 {
392 return false;
393 }
394 }
395 }
396
397 return true;
398 }
399
400 private static boolean isConvertible(Class formal, Class actual,
401 boolean possibleVarArg)
402 {
403 return IntrospectionUtils.
404 isMethodInvocationConvertible(formal, actual, possibleVarArg);
405 }
406
407 private static boolean isStrictConvertible(Class formal, Class actual,
408 boolean possibleVarArg)
409 {
410 return IntrospectionUtils.
411 isStrictMethodInvocationConvertible(formal, actual, possibleVarArg);
412 }
413 }