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

Save This Page
Home » bsf-src-2.4.0 » org.apache.bsf.util » [javadoc | source]