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