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