KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > bsh > Reflect


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.util.Vector JavaDoc;
38
39 /**
40     All of the reflection API code lies here. It is in the form of static
41     utilities. Maybe this belongs in LHS.java or a generic object
42     wrapper class.
43 */

44 /*
45     Note: This class is messy. The method and field resolution need to be
46     rewritten. Various methods in here catch NoSuchMethod or NoSuchField
47     exceptions during their searches. These should be rewritten to avoid
48     having to catch the exceptions. Method lookups are now cached at a high
49     level so they are less important, however the logic is messy.
50 */

51 class Reflect
52 {
53     /**
54         Invoke method on arbitrary object instance.
55         invocation may be static (through the object instance) or dynamic.
56         Object may be a bsh scripted object (bsh.This type).
57         @return the result of the method call
58     */

59     public static Object JavaDoc invokeObjectMethod(
60         Object JavaDoc object, String JavaDoc methodName, Object JavaDoc[] 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             );
70
71         // Plain Java object, find the java method
72
try {
73             BshClassManager bcm =
74                 interpreter == null ? null : interpreter.getClassManager();
75             Class JavaDoc clas = object.getClass();
76
77             Method method = resolveExpectedJavaMethod(
78                 bcm, clas, object, methodName, args, false );
79
80             return invokeMethod( method, object, args );
81         } catch ( UtilEvalError e ) {
82             throw e.toEvalError( callerInfo, callstack );
83         }
84     }
85
86     /**
87         Invoke a method known to be static.
88         No object instance is needed and there is no possibility of the
89         method being a bsh scripted method.
90     */

91     public static Object JavaDoc invokeStaticMethod(
92         BshClassManager bcm, Class JavaDoc clas, String JavaDoc methodName, Object JavaDoc [] args )
93         throws ReflectError, UtilEvalError, InvocationTargetException
94     {
95         Interpreter.debug("invoke static Method");
96         Method method = resolveExpectedJavaMethod(
97             bcm, clas, null, methodName, args, true );
98         return invokeMethod( method, null, args );
99     }
100
101     /**
102         Invoke the Java method on the specified object, performing needed
103         type mappings on arguments and return values.
104         @param args may be null
105     */

106     static Object JavaDoc invokeMethod(
107         Method method, Object JavaDoc object, Object JavaDoc[] args )
108         throws ReflectError, InvocationTargetException
109     {
110         if ( args == null )
111             args = new Object JavaDoc[0];
112
113         logInvokeMethod( "Invoking method (entry): ", method, args );
114
115         // Map types to assignable forms, need to keep this fast...
116
Object JavaDoc [] tmpArgs = new Object JavaDoc [ args.length ];
117         Class JavaDoc [] types = method.getParameterTypes();
118         try {
119             for (int i=0; i<args.length; i++)
120                 tmpArgs[i] = Types.castObject(
121                     args[i]/*rhs*/, types[i]/*lhsType*/, Types.ASSIGNMENT );
122         } catch ( UtilEvalError e ) {
123             throw new InterpreterError(
124                 "illegal argument type in method invocation: "+e );
125         }
126
127         // unwrap any primitives
128
tmpArgs = Primitive.unwrap( tmpArgs );
129
130         logInvokeMethod( "Invoking method (after massaging values): ",
131             method, tmpArgs );
132
133         try {
134             Object JavaDoc returnValue = method.invoke( object, tmpArgs );
135             if ( returnValue == null )
136                 returnValue = Primitive.NULL;
137             Class JavaDoc returnType = method.getReturnType();
138
139             return Primitive.wrap( returnValue, returnType );
140         } catch( IllegalAccessException JavaDoc e ) {
141             throw new ReflectError( "Cannot access method "
142                 + StringUtil.methodString(
143                     method.getName(), method.getParameterTypes() )
144                 + " in '" + method.getDeclaringClass() + "' :" + e );
145         }
146     }
147
148     public static Object JavaDoc getIndex(Object JavaDoc array, int index)
149         throws ReflectError, UtilTargetError
150     {
151         if ( Interpreter.DEBUG )
152             Interpreter.debug("getIndex: "+array+", index="+index);
153         try {
154             Object JavaDoc val = Array.get(array, index);
155             return Primitive.wrap( val, array.getClass().getComponentType() );
156         }
157         catch( ArrayIndexOutOfBoundsException JavaDoc e1 ) {
158             throw new UtilTargetError( e1 );
159         } catch(Exception JavaDoc e) {
160             throw new ReflectError("Array access:" + e);
161         }
162     }
163
164     public static void setIndex(Object JavaDoc array, int index, Object JavaDoc val)
165         throws ReflectError, UtilTargetError
166     {
167         try {
168             val = Primitive.unwrap(val);
169             Array.set(array, index, val);
170         }
171         catch( ArrayStoreException JavaDoc e2 ) {
172             throw new UtilTargetError( e2 );
173         } catch( IllegalArgumentException JavaDoc e1 ) {
174             throw new UtilTargetError(
175                 new ArrayStoreException JavaDoc( e1.toString() ) );
176         } catch(Exception JavaDoc e) {
177             throw new ReflectError("Array access:" + e);
178         }
179     }
180
181     public static Object JavaDoc getStaticFieldValue(Class JavaDoc clas, String JavaDoc fieldName)
182         throws UtilEvalError, ReflectError
183     {
184         return getFieldValue( clas, null, fieldName, true/*onlystatic*/);
185     }
186
187     /**
188     */

189     public static Object JavaDoc getObjectFieldValue( Object JavaDoc object, String JavaDoc fieldName )
190         throws UtilEvalError, ReflectError
191     {
192         if ( object instanceof This )
193             return ((This)object).namespace.getVariable( fieldName );
194         else {
195             try {
196                 return getFieldValue(
197                     object.getClass(), object, fieldName, false/*onlystatic*/);
198             } catch ( ReflectError e ) {
199                 // no field, try property acces
200

201                 if ( hasObjectPropertyGetter( object.getClass(), fieldName ) )
202                     return getObjectProperty( object, fieldName );
203                 else
204                     throw e;
205             }
206         }
207     }
208
209     static LHS getLHSStaticField(Class JavaDoc clas, String JavaDoc fieldName)
210         throws UtilEvalError, ReflectError
211     {
212         Field f = resolveExpectedJavaField(
213             clas, fieldName, true/*onlystatic*/);
214         return new LHS(f);
215     }
216
217     /**
218         Get an LHS reference to an object field.
219
220         This method also deals with the field style property access.
221         In the field does not exist we check for a property setter.
222     */

223     static LHS getLHSObjectField( Object JavaDoc object, String JavaDoc fieldName )
224         throws UtilEvalError, ReflectError
225     {
226         if ( object instanceof This )
227         {
228             // I guess this is when we pass it as an argument?
229
// Setting locally
230
boolean recurse = false;
231             return new LHS( ((This)object).namespace, fieldName, recurse );
232         }
233
234         try {
235             Field f = resolveExpectedJavaField(
236                 object.getClass(), fieldName, false/*staticOnly*/ );
237             return new LHS(object, f);
238         } catch ( ReflectError e )
239         {
240             // not a field, try property access
241
if ( hasObjectPropertySetter( object.getClass(), fieldName ) )
242                 return new LHS( object, fieldName );
243             else
244                 throw e;
245         }
246     }
247
248     private static Object JavaDoc getFieldValue(
249         Class JavaDoc clas, Object JavaDoc object, String JavaDoc fieldName, boolean staticOnly )
250         throws UtilEvalError, ReflectError
251     {
252         try {
253             Field f = resolveExpectedJavaField( clas, fieldName, staticOnly );
254
255             Object JavaDoc value = f.get(object);
256             Class JavaDoc returnType = f.getType();
257             return Primitive.wrap( value, returnType );
258
259         } catch( NullPointerException JavaDoc e ) { // shouldn't happen
260
throw new ReflectError(
261                 "???" + fieldName + " is not a static field.");
262         } catch(IllegalAccessException JavaDoc e) {
263             throw new ReflectError("Can't access field: " + fieldName);
264         }
265     }
266
267     /*
268         Note: this method and resolveExpectedJavaField should be rewritten
269         to invert this logic so that no exceptions need to be caught
270         unecessarily. This is just a temporary impl.
271         @return the field or null if not found
272     */

273     protected static Field resolveJavaField(
274         Class JavaDoc clas, String JavaDoc fieldName, boolean staticOnly )
275         throws UtilEvalError
276     {
277         try {
278             return resolveExpectedJavaField( clas, fieldName, staticOnly );
279         } catch ( ReflectError e ) {
280             return null;
281         }
282     }
283
284     /**
285         @throws ReflectError if the field is not found.
286     */

287     /*
288         Note: this should really just throw NoSuchFieldException... need
289         to change related signatures and code.
290     */

291     protected static Field resolveExpectedJavaField(
292         Class JavaDoc clas, String JavaDoc fieldName, boolean staticOnly
293     )
294         throws UtilEvalError, ReflectError
295     {
296         Field field;
297         try {
298             if ( Capabilities.haveAccessibility() )
299                 field = findAccessibleField( clas, fieldName );
300             else
301                 // Class getField() finds only public (and in interfaces, etc.)
302
field = clas.getField(fieldName);
303         }
304         catch( NoSuchFieldException JavaDoc e) {
305             throw new ReflectError("No such field: " + fieldName );
306         } catch ( SecurityException JavaDoc e ) {
307             throw new UtilTargetError(
308             "Security Exception while searching fields of: "+clas,
309             e );
310         }
311
312         if ( staticOnly && !Modifier.isStatic( field.getModifiers() ) )
313             throw new UtilEvalError(
314                 "Can't reach instance field: "+fieldName
315                 +" from static context: "+clas.getName() );
316
317         return field;
318     }
319
320     /**
321         Used when accessibility capability is available to locate an occurrance
322         of the field in the most derived class or superclass and set its
323         accessibility flag.
324         Note that this method is not needed in the simple non accessible
325         case because we don't have to hunt for fields.
326         Note that classes may declare overlapping private fields, so the
327         distinction about the most derived is important. Java doesn't normally
328         allow this kind of access (super won't show private variables) so
329         there is no real syntax for specifying which class scope to use...
330
331         @return the Field or throws NoSuchFieldException
332         @throws NoSuchFieldException if the field is not found
333     */

334     /*
335         This method should be rewritten to use getFields() and avoid catching
336         exceptions during the search.
337     */

338     private static Field findAccessibleField( Class JavaDoc clas, String JavaDoc fieldName )
339         throws UtilEvalError, NoSuchFieldException JavaDoc
340     {
341         Field field;
342
343         // Quick check catches public fields include those in interfaces
344
try {
345             field = clas.getField(fieldName);
346             ReflectManager.RMSetAccessible( field );
347             return field;
348         } catch ( NoSuchFieldException JavaDoc e ) { }
349
350         // Now, on with the hunt...
351
while ( clas != null )
352         {
353             try {
354                 field = clas.getDeclaredField(fieldName);
355                 ReflectManager.RMSetAccessible( field );
356                 return field;
357
358                 // Not found, fall through to next class
359

360             } catch(NoSuchFieldException JavaDoc e) { }
361
362             clas = clas.getSuperclass();
363         }
364         throw new NoSuchFieldException JavaDoc( fieldName );
365     }
366
367     /**
368         This method wraps resolveJavaMethod() and expects a non-null method
369         result. If the method is not found it throws a descriptive ReflectError.
370     */

371     protected static Method resolveExpectedJavaMethod(
372         BshClassManager bcm, Class JavaDoc clas, Object JavaDoc object,
373         String JavaDoc name, Object JavaDoc[] args, boolean staticOnly )
374         throws ReflectError, UtilEvalError
375     {
376         if ( object == Primitive.NULL )
377             throw new UtilTargetError( new NullPointerException JavaDoc(
378                 "Attempt to invoke method " +name+" on null value" ) );
379
380         Class JavaDoc [] types = Types.getTypes(args);
381         Method method = resolveJavaMethod( bcm, clas, name, types, staticOnly );
382
383         if ( method == null )
384             throw new ReflectError(
385                 ( staticOnly ? "Static method " : "Method " )
386                 + StringUtil.methodString(name, types) +
387                 " not found in class'" + clas.getName() + "'");
388
389         return method;
390     }
391
392     /**
393         The full blown resolver method. All other method invocation methods
394         delegate to this. The method may be static or dynamic unless
395         staticOnly is set (in which case object may be null).
396         If staticOnly is set then only static methods will be located.
397         <p/>
398
399         This method performs caching (caches discovered methods through the
400         class manager and utilizes cached methods.)
401         <p/>
402
403         This method determines whether to attempt to use non-public methods
404         based on Capabilities.haveAccessibility() and will set the accessibilty
405         flag on the method as necessary.
406         <p/>
407
408         If, when directed to find a static method, this method locates a more
409         specific matching instance method it will throw a descriptive exception
410         analogous to the error that the Java compiler would produce.
411         Note: as of 2.0.x this is a problem because there is no way to work
412         around this with a cast.
413         <p/>
414
415         @param staticOnly
416             The method located must be static, the object param may be null.
417         @return the method or null if no matching method was found.
418     */

419     protected static Method resolveJavaMethod(
420         BshClassManager bcm, Class JavaDoc clas, String JavaDoc name,
421         Class JavaDoc [] types, boolean staticOnly )
422         throws UtilEvalError
423     {
424         if ( clas == null )
425             throw new InterpreterError("null class");
426
427         // Lookup previously cached method
428
Method method = null;
429         if ( bcm == null )
430             Interpreter.debug("resolveJavaMethod UNOPTIMIZED lookup");
431         else
432             method = bcm.getResolvedMethod( clas, name, types, staticOnly );
433
434         if ( method == null )
435         {
436             boolean publicOnly = !Capabilities.haveAccessibility();
437             // Searching for the method may, itself be a priviledged action
438
try {
439                 method = findOverloadedMethod( clas, name, types, publicOnly );
440             } catch ( SecurityException JavaDoc e ) {
441                 throw new UtilTargetError(
442                 "Security Exception while searching methods of: "+clas,
443                 e );
444             }
445
446             checkFoundStaticMethod( method, staticOnly, clas );
447
448             // This is the first time we've seen this method, set accessibility
449
// Note: even if it's a public method, we may have found it in a
450
// non-public class
451
if ( method != null && !publicOnly ) {
452                 try {
453                     ReflectManager.RMSetAccessible( method );
454                 } catch ( UtilEvalError e ) { /*ignore*/ }
455             }
456
457             // If succeeded cache the resolved method.
458
if ( method != null && bcm != null )
459                 bcm.cacheResolvedMethod( clas, types, method );
460         }
461
462         return method;
463     }
464
465     /**
466         Get the candidate methods by searching the class and interface graph
467         of baseClass and resolve the most specific.
468         @return the method or null for not found
469      */

470     private static Method findOverloadedMethod(
471         Class JavaDoc baseClass, String JavaDoc methodName, Class JavaDoc[] types, boolean publicOnly )
472     {
473         if ( Interpreter.DEBUG )
474             Interpreter.debug( "Searching for method: "+
475                 StringUtil.methodString(methodName, types)
476                 + " in '" + baseClass.getName() + "'" );
477
478         Method [] methods = getCandidateMethods(
479             baseClass, methodName, types.length, publicOnly );
480
481         if ( Interpreter.DEBUG )
482             Interpreter.debug("Looking for most specific method: "+methodName);
483         Method method = findMostSpecificMethod( types, methods );
484
485         return method;
486     }
487
488     /**
489         Climb the class and interface inheritence graph of the type and collect
490         all methods matching the specified name and criterion. If publicOnly
491         is true then only public methods in *public* classes or interfaces will
492         be returned. In the normal (non-accessible) case this addresses the
493         problem that arises when a package private class or private inner class
494         implements a public interface or derives from a public type.
495         <p/>
496
497         This method primarily just delegates to gatherMethodsRecursive()
498         @see #gatherMethodsRecursive(
499             Class, String, int, boolean, java.util.Vector)
500     */

501     static Method[] getCandidateMethods(
502         Class JavaDoc baseClass, String JavaDoc methodName, int numArgs,
503         boolean publicOnly )
504     {
505         Vector JavaDoc candidates = gatherMethodsRecursive(
506             baseClass, methodName, numArgs, publicOnly, null/*candidates*/);
507
508         // return the methods in an array
509
Method [] ma = new Method[ candidates.size() ];
510         candidates.copyInto( ma );
511         return ma;
512     }
513
514     /**
515         Accumulate all methods, optionally including non-public methods,
516         class and interface, in the inheritence tree of baseClass.
517
518         This method is analogous to Class getMethods() which returns all public
519         methods in the inheritence tree.
520
521         In the normal (non-accessible) case this also addresses the problem
522         that arises when a package private class or private inner class
523         implements a public interface or derives from a public type. In other
524         words, sometimes we'll find public methods that we can't use directly
525         and we have to find the same public method in a parent class or
526         interface.
527
528         @return the candidate methods vector
529     */

530     private static Vector JavaDoc gatherMethodsRecursive(
531         Class JavaDoc baseClass, String JavaDoc methodName, int numArgs,
532         boolean publicOnly, Vector JavaDoc candidates )
533     {
534         if ( candidates == null )
535             candidates = new Vector JavaDoc();
536
537         // Add methods of the current class to the vector.
538
// In public case be careful to only add methods from a public class
539
// and to use getMethods() instead of getDeclaredMethods()
540
// (This addresses secure environments)
541
if ( publicOnly ) {
542             if ( isPublic(baseClass) )
543                 addCandidates( baseClass.getMethods(),
544                     methodName, numArgs, publicOnly, candidates );
545         } else
546             addCandidates( baseClass.getDeclaredMethods(),
547                 methodName, numArgs, publicOnly, candidates );
548
549         // Does the class or interface implement interfaces?
550
Class JavaDoc [] intfs = baseClass.getInterfaces();
551         for( int i=0; i< intfs.length; i++ )
552             gatherMethodsRecursive( intfs[i],
553                 methodName, numArgs, publicOnly, candidates );
554
555         // Do we have a superclass? (interfaces don't, etc.)
556
Class JavaDoc superclass = baseClass.getSuperclass();
557         if ( superclass != null )
558             gatherMethodsRecursive( superclass,
559                 methodName, numArgs, publicOnly, candidates );
560
561         return candidates;
562     }
563
564     private static Vector JavaDoc addCandidates(
565         Method [] methods, String JavaDoc methodName,
566         int numArgs, boolean publicOnly, Vector JavaDoc candidates )
567     {
568         for ( int i = 0; i < methods.length; i++ )
569         {
570             Method m = methods[i];
571             if ( m.getName().equals( methodName )
572                 && ( m.getParameterTypes().length == numArgs )
573                 && ( !publicOnly || isPublic( m ) )
574             )
575                 candidates.add( m );
576         }
577         return candidates;
578     }
579
580     /**
581         Primary object constructor
582         This method is simpler than those that must resolve general method
583         invocation because constructors are not inherited.
584      <p/>
585      This method determines whether to attempt to use non-public constructors
586      based on Capabilities.haveAccessibility() and will set the accessibilty
587      flag on the method as necessary.
588      <p/>
589     */

590     static Object JavaDoc constructObject( Class JavaDoc clas, Object JavaDoc[] args )
591         throws ReflectError, InvocationTargetException
592     {
593         if ( clas.isInterface() )
594             throw new ReflectError(
595                 "Can't create instance of an interface: "+clas);
596
597         Object JavaDoc obj = null;
598         Class JavaDoc[] types = Types.getTypes(args);
599         Constructor con = null;
600
601         // Find the constructor.
602
// (there are no inherited constructors to worry about)
603
Constructor[] constructors =
604             Capabilities.haveAccessibility() ?
605                 clas.getDeclaredConstructors() : clas.getConstructors() ;
606
607         if ( Interpreter.DEBUG )
608             Interpreter.debug("Looking for most specific constructor: "+clas);
609         con = findMostSpecificConstructor(types, constructors);
610         if ( con == null )
611             throw cantFindConstructor( clas, types );
612
613         if ( !isPublic( con ) )
614             try {
615                 ReflectManager.RMSetAccessible( con );
616             } catch ( UtilEvalError e ) { /*ignore*/ }
617
618         args=Primitive.unwrap( args );
619         try {
620             obj = con.newInstance( args );
621         } catch(InstantiationException JavaDoc e) {
622             throw new ReflectError("The class "+clas+" is abstract ");
623         } catch(IllegalAccessException JavaDoc e) {
624             throw new ReflectError(
625                 "We don't have permission to create an instance."
626                 +"Use setAccessibility(true) to enable access." );
627         } catch(IllegalArgumentException JavaDoc e) {
628             throw new ReflectError("The number of arguments was wrong");
629         }
630         if (obj == null)
631             throw new ReflectError("Couldn't construct the object");
632
633         return obj;
634     }
635
636     /*
637         This method should parallel findMostSpecificMethod()
638         The only reason it can't be combined is that Method and Constructor
639         don't have a common interface for their signatures
640     */

641     static Constructor findMostSpecificConstructor(
642         Class JavaDoc[] idealMatch, Constructor[] constructors)
643     {
644         int match = findMostSpecificConstructorIndex(idealMatch, constructors );
645         return ( match == -1 ) ? null : constructors[ match ];
646     }
647
648     static int findMostSpecificConstructorIndex(
649         Class JavaDoc[] idealMatch, Constructor[] constructors)
650     {
651         Class JavaDoc [][] candidates = new Class JavaDoc [ constructors.length ] [];
652         for(int i=0; i< candidates.length; i++ )
653             candidates[i] = constructors[i].getParameterTypes();
654
655         return findMostSpecificSignature( idealMatch, candidates );
656     }
657
658     /**
659         Find the best match for signature idealMatch.
660         It is assumed that the methods array holds only valid candidates
661         (e.g. method name and number of args already matched).
662         This method currently does not take into account Java 5 covariant
663         return types... which I think will require that we find the most
664         derived return type of otherwise identical best matches.
665
666         @see #findMostSpecificSignature(Class[], Class[][])
667         @param methods the set of candidate method which differ only in the
668             types of their arguments.
669     */

670     static Method findMostSpecificMethod(
671         Class JavaDoc[] idealMatch, Method[] methods )
672     {
673         // copy signatures into array for findMostSpecificMethod()
674
Class JavaDoc [][] candidateSigs = new Class JavaDoc [ methods.length ][];
675         for(int i=0; i<methods.length; i++)
676             candidateSigs[i] = methods[i].getParameterTypes();
677
678         int match = findMostSpecificSignature( idealMatch, candidateSigs );
679         return match == -1 ? null : methods[match];
680     }
681
682     /**
683         Implement JLS 15.11.2
684         Return the index of the most specific arguments match or -1 if no
685         match is found.
686         This method is used by both methods and constructors (which
687         unfortunately don't share a common interface for signature info).
688
689      @return the index of the most specific candidate
690
691      */

692     /*
693      Note: Two methods which are equally specific should not be allowed by
694      the Java compiler. In this case BeanShell currently chooses the first
695      one it finds. We could add a test for this case here (I believe) by
696      adding another isSignatureAssignable() in the other direction between
697      the target and "best" match. If the assignment works both ways then
698      neither is more specific and they are ambiguous. I'll leave this test
699      out for now because I'm not sure how much another test would impact
700      performance. Method selection is now cached at a high level, so a few
701      friendly extraneous tests shouldn't be a problem.
702     */

703     static int findMostSpecificSignature(
704         Class JavaDoc [] idealMatch, Class JavaDoc [][] candidates )
705     {
706         for ( int round = Types.FIRST_ROUND_ASSIGNABLE;
707               round <= Types.LAST_ROUND_ASSIGNABLE; round++ )
708         {
709             Class JavaDoc [] bestMatch = null;
710             int bestMatchIndex = -1;
711
712             for (int i=0; i < candidates.length; i++)
713             {
714                 Class JavaDoc[] targetMatch = candidates[i];
715
716                 // If idealMatch fits targetMatch and this is the first match
717
// or targetMatch is more specific than the best match, make it
718
// the new best match.
719
if ( Types.isSignatureAssignable(
720                         idealMatch, targetMatch, round )
721                     && ( (bestMatch == null) ||
722                         Types.isSignatureAssignable( targetMatch, bestMatch,
723                             Types.JAVA_BASE_ASSIGNABLE )
724                         )
725                 )
726                 {
727                     bestMatch = targetMatch;
728                     bestMatchIndex = i;
729                 }
730             }
731
732             if ( bestMatch != null )
733                 return bestMatchIndex;
734         }
735
736         return -1;
737     }
738
739     private static String JavaDoc accessorName( String JavaDoc getorset, String JavaDoc propName ) {
740         return getorset
741             + String.valueOf(Character.toUpperCase(propName.charAt(0)))
742             + propName.substring(1);
743     }
744
745     public static boolean hasObjectPropertyGetter(
746         Class JavaDoc clas, String JavaDoc propName )
747     {
748         String JavaDoc getterName = accessorName("get", propName );
749         try {
750             clas.getMethod( getterName, new Class JavaDoc [0] );
751             return true;
752         } catch ( NoSuchMethodException JavaDoc e ) { /* fall through */ }
753         getterName = accessorName("is", propName );
754         try {
755             Method m = clas.getMethod( getterName, new Class JavaDoc [0] );
756             return ( m.getReturnType() == Boolean.TYPE );
757         } catch ( NoSuchMethodException JavaDoc e ) {
758             return false;
759         }
760     }
761
762     public static boolean hasObjectPropertySetter(
763         Class JavaDoc clas, String JavaDoc propName )
764     {
765         String JavaDoc setterName = accessorName("set", propName );
766         Method [] methods = clas.getMethods();
767
768         // we don't know the right hand side of the assignment yet.
769
// has at least one setter of the right name?
770
for(int i=0; i<methods.length; i++)
771             if ( methods[i].getName().equals( setterName ) )
772                 return true;
773         return false;
774     }
775
776     public static Object JavaDoc getObjectProperty(
777         Object JavaDoc obj, String JavaDoc propName )
778         throws UtilEvalError, ReflectError
779     {
780         Object JavaDoc[] args = new Object JavaDoc[] { };
781
782         Interpreter.debug("property access: ");
783         Method method = null;
784
785         Exception JavaDoc e1=null, e2=null;
786         try {
787             String JavaDoc accessorName = accessorName( "get", propName );
788             method = resolveExpectedJavaMethod(
789                 null/*bcm*/, obj.getClass(), obj, accessorName, args, false );
790         } catch ( Exception JavaDoc e ) {
791             e1 = e;
792         }
793         if ( method == null )
794             try {
795                 String JavaDoc accessorName = accessorName( "is", propName );
796                 method = resolveExpectedJavaMethod(
797                     null/*bcm*/, obj.getClass(), obj,
798                     accessorName, args, false );
799                 if ( method.getReturnType() != Boolean.TYPE )
800                     method = null;
801             } catch ( Exception JavaDoc e ) {
802                 e2 = e;
803             }
804         if ( method == null )
805             throw new ReflectError("Error in property getter: "
806                 +e1 + (e2!=null?" : "+e2:"") );
807
808         try {
809             return invokeMethod( method, obj, args );
810         }
811         catch(InvocationTargetException e)
812         {
813             throw new UtilEvalError("Property accessor threw exception: "
814                 +e.getTargetException() );
815         }
816     }
817
818     public static void setObjectProperty(
819         Object JavaDoc obj, String JavaDoc propName, Object JavaDoc value)
820         throws ReflectError, UtilEvalError
821     {
822         String JavaDoc accessorName = accessorName( "set", propName );
823         Object JavaDoc[] args = new Object JavaDoc[] { value };
824
825         Interpreter.debug("property access: ");
826         try {
827             Method method = resolveExpectedJavaMethod(
828                 null/*bcm*/, obj.getClass(), obj, accessorName, args, false );
829             invokeMethod( method, obj, args );
830         }
831         catch ( InvocationTargetException e )
832         {
833             throw new UtilEvalError("Property accessor threw exception: "
834                 +e.getTargetException() );
835         }
836     }
837
838     /**
839         Return a more human readable version of the type name.
840         Specifically, array types are returned with postfix "[]" dimensions.
841         e.g. return "int []" for integer array instead of "class [I" as
842         would be returned by Class getName() in that case.
843     */

844     public static String JavaDoc normalizeClassName(Class JavaDoc type)
845     {
846         if ( !type.isArray() )
847             return type.getName();
848
849         StringBuffer JavaDoc className = new StringBuffer JavaDoc();
850         try {
851             className.append( getArrayBaseType(type).getName() +" ");
852             for(int i = 0; i < getArrayDimensions(type); i++)
853                 className.append("[]");
854         } catch( ReflectError e ) { /*shouldn't happen*/ }
855
856         return className.toString();
857     }
858
859     /**
860         returns the dimensionality of the Class
861         returns 0 if the Class is not an array class
862     */

863     public static int getArrayDimensions(Class JavaDoc arrayClass)
864     {
865         if ( !arrayClass.isArray() )
866             return 0;
867
868         return arrayClass.getName().lastIndexOf('[') + 1; // why so cute?
869
}
870
871     /**
872
873         Returns the base type of an array Class.
874         throws ReflectError if the Class is not an array class.
875     */

876     public static Class JavaDoc getArrayBaseType(Class JavaDoc arrayClass) throws ReflectError
877     {
878         if ( !arrayClass.isArray() )
879             throw new ReflectError("The class is not an array.");
880
881         return arrayClass.getComponentType();
882
883     }
884
885     /**
886         A command may be implemented as a compiled Java class containing one or
887         more static invoke() methods of the correct signature. The invoke()
888         methods must accept two additional leading arguments of the interpreter
889         and callstack, respectively. e.g. invoke(interpreter, callstack, ... )
890         This method adds the arguments and invokes the static method, returning
891         the result.
892     */

893     public static Object JavaDoc invokeCompiledCommand(
894         Class JavaDoc commandClass, Object JavaDoc [] args, Interpreter interpreter,
895         CallStack callstack )
896         throws UtilEvalError
897     {
898         // add interpereter and namespace to args list
899
Object JavaDoc[] invokeArgs = new Object JavaDoc[args.length + 2];
900         invokeArgs[0] = interpreter;
901         invokeArgs[1] = callstack;
902         System.arraycopy( args, 0, invokeArgs, 2, args.length );
903         BshClassManager bcm = interpreter.getClassManager();
904         try {
905             return Reflect.invokeStaticMethod(
906                 bcm, commandClass, "invoke", invokeArgs );
907         } catch ( InvocationTargetException e ) {
908             throw new UtilEvalError(
909                 "Error in compiled command: "+e.getTargetException() );
910         } catch ( ReflectError e ) {
911             throw new UtilEvalError("Error invoking compiled command: "+e );
912         }
913     }
914
915     private static void logInvokeMethod(
916         String JavaDoc msg, Method method, Object JavaDoc[] args )
917     {
918         if ( Interpreter.DEBUG )
919         {
920             Interpreter.debug( msg +method+" with args:" );
921             for(int i=0; i<args.length; i++)
922                 Interpreter.debug(
923                     "args["+i+"] = "+args[i]
924                     +" type = "+args[i].getClass() );
925         }
926     }
927
928     private static void checkFoundStaticMethod(
929         Method method, boolean staticOnly, Class JavaDoc clas )
930         throws UtilEvalError
931     {
932         // We're looking for a static method but found an instance method
933
if ( method != null && staticOnly && !isStatic( method ) )
934             throw new UtilEvalError(
935                 "Cannot reach instance method: "
936                 + StringUtil.methodString(
937                     method.getName(), method.getParameterTypes() )
938                 + " from static context: "+ clas.getName() );
939     }
940
941     private static ReflectError cantFindConstructor(
942         Class JavaDoc clas, Class JavaDoc [] types )
943     {
944         if ( types.length == 0 )
945             return new ReflectError(
946                 "Can't find default constructor for: "+clas);
947         else
948             return new ReflectError(
949                 "Can't find constructor: "
950                     + StringUtil.methodString( clas.getName(), types )
951                     +" in class: "+ clas.getName() );
952     }
953
954     private static boolean isPublic( Class JavaDoc c ) {
955         return Modifier.isPublic( c.getModifiers() );
956     }
957     private static boolean isPublic( Method m ) {
958         return Modifier.isPublic( m.getModifiers() );
959     }
960     private static boolean isPublic( Constructor c ) {
961         return Modifier.isPublic( c.getModifiers() );
962     }
963     private static boolean isStatic( Method m ) {
964         return Modifier.isStatic( m.getModifiers() );
965     }
966 }
967
968
Popular Tags