001: /*
002: * Copyright 2004,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.bsf.util;
018:
019: import java.lang.reflect.Constructor;
020: import java.lang.reflect.Method;
021: import java.lang.reflect.Modifier;
022: import java.util.Enumeration;
023: import java.util.Vector;
024:
025: /**
026: * This file is a collection of reflection utilities for dealing with
027: * methods and constructors.
028: *
029: * @author Sanjiva Weerawarana
030: * @author Joseph Kesselman
031: */
032: public class MethodUtils {
033:
034: /** Internal Class for getEntryPoint(). Implements 15.11.2.2 MORE
035: SPECIFIC rules.
036:
037: Retains a list of methods (already known to match the
038: arguments). As each method is added, we check against past entries
039: to determine which if any is "more specific" -- defined as having
040: _all_ its arguments (not just a preponderance) be
041: method-convertable into those of another. If such a relationship
042: is found, the more-specific method is retained and the
043: less-specific method is discarded. At the end, if this has yielded
044: a single winner it is considered the Most Specific Method and
045: hence the one that should be invoked. Otherwise, a
046: NoSuchMethodException is thrown.
047:
048: PERFORMANCE VERSUS ARCHITECTURE: Arguably, this should "have-a"
049: Vector. But the code is 6% smaller, and possibly faster, if we
050: code it as "is-a" Vector. Since it's an inner class, nobody's
051: likely to abuse the privilage.
052:
053: Note: "Static" in the case of an inner class means "Does not
054: reference instance data in the outer class", and is required since
055: our caller is a static method. */
056: private static class MoreSpecific extends Vector {
057: /** Submit an entry-point to the list. May be discarded if a past
058: entry is more specific, or may cause others to be discarded it
059: if is more specific.
060:
061: newEntry: Method or Constructor under consideration.
062: */
063: void addItem(Object newEntry) {
064: if (size() == 0)
065: addElement(newEntry);
066: else {
067: Class[] newargs = entryGetParameterTypes(newEntry);
068: boolean keep = true;
069: for (Enumeration e = elements(); keep
070: & e.hasMoreElements();) {
071: Object oldEntry = e.nextElement();
072: // CAVEAT: Implicit references to enclosing class!
073: Class[] oldargs = entryGetParameterTypes(oldEntry);
074: if (areMethodConvertable(oldargs, newargs))
075: removeElement(oldEntry); // New more specific; discard old
076: else if (areMethodConvertable(newargs, oldargs))
077: keep = false; // Old more specific; discard new
078: // Else they're tied. Keep both and hope someone beats both.
079: }
080: if (keep)
081: addElement(newEntry);
082: }
083: }
084:
085: /** Obtain the single Most Specific entry-point. If there is no clear
086: winner, or if the list is empty, throw NoSuchMethodException.
087:
088: Arguments describe the call we were hoping to resolve. They are
089: used to throw a nice verbose exception if something goes wrong.
090: */
091: Object getMostSpecific(Class targetClass, String methodName,
092: Class[] argTypes, boolean isStaticReference)
093: throws NoSuchMethodException {
094: if (size() == 1)
095: return firstElement();
096: if (size() > 1) {
097: StringBuffer buf = new StringBuffer();
098: Enumeration e = elements();
099: buf.append(e.nextElement());
100: while (e.hasMoreElements())
101: buf.append(" and ").append(e.nextElement());
102: throw new NoSuchMethodException(callToString(
103: targetClass, methodName, argTypes,
104: isStaticReference)
105: + " is ambiguous. It matches " + buf.toString());
106: }
107: return null;
108: }
109: }
110:
111: /** Convenience method: Test an entire parameter-list/argument-list pair
112: for isMethodConvertable(), qv.
113: */
114: static private boolean areMethodConvertable(Class[] parms,
115: Class[] args) {
116: if (parms.length != args.length)
117: return false;
118:
119: for (int i = 0; i < parms.length; ++i)
120: if (!isMethodConvertable(parms[i], args[i]))
121: return false;
122:
123: return true;
124: }
125:
126: /** Internal subroutine for getEntryPoint(): Format arguments as a
127: string describing the function being searched for. Used in
128: verbose exceptions. */
129: private static String callToString(Class targetClass,
130: String methodName, Class[] argTypes,
131: boolean isStaticReference) {
132: StringBuffer buf = new StringBuffer();
133: if (isStaticReference)
134: buf.append("static ");
135: buf.append(StringUtils.getClassName(targetClass));
136: if (methodName != null)
137: buf.append(".").append(methodName);
138: buf.append("(");
139: if (argTypes != null && argTypes.length > 0) {
140: if (false) {
141: // ????? Sanjiva has an ArrayToString method. Using it would
142: // save a few bytes, at cost of giving up some reusability.
143: } else {
144: buf.append(StringUtils.getClassName(argTypes[0]));
145: for (int i = 1; i < argTypes.length; i++) {
146: buf.append(",").append(
147: StringUtils.getClassName(argTypes[i]));
148: }
149: }
150: } else
151: buf.append("[none]");
152: buf.append(")");
153: return buf.toString();
154: }
155:
156: /** Utility function: obtain common data from either Method or
157: Constructor. (In lieu of an EntryPoint interface.) */
158: static int entryGetModifiers(Object entry) {
159: return (entry instanceof Method) ? ((Method) entry)
160: .getModifiers() : ((Constructor) entry).getModifiers();
161: }
162:
163: // The common lookup code would be much easier if Method and
164: // Constructor shared an "EntryPoint" Interface. Unfortunately, even
165: // though their APIs are almost identical, they don't. These calls
166: // are a workaround... at the cost of additional runtime overhead
167: // and some extra bytecodes.
168: //
169: // (A JDK bug report has been submitted requesting that they add the
170: // Interface; it would be easy, harmless, and useful.)
171:
172: /** Utility function: obtain common data from either Method or
173: Constructor. (In lieu of an EntryPoint interface.) */
174: static String entryGetName(Object entry) {
175: return (entry instanceof Method) ? ((Method) entry).getName()
176: : ((Constructor) entry).getName();
177: }
178:
179: /** Utility function: obtain common data from either Method or
180: Constructor. (In lieu of an EntryPoint interface.) */
181: static Class[] entryGetParameterTypes(Object entry) {
182: return (entry instanceof Method) ? ((Method) entry)
183: .getParameterTypes() : ((Constructor) entry)
184: .getParameterTypes();
185: }
186:
187: /** Utility function: obtain common data from either Method or
188: Constructor. (In lieu of an EntryPoint interface.) */
189: static String entryToString(Object entry) {
190: return (entry instanceof Method) ? ((Method) entry).toString()
191: : ((Constructor) entry).toString();
192: }
193:
194: //////////////////////////////////////////////////////////////////////////
195:
196: /** Class.getConstructor() finds only the entry point (if any)
197: _exactly_ matching the specified argument types. Our implmentation
198: can decide between several imperfect matches, using the same
199: search algorithm as the Java compiler.
200:
201: Note that all constructors are static by definition, so
202: isStaticReference is true.
203:
204: @exception NoSuchMethodException if constructor not found.
205: */
206: static public Constructor getConstructor(Class targetClass,
207: Class[] argTypes) throws SecurityException,
208: NoSuchMethodException {
209: return (Constructor) getEntryPoint(targetClass, null, argTypes,
210: true);
211: }
212:
213: //////////////////////////////////////////////////////////////////////////
214:
215: /**
216: * Search for entry point, per Java Language Spec 1.0
217: * as amended, verified by comparison against compiler behavior.
218: *
219: * @param targetClass Class object for the class to be queried.
220: * @param methodName Name of method to invoke, or null for constructor.
221: * Only Public methods will be accepted.
222: * @param argTypes Classes of intended arguments. Note that primitives
223: * must be specified via their TYPE equivalents,
224: * rather than as their wrapper classes -- Integer.TYPE
225: * rather than Integer. "null" may be passed in as an
226: * indication that you intend to invoke the method with
227: * a literal null argument and therefore can accept
228: * any object type in this position.
229: * @param isStaticReference If true, and if the target is a Class object,
230: * only static methods will be accepted as valid matches.
231: *
232: * @return a Method or Constructor of the appropriate signature
233: *
234: * @exception SecurityException if security violation
235: * @exception NoSuchMethodException if no such method
236: */
237: static private Object getEntryPoint(Class targetClass,
238: String methodName, Class[] argTypes,
239: boolean isStaticReference) throws SecurityException,
240: NoSuchMethodException {
241: // 15.11.1: OBTAIN STARTING CLASS FOR SEARCH
242: Object m = null;
243:
244: // 15.11.2 DETERMINE ARGUMENT SIGNATURE
245: // (Passed in as argTypes array.)
246:
247: // Shortcut: If an exact match exists, return it.
248: try {
249: if (methodName != null) {
250: m = targetClass.getMethod(methodName, argTypes);
251: if (isStaticReference
252: && !Modifier.isStatic(entryGetModifiers(m))) {
253: throw new NoSuchMethodException(callToString(
254: targetClass, methodName, argTypes,
255: isStaticReference)
256: + " resolved to instance " + m);
257: }
258: return m;
259: } else
260: return targetClass.getConstructor(argTypes);
261:
262: } catch (NoSuchMethodException e) {
263: // no-args has no alternatives!
264: if (argTypes == null || argTypes.length == 0) {
265: throw new NoSuchMethodException(callToString(
266: targetClass, methodName, argTypes,
267: isStaticReference)
268: + " not found.");
269: }
270: // Else fall through.
271: }
272:
273: // Well, _that_ didn't work. Time to search for the Most Specific
274: // matching function. NOTE that conflicts are possible!
275:
276: // 15.11.2.1 ACCESSIBLE: We apparently need to gather from two
277: // sources to be sure we have both instance and static methods.
278: Object[] methods;
279: if (methodName != null) {
280: methods = targetClass.getMethods();
281: } else {
282: methods = targetClass.getConstructors();
283: }
284: if (0 == methods.length) {
285: throw new NoSuchMethodException("No methods!");
286: }
287:
288: MoreSpecific best = new MoreSpecific();
289: for (int i = 0; i < methods.length; ++i) {
290: Object mi = methods[i];
291: if (
292: // 15.11.2.1 ACCESSIBLE: Method is public.
293: Modifier.isPublic(entryGetModifiers(mi)) &&
294: // 15.11.2.1 APPLICABLE: Right method name (or c'tor)
295: (methodName == null || entryGetName(mi).equals(
296: methodName)) &&
297: // 15.11.2.1 APPLICABLE: Parameters match arguments
298: areMethodConvertable(entryGetParameterTypes(mi),
299: argTypes))
300: // 15.11.2.2 MORE SPECIFIC displace less specific.
301: best.addItem(mi);
302: }
303:
304: // May throw NoSuchMethodException; we pass in info needed to
305: // create a useful exception
306: m = best.getMostSpecific(targetClass, methodName, argTypes,
307: isStaticReference);
308:
309: // 15.11.3 APPROPRIATE: Class invocation can call only static
310: // methods. Note that the defined order of evaluation permits a
311: // call to be resolved to an inappropriate method and then
312: // rejected, rather than finding the best of the appropriate
313: // methods.
314: //
315: // Constructors are never static, so we don't test them.
316: if (m == null) {
317: throw new NoSuchMethodException(callToString(targetClass,
318: methodName, argTypes, isStaticReference)
319: + " -- no signature match");
320: }
321:
322: if (methodName != null && isStaticReference
323: && !Modifier.isStatic(entryGetModifiers(m))) {
324: throw new NoSuchMethodException(callToString(targetClass,
325: methodName, argTypes, isStaticReference)
326: + " resolved to instance: " + m);
327: }
328:
329: return m;
330: }
331:
332: //////////////////////////////////////////////////////////////////////////
333:
334: /* Class.getMethod() finds only the entry point (if any) _exactly_
335: matching the specified argument types. Our implmentation can
336: decide between several imperfect matches, using the same search
337: algorithm as the Java compiler.
338:
339: This version more closely resembles Class.getMethod() -- we always
340: ask the Class for the method. It differs in testing for
341: appropriateness before returning the method; if the query is
342: being made via a static reference, only static methods will be
343: found and returned. */
344: static public Method getMethod(Class target, String methodName,
345: Class[] argTypes, boolean isStaticReference)
346: throws SecurityException, NoSuchMethodException {
347: return (Method) getEntryPoint(target, methodName, argTypes,
348: isStaticReference);
349: }
350:
351: //////////////////////////////////////////////////////////////////////////
352:
353: /**
354: * Class.getMethod() finds only the entry point (if any) _exactly_
355: * matching the specified argument types. Our implmentation can
356: * decide between several imperfect matches, using the same search
357: * algorithm as the Java compiler.
358: *
359: * This version emulates the compiler behavior by allowing lookup to
360: * be performed against either a class or an instance -- classname.foo()
361: * must be a static method call, instance.foo() can invoke either static
362: * or instance methods.
363: *
364: * @param target object on which call is to be made
365: * @param methodName name of method I'm lookin' for
366: * @param argTypes array of argument types of method
367: *
368: * @return the desired method
369: *
370: * @exception SecurityException if security violation
371: * @exception NoSuchMethodException if no such method
372: */
373: static public Method getMethod(Object target, String methodName,
374: Class[] argTypes) throws SecurityException,
375: NoSuchMethodException {
376: boolean staticRef = target instanceof Class;
377: return getMethod(
378: staticRef ? (Class) target : target.getClass(),
379: methodName, argTypes, staticRef);
380: }
381:
382: /** Determine whether a given type can accept assignments of another
383: type. Note that class.isAssignable() is _not_ a complete test!
384: (This method is not needed by getMethod() or getConstructor(), but
385: is provided as a convenience for other users.)
386:
387: parm: The type given in the method's signature.
388: arg: The type we want to pass in.
389:
390: Legal ASSIGNMENT CONVERSIONS (5.2) are METHOD CONVERSIONS (5.3)
391: plus implicit narrowing of int to byte, short or char. */
392: static private boolean isAssignmentConvertable(Class parm, Class arg) {
393: return (arg.equals(Integer.TYPE) && (parm.equals(Byte.TYPE)
394: || parm.equals(Short.TYPE) || parm
395: .equals(Character.TYPE)))
396: || isMethodConvertable(parm, arg);
397: }
398:
399: /** Determine whether a given method parameter type can accept
400: arguments of another type.
401:
402: parm: The type given in the method's signature.
403: arg: The type we want to pass in.
404:
405: Legal METHOD CONVERSIONS (5.3) are Identity, Widening Primitive
406: Conversion, or Widening Reference Conversion. NOTE that this is a
407: subset of the legal ASSIGNMENT CONVERSIONS (5.2) -- in particular,
408: we can't implicitly narrow int to byte, short or char.
409:
410: SPECIAL CASE: In order to permit invoking methods with literal
411: "null" values, setting the arg Class to null will be taken as a
412: request to match any Class type. POSSIBLE PROBLEM: This may match
413: a primitive type, which really should not accept a null value... but
414: I'm not sure how best to distinguish those, short of enumerating them
415: */
416: static private boolean isMethodConvertable(Class parm, Class arg) {
417: if (parm.equals(arg)) // If same class, short-circuit now!
418: return true;
419:
420: // Accept any type EXCEPT primitives (which can't have null values).
421: if (arg == null) {
422: return !parm.isPrimitive();
423: }
424:
425: // Arrays are convertable if their elements are convertable
426: // ????? Does this have to be done before isAssignableFrom, or
427: // does it successfully handle arrays of primatives?
428: while (parm.isArray()) {
429: if (!arg.isArray())
430: return false; // Unequal array depth
431: else {
432: parm = parm.getComponentType();
433: arg = arg.getComponentType();
434: }
435: }
436: if (arg.isArray())
437: return false; // Unequal array depth
438:
439: // Despite its name, the 1.1.6 docs say that this function does
440: // NOT return true for all legal ASSIGNMENT CONVERSIONS
441: // (5.2):
442: // "Specifically, this method tests whether the type
443: // represented by the specified class can be converted
444: // to the type represented by this Class object via
445: // an identity conversion or via a widening reference
446: // conversion."
447: if (parm.isAssignableFrom(arg))
448: return true;
449:
450: // That leaves us the Widening Primitives case. Four possibilities:
451: // void (can only convert to void), boolean (can only convert to boolean),
452: // numeric (which are sequenced) and char (which inserts itself into the
453: // numerics by promoting to int or larger)
454:
455: if (parm.equals(Void.TYPE) || parm.equals(Boolean.TYPE)
456: || arg.equals(Void.TYPE) || arg.equals(Boolean.TYPE))
457: return false;
458:
459: Class[] primTypes = { Character.TYPE, Byte.TYPE, Short.TYPE,
460: Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE };
461: int parmscore, argscore;
462:
463: for (parmscore = 0; parmscore < primTypes.length; ++parmscore)
464: if (parm.equals(primTypes[parmscore]))
465: break;
466: if (parmscore >= primTypes.length)
467: return false; // Off the end
468:
469: for (argscore = 0; argscore < primTypes.length; ++argscore)
470: if (arg.equals(primTypes[argscore]))
471: break;
472: if (argscore >= primTypes.length)
473: return false; // Off the end
474:
475: // OK if ordered AND NOT char-to-smaller-than-int
476: return (argscore < parmscore && (argscore != 0 || parmscore > 2));
477: }
478: }
|