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