001: package org.apache.velocity.util.introspection;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.lang.reflect.Method;
023: import java.util.ArrayList;
024: import java.util.Hashtable;
025: import java.util.Iterator;
026: import java.util.LinkedList;
027: import java.util.List;
028: import java.util.Map;
029:
030: /**
031: *
032: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
033: * @author <a href="mailto:bob@werken.com">Bob McWhirter</a>
034: * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a>
035: * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
036: * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
037: * @version $Id: MethodMap.java 476785 2006-11-19 10:06:21Z henning $
038: */
039: public class MethodMap {
040: private static final int MORE_SPECIFIC = 0;
041: private static final int LESS_SPECIFIC = 1;
042: private static final int INCOMPARABLE = 2;
043:
044: /**
045: * Keep track of all methods with the same name.
046: */
047: Map methodByNameMap = new Hashtable();
048:
049: /**
050: * Add a method to a list of methods by name.
051: * For a particular class we are keeping track
052: * of all the methods with the same name.
053: * @param method
054: */
055: public void add(Method method) {
056: String methodName = method.getName();
057:
058: List l = get(methodName);
059:
060: if (l == null) {
061: l = new ArrayList();
062: methodByNameMap.put(methodName, l);
063: }
064:
065: l.add(method);
066: }
067:
068: /**
069: * Return a list of methods with the same name.
070: *
071: * @param key
072: * @return List list of methods
073: */
074: public List get(String key) {
075: return (List) methodByNameMap.get(key);
076: }
077:
078: /**
079: * <p>
080: * Find a method. Attempts to find the
081: * most specific applicable method using the
082: * algorithm described in the JLS section
083: * 15.12.2 (with the exception that it can't
084: * distinguish a primitive type argument from
085: * an object type argument, since in reflection
086: * primitive type arguments are represented by
087: * their object counterparts, so for an argument of
088: * type (say) java.lang.Integer, it will not be able
089: * to decide between a method that takes int and a
090: * method that takes java.lang.Integer as a parameter.
091: * </p>
092: *
093: * <p>
094: * This turns out to be a relatively rare case
095: * where this is needed - however, functionality
096: * like this is needed.
097: * </p>
098: *
099: * @param methodName name of method
100: * @param args the actual arguments with which the method is called
101: * @return the most specific applicable method, or null if no
102: * method is applicable.
103: * @throws AmbiguousException if there is more than one maximally
104: * specific applicable method
105: */
106: public Method find(String methodName, Object[] args)
107: throws AmbiguousException {
108: List methodList = get(methodName);
109:
110: if (methodList == null) {
111: return null;
112: }
113:
114: int l = args.length;
115: Class[] classes = new Class[l];
116:
117: for (int i = 0; i < l; ++i) {
118: Object arg = args[i];
119:
120: /*
121: * if we are careful down below, a null argument goes in there
122: * so we can know that the null was passed to the method
123: */
124: classes[i] = arg == null ? null : arg.getClass();
125: }
126:
127: return getMostSpecific(methodList, classes);
128: }
129:
130: /**
131: * Simple distinguishable exception, used when
132: * we run across ambiguous overloading. Caught
133: * by the introspector.
134: */
135: public static class AmbiguousException extends RuntimeException {
136: /**
137: * Version Id for serializable
138: */
139: private static final long serialVersionUID = -2314636505414551663L;
140: }
141:
142: private static Method getMostSpecific(List methods, Class[] classes)
143: throws AmbiguousException {
144: LinkedList applicables = getApplicables(methods, classes);
145:
146: if (applicables.isEmpty()) {
147: return null;
148: }
149:
150: if (applicables.size() == 1) {
151: return (Method) applicables.getFirst();
152: }
153:
154: /*
155: * This list will contain the maximally specific methods. Hopefully at
156: * the end of the below loop, the list will contain exactly one method,
157: * (the most specific method) otherwise we have ambiguity.
158: */
159:
160: LinkedList maximals = new LinkedList();
161:
162: for (Iterator applicable = applicables.iterator(); applicable
163: .hasNext();) {
164: Method app = (Method) applicable.next();
165: Class[] appArgs = app.getParameterTypes();
166: boolean lessSpecific = false;
167:
168: for (Iterator maximal = maximals.iterator(); !lessSpecific
169: && maximal.hasNext();) {
170: Method max = (Method) maximal.next();
171:
172: switch (moreSpecific(appArgs, max.getParameterTypes())) {
173: case MORE_SPECIFIC: {
174: /*
175: * This method is more specific than the previously
176: * known maximally specific, so remove the old maximum.
177: */
178:
179: maximal.remove();
180: break;
181: }
182:
183: case LESS_SPECIFIC: {
184: /*
185: * This method is less specific than some of the
186: * currently known maximally specific methods, so we
187: * won't add it into the set of maximally specific
188: * methods
189: */
190:
191: lessSpecific = true;
192: break;
193: }
194: }
195: }
196:
197: if (!lessSpecific) {
198: maximals.addLast(app);
199: }
200: }
201:
202: if (maximals.size() > 1) {
203: // We have more than one maximally specific method
204: throw new AmbiguousException();
205: }
206:
207: return (Method) maximals.getFirst();
208: }
209:
210: /**
211: * Determines which method signature (represented by a class array) is more
212: * specific. This defines a partial ordering on the method signatures.
213: * @param c1 first signature to compare
214: * @param c2 second signature to compare
215: * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
216: * c1 is less specific than c2, INCOMPARABLE if they are incomparable.
217: */
218: private static int moreSpecific(Class[] c1, Class[] c2) {
219: boolean c1MoreSpecific = false;
220: boolean c2MoreSpecific = false;
221:
222: for (int i = 0; i < c1.length; ++i) {
223: if (c1[i] != c2[i]) {
224: c1MoreSpecific = c1MoreSpecific
225: || isStrictMethodInvocationConvertible(c2[i],
226: c1[i]);
227: c2MoreSpecific = c2MoreSpecific
228: || isStrictMethodInvocationConvertible(c1[i],
229: c2[i]);
230: }
231: }
232:
233: if (c1MoreSpecific) {
234: if (c2MoreSpecific) {
235: /*
236: * Incomparable due to cross-assignable arguments (i.e.
237: * foo(String, Object) vs. foo(Object, String))
238: */
239:
240: return INCOMPARABLE;
241: }
242:
243: return MORE_SPECIFIC;
244: }
245:
246: if (c2MoreSpecific) {
247: return LESS_SPECIFIC;
248: }
249:
250: /*
251: * Incomparable due to non-related arguments (i.e.
252: * foo(Runnable) vs. foo(Serializable))
253: */
254:
255: return INCOMPARABLE;
256: }
257:
258: /**
259: * Returns all methods that are applicable to actual argument types.
260: * @param methods list of all candidate methods
261: * @param classes the actual types of the arguments
262: * @return a list that contains only applicable methods (number of
263: * formal and actual arguments matches, and argument types are assignable
264: * to formal types through a method invocation conversion).
265: */
266: private static LinkedList getApplicables(List methods,
267: Class[] classes) {
268: LinkedList list = new LinkedList();
269:
270: for (Iterator imethod = methods.iterator(); imethod.hasNext();) {
271: Method method = (Method) imethod.next();
272:
273: if (isApplicable(method, classes)) {
274: list.add(method);
275: }
276:
277: }
278: return list;
279: }
280:
281: /**
282: * Returns true if the supplied method is applicable to actual
283: * argument types.
284: *
285: * @param method method that will be called
286: * @param classes arguments to method
287: * @return true if method is applicable to arguments
288: */
289: private static boolean isApplicable(Method method, Class[] classes) {
290: Class[] methodArgs = method.getParameterTypes();
291:
292: if (methodArgs.length != classes.length) {
293: return false;
294: }
295:
296: for (int i = 0; i < classes.length; ++i) {
297: if (!isMethodInvocationConvertible(methodArgs[i],
298: classes[i])) {
299: return false;
300: }
301: }
302:
303: return true;
304: }
305:
306: /**
307: * Determines whether a type represented by a class object is
308: * convertible to another type represented by a class object using a
309: * method invocation conversion, treating object types of primitive
310: * types as if they were primitive types (that is, a Boolean actual
311: * parameter type matches boolean primitive formal type). This behavior
312: * is because this method is used to determine applicable methods for
313: * an actual parameter list, and primitive types are represented by
314: * their object duals in reflective method calls.
315: *
316: * @param formal the formal parameter type to which the actual
317: * parameter type should be convertible
318: * @param actual the actual parameter type.
319: * @return true if either formal type is assignable from actual type,
320: * or formal is a primitive type and actual is its corresponding object
321: * type or an object type of a primitive type that can be converted to
322: * the formal type.
323: */
324: private static boolean isMethodInvocationConvertible(Class formal,
325: Class actual) {
326: /*
327: * if it's a null, it means the arg was null
328: */
329: if (actual == null && !formal.isPrimitive()) {
330: return true;
331: }
332:
333: /*
334: * Check for identity or widening reference conversion
335: */
336:
337: if (actual != null && formal.isAssignableFrom(actual)) {
338: return true;
339: }
340:
341: /*
342: * Check for boxing with widening primitive conversion. Note that
343: * actual parameters are never primitives.
344: */
345:
346: if (formal.isPrimitive()) {
347: if (formal == Boolean.TYPE && actual == Boolean.class)
348: return true;
349: if (formal == Character.TYPE && actual == Character.class)
350: return true;
351: if (formal == Byte.TYPE && actual == Byte.class)
352: return true;
353: if (formal == Short.TYPE
354: && (actual == Short.class || actual == Byte.class))
355: return true;
356: if (formal == Integer.TYPE
357: && (actual == Integer.class
358: || actual == Short.class || actual == Byte.class))
359: return true;
360: if (formal == Long.TYPE
361: && (actual == Long.class || actual == Integer.class
362: || actual == Short.class || actual == Byte.class))
363: return true;
364: if (formal == Float.TYPE
365: && (actual == Float.class || actual == Long.class
366: || actual == Integer.class
367: || actual == Short.class || actual == Byte.class))
368: return true;
369: if (formal == Double.TYPE
370: && (actual == Double.class || actual == Float.class
371: || actual == Long.class
372: || actual == Integer.class
373: || actual == Short.class || actual == Byte.class))
374: return true;
375: }
376:
377: return false;
378: }
379:
380: /**
381: * Determines whether a type represented by a class object is
382: * convertible to another type represented by a class object using a
383: * method invocation conversion, without matching object and primitive
384: * types. This method is used to determine the more specific type when
385: * comparing signatures of methods.
386: *
387: * @param formal the formal parameter type to which the actual
388: * parameter type should be convertible
389: * @param actual the actual parameter type.
390: * @return true if either formal type is assignable from actual type,
391: * or formal and actual are both primitive types and actual can be
392: * subject to widening conversion to formal.
393: */
394: private static boolean isStrictMethodInvocationConvertible(
395: Class formal, Class actual) {
396: /*
397: * we shouldn't get a null into, but if so
398: */
399: if (actual == null && !formal.isPrimitive()) {
400: return true;
401: }
402:
403: /*
404: * Check for identity or widening reference conversion
405: */
406:
407: if (formal.isAssignableFrom(actual)) {
408: return true;
409: }
410:
411: /*
412: * Check for widening primitive conversion.
413: */
414:
415: if (formal.isPrimitive()) {
416: if (formal == Short.TYPE && (actual == Byte.TYPE))
417: return true;
418: if (formal == Integer.TYPE
419: && (actual == Short.TYPE || actual == Byte.TYPE))
420: return true;
421: if (formal == Long.TYPE
422: && (actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE))
423: return true;
424: if (formal == Float.TYPE
425: && (actual == Long.TYPE || actual == Integer.TYPE
426: || actual == Short.TYPE || actual == Byte.TYPE))
427: return true;
428: if (formal == Double.TYPE
429: && (actual == Float.TYPE || actual == Long.TYPE
430: || actual == Integer.TYPE
431: || actual == Short.TYPE || actual == Byte.TYPE))
432: return true;
433: }
434: return false;
435: }
436: }
|