001: /*****************************************************************************
002: * *
003: * This file is part of the BeanShell Java Scripting distribution. *
004: * Documentation and updates may be found at http://www.beanshell.org/ *
005: * *
006: * Sun Public License Notice: *
007: * *
008: * The contents of this file are subject to the Sun Public License Version *
009: * 1.0 (the "License"); you may not use this file except in compliance with *
010: * the License. A copy of the License is available at http://www.sun.com *
011: * *
012: * The Original Code is BeanShell. The Initial Developer of the Original *
013: * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright *
014: * (C) 2000. All Rights Reserved. *
015: * *
016: * GNU Public License Notice: *
017: * *
018: * Alternatively, the contents of this file may be used under the terms of *
019: * the GNU Lesser General Public License (the "LGPL"), in which case the *
020: * provisions of LGPL are applicable instead of those above. If you wish to *
021: * allow use of your version of this file only under the terms of the LGPL *
022: * and not to allow others to use your version of this file under the SPL, *
023: * indicate your decision by deleting the provisions above and replace *
024: * them with the notice and other provisions required by the LGPL. If you *
025: * do not delete the provisions above, a recipient may use your version of *
026: * this file under either the SPL or the LGPL. *
027: * *
028: * Patrick Niemeyer (pat@pat.net) *
029: * Author of Learning Java, O'Reilly & Associates *
030: * http://www.pat.net/~pat/ *
031: * *
032: *****************************************************************************/package bsh;
033:
034: import java.lang.reflect.*;
035: import java.util.Vector;
036:
037: /**
038: * All of the reflection API code lies here. It is in the form of static
039: * utilities. Maybe this belongs in LHS.java or a generic object
040: * wrapper class.
041: *
042: * @author Pat Niemeyer
043: * @author Daniel Leuck
044: */
045: /*
046: Note: This class is messy. The method and field resolution need to be
047: rewritten. Various methods in here catch NoSuchMethod or NoSuchField
048: exceptions during their searches. These should be rewritten to avoid
049: having to catch the exceptions. Method lookups are now cached at a high
050: level so they are less important, however the logic is messy.
051: */
052: class Reflect {
053: /**
054: Invoke method on arbitrary object instance.
055: invocation may be static (through the object instance) or dynamic.
056: Object may be a bsh scripted object (bsh.This type).
057: @return the result of the method call
058: */
059: public static Object invokeObjectMethod(Object object,
060: String methodName, Object[] args, Interpreter interpreter,
061: CallStack callstack, SimpleNode callerInfo)
062: throws ReflectError, EvalError, InvocationTargetException {
063: // Bsh scripted object
064: if (object instanceof This
065: && !This.isExposedThisMethod(methodName))
066: return ((This) object).invokeMethod(methodName, args,
067: interpreter, callstack, callerInfo, false/*delcaredOnly*/
068: );
069:
070: // Plain Java object, find the java method
071: try {
072: BshClassManager bcm = interpreter == null ? null
073: : interpreter.getClassManager();
074: Class clas = object.getClass();
075:
076: Method method = resolveExpectedJavaMethod(bcm, clas,
077: object, methodName, args, false);
078:
079: return invokeMethod(method, object, args);
080: } catch (UtilEvalError e) {
081: throw e.toEvalError(callerInfo, callstack);
082: }
083: }
084:
085: /**
086: Invoke a method known to be static.
087: No object instance is needed and there is no possibility of the
088: method being a bsh scripted method.
089: */
090: public static Object invokeStaticMethod(BshClassManager bcm,
091: Class clas, String methodName, Object[] args)
092: throws ReflectError, UtilEvalError,
093: InvocationTargetException {
094: Interpreter.debug("invoke static Method");
095: Method method = resolveExpectedJavaMethod(bcm, clas, null,
096: methodName, args, true);
097: return invokeMethod(method, null, args);
098: }
099:
100: /**
101: Invoke the Java method on the specified object, performing needed
102: type mappings on arguments and return values.
103: @param args may be null
104: */
105: static Object invokeMethod(Method method, Object object,
106: Object[] args) throws ReflectError,
107: InvocationTargetException {
108: if (args == null)
109: args = new Object[0];
110:
111: logInvokeMethod("Invoking method (entry): ", method, args);
112:
113: // Map types to assignable forms, need to keep this fast...
114: Object[] tmpArgs = new Object[args.length];
115: Class[] types = method.getParameterTypes();
116: try {
117: for (int i = 0; i < args.length; i++)
118: tmpArgs[i] = Types.castObject(args[i]/*rhs*/,
119: types[i]/*lhsType*/, Types.ASSIGNMENT);
120: } catch (UtilEvalError e) {
121: throw new InterpreterError(
122: "illegal argument type in method invocation: " + e);
123: }
124:
125: // unwrap any primitives
126: tmpArgs = Primitive.unwrap(tmpArgs);
127:
128: logInvokeMethod("Invoking method (after massaging values): ",
129: method, tmpArgs);
130:
131: try {
132: Object returnValue = method.invoke(object, tmpArgs);
133: if (returnValue == null)
134: returnValue = Primitive.NULL;
135: Class returnType = method.getReturnType();
136:
137: return Primitive.wrap(returnValue, returnType);
138: } catch (IllegalAccessException e) {
139: throw new ReflectError("Cannot access method "
140: + StringUtil.methodString(method.getName(), method
141: .getParameterTypes()) + " in '"
142: + method.getDeclaringClass() + "' :" + e);
143: }
144: }
145:
146: public static Object getIndex(Object array, int index)
147: throws ReflectError, UtilTargetError {
148: if (Interpreter.DEBUG)
149: Interpreter
150: .debug("getIndex: " + array + ", index=" + index);
151: try {
152: Object val = Array.get(array, index);
153: return Primitive.wrap(val, array.getClass()
154: .getComponentType());
155: } catch (ArrayIndexOutOfBoundsException e1) {
156: throw new UtilTargetError(e1);
157: } catch (Exception e) {
158: throw new ReflectError("Array access:" + e);
159: }
160: }
161:
162: public static void setIndex(Object array, int index, Object val)
163: throws ReflectError, UtilTargetError {
164: try {
165: val = Primitive.unwrap(val);
166: Array.set(array, index, val);
167: } catch (ArrayStoreException e2) {
168: throw new UtilTargetError(e2);
169: } catch (IllegalArgumentException e1) {
170: throw new UtilTargetError(new ArrayStoreException(e1
171: .toString()));
172: } catch (Exception e) {
173: throw new ReflectError("Array access:" + e);
174: }
175: }
176:
177: public static Object getStaticFieldValue(Class clas,
178: String fieldName) throws UtilEvalError, ReflectError {
179: return getFieldValue(clas, null, fieldName, true/*onlystatic*/);
180: }
181:
182: /**
183: * Check for a field with the given name in a java object or scripted object
184: * if the field exists fetch the value, if not check for a property value.
185: * If neither is found return Primitive.VOID.
186: */
187: public static Object getObjectFieldValue(Object object,
188: String fieldName) throws UtilEvalError, ReflectError {
189: if (object instanceof This) {
190: This t = (This) object;
191: return t.namespace.getVariableOrProperty(fieldName, null);
192: } else {
193: try {
194: return getFieldValue(object.getClass(), object,
195: fieldName, false/*onlystatic*/);
196: } catch (ReflectError e) {
197: // no field, try property acces
198:
199: if (hasObjectPropertyGetter(object.getClass(),
200: fieldName))
201: return getObjectProperty(object, fieldName);
202: else
203: throw e;
204: }
205: }
206: }
207:
208: static LHS getLHSStaticField(Class clas, String fieldName)
209: throws UtilEvalError, ReflectError {
210: Field f = resolveExpectedJavaField(clas, fieldName, true/*onlystatic*/);
211: return new LHS(f);
212: }
213:
214: /**
215: Get an LHS reference to an object field.
216:
217: This method also deals with the field style property access.
218: In the field does not exist we check for a property setter.
219: */
220: static LHS getLHSObjectField(Object object, String fieldName)
221: throws UtilEvalError, ReflectError {
222: if (object instanceof This) {
223: // I guess this is when we pass it as an argument?
224: // Setting locally
225: boolean recurse = false;
226: return new LHS(((This) object).namespace, fieldName,
227: recurse);
228: }
229:
230: try {
231: Field f = resolveExpectedJavaField(object.getClass(),
232: fieldName, false/*staticOnly*/);
233: return new LHS(object, f);
234: } catch (ReflectError e) {
235: // not a field, try property access
236: if (hasObjectPropertySetter(object.getClass(), fieldName))
237: return new LHS(object, fieldName);
238: else
239: throw e;
240: }
241: }
242:
243: private static Object getFieldValue(Class clas, Object object,
244: String fieldName, boolean staticOnly) throws UtilEvalError,
245: ReflectError {
246: try {
247: Field f = resolveExpectedJavaField(clas, fieldName,
248: staticOnly);
249:
250: Object value = f.get(object);
251: Class returnType = f.getType();
252: return Primitive.wrap(value, returnType);
253:
254: } catch (NullPointerException e) { // shouldn't happen
255: throw new ReflectError("???" + fieldName
256: + " is not a static field.");
257: } catch (IllegalAccessException e) {
258: throw new ReflectError("Can't access field: " + fieldName);
259: }
260: }
261:
262: /*
263: Note: this method and resolveExpectedJavaField should be rewritten
264: to invert this logic so that no exceptions need to be caught
265: unecessarily. This is just a temporary impl.
266: @return the field or null if not found
267: */
268: protected static Field resolveJavaField(Class clas,
269: String fieldName, boolean staticOnly) throws UtilEvalError {
270: try {
271: return resolveExpectedJavaField(clas, fieldName, staticOnly);
272: } catch (ReflectError e) {
273: return null;
274: }
275: }
276:
277: /**
278: @throws ReflectError if the field is not found.
279: */
280: /*
281: Note: this should really just throw NoSuchFieldException... need
282: to change related signatures and code.
283: */
284: protected static Field resolveExpectedJavaField(Class clas,
285: String fieldName, boolean staticOnly) throws UtilEvalError,
286: ReflectError {
287: Field field;
288: try {
289: if (Capabilities.haveAccessibility())
290: field = findAccessibleField(clas, fieldName);
291: else
292: // Class getField() finds only public (and in interfaces, etc.)
293: field = clas.getField(fieldName);
294: } catch (NoSuchFieldException e) {
295: throw new ReflectError("No such field: " + fieldName);
296: } catch (SecurityException e) {
297: throw new UtilTargetError(
298: "Security Exception while searching fields of: "
299: + clas, e);
300: }
301:
302: if (staticOnly && !Modifier.isStatic(field.getModifiers()))
303: throw new UtilEvalError("Can't reach instance field: "
304: + fieldName + " from static context: "
305: + clas.getName());
306:
307: return field;
308: }
309:
310: /**
311: Used when accessibility capability is available to locate an occurrance
312: of the field in the most derived class or superclass and set its
313: accessibility flag.
314: Note that this method is not needed in the simple non accessible
315: case because we don't have to hunt for fields.
316: Note that classes may declare overlapping private fields, so the
317: distinction about the most derived is important. Java doesn't normally
318: allow this kind of access (super won't show private variables) so
319: there is no real syntax for specifying which class scope to use...
320:
321: @return the Field or throws NoSuchFieldException
322: @throws NoSuchFieldException if the field is not found
323: */
324: /*
325: This method should be rewritten to use getFields() and avoid catching
326: exceptions during the search.
327: */
328: private static Field findAccessibleField(Class clas,
329: String fieldName) throws UtilEvalError,
330: NoSuchFieldException {
331: Field field;
332:
333: // Quick check catches public fields include those in interfaces
334: try {
335: field = clas.getField(fieldName);
336: ReflectManager.RMSetAccessible(field);
337: return field;
338: } catch (NoSuchFieldException e) {
339: }
340:
341: // Now, on with the hunt...
342: while (clas != null) {
343: try {
344: field = clas.getDeclaredField(fieldName);
345: ReflectManager.RMSetAccessible(field);
346: return field;
347:
348: // Not found, fall through to next class
349:
350: } catch (NoSuchFieldException e) {
351: }
352:
353: clas = clas.getSuperclass();
354: }
355: throw new NoSuchFieldException(fieldName);
356: }
357:
358: /**
359: This method wraps resolveJavaMethod() and expects a non-null method
360: result. If the method is not found it throws a descriptive ReflectError.
361: */
362: protected static Method resolveExpectedJavaMethod(
363: BshClassManager bcm, Class clas, Object object,
364: String name, Object[] args, boolean staticOnly)
365: throws ReflectError, UtilEvalError {
366: if (object == Primitive.NULL)
367: throw new UtilTargetError(new NullPointerException(
368: "Attempt to invoke method " + name
369: + " on null value"));
370:
371: Class[] types = Types.getTypes(args);
372: Method method = resolveJavaMethod(bcm, clas, name, types,
373: staticOnly);
374:
375: if (method == null)
376: throw new ReflectError((staticOnly ? "Static method "
377: : "Method ")
378: + StringUtil.methodString(name, types)
379: + " not found in class'" + clas.getName() + "'");
380:
381: return method;
382: }
383:
384: /**
385: The full blown resolver method. All other method invocation methods
386: delegate to this. The method may be static or dynamic unless
387: staticOnly is set (in which case object may be null).
388: If staticOnly is set then only static methods will be located.
389: <p/>
390:
391: This method performs caching (caches discovered methods through the
392: class manager and utilizes cached methods.)
393: <p/>
394:
395: This method determines whether to attempt to use non-public methods
396: based on Capabilities.haveAccessibility() and will set the accessibilty
397: flag on the method as necessary.
398: <p/>
399:
400: If, when directed to find a static method, this method locates a more
401: specific matching instance method it will throw a descriptive exception
402: analogous to the error that the Java compiler would produce.
403: Note: as of 2.0.x this is a problem because there is no way to work
404: around this with a cast.
405: <p/>
406:
407: @param staticOnly
408: The method located must be static, the object param may be null.
409: @return the method or null if no matching method was found.
410: */
411: protected static Method resolveJavaMethod(BshClassManager bcm,
412: Class clas, String name, Class[] types, boolean staticOnly)
413: throws UtilEvalError {
414: if (clas == null)
415: throw new InterpreterError("null class");
416:
417: // Lookup previously cached method
418: Method method = null;
419: if (bcm == null)
420: Interpreter.debug("resolveJavaMethod UNOPTIMIZED lookup");
421: else
422: method = bcm.getResolvedMethod(clas, name, types,
423: staticOnly);
424:
425: if (method == null) {
426: boolean publicOnly = !Capabilities.haveAccessibility();
427: // Searching for the method may, itself be a priviledged action
428: try {
429: method = findOverloadedMethod(clas, name, types,
430: publicOnly);
431: } catch (SecurityException e) {
432: throw new UtilTargetError(
433: "Security Exception while searching methods of: "
434: + clas, e);
435: }
436:
437: checkFoundStaticMethod(method, staticOnly, clas);
438:
439: // This is the first time we've seen this method, set accessibility
440: // Note: even if it's a public method, we may have found it in a
441: // non-public class
442: if (method != null && !publicOnly) {
443: try {
444: ReflectManager.RMSetAccessible(method);
445: } catch (UtilEvalError e) { /*ignore*/
446: }
447: }
448:
449: // If succeeded cache the resolved method.
450: if (method != null && bcm != null)
451: bcm.cacheResolvedMethod(clas, types, method);
452: }
453:
454: return method;
455: }
456:
457: /**
458: Get the candidate methods by searching the class and interface graph
459: of baseClass and resolve the most specific.
460: @return the method or null for not found
461: */
462: private static Method findOverloadedMethod(Class baseClass,
463: String methodName, Class[] types, boolean publicOnly) {
464: if (Interpreter.DEBUG)
465: Interpreter.debug("Searching for method: "
466: + StringUtil.methodString(methodName, types)
467: + " in '" + baseClass.getName() + "'");
468:
469: Method[] methods = getCandidateMethods(baseClass, methodName,
470: types.length, publicOnly);
471:
472: if (Interpreter.DEBUG)
473: Interpreter.debug("Looking for most specific method: "
474: + methodName);
475: Method method = findMostSpecificMethod(types, methods);
476:
477: return method;
478: }
479:
480: /**
481: Climb the class and interface inheritence graph of the type and collect
482: all methods matching the specified name and criterion. If publicOnly
483: is true then only public methods in *public* classes or interfaces will
484: be returned. In the normal (non-accessible) case this addresses the
485: problem that arises when a package private class or private inner class
486: implements a public interface or derives from a public type.
487: <p/>
488:
489: This method primarily just delegates to gatherMethodsRecursive()
490: @see #gatherMethodsRecursive(
491: Class, String, int, boolean, java.util.Vector)
492: */
493: static Method[] getCandidateMethods(Class baseClass,
494: String methodName, int numArgs, boolean publicOnly) {
495: Vector candidates = gatherMethodsRecursive(baseClass,
496: methodName, numArgs, publicOnly, null/*candidates*/);
497:
498: // return the methods in an array
499: Method[] ma = new Method[candidates.size()];
500: candidates.copyInto(ma);
501: return ma;
502: }
503:
504: /**
505: Accumulate all methods, optionally including non-public methods,
506: class and interface, in the inheritence tree of baseClass.
507:
508: This method is analogous to Class getMethods() which returns all public
509: methods in the inheritence tree.
510:
511: In the normal (non-accessible) case this also addresses the problem
512: that arises when a package private class or private inner class
513: implements a public interface or derives from a public type. In other
514: words, sometimes we'll find public methods that we can't use directly
515: and we have to find the same public method in a parent class or
516: interface.
517:
518: @return the candidate methods vector
519: */
520: private static Vector gatherMethodsRecursive(Class baseClass,
521: String methodName, int numArgs, boolean publicOnly,
522: Vector candidates) {
523: if (candidates == null)
524: candidates = new Vector();
525:
526: // Add methods of the current class to the vector.
527: // In public case be careful to only add methods from a public class
528: // and to use getMethods() instead of getDeclaredMethods()
529: // (This addresses secure environments)
530: if (publicOnly) {
531: if (isPublic(baseClass))
532: addCandidates(baseClass.getMethods(), methodName,
533: numArgs, publicOnly, candidates);
534: } else
535: addCandidates(baseClass.getDeclaredMethods(), methodName,
536: numArgs, publicOnly, candidates);
537:
538: // Does the class or interface implement interfaces?
539: Class[] intfs = baseClass.getInterfaces();
540: for (int i = 0; i < intfs.length; i++)
541: gatherMethodsRecursive(intfs[i], methodName, numArgs,
542: publicOnly, candidates);
543:
544: // Do we have a superclass? (interfaces don't, etc.)
545: Class super class = baseClass.getSuperclass();
546: if (super class != null)
547: gatherMethodsRecursive(super class, methodName, numArgs,
548: publicOnly, candidates);
549:
550: return candidates;
551: }
552:
553: private static Vector addCandidates(Method[] methods,
554: String methodName, int numArgs, boolean publicOnly,
555: Vector candidates) {
556: for (int i = 0; i < methods.length; i++) {
557: Method m = methods[i];
558: if (m.getName().equals(methodName)
559: && (m.getParameterTypes().length == numArgs)
560: && (!publicOnly || isPublic(m)))
561: candidates.add(m);
562: }
563: return candidates;
564: }
565:
566: /**
567: Primary object constructor
568: This method is simpler than those that must resolve general method
569: invocation because constructors are not inherited.
570: <p/>
571: This method determines whether to attempt to use non-public constructors
572: based on Capabilities.haveAccessibility() and will set the accessibilty
573: flag on the method as necessary.
574: <p/>
575: */
576: static Object constructObject(Class clas, Object[] args)
577: throws ReflectError, InvocationTargetException {
578: if (clas.isInterface())
579: throw new ReflectError(
580: "Can't create instance of an interface: " + clas);
581:
582: Object obj = null;
583: Class[] types = Types.getTypes(args);
584: Constructor con = null;
585:
586: // Find the constructor.
587: // (there are no inherited constructors to worry about)
588: Constructor[] constructors = Capabilities.haveAccessibility() ? clas
589: .getDeclaredConstructors()
590: : clas.getConstructors();
591:
592: if (Interpreter.DEBUG)
593: Interpreter.debug("Looking for most specific constructor: "
594: + clas);
595: con = findMostSpecificConstructor(types, constructors);
596: if (con == null)
597: throw cantFindConstructor(clas, types);
598:
599: if (!isPublic(con))
600: try {
601: ReflectManager.RMSetAccessible(con);
602: } catch (UtilEvalError e) { /*ignore*/
603: }
604:
605: args = Primitive.unwrap(args);
606: try {
607: obj = con.newInstance(args);
608: } catch (InstantiationException e) {
609: throw new ReflectError("The class " + clas
610: + " is abstract ");
611: } catch (IllegalAccessException e) {
612: throw new ReflectError(
613: "We don't have permission to create an instance."
614: + "Use setAccessibility(true) to enable access.");
615: } catch (IllegalArgumentException e) {
616: throw new ReflectError("The number of arguments was wrong");
617: }
618: if (obj == null)
619: throw new ReflectError("Couldn't construct the object");
620:
621: return obj;
622: }
623:
624: /*
625: This method should parallel findMostSpecificMethod()
626: The only reason it can't be combined is that Method and Constructor
627: don't have a common interface for their signatures
628: */
629: static Constructor findMostSpecificConstructor(Class[] idealMatch,
630: Constructor[] constructors) {
631: int match = findMostSpecificConstructorIndex(idealMatch,
632: constructors);
633: return (match == -1) ? null : constructors[match];
634: }
635:
636: static int findMostSpecificConstructorIndex(Class[] idealMatch,
637: Constructor[] constructors) {
638: Class[][] candidates = new Class[constructors.length][];
639: for (int i = 0; i < candidates.length; i++)
640: candidates[i] = constructors[i].getParameterTypes();
641:
642: return findMostSpecificSignature(idealMatch, candidates);
643: }
644:
645: /**
646: Find the best match for signature idealMatch.
647: It is assumed that the methods array holds only valid candidates
648: (e.g. method name and number of args already matched).
649: This method currently does not take into account Java 5 covariant
650: return types... which I think will require that we find the most
651: derived return type of otherwise identical best matches.
652:
653: @see #findMostSpecificSignature(Class[], Class[][])
654: @param methods the set of candidate method which differ only in the
655: types of their arguments.
656: */
657: static Method findMostSpecificMethod(Class[] idealMatch,
658: Method[] methods) {
659: // copy signatures into array for findMostSpecificMethod()
660: Class[][] candidateSigs = new Class[methods.length][];
661: for (int i = 0; i < methods.length; i++)
662: candidateSigs[i] = methods[i].getParameterTypes();
663:
664: int match = findMostSpecificSignature(idealMatch, candidateSigs);
665: return match == -1 ? null : methods[match];
666: }
667:
668: /**
669: Implement JLS 15.11.2
670: Return the index of the most specific arguments match or -1 if no
671: match is found.
672: This method is used by both methods and constructors (which
673: unfortunately don't share a common interface for signature info).
674:
675: @return the index of the most specific candidate
676:
677: */
678: /*
679: Note: Two methods which are equally specific should not be allowed by
680: the Java compiler. In this case BeanShell currently chooses the first
681: one it finds. We could add a test for this case here (I believe) by
682: adding another isSignatureAssignable() in the other direction between
683: the target and "best" match. If the assignment works both ways then
684: neither is more specific and they are ambiguous. I'll leave this test
685: out for now because I'm not sure how much another test would impact
686: performance. Method selection is now cached at a high level, so a few
687: friendly extraneous tests shouldn't be a problem.
688: */
689: static int findMostSpecificSignature(Class[] idealMatch,
690: Class[][] candidates) {
691: for (int round = Types.FIRST_ROUND_ASSIGNABLE; round <= Types.LAST_ROUND_ASSIGNABLE; round++) {
692: Class[] bestMatch = null;
693: int bestMatchIndex = -1;
694:
695: for (int i = 0; i < candidates.length; i++) {
696: Class[] targetMatch = candidates[i];
697:
698: // If idealMatch fits targetMatch and this is the first match
699: // or targetMatch is more specific than the best match, make it
700: // the new best match.
701: if (Types.isSignatureAssignable(idealMatch,
702: targetMatch, round)
703: && ((bestMatch == null) || (Types
704: .isSignatureAssignable(targetMatch,
705: bestMatch,
706: Types.JAVA_BASE_ASSIGNABLE) && !Types
707: .areSignaturesEqual(targetMatch,
708: bestMatch)))) {
709: bestMatch = targetMatch;
710: bestMatchIndex = i;
711: }
712: }
713:
714: if (bestMatch != null)
715: return bestMatchIndex;
716: }
717:
718: return -1;
719: }
720:
721: static String accessorName(String getorset, String propName) {
722: return getorset
723: + String.valueOf(Character.toUpperCase(propName
724: .charAt(0))) + propName.substring(1);
725: }
726:
727: public static boolean hasObjectPropertyGetter(Class clas,
728: String propName) {
729: String getterName = accessorName("get", propName);
730: try {
731: clas.getMethod(getterName, new Class[0]);
732: return true;
733: } catch (NoSuchMethodException e) { /* fall through */
734: }
735: getterName = accessorName("is", propName);
736: try {
737: Method m = clas.getMethod(getterName, new Class[0]);
738: return (m.getReturnType() == Boolean.TYPE);
739: } catch (NoSuchMethodException e) {
740: return false;
741: }
742: }
743:
744: public static boolean hasObjectPropertySetter(Class clas,
745: String propName) {
746: String setterName = accessorName("set", propName);
747: Method[] methods = clas.getMethods();
748:
749: // we don't know the right hand side of the assignment yet.
750: // has at least one setter of the right name?
751: for (int i = 0; i < methods.length; i++)
752: if (methods[i].getName().equals(setterName))
753: return true;
754: return false;
755: }
756:
757: public static Object getObjectProperty(Object obj, String propName)
758: throws UtilEvalError, ReflectError {
759: Object[] args = new Object[] {};
760:
761: Interpreter.debug("property access: ");
762: Method method = null;
763:
764: Exception e1 = null, e2 = null;
765: try {
766: String accessorName = accessorName("get", propName);
767: method = resolveExpectedJavaMethod(null/*bcm*/, obj
768: .getClass(), obj, accessorName, args, false);
769: } catch (Exception e) {
770: e1 = e;
771: }
772: if (method == null)
773: try {
774: String accessorName = accessorName("is", propName);
775: method = resolveExpectedJavaMethod(null/*bcm*/, obj
776: .getClass(), obj, accessorName, args, false);
777: if (method.getReturnType() != Boolean.TYPE)
778: method = null;
779: } catch (Exception e) {
780: e2 = e;
781: }
782: if (method == null)
783: throw new ReflectError("Error in property getter: " + e1
784: + (e2 != null ? " : " + e2 : ""));
785:
786: try {
787: return invokeMethod(method, obj, args);
788: } catch (InvocationTargetException e) {
789: throw new UtilEvalError(
790: "Property accessor threw exception: "
791: + e.getTargetException());
792: }
793: }
794:
795: public static void setObjectProperty(Object obj, String propName,
796: Object value) throws ReflectError, UtilEvalError {
797: String accessorName = accessorName("set", propName);
798: Object[] args = new Object[] { value };
799:
800: Interpreter.debug("property access: ");
801: try {
802: Method method = resolveExpectedJavaMethod(null/*bcm*/, obj
803: .getClass(), obj, accessorName, args, false);
804: invokeMethod(method, obj, args);
805: } catch (InvocationTargetException e) {
806: throw new UtilEvalError(
807: "Property accessor threw exception: "
808: + e.getTargetException());
809: }
810: }
811:
812: /**
813: Return a more human readable version of the type name.
814: Specifically, array types are returned with postfix "[]" dimensions.
815: e.g. return "int []" for integer array instead of "class [I" as
816: would be returned by Class getName() in that case.
817: */
818: public static String normalizeClassName(Class type) {
819: if (!type.isArray())
820: return type.getName();
821:
822: StringBuffer className = new StringBuffer();
823: try {
824: className.append(getArrayBaseType(type).getName() + " ");
825: for (int i = 0; i < getArrayDimensions(type); i++)
826: className.append("[]");
827: } catch (ReflectError e) { /*shouldn't happen*/
828: }
829:
830: return className.toString();
831: }
832:
833: /**
834: returns the dimensionality of the Class
835: returns 0 if the Class is not an array class
836: */
837: public static int getArrayDimensions(Class arrayClass) {
838: if (!arrayClass.isArray())
839: return 0;
840:
841: return arrayClass.getName().lastIndexOf('[') + 1; // why so cute?
842: }
843:
844: /**
845:
846: Returns the base type of an array Class.
847: throws ReflectError if the Class is not an array class.
848: */
849: public static Class getArrayBaseType(Class arrayClass)
850: throws ReflectError {
851: if (!arrayClass.isArray())
852: throw new ReflectError("The class is not an array.");
853:
854: return arrayClass.getComponentType();
855:
856: }
857:
858: /**
859: A command may be implemented as a compiled Java class containing one or
860: more static invoke() methods of the correct signature. The invoke()
861: methods must accept two additional leading arguments of the interpreter
862: and callstack, respectively. e.g. invoke(interpreter, callstack, ... )
863: This method adds the arguments and invokes the static method, returning
864: the result.
865: */
866: public static Object invokeCompiledCommand(Class commandClass,
867: Object[] args, Interpreter interpreter, CallStack callstack)
868: throws UtilEvalError {
869: // add interpereter and namespace to args list
870: Object[] invokeArgs = new Object[args.length + 2];
871: invokeArgs[0] = interpreter;
872: invokeArgs[1] = callstack;
873: System.arraycopy(args, 0, invokeArgs, 2, args.length);
874: BshClassManager bcm = interpreter.getClassManager();
875: try {
876: return Reflect.invokeStaticMethod(bcm, commandClass,
877: "invoke", invokeArgs);
878: } catch (InvocationTargetException e) {
879: throw new UtilEvalError("Error in compiled command: "
880: + e.getTargetException());
881: } catch (ReflectError e) {
882: throw new UtilEvalError("Error invoking compiled command: "
883: + e);
884: }
885: }
886:
887: private static void logInvokeMethod(String msg, Method method,
888: Object[] args) {
889: if (Interpreter.DEBUG) {
890: Interpreter.debug(msg + method + " with args:");
891: for (int i = 0; i < args.length; i++)
892: Interpreter.debug("args[" + i + "] = " + args[i]
893: + " type = " + args[i].getClass());
894: }
895: }
896:
897: private static void checkFoundStaticMethod(Method method,
898: boolean staticOnly, Class clas) throws UtilEvalError {
899: // We're looking for a static method but found an instance method
900: if (method != null && staticOnly && !isStatic(method))
901: throw new UtilEvalError("Cannot reach instance method: "
902: + StringUtil.methodString(method.getName(), method
903: .getParameterTypes())
904: + " from static context: " + clas.getName());
905: }
906:
907: private static ReflectError cantFindConstructor(Class clas,
908: Class[] types) {
909: if (types.length == 0)
910: return new ReflectError(
911: "Can't find default constructor for: " + clas);
912: else
913: return new ReflectError("Can't find constructor: "
914: + StringUtil.methodString(clas.getName(), types)
915: + " in class: " + clas.getName());
916: }
917:
918: private static boolean isPublic(Class c) {
919: return Modifier.isPublic(c.getModifiers());
920: }
921:
922: private static boolean isPublic(Method m) {
923: return Modifier.isPublic(m.getModifiers());
924: }
925:
926: private static boolean isPublic(Constructor c) {
927: return Modifier.isPublic(c.getModifiers());
928: }
929:
930: private static boolean isStatic(Method m) {
931: return Modifier.isStatic(m.getModifiers());
932: }
933: }
|