PageRenderTime 163ms CodeModel.GetById 30ms app.highlight 101ms RepoModel.GetById 20ms app.codeStats 1ms

/jEdit/tags/jedit-4-2-pre4/bsh/Reflect.java

#
Java | 1183 lines | 709 code | 132 blank | 342 comment | 197 complexity | 9ee11c7dbdd03c5ec344420f5d19bb43 MD5 | raw file
   1/*****************************************************************************
   2 *                                                                           *
   3 *  This file is part of the BeanShell Java Scripting distribution.          *
   4 *  Documentation and updates may be found at http://www.beanshell.org/      *
   5 *                                                                           *
   6 *  Sun Public License Notice:                                               *
   7 *                                                                           *
   8 *  The contents of this file are subject to the Sun Public License Version  *
   9 *  1.0 (the "License"); you may not use this file except in compliance with *
  10 *  the License. A copy of the License is available at http://www.sun.com    * 
  11 *                                                                           *
  12 *  The Original Code is BeanShell. The Initial Developer of the Original    *
  13 *  Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright     *
  14 *  (C) 2000.  All Rights Reserved.                                          *
  15 *                                                                           *
  16 *  GNU Public License Notice:                                               *
  17 *                                                                           *
  18 *  Alternatively, the contents of this file may be used under the terms of  *
  19 *  the GNU Lesser General Public License (the "LGPL"), in which case the    *
  20 *  provisions of LGPL are applicable instead of those above. If you wish to *
  21 *  allow use of your version of this file only under the  terms of the LGPL *
  22 *  and not to allow others to use your version of this file under the SPL,  *
  23 *  indicate your decision by deleting the provisions above and replace      *
  24 *  them with the notice and other provisions required by the LGPL.  If you  *
  25 *  do not delete the provisions above, a recipient may use your version of  *
  26 *  this file under either the SPL or the LGPL.                              *
  27 *                                                                           *
  28 *  Patrick Niemeyer (pat@pat.net)                                           *
  29 *  Author of Learning Java, O'Reilly & Associates                           *
  30 *  http://www.pat.net/~pat/                                                 *
  31 *                                                                           *
  32 *****************************************************************************/
  33
  34package bsh;
  35
  36import java.lang.reflect.*;
  37import java.io.*;
  38import java.util.Vector;
  39
  40/**
  41    All of the reflection API code lies here.  It is in the form
  42	of static utilities.  Maybe this belongs in LHS.java or a generic object 
  43	wrapper class.
  44
  45	Note: More work to do in here to fix up the extended signature matching.
  46	need to work in a search along with findMostSpecificSignature...
  47	<p>
  48
  49	Note: there are lots of cases here where the Java reflection API makes
  50	us catch exceptions (e.g. NoSuchFieldException) in order to do basic
  51	searching.  This has to be inefficient...  I wish they would add a more
  52	normal Java API for locating fields.
  53*/
  54class Reflect 
  55{
  56    /**
  57		Invoke method on arbitrary object.
  58		invocation may be static (through the object instance) or dynamic.
  59		Object may be a bsh scripted object (This type).
  60	*/
  61    public static Object invokeObjectMethod(
  62		Object object, String methodName, Object[] args, 
  63		Interpreter interpreter, CallStack callstack, SimpleNode callerInfo ) 
  64		throws ReflectError, EvalError, InvocationTargetException
  65	{
  66		// Bsh scripted object
  67		if ( object instanceof This && !passThisMethod( methodName) ) 
  68			return ((This)object).invokeMethod( 
  69				methodName, args, interpreter, callstack, callerInfo );
  70		else 
  71		// Java object
  72		{ 
  73			// find the java method
  74			try {
  75				BshClassManager bcm = callstack.top().getClassManager();
  76				Class clas = object.getClass();
  77
  78				Method method = resolveJavaMethod( 
  79					bcm, clas, object, methodName, args, false );
  80
  81				return invokeOnMethod( method, object, args );
  82			} catch ( UtilEvalError e ) {
  83				throw e.toEvalError( callerInfo, callstack );
  84			}
  85		}
  86    }
  87
  88    /** 
  89		Invoke a method known to be static.
  90		No object instance is needed and there is no possibility of the 
  91		method being a bsh scripted method.
  92	*/
  93    public static Object invokeStaticMethod(
  94		BshClassManager bcm, Class clas, String methodName, Object [] args )
  95        throws ReflectError, UtilEvalError, InvocationTargetException
  96    {
  97        Interpreter.debug("invoke static Method");
  98        Method method = resolveJavaMethod( 
  99			bcm, clas, null, methodName, args, true );
 100		return invokeOnMethod( method, null, args );
 101    }
 102
 103	/**
 104	*/
 105	private static Object invokeOnMethod( 
 106		Method method, Object object, Object[] args ) 
 107		throws ReflectError, InvocationTargetException
 108	{
 109		if ( Interpreter.DEBUG ) 
 110		{
 111			Interpreter.debug("Invoking method (entry): "
 112				+method+" with args:" );
 113			for(int i=0; i<args.length; i++)
 114				Interpreter.debug(
 115					"args["+i+"] = "+args[i]
 116					+" type = "+args[i].getClass() );
 117		}
 118		
 119		// Map types to assignable forms, need to keep this fast...
 120		Object [] tmpArgs = new Object [ args.length ];
 121		Class [] types = method.getParameterTypes();
 122		try {
 123			for (int i=0; i<args.length; i++)
 124				tmpArgs[i] = NameSpace.getAssignableForm( args[i], types[i] );
 125		} catch ( UtilEvalError e ) {
 126			throw new InterpreterError(
 127				"illegal argument type in method invocation: "+e );
 128		}
 129
 130		// unwrap any primitives
 131		tmpArgs = unwrapPrimitives( tmpArgs );
 132
 133		if ( Interpreter.DEBUG ) 
 134		{
 135			Interpreter.debug("Invoking method (after massaging values): "
 136				+method+" with tmpArgs:" );
 137			for(int i=0; i<tmpArgs.length; i++)
 138				Interpreter.debug(
 139					"tmpArgs["+i+"] = "+tmpArgs[i]
 140					+" type = "+tmpArgs[i].getClass() );
 141		}
 142
 143		try 
 144		{
 145			Object returnValue = method.invoke( object, tmpArgs );
 146			if ( returnValue == null )
 147				returnValue = Primitive.NULL;
 148			Class returnType = method.getReturnType();
 149
 150			return wrapPrimitive( returnValue, returnType );
 151		} catch( IllegalAccessException e ) {
 152			throw new ReflectError( "Cannot access method " 
 153				+ StringUtil.methodString(
 154					method.getName(), method.getParameterTypes() ) 
 155				+ " in '" + method.getDeclaringClass() + "' :" + e );
 156		}
 157	}
 158
 159	/**
 160		Allow invocations of these method names on This type objects.
 161		Don't give bsh.This a chance to override their behavior.
 162		<p>
 163
 164		If the method is passed here the invocation will actually happen on
 165		the bsh.This object via the regular reflective method invocation 
 166		mechanism.  If not, then the method is evaluated by bsh.This itself
 167		as a scripted method call.
 168	*/
 169	private static boolean passThisMethod( String name ) 
 170	{
 171		return 
 172			name.equals("getClass") 
 173			|| name.equals("invokeMethod")
 174			|| name.equals("getInterface")
 175			// These are necessary to let us test synchronization from scripts
 176			|| name.equals("wait") 
 177			|| name.equals("notify")
 178			|| name.equals("notifyAll");
 179	}
 180
 181    public static Object getIndex(Object array, int index)
 182        throws ReflectError, UtilTargetError
 183    {
 184		if ( Interpreter.DEBUG ) 
 185			Interpreter.debug("getIndex: "+array+", index="+index);
 186        try {
 187            Object val = Array.get(array, index);
 188            return wrapPrimitive(val, array.getClass().getComponentType());
 189        }
 190        catch( ArrayIndexOutOfBoundsException  e1 ) {
 191			throw new UtilTargetError( e1 );
 192        } catch(Exception e) {
 193            throw new ReflectError("Array access:" + e);
 194        }
 195    }
 196
 197    public static void setIndex(Object array, int index, Object val)
 198        throws ReflectError, UtilTargetError
 199    {
 200        try {
 201            val = Primitive.unwrap(val);
 202            Array.set(array, index, val);
 203        }
 204        catch( ArrayStoreException e2 ) {
 205			throw new UtilTargetError( e2 );
 206        } catch( IllegalArgumentException e1 ) {
 207			throw new UtilTargetError( 
 208				new ArrayStoreException( e1.toString() ) );
 209        } catch(Exception e) {
 210            throw new ReflectError("Array access:" + e);
 211        }
 212    }
 213
 214    public static Object getStaticField(Class clas, String fieldName)
 215        throws UtilEvalError, ReflectError
 216    {
 217        return getFieldValue(clas, null, fieldName);
 218    }
 219
 220    public static Object getObjectField( Object object, String fieldName )
 221        throws UtilEvalError, ReflectError
 222    {
 223		if ( object instanceof This )
 224			return ((This)object).namespace.getVariable( fieldName );
 225		else {
 226			try {
 227				return getFieldValue(object.getClass(), object, fieldName);
 228			} catch ( ReflectError e ) {
 229				// no field, try property acces
 230
 231				if ( hasObjectPropertyGetter( object.getClass(), fieldName ) )
 232					return getObjectProperty( object, fieldName );
 233				else
 234					throw e;
 235			}
 236		}
 237    }
 238
 239    static LHS getLHSStaticField(Class clas, String fieldName)
 240        throws UtilEvalError, ReflectError
 241    {
 242        Field f = getField(clas, fieldName);
 243        return new LHS(f);
 244    }
 245
 246	/**
 247		Get an LHS reference to an object field.
 248
 249		This method also deals with the field style property access.
 250		In the field does not exist we check for a property setter.
 251	*/
 252    static LHS getLHSObjectField( Object object, String fieldName )
 253        throws UtilEvalError, ReflectError
 254    {
 255		if ( object instanceof This )
 256		{
 257			// I guess this is when we pass it as an argument?
 258			// Setting locally
 259			boolean recurse = false; 
 260			return new LHS( ((This)object).namespace, fieldName, recurse );
 261		}
 262
 263		try {
 264			Field f = getField(object.getClass(), fieldName);
 265			return new LHS(object, f);
 266		} catch ( ReflectError e ) {
 267			// not a field, try property access
 268
 269			if ( hasObjectPropertySetter( object.getClass(), fieldName ) )
 270				return new LHS( object, fieldName );
 271			else
 272				throw e;
 273		}
 274    }
 275
 276    private static Object getFieldValue(
 277		Class clas, Object object, String fieldName) 
 278		throws UtilEvalError, ReflectError
 279    {
 280        try {
 281            Field f = getField(clas, fieldName);
 282
 283            if ( f == null )
 284                throw new ReflectError("internal: field not found:"+fieldName);
 285
 286            Object value = f.get(object);
 287            Class returnType = f.getType();
 288            return wrapPrimitive(value, returnType);
 289
 290        }
 291        catch(NullPointerException e) {
 292            throw new ReflectError(
 293				"???" + fieldName + " is not a static field.");
 294        }
 295        catch(IllegalAccessException e) {
 296            throw new ReflectError("Can't access field: " + fieldName);
 297        }
 298    }
 299
 300	/**
 301		All field lookup should come through here.
 302		i.e. this method owns Class getField();
 303	*/
 304    private static Field getField(Class clas, String fieldName)
 305        throws UtilEvalError, ReflectError
 306    {
 307        try
 308        {
 309			if ( Capabilities.haveAccessibility() )
 310				return findAccessibleField( clas, fieldName );
 311			else
 312				// this one only finds public (and in interfaces, etc.)
 313				return clas.getField(fieldName);
 314        }
 315        catch( NoSuchFieldException e)
 316        {
 317			// try declaredField
 318            throw new ReflectError("No such field: " + fieldName );
 319        }
 320    }
 321
 322	/**
 323		Used when accessibility capability is available to locate an occurrance
 324		of the field in the most derived class or superclass and set its 
 325		accessibility flag.
 326		Note that this method is not needed in the simple non accessible
 327		case because we don't have to hunt for fields.
 328		Note that classes may declare overlapping private fields, so the 
 329		distinction about the most derived is important.  Java doesn't normally
 330		allow this kind of access (super won't show private variables) so 
 331		there is no real syntax for specifying which class scope to use...
 332	*/
 333	private static Field findAccessibleField( Class clas, String fieldName ) 
 334		throws UtilEvalError, NoSuchFieldException
 335	{
 336		// Quick check catches public fields include those in interfaces
 337		try {
 338			return clas.getField(fieldName);
 339		} catch ( NoSuchFieldException e ) { }
 340
 341		// Now, on with the hunt...
 342		while ( clas != null )
 343		{
 344			try {
 345				Field field = clas.getDeclaredField(fieldName);
 346				if ( ReflectManager.RMSetAccessible( field ) )
 347					return field;
 348
 349			/*
 350				// Try interfaces of class for the field (has to be public)
 351				Class [] interfaces = clas.getInterfaces();
 352				for(int i=0; i<interfaces.length;i++) {
 353					try {
 354						return interfaces[i].getField( fieldName );
 355					} catch ( NoSuchFieldException e ) { }
 356				}
 357			*/
 358				// Not found, fall through to next class
 359
 360			} catch(NoSuchFieldException e) { }
 361
 362			clas = clas.getSuperclass();
 363		}
 364		throw new NoSuchFieldException( fieldName );
 365	}
 366
 367    /**
 368        The full blown resolver method.  Everybody should come here.
 369		The method may be static or dynamic unless onlyStatic is set
 370		(in which case object may be null).
 371
 372		@param onlyStatic 
 373			The method located must be static, the object param may be null.
 374		@throws ReflectError if method is not found
 375	*/
 376	/*
 377		Note: Method invocation could probably be speeded up if we eliminated
 378		the throwing of exceptions in the search for the proper method.
 379		After 1.3 we are caching method resolution anyway... shouldn't matter
 380		much.
 381    */
 382    static Method resolveJavaMethod (
 383		BshClassManager bcm, Class clas, Object object, 
 384		String name, Object[] args, boolean onlyStatic
 385	)
 386        throws ReflectError, UtilEvalError
 387    {
 388		Method method = null;
 389
 390		if ( bcm == null ) 
 391			Interpreter.debug("resolveJavaMethod UNOPTIMIZED lookup");
 392		else
 393		{
 394			method = bcm.getResolvedMethod( clas, name, args, onlyStatic );
 395			if ( method != null )
 396				return method;
 397		}
 398
 399		// This should probably be handled higher up
 400		if ( object == Primitive.NULL )
 401			throw new UtilTargetError( new NullPointerException(
 402				"Attempt to invoke method " +name+" on null value" ) );
 403
 404        Class [] types = getTypes(args);
 405
 406		// this was unecessary and counterproductive
 407        //args=unwrapPrimitives(args);
 408
 409		// First try for an accessible version of the exact match.
 410
 411		if ( Interpreter.DEBUG )
 412			Interpreter.debug( "Searching for method: "+
 413				StringUtil.methodString(name, types)
 414					+ " in '" + clas.getName() + "'" );
 415
 416// Why do we do this?  Won't the overloaded resolution below find it
 417// just as well -- try to merge these next 
 418
 419		try {
 420			method  = findAccessibleMethod(clas, name, types, onlyStatic);
 421		} catch ( SecurityException e ) { }
 422
 423		if ( Interpreter.DEBUG && method != null )
 424			Interpreter.debug("findAccessibleMethod found: "+ method );
 425
 426		// Look for an overloaded / standard Java assignable match
 427		// (First find the method, then find accessible version of it)
 428		if ( method == null ) 
 429		{
 430			// If no args stop here, can't do better than exact match above
 431			if ( types.length == 0 )
 432				throw new ReflectError(
 433					"No args "+ ( onlyStatic ? "static " : "" )
 434					+"method " + StringUtil.methodString(name, types) + 
 435					" not found in class'" + clas.getName() + "'");
 436
 437			Method [] methods = clas.getMethods();
 438			if ( onlyStatic )
 439				methods = retainStaticMethods( methods );
 440
 441			method = findMostSpecificMethod( name, types, methods );
 442
 443			if ( Interpreter.DEBUG && method != null )
 444				Interpreter.debug("findMostSpecificMethod found: "+ method );
 445
 446			// try to find an extended method
 447			if ( method == null )
 448			{
 449				method = findExtendedMethod( name, args, methods );
 450
 451				if ( Interpreter.DEBUG && method != null )
 452					Interpreter.debug("findExtendedMethod found: "+ method );
 453			}
 454
 455			// If we found an assignable or extended method, make sure we have 
 456			// an accessible version of it
 457			if ( method != null ) 
 458			{
 459				try {
 460					method = findAccessibleMethod( clas, method.getName(), 
 461						method.getParameterTypes(), onlyStatic);
 462				} catch ( SecurityException e ) { }
 463				if ( Interpreter.DEBUG && method == null )
 464					Interpreter.debug(
 465						"had a method, but it wasn't accessible");
 466			}
 467		}
 468
 469		// If we didn't find anything throw error
 470		if ( method == null )
 471			throw new ReflectError(
 472				( onlyStatic ? "Static method " : "Method " )
 473				+ StringUtil.methodString(name, types) + 
 474				" not found in class'" + clas.getName() + "'");
 475
 476		// Succeeded.  Cache the resolved method.
 477		if ( bcm != null )
 478			bcm.cacheResolvedMethod( clas, args, method );
 479
 480		return method;
 481	}
 482
 483	/**
 484		Return only the static methods
 485	*/
 486	private static Method [] retainStaticMethods( Method [] methods ) {
 487		Vector v = new Vector();
 488		for(int i=0; i<methods.length; i++)
 489			if ( Modifier.isStatic( methods[i].getModifiers() ) )
 490				v.addElement( methods[i] );
 491
 492		Method [] ma = new Method [ v.size() ];
 493		v.copyInto( ma );
 494		return ma;
 495	}
 496
 497	/**
 498		Locate a version of the method with the exact signature specified 
 499		that is accessible via a public interface or through a public 
 500		superclass or - if accessibility is on - through any interface or
 501		superclass.
 502
 503		In the normal (non-accessible) case this still solves the problem that 
 504		arises when a package private class or private inner class implements a 
 505		public interface or derives from a public type.
 506
 507		@param onlyStatic the method located must be static.
 508		@return null on not found
 509	*/
 510	static Method findAccessibleMethod( 
 511		Class clas, String name, Class [] types, boolean onlyStatic ) 
 512		throws UtilEvalError
 513	{
 514		Method meth = null;
 515		Method inaccessibleVersion = null;
 516		Vector classQ = new Vector();
 517
 518		classQ.addElement( clas );
 519		Method found = null;
 520		while ( classQ.size() > 0 ) 
 521		{
 522			Class c = (Class)classQ.firstElement();
 523			classQ.removeElementAt(0);
 524
 525			// Is this it?
 526			// Is the class public or can we use accessibility?
 527			if ( Modifier.isPublic( c.getModifiers() )
 528				|| ( Capabilities.haveAccessibility() ) )
 529			{
 530				try 
 531				{
 532					meth = c.getDeclaredMethod( name, types );
 533
 534					// Is the method public or are we in accessibility mode?
 535					if ( ( Modifier.isPublic( meth.getModifiers() )
 536						&& Modifier.isPublic( c.getModifiers() ) )
 537						|| ( Capabilities.haveAccessibility() 
 538							&& ReflectManager.RMSetAccessible( meth ) ) )
 539					{
 540						found = meth; // Yes, it is.
 541						break;
 542					}
 543					else
 544					{
 545						// Found at least one matching method but couldn't use
 546						inaccessibleVersion = meth;
 547					}
 548				} catch ( NoSuchMethodException e ) { 
 549					// ignore and move on
 550				}
 551			}
 552			// No, it is not.
 553			
 554			// Is this a class?
 555			if ( !c.isInterface() ) {
 556				Class superclass = c.getSuperclass();
 557				if ( superclass != null )
 558					classQ.addElement((Object)superclass);
 559			}
 560
 561			// search all of its interfaces breadth first
 562			Class [] intfs = c.getInterfaces();
 563			for( int i=0; i< intfs.length; i++ )
 564				classQ.addElement((Object)intfs[i]);
 565		}
 566
 567		/* 
 568			If we found one and it satisfies onlyStatic return it
 569			
 570			Note: I don't believe it is necessary to check for the static
 571			condition in the above search because the Java compiler will not
 572			let dynamic and static methods hide/override one another.  So
 573			we simply check what is found, if any, at the end.
 574		*/
 575		if ( found != null &&
 576			( !onlyStatic || Modifier.isStatic( found.getModifiers() ) ) )
 577			return found;
 578
 579		/*
 580			Not sure if this the best place to do this...
 581		*/
 582		if ( inaccessibleVersion != null )
 583			throw new UtilEvalError("Found non-public method: "
 584				+inaccessibleVersion
 585				+".  Use setAccessibility(true) to enable access to "
 586				+" private and protected members of classes." );
 587		
 588		return null;
 589	}
 590
 591    private static Object wrapPrimitive(
 592		Object value, Class returnType) throws ReflectError
 593    {
 594        if(value == null)
 595            return Primitive.NULL;
 596
 597        if(returnType == Void.TYPE)
 598            return Primitive.VOID;
 599
 600        else
 601            if(returnType.isPrimitive())
 602            {
 603                if(value instanceof Number)
 604                    return new Primitive((Number)value);
 605                if(value instanceof Boolean)
 606                    return new Primitive((Boolean)value);
 607                if(value instanceof Character)
 608                    return new Primitive((Character)value);
 609
 610                throw new ReflectError("Something bad happened");
 611            }
 612            else
 613                return value;
 614    }
 615
 616    public static Class[] getTypes( Object[] args )
 617    {
 618        if ( args == null )
 619            return new Class[0];
 620
 621        Class[] types = new Class[ args.length ];
 622
 623        for( int i=0; i<args.length; i++ )
 624        {
 625			if ( args[i] == null )
 626				types[i] = null;
 627            else if ( args[i] instanceof Primitive )
 628                types[i] = ((Primitive)args[i]).getType();
 629            else
 630                types[i] = args[i].getClass();
 631        }
 632
 633        return types;
 634    }
 635
 636    /*
 637        Unwrap Primitive wrappers to their java.lang wrapper values.
 638		e.g. Primitive(42) becomes Integer(42)
 639    */
 640    private static Object [] unwrapPrimitives( Object[] args )
 641    {
 642		Object [] oa = new Object[ args.length ];
 643        for(int i=0; i<args.length; i++)
 644            oa[i] = Primitive.unwrap( args[i] );
 645		return oa;
 646    }
 647
 648	/*
 649    private static Object unwrapPrimitive( Object arg )
 650    {
 651        if ( arg instanceof Primitive )
 652            return((Primitive)arg).getValue();
 653        else
 654            return arg;
 655    }
 656	*/
 657
 658	/**
 659		Primary object constructor
 660		This method is simpler than those that must resolve general method
 661		invocation because constructors are not inherited.
 662	*/
 663    static Object constructObject( Class clas, Object[] args )
 664        throws ReflectError, InvocationTargetException
 665    {
 666		if ( clas.isInterface() )
 667			throw new ReflectError(
 668				"Can't create instance of an interface: "+clas);
 669
 670        Object obj = null;
 671        Class[] types = getTypes(args);
 672		// this wasn't necessary until we invoke it, moved...
 673        //args=unwrapPrimitives(args);
 674        Constructor con = null;
 675
 676		/* 
 677			Find an appropriate constructor
 678			use declared here to see package and private as well
 679			(there are no inherited constructors to worry about) 
 680		*/
 681		Constructor[] constructors = clas.getDeclaredConstructors();
 682		if ( Interpreter.DEBUG ) 
 683			Interpreter.debug("Looking for most specific constructor: "+clas);
 684		con = findMostSpecificConstructor(types, constructors);
 685
 686		if ( con == null )
 687			if ( types.length == 0 )
 688				throw new ReflectError(
 689					"Can't find default constructor for: "+clas);
 690			else
 691				con = findExtendedConstructor(args, constructors);
 692
 693		if ( con == null )
 694			throw new ReflectError(
 695				"Can't find constructor: " 
 696				+ StringUtil.methodString( clas.getName(), types )
 697				+" in class: "+ clas.getName() );;
 698
 699        try {
 700        	args=unwrapPrimitives( args );
 701            obj = con.newInstance( args );
 702        } catch(InstantiationException e) {
 703            throw new ReflectError("the class is abstract ");
 704        } catch(IllegalAccessException e) {
 705            throw new ReflectError(
 706				"we don't have permission to create an instance");
 707        } catch(IllegalArgumentException e) {
 708            throw new ReflectError("the number of arguments was wrong");
 709        } 
 710		if (obj == null)
 711            throw new ReflectError("couldn't construct the object");
 712
 713        return obj;
 714    }
 715
 716    /**
 717        Implement JLS 15.11.2 for method resolution
 718		@return null on no match
 719    */
 720    static Method findMostSpecificMethod(
 721		String name, Class[] idealMatch, Method[] methods )
 722    {
 723		// Pull out the method signatures with matching names
 724		Vector sigs = new Vector();
 725		Vector meths = new Vector();
 726		for(int i=0; i<methods.length; i++)
 727		{
 728			// method matches name 
 729			if ( methods[i].getName().equals( name )  ) 
 730			{
 731				meths.addElement( methods[i] );
 732				sigs.addElement( methods[i].getParameterTypes() );
 733			}
 734		}
 735
 736		Class [][] candidates = new Class [ sigs.size() ][];
 737		sigs.copyInto( candidates );
 738
 739		if ( Interpreter.DEBUG ) 
 740			Interpreter.debug("Looking for most specific method: "+name);
 741		int match = findMostSpecificSignature( idealMatch, candidates );
 742		if ( match == -1 )
 743			return null;
 744		else
 745			return (Method)meths.elementAt( match );
 746    }
 747
 748    /*
 749        This method should parallel findMostSpecificMethod()
 750    */
 751    static Constructor findMostSpecificConstructor(
 752		Class[] idealMatch, Constructor[] constructors)
 753    {
 754		// We don't have to worry about the name of our constructors
 755
 756		Class [][] candidates = new Class [ constructors.length ] [];
 757		for(int i=0; i< candidates.length; i++ )
 758			candidates[i] = constructors[i].getParameterTypes();
 759
 760		int match = findMostSpecificSignature( idealMatch, candidates );
 761		if ( match == -1 )
 762			return null;
 763		else
 764			return constructors[ match ];
 765    }
 766
 767	/**
 768		findExtendedMethod uses the NameSpace.getAssignableForm() method to 
 769		determine compatability of arguments.  This allows special (non
 770		standard Java) bsh widening operations.  
 771		
 772		Note that this method examines the *arguments* themselves not the types.
 773
 774		@param args the arguments
 775		@return null on not found
 776	*/
 777	/*
 778		Note: shouldn't we use something analagous to findMostSpecificSignature
 779		on a result set, rather than choosing the first one we find?
 780		(findMostSpecificSignature doesn't know about extended types).
 781	*/ 
 782    static Method findExtendedMethod(
 783		String name, Object[] args, Method[] methods )
 784    {
 785        for(int i = 0; i < methods.length; i++) 
 786		{
 787            Method currentMethod = methods[i];
 788			Class[] parameterTypes = currentMethod.getParameterTypes();
 789            if ( name.equals( currentMethod.getName() ) 
 790					&& argsAssignable( parameterTypes, args ) ) 
 791				return currentMethod;
 792        }
 793
 794        return null;
 795    }
 796
 797	/**
 798		This uses the NameSpace.getAssignableForm() method to determine
 799		compatability of args.  This allows special (non standard Java) bsh 
 800		widening operations...
 801	*/
 802    static Constructor findExtendedConstructor(
 803		Object[] args, Constructor[] constructors )
 804    {
 805        for(int i = 0; i < constructors.length; i++) 
 806		{
 807            Constructor currentConstructor = constructors[i];
 808            Class[] parameterTypes = currentConstructor.getParameterTypes();
 809            if ( argsAssignable( parameterTypes, args ) ) 
 810				return currentConstructor;
 811        }
 812
 813        return null;
 814    }
 815
 816	/**
 817		Arguments are assignable as defined by NameSpace.getAssignableForm()
 818		which takes into account special bsh conversions such as XThis and (ug)
 819		primitive wrapper promotion.
 820	*/
 821	private static boolean argsAssignable( Class [] parameters, Object [] args )
 822	{
 823		if ( parameters.length != args.length )
 824			return false;
 825
 826		try {
 827			for(int j = 0; j < parameters.length; j++)
 828				NameSpace.getAssignableForm( args[j], parameters[j]);
 829		} catch ( UtilEvalError e ) {
 830			return false;
 831		}
 832		return true;
 833	}
 834
 835
 836	/**
 837        Implement JLS 15.11.2
 838		Return the index of the most specific arguments match or -1 if no	
 839		match is found.
 840	*/
 841	static int findMostSpecificSignature(
 842		Class [] idealMatch, Class [][] candidates )
 843	{
 844		Class [] bestMatch = null;
 845		int bestMatchIndex = -1;
 846
 847		for (int i=0; i < candidates.length; i++) {
 848			Class[] targetMatch = candidates[i];
 849
 850            /*
 851                If idealMatch fits targetMatch and this is the first match 
 852				or targetMatch is more specific than the best match, make it 
 853				the new best match.
 854            */
 855			if ( isSignatureAssignable(idealMatch, targetMatch ) &&
 856				((bestMatch == null) ||
 857					isSignatureAssignable( targetMatch, bestMatch )))
 858			{
 859				bestMatch = targetMatch;
 860				bestMatchIndex = i;
 861			}
 862		}
 863
 864		if ( bestMatch != null ) {
 865			/*
 866			if ( Interpreter.DEBUG ) 
 867				Interpreter.debug("best match: " 
 868				+ StringUtil.methodString("args",bestMatch));
 869			*/
 870				
 871			return bestMatchIndex;
 872		}
 873		else {
 874			Interpreter.debug("no match found");
 875			return -1;
 876		}
 877	}
 878
 879	/**
 880		Is the 'from' signature (argument types) assignable to the 'to' 
 881		signature (candidate method types) using isJavaAssignableFrom()?
 882		This method handles the special case of null values in 'to' types 
 883		indicating a loose type and matching anything.
 884	*/
 885    private static boolean isSignatureAssignable( Class[] from, Class[] to )
 886    {
 887        if ( from.length != to.length )
 888            return false;
 889
 890        for(int i=0; i<from.length; i++)
 891        {
 892			// Null 'to' type indicates loose type.  Match anything.
 893			if ( to[i] == null )
 894				continue;
 895
 896            if ( !isJavaAssignableFrom( to[i], from[i] ) )
 897                return false;
 898        }
 899
 900        return true;
 901    }
 902
 903    /**
 904		Is a standard Java assignment legal from the rhs type to the lhs type
 905		in a normal assignment?
 906		<p/>
 907		For Java primitive TYPE classes this method takes primitive promotion
 908		into account.  The ordinary Class.isAssignableFrom() does not take 
 909		primitive promotion conversions into account.  Note that Java allows
 910		additional assignments without a cast in combination with variable
 911		declarations.  Those are handled elsewhere (maybe should be here with a
 912		flag?)
 913		<p/>
 914		This class accepts a null rhs type indicating that the rhs was the
 915		value Primitive.NULL and allows it to be assigned to any object lhs
 916		type (non primitive)
 917		<p/>
 918
 919		Note that the getAssignableForm() method in NameSpace is the primary
 920		bsh method for checking assignability.  It adds additional bsh
 921		conversions, etc. (need to clarify what)
 922
 923		@param lhs assigning from rhs to lhs
 924		@param rhs assigning from rhs to lhs
 925	*/
 926    static boolean isJavaAssignableFrom( Class lhs, Class rhs )
 927    {
 928		// null 'from' type corresponds to type of Primitive.NULL
 929		// assign to any object type
 930		if ( rhs == null ) 
 931			return !lhs.isPrimitive();
 932
 933		if ( lhs.isPrimitive() && rhs.isPrimitive() )
 934		{
 935			if ( lhs == rhs )
 936				return true;
 937
 938			// handle primitive widening conversions - JLS 5.1.2
 939			if ( (rhs == Byte.TYPE) && 
 940				(lhs == Short.TYPE || lhs == Integer.TYPE ||
 941                lhs == Long.TYPE || lhs == Float.TYPE || lhs == Double.TYPE))
 942                    return true;
 943
 944            if ( (rhs == Short.TYPE) && 
 945				(lhs == Integer.TYPE || lhs == Long.TYPE ||
 946                lhs == Float.TYPE || lhs == Double.TYPE))
 947                    return true;
 948
 949            if ((rhs == Character.TYPE) && 
 950				(lhs == Integer.TYPE || lhs == Long.TYPE ||
 951                lhs == Float.TYPE || lhs == Double.TYPE))
 952                    return true;
 953
 954            if ((rhs == Integer.TYPE) && 
 955				(lhs == Long.TYPE || lhs == Float.TYPE ||
 956                lhs == Double.TYPE))
 957                    return true;
 958
 959            if ((rhs == Long.TYPE) && 
 960				(lhs == Float.TYPE || lhs == Double.TYPE))
 961                return true;
 962
 963            if ((rhs == Float.TYPE) && (lhs == Double.TYPE))
 964                return true;
 965        }
 966        else
 967            if ( lhs.isAssignableFrom(rhs) )
 968                return true;
 969
 970        return false;
 971    }
 972
 973	private static String accessorName( String getorset, String propName ) {
 974        return getorset 
 975			+ String.valueOf(Character.toUpperCase(propName.charAt(0))) 
 976			+ propName.substring(1);
 977	}
 978
 979    public static boolean hasObjectPropertyGetter( 
 980		Class clas, String propName ) 
 981	{
 982		String getterName = accessorName("get", propName );
 983		try {
 984			clas.getMethod( getterName, new Class [0] );
 985			return true;
 986		} catch ( NoSuchMethodException e ) { /* fall through */ }
 987		getterName = accessorName("is", propName );
 988		try {
 989			Method m = clas.getMethod( getterName, new Class [0] );
 990			return ( m.getReturnType() == Boolean.TYPE );
 991		} catch ( NoSuchMethodException e ) {
 992			return false;
 993		}
 994	}
 995
 996    public static boolean hasObjectPropertySetter( 
 997		Class clas, String propName ) 
 998	{
 999		String setterName = accessorName("set", propName );
1000		Class [] sig = new Class [] { clas };
1001		Method [] methods = clas.getMethods();
1002
1003		// we don't know the right hand side of the assignment yet.
1004		// has at least one setter of the right name?
1005		for(int i=0; i<methods.length; i++)
1006			if ( methods[i].getName().equals( setterName ) )
1007				return true;
1008		return false;
1009	}
1010
1011    public static Object getObjectProperty(
1012		Object obj, String propName )
1013        throws UtilEvalError, ReflectError
1014    {
1015        Object[] args = new Object[] { };
1016
1017        Interpreter.debug("property access: ");
1018		Method method = null;
1019
1020		Exception e1=null, e2=null;
1021		try {
1022			String accessorName = accessorName( "get", propName );
1023			method = resolveJavaMethod( 
1024				null/*bcm*/, obj.getClass(), obj, accessorName, args, false );
1025		} catch ( Exception e ) { 
1026			e1 = e;
1027		}
1028		if ( method == null )
1029			try {
1030				String accessorName = accessorName( "is", propName );
1031				method = resolveJavaMethod( null/*bcm*/, obj.getClass(), obj, 
1032					accessorName, args, false );
1033				if ( method.getReturnType() != Boolean.TYPE )
1034					method = null;
1035			} catch ( Exception e ) { 
1036				e2 = e;
1037			}
1038		if ( method == null )
1039			throw new ReflectError("Error in property getter: "
1040				+e1 + (e2!=null?" : "+e2:"") );
1041
1042        try {
1043			return invokeOnMethod( method, obj, args );
1044        }
1045        catch(InvocationTargetException e)
1046        {
1047            throw new UtilEvalError("Property accessor threw exception: "
1048				+e.getTargetException() );
1049        }
1050    }
1051
1052    public static void setObjectProperty(
1053		Object obj, String propName, Object value)
1054        throws ReflectError, UtilEvalError
1055    {
1056        String accessorName = accessorName( "set", propName );
1057        Object[] args = new Object[] { value };
1058
1059        Interpreter.debug("property access: ");
1060        try {
1061			Method method = resolveJavaMethod( 
1062				null/*bcm*/, obj.getClass(), obj, accessorName, args, false );
1063			invokeOnMethod( method, obj, args );
1064        }
1065        catch ( InvocationTargetException e )
1066        {
1067            throw new UtilEvalError("Property accessor threw exception: "
1068				+e.getTargetException() );
1069        }
1070    }
1071
1072    /** 
1073		This method is meant to convert a JVM-array class name to the correct
1074    	'fully-qualified name' for the array class - JLS 6.7
1075	*/
1076    public static String normalizeClassName(Class type)
1077    {
1078        if(!type.isArray())
1079            return type.getName();
1080
1081        StringBuffer className = new StringBuffer();
1082        try
1083        {
1084            className.append(getArrayBaseType(type).getName());
1085            for(int i = 0; i < getArrayDimensions(type); i++)
1086                className.append("[]");
1087        }
1088        catch(Exception e) { }
1089
1090        return className.toString();
1091    }
1092
1093	/**
1094		returns the dimensionality of the Class
1095		returns 0 if the Class is not an array class
1096	*/
1097    public static int getArrayDimensions(Class arrayClass)
1098    {
1099        if(!arrayClass.isArray())
1100            return 0;
1101
1102        return arrayClass.getName().lastIndexOf('[') + 1;
1103    }
1104
1105    /**
1106
1107		Returns the base type of an array Class.
1108    	throws ReflectError if the Class is not an array class.
1109	*/
1110    public static Class getArrayBaseType(Class arrayClass) throws ReflectError
1111    {
1112        if(!arrayClass.isArray())
1113            throw new ReflectError("The class is not an array.");
1114
1115		return arrayClass.getComponentType();
1116
1117    }
1118
1119	/**
1120		A command may be implemented as a compiled Java class containing one or
1121		more static invoke() methods of the correct signature.  The invoke()
1122		methods must accept two additional leading arguments of the interpreter
1123		and callstack, respectively. e.g. invoke(interpreter, callstack, ... )
1124		This method adds the arguments and invokes the static method, returning
1125		the result.
1126	*/
1127	public static Object invokeCompiledCommand( 
1128		Class commandClass, Object [] args, Interpreter interpreter, 
1129		CallStack callstack )
1130		throws UtilEvalError
1131	{
1132        // add interpereter and namespace to args list
1133        Object[] invokeArgs = new Object[args.length + 2];
1134        invokeArgs[0] = interpreter;
1135        invokeArgs[1] = callstack;
1136        System.arraycopy( args, 0, invokeArgs, 2, args.length );
1137		BshClassManager bcm = interpreter.getClassManager();
1138		try {
1139        	return Reflect.invokeStaticMethod( 
1140				bcm, commandClass, "invoke", invokeArgs );
1141		} catch ( InvocationTargetException e ) {
1142			throw new UtilEvalError(
1143				"Error in compiled command: "+e.getTargetException() );
1144		} catch ( ReflectError e ) {
1145			throw new UtilEvalError("Error invoking compiled command: "+e );
1146		}
1147	}
1148}
1149
1150/*
1151Ok, I wrote this... should we use it in lieu of the pair of methods?
1152I guess not...
1153
1154    private static Object findExtendedMethodOrConstructor(
1155		String name, Object[] args, Object [] methodsOrConstructors )
1156    {
1157        for(int i = 0; i < methodsOrConstructors.length; i++) 
1158		{
1159            Object currentMethodOrConstructor = methodsOrConstructors[i];
1160			Class[] parameterTypes = 
1161				getParameterTypes( currentMethodOrConstructor );
1162
1163			if ( currentMethodOrConstructor instanceof Method
1164				&& !name.equals(
1165					((Method)currentMethodOrConstructor).getName() ) )
1166				continue;
1167
1168            if ( argsAssignable( parameterTypes, args ) )
1169				return currentMethodOrConstructor;
1170        }
1171
1172        return null;
1173    }
1174
1175	private static Class [] getParameterTypes( Object methodOrConstructor )
1176	{
1177		if ( methodOrConstructor instanceof Method )
1178			return ((Method)methodOrConstructor).getParameterTypes();
1179		else
1180			return ((Constructor)methodOrConstructor).getParameterTypes();
1181	}
1182*/
1183