PageRenderTime 153ms CodeModel.GetById 77ms app.highlight 65ms RepoModel.GetById 1ms app.codeStats 1ms

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

#
Java | 968 lines | 609 code | 98 blank | 261 comment | 105 complexity | 21fc04cf5f27f7322efaef1914bec453 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/*
 46	Note: This class is messy.  The method and field resolution need to be
 47	rewritten.  Various methods in here catch NoSuchMethod or NoSuchField
 48	exceptions during their searches.  These should be rewritten to avoid
 49	having to catch the exceptions.  Method lookups are now cached at a high 
 50	level so they are less important, however the logic is messy.
 51*/
 52class Reflect 
 53{
 54    /**
 55		Invoke method on arbitrary object.
 56		invocation may be static (through the object instance) or dynamic.
 57		Object may be a bsh scripted object (This type).
 58	*/
 59    public static Object invokeObjectMethod(
 60		Object object, String methodName, Object[] args, 
 61		Interpreter interpreter, CallStack callstack, SimpleNode callerInfo ) 
 62		throws ReflectError, EvalError, InvocationTargetException
 63	{
 64		// Bsh scripted object
 65		if ( object instanceof This && !This.isExposedThisMethod( methodName) ) 
 66			return ((This)object).invokeMethod( 
 67				methodName, args, interpreter, callstack, callerInfo,
 68				false/*delcaredOnly*/ );
 69		else 
 70		// Java object
 71		{ 
 72			// find the java method
 73			try {
 74				BshClassManager bcm = 
 75					interpreter == null ? null : interpreter.getClassManager();
 76				Class clas = object.getClass();
 77
 78				Method method = resolveExpectedJavaMethod( 
 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 = resolveExpectedJavaMethod( 
 99			bcm, clas, null, methodName, args, true );
100		return invokeOnMethod( method, null, args );
101    }
102
103	/**
104		Invoke the Java method on the specified object.
105		@param args may be null
106	*/
107	static Object invokeOnMethod( 
108		Method method, Object object, Object[] args ) 
109		throws ReflectError, InvocationTargetException
110	{
111		if ( args == null )
112			args = new Object[0];
113
114		if ( Interpreter.DEBUG ) 
115		{
116			Interpreter.debug("Invoking method (entry): "
117				+method+" with args:" );
118			for(int i=0; i<args.length; i++)
119				Interpreter.debug(
120					"args["+i+"] = "+args[i]
121					+" type = "+args[i].getClass() );
122		}
123		
124		// Map types to assignable forms, need to keep this fast...
125		Object [] tmpArgs = new Object [ args.length ];
126		Class [] types = method.getParameterTypes();
127		try {
128			for (int i=0; i<args.length; i++)
129				tmpArgs[i] = Types.getAssignableForm( args[i], types[i] );
130		} catch ( UtilEvalError e ) {
131			throw new InterpreterError(
132				"illegal argument type in method invocation: "+e );
133		}
134
135		// unwrap any primitives
136		tmpArgs = Primitive.unwrap( tmpArgs );
137
138		if ( Interpreter.DEBUG ) 
139		{
140			Interpreter.debug("Invoking method (after massaging values): "
141				+method+" with tmpArgs:" );
142			for(int i=0; i<tmpArgs.length; i++)
143				Interpreter.debug(
144					"tmpArgs["+i+"] = "+tmpArgs[i]
145					+" type = "+tmpArgs[i].getClass() );
146		}
147
148		try {
149			Object returnValue = method.invoke( object, tmpArgs );
150			if ( returnValue == null )
151				returnValue = Primitive.NULL;
152			Class returnType = method.getReturnType();
153
154			return Primitive.wrap( returnValue, returnType );
155		} catch( IllegalAccessException e ) {
156			throw new ReflectError( "Cannot access method " 
157				+ StringUtil.methodString(
158					method.getName(), method.getParameterTypes() ) 
159				+ " in '" + method.getDeclaringClass() + "' :" + e );
160		}
161	}
162
163    public static Object getIndex(Object array, int index)
164        throws ReflectError, UtilTargetError
165    {
166		if ( Interpreter.DEBUG ) 
167			Interpreter.debug("getIndex: "+array+", index="+index);
168        try {
169            Object val = Array.get(array, index);
170            return Primitive.wrap( val, array.getClass().getComponentType() );
171        }
172        catch( ArrayIndexOutOfBoundsException  e1 ) {
173			throw new UtilTargetError( e1 );
174        } catch(Exception e) {
175            throw new ReflectError("Array access:" + e);
176        }
177    }
178
179    public static void setIndex(Object array, int index, Object val)
180        throws ReflectError, UtilTargetError
181    {
182        try {
183            val = Primitive.unwrap(val);
184            Array.set(array, index, val);
185        }
186        catch( ArrayStoreException e2 ) {
187			throw new UtilTargetError( e2 );
188        } catch( IllegalArgumentException e1 ) {
189			throw new UtilTargetError( 
190				new ArrayStoreException( e1.toString() ) );
191        } catch(Exception e) {
192            throw new ReflectError("Array access:" + e);
193        }
194    }
195
196    public static Object getStaticField(Class clas, String fieldName)
197        throws UtilEvalError, ReflectError
198    {
199        return getFieldValue( clas, null, fieldName, true/*onlystatic*/);
200    }
201
202	/**
203	*/
204    public static Object getObjectField( Object object, String fieldName )
205        throws UtilEvalError, ReflectError
206    {
207		if ( object instanceof This )
208			return ((This)object).namespace.getVariable( fieldName );
209		else {
210			try {
211				return getFieldValue(
212					object.getClass(), object, fieldName, false/*onlystatic*/);
213			} catch ( ReflectError e ) {
214				// no field, try property acces
215
216				if ( hasObjectPropertyGetter( object.getClass(), fieldName ) )
217					return getObjectProperty( object, fieldName );
218				else
219					throw e;
220			}
221		}
222    }
223
224    static LHS getLHSStaticField(Class clas, String fieldName)
225        throws UtilEvalError, ReflectError
226    {
227        Field f = resolveExpectedJavaField( 
228			clas, fieldName, true/*onlystatic*/);
229        return new LHS(f);
230    }
231
232	/**
233		Get an LHS reference to an object field.
234
235		This method also deals with the field style property access.
236		In the field does not exist we check for a property setter.
237	*/
238    static LHS getLHSObjectField( Object object, String fieldName )
239        throws UtilEvalError, ReflectError
240    {
241		if ( object instanceof This )
242		{
243			// I guess this is when we pass it as an argument?
244			// Setting locally
245			boolean recurse = false; 
246			return new LHS( ((This)object).namespace, fieldName, recurse );
247		}
248
249		try {
250			Field f = resolveExpectedJavaField( 
251				object.getClass(), fieldName, false/*onlyStatic*/ );
252			return new LHS(object, f);
253		} catch ( ReflectError e ) 
254		{
255			// not a field, try property access
256			if ( hasObjectPropertySetter( object.getClass(), fieldName ) )
257				return new LHS( object, fieldName );
258			else
259				throw e;
260		}
261    }
262
263    private static Object getFieldValue(
264		Class clas, Object object, String fieldName, boolean onlyStatic ) 
265		throws UtilEvalError, ReflectError
266    {
267        try {
268            Field f = resolveExpectedJavaField( clas, fieldName, onlyStatic );
269
270            Object value = f.get(object);
271            Class returnType = f.getType();
272            return Primitive.wrap( value, returnType );
273
274        } catch( NullPointerException e ) { // shouldn't happen
275            throw new ReflectError(
276				"???" + fieldName + " is not a static field.");
277        } catch(IllegalAccessException e) {
278            throw new ReflectError("Can't access field: " + fieldName);
279        }
280    }
281
282	/**
283	*/
284	/*
285		Note: this method and resolveExpectedJavaField should be rewritten
286		to invert this logic so that no exceptions need to be caught
287		unecessarily.  This is just a temporary impl.
288		@return the field or null if not found
289	*/
290    protected static Field resolveJavaField( 
291		Class clas, String fieldName, boolean onlyStatic )
292        throws UtilEvalError
293    {
294		try {
295			return resolveExpectedJavaField( clas, fieldName, onlyStatic );
296		} catch ( ReflectError e ) { 
297			return null;
298		}
299	}
300
301	/**
302		@throws ReflectError if the field is not found.
303	*/
304	/*
305		Note: this should really just throw NoSuchFieldException... need
306		to change related signatures and code.
307	*/
308    protected static Field resolveExpectedJavaField( 
309		Class clas, String fieldName, boolean onlyStatic
310	)
311        throws UtilEvalError, ReflectError
312    {
313		Field f;
314        try {
315			if ( Capabilities.haveAccessibility() )
316				f = findAccessibleField( clas, fieldName );
317			else
318				// this one only finds public (and in interfaces, etc.)
319				f = clas.getField(fieldName);
320        }
321        catch( NoSuchFieldException e)
322        {
323			// try declaredField
324            throw new ReflectError("No such field: " + fieldName );
325        }
326
327		if ( onlyStatic && !Modifier.isStatic( f.getModifiers() ) )
328			throw new UtilEvalError(
329				"Can't reach instance field: "+fieldName
330				+" from static context: "+clas.getName() );
331
332		return f;
333    }
334
335	/**
336		Used when accessibility capability is available to locate an occurrance
337		of the field in the most derived class or superclass and set its 
338		accessibility flag.
339		Note that this method is not needed in the simple non accessible
340		case because we don't have to hunt for fields.
341		Note that classes may declare overlapping private fields, so the 
342		distinction about the most derived is important.  Java doesn't normally
343		allow this kind of access (super won't show private variables) so 
344		there is no real syntax for specifying which class scope to use...
345
346		@return the Field or throws NoSuchFieldException
347		@throws NoSuchFieldException if the field is not found
348	*/
349	/*
350		This method should be rewritten to use getFields() and avoid catching
351		exceptions during the search.
352	*/
353	private static Field findAccessibleField( Class clas, String fieldName ) 
354		throws UtilEvalError, NoSuchFieldException
355	{
356		Field field;
357
358		// Quick check catches public fields include those in interfaces
359		try {
360			field = clas.getField(fieldName);
361			ReflectManager.RMSetAccessible( field );
362			return field;
363		} catch ( NoSuchFieldException e ) { }
364
365		// Now, on with the hunt...
366		while ( clas != null )
367		{
368			try {
369				field = clas.getDeclaredField(fieldName);
370				ReflectManager.RMSetAccessible( field );
371				return field;
372
373				// Not found, fall through to next class
374
375			} catch(NoSuchFieldException e) { }
376
377			clas = clas.getSuperclass();
378		}
379		throw new NoSuchFieldException( fieldName );
380	}
381
382	/**
383		This method expects a non-null method from resolveJavaMethod().
384		If the method is not found it throws a descriptive ReflectError.
385	*/
386    protected static Method resolveExpectedJavaMethod(
387		BshClassManager bcm, Class clas, Object object, 
388		String name, Object[] args, boolean onlyStatic )
389        throws ReflectError, UtilEvalError
390    {
391		Method method = resolveJavaMethod( 
392			bcm, clas, object, name, args, onlyStatic );
393
394		if ( method != null )
395			return method;
396
397		Class [] types = Types.getTypes(args);
398		throw new ReflectError(
399			( onlyStatic ? "Static method " : "Method " )
400			+ StringUtil.methodString(name, types) + 
401			" not found in class'" + clas.getName() + "'");
402	}
403
404    /**
405        The full blown resolver method.  All other method invocation methods
406		delegate to this.  The method may be static or dynamic unless
407		onlyStatic is set (in which case object may be null).
408		If onlyStatic is set the only static methods will be located.
409		<p/>
410		This method performs Java method caching internally.
411
412		@param onlyStatic 
413			The method located must be static, the object param may be null.
414		@return the method or null if no matching method was found.
415		@deprecated
416	*/
417	/*
418		Note: object is only used here for precondition... get rid of it?
419	*/
420    protected static Method resolveJavaMethod(
421		BshClassManager bcm, Class clas, Object object, 
422		String name, Object[] args, boolean onlyStatic )
423        throws UtilEvalError
424    {
425		// Why is object in the args?
426		if ( object == Primitive.NULL )
427			throw new UtilTargetError( new NullPointerException(
428				"Attempt to invoke method " +name+" on null value" ) );
429
430        Class [] types = Types.getTypes(args);
431		return resolveJavaMethod( bcm, clas, name, types, onlyStatic );
432	}
433
434	/*
435		Notes:
436
437		This is broken.  It finds public but less specific methods over
438		non-public but more specific ones.
439
440		findMostSpecficMethod() needs to be rewritten to eliminate 
441		findAccessibleMethod.  We should implement the findMostSpecificMethod 
442		that uses the publicOnly flag.  FindMostSpecificMethod should also
443		operate in two passes to give standard Java assignable matches priority
444		over extended bsh type matches.
445	*/
446    protected static Method resolveJavaMethod(
447		BshClassManager bcm, Class clas, String name, 
448		Class [] types, boolean onlyStatic )
449        throws UtilEvalError
450    {
451		if ( clas == null )
452			throw new InterpreterError("null class");
453
454		Method method = null;
455		if ( bcm == null ) 
456			Interpreter.debug("resolveJavaMethod UNOPTIMIZED lookup");
457		else {
458			method = bcm.getResolvedMethod( clas, name, types, onlyStatic );
459			if ( method != null )
460				return method;
461		}
462
463		if ( Interpreter.DEBUG )
464			Interpreter.debug( "Searching for method: "+
465				StringUtil.methodString(name, types)
466					+ " in '" + clas.getName() + "'" );
467
468		/*
469			First try for an accessible version of the exact match.
470			This first lookup seems redundant with below, but is apparently
471			needed.  This whole thing is messy.
472		*/
473		try {
474			method  = findAccessibleMethod( clas, name, types );
475		} catch ( SecurityException e ) { }
476
477		// If not found and there are arguments to match -
478		// Look for an overloaded assignable match
479		// (First find the method, then find accessible version of it)
480		if ( method == null && types.length > 0 ) 
481		{
482			// Gather all of the methods of class and parents
483			Vector mv = new Vector();
484			Class c = clas;
485			while( c != null )
486			{
487				Method [] m = c.getDeclaredMethods();
488				for(int i=0; i<m.length; i++)
489					mv.add( m[i] );
490				c = c.getSuperclass();
491			}
492			Method [] methods = new Method [mv.size()];
493			mv.copyInto( methods );
494
495			boolean publicOnly = !Capabilities.haveAccessibility();
496			method = findMostSpecificMethod( name, types, methods, publicOnly );
497
498			if ( method != null && !Modifier.isPublic( method.getModifiers() ) )
499			{
500				try {
501					ReflectManager.RMSetAccessible( method );
502				} catch ( UtilEvalError e ) { /*ignore*/ }
503			}
504/*
505			// If found a method, make sure we have accessible version of it
506			if ( method != null ) 
507			{
508				try {
509					method = findAccessibleMethod( 
510						clas, method.getName(), method.getParameterTypes() );
511				} catch ( SecurityException e ) { /leave null/ }
512				if ( Interpreter.DEBUG && method == null )
513					Interpreter.debug(
514						"had a method, but it wasn't accessible");
515			}
516	*/
517
518		}
519
520		if ( method != null 
521			&& onlyStatic && !Modifier.isStatic( method.getModifiers() ) 
522		)
523			throw new UtilEvalError(
524				"Cannot reach instance method: "
525				+ StringUtil.methodString(
526					method.getName(), method.getParameterTypes() )
527				+ " from static context: "+ clas.getName() );
528
529		// Succeeded.  Cache the resolved method.
530		if ( method != null && bcm != null )
531			bcm.cacheResolvedMethod( clas, types, method );
532
533		return method;
534	}
535
536	/**
537		Locate a version of the method with the exact signature specified 
538		that is accessible via a public interface or through a public 
539		superclass or - if accessibility is on - through any interface or
540		superclass.
541
542		In the normal (non-accessible) case this still solves the problem that 
543		arises when a package private class or private inner class implements a 
544		public interface or derives from a public type.
545
546		@param onlyStatic the method located must be static.
547		@return null on not found
548	*/
549	/*
550		Notes: See notes on findMostSpecificMethod.
551		This method should be rolled into findMostSpecificMethod.
552	*/
553	static Method findAccessibleMethod( 
554		Class clas, String name, Class [] types ) 
555		throws UtilEvalError
556	{
557		Method meth = null;
558		Method inaccessibleVersion = null;
559		Vector classQ = new Vector();
560
561		classQ.addElement( clas );
562		Method found = null;
563		while ( classQ.size() > 0 ) 
564		{
565			Class c = (Class)classQ.firstElement();
566			classQ.removeElementAt(0);
567
568			// Is this it?
569			// Is the class public or can we use accessibility?
570			if ( Modifier.isPublic( c.getModifiers() )
571				|| ( Capabilities.haveAccessibility() ) )
572			{
573				try 
574				{
575					meth = c.getDeclaredMethod( name, types );
576
577					// Is the method public or are we in accessibility mode?
578					if ( ( Modifier.isPublic( meth.getModifiers() )
579						&& Modifier.isPublic( c.getModifiers() ) )
580						|| ( Capabilities.haveAccessibility() 
581							&& ReflectManager.RMSetAccessible( meth ) ) )
582					{
583						found = meth; // Yes, it is.
584						break;
585					}
586					else
587					{
588						// Found at least one matching method but couldn't use
589						inaccessibleVersion = meth;
590					}
591				} catch ( NoSuchMethodException e ) { 
592					// ignore and move on
593				}
594			}
595			// No, it is not.
596			
597			// Is this a class?
598			if ( !c.isInterface() ) {
599				Class superclass = c.getSuperclass();
600				if ( superclass != null )
601					classQ.addElement((Object)superclass);
602			}
603
604			// search all of its interfaces breadth first
605			Class [] intfs = c.getInterfaces();
606			for( int i=0; i< intfs.length; i++ )
607				classQ.addElement((Object)intfs[i]);
608		}
609
610		if ( found != null )
611			return found;
612
613		if ( inaccessibleVersion != null )
614			throw new UtilEvalError("Found non-public method: "
615				+inaccessibleVersion
616				+".  Use setAccessibility(true) to enable access to "
617				+" private and protected members of classes." );
618		
619		return null; 
620	}
621
622	/**
623		Primary object constructor
624		This method is simpler than those that must resolve general method
625		invocation because constructors are not inherited.
626	*/
627    static Object constructObject( Class clas, Object[] args )
628        throws ReflectError, InvocationTargetException
629    {
630		if ( clas.isInterface() )
631			throw new ReflectError(
632				"Can't create instance of an interface: "+clas);
633
634        Object obj = null;
635        Class[] types = Types.getTypes(args);
636        Constructor con = null;
637
638		/* 
639			Find an appropriate constructor.
640			use declared here to see package and private as well
641			(there are no inherited constructors to worry about) 
642		*/
643		Constructor[] constructors = clas.getDeclaredConstructors();
644		if ( Interpreter.DEBUG ) 
645			Interpreter.debug("Looking for most specific constructor: "+clas);
646		con = findMostSpecificConstructor(types, constructors);
647
648		if ( con == null )
649		{
650			if ( types.length == 0 )
651				throw new ReflectError(
652					"Can't find default constructor for: "+clas);
653			else
654				throw new ReflectError(
655					"Can't find constructor: " 
656					+ StringUtil.methodString( clas.getName(), types )
657					+" in class: "+ clas.getName() );
658		}
659
660		if ( !Modifier.isPublic( con.getModifiers() )
661			&& Capabilities.haveAccessibility() )
662			try {
663				ReflectManager.RMSetAccessible( con );
664			} catch ( UtilEvalError e ) { /*ignore*/ }
665
666        args=Primitive.unwrap( args );
667        try {
668            obj = con.newInstance( args );
669        } catch(InstantiationException e) {
670            throw new ReflectError("the class is abstract ");
671        } catch(IllegalAccessException e) {
672            throw new ReflectError(
673				"We don't have permission to create an instance."
674				+"Use setAccessibility(true) to enable access." );
675        } catch(IllegalArgumentException e) {
676            throw new ReflectError("the number of arguments was wrong");
677        } 
678		if (obj == null)
679            throw new ReflectError("couldn't construct the object");
680
681        return obj;
682    }
683
684    /**
685        Implement JLS 15.11.2 for method resolution
686		@return null on no match
687    */
688	/*
689		Notes:
690
691		This is broken.  It finds public but less specific methods over
692		non-public but more specific ones.
693
694		This method needs to be rewritten to eliminate findAccessibleMethod.
695		We should implement the findMostSpecificMethod that uses the publicOnly
696		flag.  FindMostSpecificMethod should also operate in two passes to give
697		standard Java assignable matches priority over extended bsh type
698		matches.
699	*/
700    static Method findMostSpecificMethod(
701		String name, Class[] idealMatch, Method[] methods,
702		boolean publicOnly )
703    {
704		// Pull out the method signatures with matching names
705		Vector sigs = new Vector();
706		Vector meths = new Vector();
707		for(int i=0; i<methods.length; i++)
708		{
709			if ( publicOnly && !Modifier.isPublic( methods[i].getModifiers() ) )
710				continue;
711
712			// method matches name 
713			if ( methods[i].getName().equals( name ) ) 
714			{
715				meths.addElement( methods[i] );
716				sigs.addElement( methods[i].getParameterTypes() );
717			}
718		}
719
720		Class [][] candidates = new Class [ sigs.size() ][];
721		sigs.copyInto( candidates );
722
723		if ( Interpreter.DEBUG ) 
724			Interpreter.debug("Looking for most specific method: "+name);
725		int match = findMostSpecificSignature( idealMatch, candidates );
726		if ( match == -1 )
727			return null;
728		else
729			return (Method)meths.elementAt( match );
730    }
731
732    /*
733        This method should parallel findMostSpecificMethod()
734    */
735    static Constructor findMostSpecificConstructor(
736		Class[] idealMatch, Constructor[] constructors)
737    {
738		int match = 
739			findMostSpecificConstructorIndex( idealMatch, constructors );
740		if ( match == -1 )
741			return null;
742		else
743			return constructors[ match ];
744    }
745
746    static int findMostSpecificConstructorIndex(
747		Class[] idealMatch, Constructor[] constructors)
748    {
749		Class [][] candidates = new Class [ constructors.length ] [];
750		for(int i=0; i< candidates.length; i++ )
751			candidates[i] = constructors[i].getParameterTypes();
752
753		return findMostSpecificSignature( idealMatch, candidates );
754    }
755
756	/**
757        Implement JLS 15.11.2
758		Return the index of the most specific arguments match or -1 if no	
759		match is found.
760	*/
761	static int findMostSpecificSignature(
762		Class [] idealMatch, Class [][] candidates )
763	{
764		Class [] bestMatch = null;
765		int bestMatchIndex = -1;
766
767		for (int i=0; i < candidates.length; i++) {
768			Class[] targetMatch = candidates[i];
769
770            /*
771                If idealMatch fits targetMatch and this is the first match 
772				or targetMatch is more specific than the best match, make it 
773				the new best match.
774            */
775			if ( Types.isSignatureAssignable(idealMatch, targetMatch ) &&
776				((bestMatch == null) ||
777					Types.isSignatureAssignable( targetMatch, bestMatch )))
778			{
779				bestMatch = targetMatch;
780				bestMatchIndex = i;
781			}
782		}
783
784		if ( bestMatch != null )
785			return bestMatchIndex;
786		else
787			return -1;
788	}
789
790	private static String accessorName( String getorset, String propName ) {
791        return getorset 
792			+ String.valueOf(Character.toUpperCase(propName.charAt(0))) 
793			+ propName.substring(1);
794	}
795
796    public static boolean hasObjectPropertyGetter( 
797		Class clas, String propName ) 
798	{
799		String getterName = accessorName("get", propName );
800		try {
801			clas.getMethod( getterName, new Class [0] );
802			return true;
803		} catch ( NoSuchMethodException e ) { /* fall through */ }
804		getterName = accessorName("is", propName );
805		try {
806			Method m = clas.getMethod( getterName, new Class [0] );
807			return ( m.getReturnType() == Boolean.TYPE );
808		} catch ( NoSuchMethodException e ) {
809			return false;
810		}
811	}
812
813    public static boolean hasObjectPropertySetter( 
814		Class clas, String propName ) 
815	{
816		String setterName = accessorName("set", propName );
817		Class [] sig = new Class [] { clas };
818		Method [] methods = clas.getMethods();
819
820		// we don't know the right hand side of the assignment yet.
821		// has at least one setter of the right name?
822		for(int i=0; i<methods.length; i++)
823			if ( methods[i].getName().equals( setterName ) )
824				return true;
825		return false;
826	}
827
828    public static Object getObjectProperty(
829		Object obj, String propName )
830        throws UtilEvalError, ReflectError
831    {
832        Object[] args = new Object[] { };
833
834        Interpreter.debug("property access: ");
835		Method method = null;
836
837		Exception e1=null, e2=null;
838		try {
839			String accessorName = accessorName( "get", propName );
840			method = resolveExpectedJavaMethod( 
841				null/*bcm*/, obj.getClass(), obj, accessorName, args, false );
842		} catch ( Exception e ) { 
843			e1 = e;
844		}
845		if ( method == null )
846			try {
847				String accessorName = accessorName( "is", propName );
848				method = resolveExpectedJavaMethod( 
849					null/*bcm*/, obj.getClass(), obj, 
850					accessorName, args, false );
851				if ( method.getReturnType() != Boolean.TYPE )
852					method = null;
853			} catch ( Exception e ) { 
854				e2 = e;
855			}
856		if ( method == null )
857			throw new ReflectError("Error in property getter: "
858				+e1 + (e2!=null?" : "+e2:"") );
859
860        try {
861			return invokeOnMethod( method, obj, args );
862        }
863        catch(InvocationTargetException e)
864        {
865            throw new UtilEvalError("Property accessor threw exception: "
866				+e.getTargetException() );
867        }
868    }
869
870    public static void setObjectProperty(
871		Object obj, String propName, Object value)
872        throws ReflectError, UtilEvalError
873    {
874        String accessorName = accessorName( "set", propName );
875        Object[] args = new Object[] { value };
876
877        Interpreter.debug("property access: ");
878        try {
879			Method method = resolveExpectedJavaMethod( 
880				null/*bcm*/, obj.getClass(), obj, accessorName, args, false );
881			invokeOnMethod( method, obj, args );
882        }
883        catch ( InvocationTargetException e )
884        {
885            throw new UtilEvalError("Property accessor threw exception: "
886				+e.getTargetException() );
887        }
888    }
889
890    /** 
891		Return a more human readable version of the type name.
892		Specifically, array types are returned with postfix "[]" dimensions.
893		e.g. return "int []" for integer array instead of "class [I" as
894		would be returned by Class getName() in that case.
895	*/
896    public static String normalizeClassName(Class type)
897    {
898        if ( !type.isArray() )
899            return type.getName();
900
901        StringBuffer className = new StringBuffer();
902        try {
903            className.append( getArrayBaseType(type).getName() +" ");
904            for(int i = 0; i < getArrayDimensions(type); i++)
905                className.append("[]");
906        } catch( ReflectError e ) { /*shouldn't happen*/ }
907
908        return className.toString();
909    }
910
911	/**
912		returns the dimensionality of the Class
913		returns 0 if the Class is not an array class
914	*/
915    public static int getArrayDimensions(Class arrayClass)
916    {
917        if ( !arrayClass.isArray() )
918            return 0;
919
920        return arrayClass.getName().lastIndexOf('[') + 1;  // why so cute?
921    }
922
923    /**
924
925		Returns the base type of an array Class.
926    	throws ReflectError if the Class is not an array class.
927	*/
928    public static Class getArrayBaseType(Class arrayClass) throws ReflectError
929    {
930        if ( !arrayClass.isArray() )
931            throw new ReflectError("The class is not an array.");
932
933		return arrayClass.getComponentType();
934
935    }
936
937	/**
938		A command may be implemented as a compiled Java class containing one or
939		more static invoke() methods of the correct signature.  The invoke()
940		methods must accept two additional leading arguments of the interpreter
941		and callstack, respectively. e.g. invoke(interpreter, callstack, ... )
942		This method adds the arguments and invokes the static method, returning
943		the result.
944	*/
945	public static Object invokeCompiledCommand( 
946		Class commandClass, Object [] args, Interpreter interpreter, 
947		CallStack callstack )
948		throws UtilEvalError
949	{
950        // add interpereter and namespace to args list
951        Object[] invokeArgs = new Object[args.length + 2];
952        invokeArgs[0] = interpreter;
953        invokeArgs[1] = callstack;
954        System.arraycopy( args, 0, invokeArgs, 2, args.length );
955		BshClassManager bcm = interpreter.getClassManager();
956		try {
957        	return Reflect.invokeStaticMethod( 
958				bcm, commandClass, "invoke", invokeArgs );
959		} catch ( InvocationTargetException e ) {
960			throw new UtilEvalError(
961				"Error in compiled command: "+e.getTargetException() );
962		} catch ( ReflectError e ) {
963			throw new UtilEvalError("Error invoking compiled command: "+e );
964		}
965	}
966
967}
968