| /* |
| * Copyright 2004,2004 The Apache Software Foundation. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.bsf.util; |
| |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.util.Enumeration; |
| import java.util.Vector; |
| |
| /** |
| * This file is a collection of reflection utilities for dealing with |
| * methods and constructors. |
| * |
| * @author Sanjiva Weerawarana |
| * @author Joseph Kesselman |
| */ |
| public class MethodUtils { |
| |
| /** Internal Class for getEntryPoint(). Implements 15.11.2.2 MORE |
| SPECIFIC rules. |
| |
| Retains a list of methods (already known to match the |
| arguments). As each method is added, we check against past entries |
| to determine which if any is "more specific" -- defined as having |
| _all_ its arguments (not just a preponderance) be |
| method-convertable into those of another. If such a relationship |
| is found, the more-specific method is retained and the |
| less-specific method is discarded. At the end, if this has yielded |
| a single winner it is considered the Most Specific Method and |
| hence the one that should be invoked. Otherwise, a |
| NoSuchMethodException is thrown. |
| |
| PERFORMANCE VERSUS ARCHITECTURE: Arguably, this should "have-a" |
| Vector. But the code is 6% smaller, and possibly faster, if we |
| code it as "is-a" Vector. Since it's an inner class, nobody's |
| likely to abuse the privilage. |
| |
| Note: "Static" in the case of an inner class means "Does not |
| reference instance data in the outer class", and is required since |
| our caller is a static method. */ |
| private static class MoreSpecific |
| extends Vector |
| { |
| /** Submit an entry-point to the list. May be discarded if a past |
| entry is more specific, or may cause others to be discarded it |
| if is more specific. |
| |
| newEntry: Method or Constructor under consideration. |
| */ |
| void addItem (Object newEntry) |
| { |
| if(size()==0) |
| addElement(newEntry); |
| else |
| { |
| Class[] newargs=entryGetParameterTypes(newEntry); |
| boolean keep=true; |
| for (Enumeration e = elements(); |
| keep & e.hasMoreElements() ; |
| ) |
| { |
| Object oldEntry=e.nextElement(); |
| // CAVEAT: Implicit references to enclosing class! |
| Class[] oldargs=entryGetParameterTypes(oldEntry); |
| if(areMethodConvertable(oldargs,newargs)) |
| removeElement(oldEntry); // New more specific; discard old |
| else if(areMethodConvertable(newargs,oldargs)) |
| keep=false; // Old more specific; discard new |
| // Else they're tied. Keep both and hope someone beats both. |
| } |
| if(keep) |
| addElement(newEntry); |
| } |
| } |
| |
| /** Obtain the single Most Specific entry-point. If there is no clear |
| winner, or if the list is empty, throw NoSuchMethodException. |
| |
| Arguments describe the call we were hoping to resolve. They are |
| used to throw a nice verbose exception if something goes wrong. |
| */ |
| Object getMostSpecific(Class targetClass,String methodName, |
| Class[] argTypes,boolean isStaticReference) |
| throws NoSuchMethodException |
| { |
| if(size()==1) |
| return firstElement(); |
| if(size()>1) |
| { |
| StringBuffer buf=new StringBuffer(); |
| Enumeration e=elements(); |
| buf.append(e.nextElement()); |
| while(e.hasMoreElements()) |
| buf.append(" and ").append(e.nextElement()); |
| throw new NoSuchMethodException (callToString(targetClass, |
| methodName, |
| argTypes, |
| isStaticReference)+ |
| " is ambiguous. It matches "+ |
| buf.toString()); |
| } |
| return null; |
| } |
| } |
| |
| /** Convenience method: Test an entire parameter-list/argument-list pair |
| for isMethodConvertable(), qv. |
| */ |
| static private boolean areMethodConvertable(Class[] parms,Class[] args) |
| { |
| if(parms.length!=args.length) |
| return false; |
| |
| for(int i=0;i<parms.length;++i) |
| if(!isMethodConvertable(parms[i],args[i])) |
| return false; |
| |
| return true; |
| } |
| /** Internal subroutine for getEntryPoint(): Format arguments as a |
| string describing the function being searched for. Used in |
| verbose exceptions. */ |
| private static String callToString(Class targetClass,String methodName, |
| Class[] argTypes,boolean isStaticReference) |
| { |
| StringBuffer buf = new StringBuffer(); |
| if(isStaticReference) |
| buf.append("static "); |
| buf.append(StringUtils.getClassName(targetClass)); |
| if(methodName!=null) |
| buf.append(".").append(methodName); |
| buf.append("("); |
| if (argTypes != null && argTypes.length>0) { |
| if(false) |
| { |
| // ????? Sanjiva has an ArrayToString method. Using it would |
| // save a few bytes, at cost of giving up some reusability. |
| } |
| else |
| { |
| buf.append(StringUtils.getClassName(argTypes[0])); |
| for (int i = 1; i < argTypes.length; i++) { |
| buf.append(",").append(StringUtils.getClassName(argTypes[i])); |
| } |
| } |
| } |
| else |
| buf.append("[none]"); |
| buf.append(")"); |
| return buf.toString(); |
| } |
| /** Utility function: obtain common data from either Method or |
| Constructor. (In lieu of an EntryPoint interface.) */ |
| static int entryGetModifiers(Object entry) |
| { |
| return (entry instanceof Method) |
| ? ((Method)entry).getModifiers() |
| : ((Constructor)entry).getModifiers(); |
| } |
| // The common lookup code would be much easier if Method and |
| // Constructor shared an "EntryPoint" Interface. Unfortunately, even |
| // though their APIs are almost identical, they don't. These calls |
| // are a workaround... at the cost of additional runtime overhead |
| // and some extra bytecodes. |
| // |
| // (A JDK bug report has been submitted requesting that they add the |
| // Interface; it would be easy, harmless, and useful.) |
| |
| /** Utility function: obtain common data from either Method or |
| Constructor. (In lieu of an EntryPoint interface.) */ |
| static String entryGetName(Object entry) |
| { |
| return (entry instanceof Method) |
| ? ((Method)entry).getName() |
| : ((Constructor)entry).getName(); |
| } |
| /** Utility function: obtain common data from either Method or |
| Constructor. (In lieu of an EntryPoint interface.) */ |
| static Class[] entryGetParameterTypes(Object entry) |
| { |
| return (entry instanceof Method) |
| ? ((Method)entry).getParameterTypes() |
| : ((Constructor)entry).getParameterTypes(); |
| } |
| /** Utility function: obtain common data from either Method or |
| Constructor. (In lieu of an EntryPoint interface.) */ |
| static String entryToString(Object entry) |
| { |
| return (entry instanceof Method) |
| ? ((Method)entry).toString() |
| : ((Constructor)entry).toString(); |
| } |
| ////////////////////////////////////////////////////////////////////////// |
| |
| /** Class.getConstructor() finds only the entry point (if any) |
| _exactly_ matching the specified argument types. Our implmentation |
| can decide between several imperfect matches, using the same |
| search algorithm as the Java compiler. |
| |
| Note that all constructors are static by definition, so |
| isStaticReference is true. |
| |
| @exception NoSuchMethodException if constructor not found. |
| */ |
| static public Constructor getConstructor(Class targetClass, Class[] argTypes) |
| throws SecurityException, NoSuchMethodException |
| { |
| return (Constructor) getEntryPoint(targetClass,null,argTypes,true); |
| } |
| ////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Search for entry point, per Java Language Spec 1.0 |
| * as amended, verified by comparison against compiler behavior. |
| * |
| * @param targetClass Class object for the class to be queried. |
| * @param methodName Name of method to invoke, or null for constructor. |
| * Only Public methods will be accepted. |
| * @param argTypes Classes of intended arguments. Note that primitives |
| * must be specified via their TYPE equivalents, |
| * rather than as their wrapper classes -- Integer.TYPE |
| * rather than Integer. "null" may be passed in as an |
| * indication that you intend to invoke the method with |
| * a literal null argument and therefore can accept |
| * any object type in this position. |
| * @param isStaticReference If true, and if the target is a Class object, |
| * only static methods will be accepted as valid matches. |
| * |
| * @return a Method or Constructor of the appropriate signature |
| * |
| * @exception SecurityException if security violation |
| * @exception NoSuchMethodException if no such method |
| */ |
| static private Object getEntryPoint(Class targetClass, |
| String methodName, |
| Class[] argTypes, |
| boolean isStaticReference) |
| throws SecurityException, NoSuchMethodException |
| { |
| // 15.11.1: OBTAIN STARTING CLASS FOR SEARCH |
| Object m=null; |
| |
| // 15.11.2 DETERMINE ARGUMENT SIGNATURE |
| // (Passed in as argTypes array.) |
| |
| // Shortcut: If an exact match exists, return it. |
| try { |
| if(methodName!=null) |
| { |
| m=targetClass.getMethod (methodName, argTypes); |
| if(isStaticReference && |
| !Modifier.isStatic(entryGetModifiers(m)) ) |
| { |
| throw |
| new NoSuchMethodException (callToString (targetClass, |
| methodName, |
| argTypes, |
| isStaticReference)+ |
| " resolved to instance " + m); |
| } |
| return m; |
| } |
| else |
| return targetClass.getConstructor (argTypes); |
| |
| } catch (NoSuchMethodException e) { |
| // no-args has no alternatives! |
| if(argTypes==null || argTypes.length==0) |
| { |
| throw |
| new NoSuchMethodException (callToString (targetClass, |
| methodName, |
| argTypes, |
| isStaticReference)+ |
| " not found."); |
| } |
| // Else fall through. |
| } |
| |
| // Well, _that_ didn't work. Time to search for the Most Specific |
| // matching function. NOTE that conflicts are possible! |
| |
| // 15.11.2.1 ACCESSIBLE: We apparently need to gather from two |
| // sources to be sure we have both instance and static methods. |
| Object[] methods; |
| if(methodName!=null) |
| { |
| methods=targetClass.getMethods(); |
| } |
| else |
| { |
| methods=targetClass.getConstructors(); |
| } |
| if(0==methods.length) |
| { |
| throw new NoSuchMethodException("No methods!"); |
| } |
| |
| MoreSpecific best=new MoreSpecific(); |
| for(int i=0;i<methods.length;++i) |
| { |
| Object mi=methods[i]; |
| if ( |
| // 15.11.2.1 ACCESSIBLE: Method is public. |
| Modifier.isPublic(entryGetModifiers(mi)) |
| && |
| // 15.11.2.1 APPLICABLE: Right method name (or c'tor) |
| (methodName==null || entryGetName(mi).equals(methodName) ) |
| && |
| // 15.11.2.1 APPLICABLE: Parameters match arguments |
| areMethodConvertable(entryGetParameterTypes(mi),argTypes) |
| ) |
| // 15.11.2.2 MORE SPECIFIC displace less specific. |
| best.addItem(mi); |
| } |
| |
| // May throw NoSuchMethodException; we pass in info needed to |
| // create a useful exception |
| m=best.getMostSpecific(targetClass,methodName,argTypes,isStaticReference); |
| |
| // 15.11.3 APPROPRIATE: Class invocation can call only static |
| // methods. Note that the defined order of evaluation permits a |
| // call to be resolved to an inappropriate method and then |
| // rejected, rather than finding the best of the appropriate |
| // methods. |
| // |
| // Constructors are never static, so we don't test them. |
| if(m==null) |
| { |
| throw new NoSuchMethodException (callToString(targetClass, |
| methodName, |
| argTypes, |
| isStaticReference)+ |
| " -- no signature match"); |
| } |
| |
| if( methodName!=null && |
| isStaticReference && |
| !Modifier.isStatic(entryGetModifiers(m)) ) |
| { |
| throw new NoSuchMethodException (callToString(targetClass, |
| methodName, |
| argTypes, |
| isStaticReference)+ |
| " resolved to instance: "+m); |
| } |
| |
| return m; |
| } |
| ////////////////////////////////////////////////////////////////////////// |
| |
| /* Class.getMethod() finds only the entry point (if any) _exactly_ |
| matching the specified argument types. Our implmentation can |
| decide between several imperfect matches, using the same search |
| algorithm as the Java compiler. |
| |
| This version more closely resembles Class.getMethod() -- we always |
| ask the Class for the method. It differs in testing for |
| appropriateness before returning the method; if the query is |
| being made via a static reference, only static methods will be |
| found and returned. */ |
| static public Method getMethod(Class target,String methodName, |
| Class[] argTypes,boolean isStaticReference) |
| throws SecurityException, NoSuchMethodException |
| { |
| return (Method)getEntryPoint(target,methodName,argTypes,isStaticReference); |
| } |
| ////////////////////////////////////////////////////////////////////////// |
| |
| /** |
| * Class.getMethod() finds only the entry point (if any) _exactly_ |
| * matching the specified argument types. Our implmentation can |
| * decide between several imperfect matches, using the same search |
| * algorithm as the Java compiler. |
| * |
| * This version emulates the compiler behavior by allowing lookup to |
| * be performed against either a class or an instance -- classname.foo() |
| * must be a static method call, instance.foo() can invoke either static |
| * or instance methods. |
| * |
| * @param target object on which call is to be made |
| * @param methodName name of method I'm lookin' for |
| * @param argTypes array of argument types of method |
| * |
| * @return the desired method |
| * |
| * @exception SecurityException if security violation |
| * @exception NoSuchMethodException if no such method |
| */ |
| static public Method getMethod(Object target,String methodName, |
| Class[] argTypes) |
| throws SecurityException, NoSuchMethodException |
| { |
| boolean staticRef=target instanceof Class; |
| return getMethod( staticRef ? (Class)target : target.getClass(), |
| methodName,argTypes,staticRef); |
| } |
| /** Determine whether a given type can accept assignments of another |
| type. Note that class.isAssignable() is _not_ a complete test! |
| (This method is not needed by getMethod() or getConstructor(), but |
| is provided as a convenience for other users.) |
| |
| parm: The type given in the method's signature. |
| arg: The type we want to pass in. |
| |
| Legal ASSIGNMENT CONVERSIONS (5.2) are METHOD CONVERSIONS (5.3) |
| plus implicit narrowing of int to byte, short or char. */ |
| static private boolean isAssignmentConvertable(Class parm,Class arg) |
| { |
| return |
| (arg.equals(Integer.TYPE) && |
| (parm.equals(Byte.TYPE) || |
| parm.equals(Short.TYPE) || |
| parm.equals(Character.TYPE) |
| ) |
| ) || |
| isMethodConvertable(parm,arg); |
| } |
| /** Determine whether a given method parameter type can accept |
| arguments of another type. |
| |
| parm: The type given in the method's signature. |
| arg: The type we want to pass in. |
| |
| Legal METHOD CONVERSIONS (5.3) are Identity, Widening Primitive |
| Conversion, or Widening Reference Conversion. NOTE that this is a |
| subset of the legal ASSIGNMENT CONVERSIONS (5.2) -- in particular, |
| we can't implicitly narrow int to byte, short or char. |
| |
| SPECIAL CASE: In order to permit invoking methods with literal |
| "null" values, setting the arg Class to null will be taken as a |
| request to match any Class type. POSSIBLE PROBLEM: This may match |
| a primitive type, which really should not accept a null value... but |
| I'm not sure how best to distinguish those, short of enumerating them |
| */ |
| static private boolean isMethodConvertable(Class parm, Class arg) |
| { |
| if (parm.equals(arg)) // If same class, short-circuit now! |
| return true; |
| |
| // Accept any type EXCEPT primitives (which can't have null values). |
| if (arg == null) |
| { |
| return !parm.isPrimitive(); |
| } |
| |
| // Arrays are convertable if their elements are convertable |
| // ????? Does this have to be done before isAssignableFrom, or |
| // does it successfully handle arrays of primatives? |
| while(parm.isArray()) |
| { |
| if(!arg.isArray()) |
| return false; // Unequal array depth |
| else |
| { |
| parm=parm.getComponentType(); |
| arg=arg.getComponentType(); |
| } |
| } |
| if(arg.isArray()) |
| return false; // Unequal array depth |
| |
| // Despite its name, the 1.1.6 docs say that this function does |
| // NOT return true for all legal ASSIGNMENT CONVERSIONS |
| // (5.2): |
| // "Specifically, this method tests whether the type |
| // represented by the specified class can be converted |
| // to the type represented by this Class object via |
| // an identity conversion or via a widening reference |
| // conversion." |
| if(parm.isAssignableFrom(arg)) |
| return true; |
| |
| // That leaves us the Widening Primitives case. Four possibilities: |
| // void (can only convert to void), boolean (can only convert to boolean), |
| // numeric (which are sequenced) and char (which inserts itself into the |
| // numerics by promoting to int or larger) |
| |
| if(parm.equals(Void.TYPE) || parm.equals(Boolean.TYPE) || |
| arg.equals(Void.TYPE) || arg.equals(Boolean.TYPE)) |
| return false; |
| |
| Class[] primTypes={ Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, |
| Long.TYPE, Float.TYPE, Double.TYPE }; |
| int parmscore,argscore; |
| |
| for(parmscore=0;parmscore<primTypes.length;++parmscore) |
| if (parm.equals(primTypes[parmscore])) |
| break; |
| if(parmscore>=primTypes.length) |
| return false; // Off the end |
| |
| for(argscore=0;argscore<primTypes.length;++argscore) |
| if (arg.equals(primTypes[argscore])) |
| break; |
| if(argscore>=primTypes.length) |
| return false; // Off the end |
| |
| // OK if ordered AND NOT char-to-smaller-than-int |
| return (argscore<parmscore && (argscore!=0 || parmscore>2) ); |
| } |
| } |