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