Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: bsh/Reflect.java


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  
34  package bsh;
35  
36  import java.lang.reflect.*;
37  import java.io.*;
38  import 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  */
54  class 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