0001: /*****************************************************************************
0002: * *
0003: * This file is part of the BeanShell Java Scripting distribution. *
0004: * Documentation and updates may be found at http://www.beanshell.org/ *
0005: * *
0006: * Sun Public License Notice: *
0007: * *
0008: * The contents of this file are subject to the Sun Public License Version *
0009: * 1.0 (the "License"); you may not use this file except in compliance with *
0010: * the License. A copy of the License is available at http://www.sun.com *
0011: * *
0012: * The Original Code is BeanShell. The Initial Developer of the Original *
0013: * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright *
0014: * (C) 2000. All Rights Reserved. *
0015: * *
0016: * GNU Public License Notice: *
0017: * *
0018: * Alternatively, the contents of this file may be used under the terms of *
0019: * the GNU Lesser General Public License (the "LGPL"), in which case the *
0020: * provisions of LGPL are applicable instead of those above. If you wish to *
0021: * allow use of your version of this file only under the terms of the LGPL *
0022: * and not to allow others to use your version of this file under the SPL, *
0023: * indicate your decision by deleting the provisions above and replace *
0024: * them with the notice and other provisions required by the LGPL. If you *
0025: * do not delete the provisions above, a recipient may use your version of *
0026: * this file under either the SPL or the LGPL. *
0027: * *
0028: * Patrick Niemeyer (pat@pat.net) *
0029: * Author of Learning Java, O'Reilly & Associates *
0030: * http://www.pat.net/~pat/ *
0031: * *
0032: *****************************************************************************/package org.gjt.sp.jedit.bsh;
0033:
0034: import java.lang.reflect.Array;
0035: import java.lang.reflect.InvocationTargetException;
0036:
0037: /**
0038: What's in a name? I'll tell you...
0039: Name() is a somewhat ambiguous thing in the grammar and so is this.
0040: <p>
0041:
0042: This class is a name resolver. It holds a possibly ambiguous dot
0043: separated name and reference to a namespace in which it allegedly lives.
0044: It provides methods that attempt to resolve the name to various types of
0045: entities: e.g. an Object, a Class, a declared scripted BeanShell method.
0046: <p>
0047:
0048: Name objects are created by the factory method NameSpace getNameResolver(),
0049: which caches them subject to a class namespace change. This means that
0050: we can cache information about various types of resolution here.
0051: Currently very little if any information is cached. However with a future
0052: "optimize" setting that defeats certain dynamic behavior we might be able
0053: to cache quite a bit.
0054: */
0055: /*
0056: <strong>Implementation notes</strong>
0057: <pre>
0058: Thread safety: all of the work methods in this class must be synchronized
0059: because they share the internal intermediate evaluation state.
0060:
0061: Note about invokeMethod(): We could simply use resolveMethod and return
0062: the MethodInvoker (BshMethod or JavaMethod) however there is no easy way
0063: for the AST (BSHMehodInvocation) to use this as it doesn't have type
0064: information about the target to resolve overloaded methods.
0065: (In Java, overloaded methods are resolved at compile time... here they
0066: are, of necessity, dynamic). So it would have to do what we do here
0067: and cache by signature. We now do that for the client in Reflect.java.
0068:
0069: Note on this.caller resolution:
0070: Although references like these do work:
0071:
0072: this.caller.caller.caller... // works
0073:
0074: the equivalent using successive calls:
0075:
0076: // does *not* work
0077: for( caller=this.caller; caller != null; caller = caller.caller );
0078:
0079: is prohibited by the restriction that you can only call .caller on a
0080: literal this or caller reference. The effect is that magic caller
0081: reference only works through the current 'this' reference.
0082: The real explanation is that This referernces do not really know anything
0083: about their depth on the call stack. It might even be hard to define
0084: such a thing...
0085:
0086: For those purposes we provide :
0087:
0088: this.callstack
0089:
0090: </pre>
0091: */
0092: class Name implements java.io.Serializable {
0093: // These do not change during evaluation
0094: public NameSpace namespace;
0095: String value = null;
0096:
0097: // ---------------------------------------------------------
0098: // The following instance variables mutate during evaluation and should
0099: // be reset by the reset() method where necessary
0100:
0101: // For evaluation
0102: /** Remaining text to evaluate */
0103: private String evalName;
0104: /**
0105: The last part of the name evaluated. This is really only used for
0106: this, caller, and super resolution.
0107: */
0108: private String lastEvalName;
0109: private static String FINISHED = null; // null evalname and we're finished
0110: private Object evalBaseObject; // base object for current eval
0111:
0112: private int callstackDepth; // number of times eval hit 'this.caller'
0113:
0114: //
0115: // End mutable instance variables.
0116: // ---------------------------------------------------------
0117:
0118: // Begin Cached result structures
0119: // These are optimizations
0120:
0121: // Note: it's ok to cache class resolution here because when the class
0122: // space changes the namespace will discard cached names.
0123:
0124: /**
0125: The result is a class
0126: */
0127: Class asClass;
0128:
0129: /**
0130: The result is a static method call on the following class
0131: */
0132: Class classOfStaticMethod;
0133:
0134: // End Cached result structures
0135:
0136: private void reset() {
0137: evalName = value;
0138: evalBaseObject = null;
0139: callstackDepth = 0;
0140: }
0141:
0142: /**
0143: This constructor should *not* be used in general.
0144: Use NameSpace getNameResolver() which supports caching.
0145: @see NameSpace getNameResolver().
0146: */
0147: // I wish I could make this "friendly" to only NameSpace
0148: Name(NameSpace namespace, String s) {
0149: this .namespace = namespace;
0150: value = s;
0151: }
0152:
0153: /**
0154: Resolve possibly complex name to an object value.
0155:
0156: Throws EvalError on various failures.
0157: A null object value is indicated by a Primitive.NULL.
0158: A return type of Primitive.VOID comes from attempting to access
0159: an undefined variable.
0160:
0161: Some cases:
0162: myVariable
0163: myVariable.foo
0164: myVariable.foo.bar
0165: java.awt.GridBagConstraints.BOTH
0166: my.package.stuff.MyClass.someField.someField...
0167:
0168: Interpreter reference is necessary to allow resolution of
0169: "this.interpreter" magic field.
0170: CallStack reference is necessary to allow resolution of
0171: "this.caller" magic field.
0172: "this.callstack" magic field.
0173: */
0174: public Object toObject(CallStack callstack, Interpreter interpreter)
0175: throws UtilEvalError {
0176: return toObject(callstack, interpreter, false);
0177: }
0178:
0179: /**
0180: @see toObject()
0181: @param forceClass if true then resolution will only produce a class.
0182: This is necessary to disambiguate in cases where the grammar knows
0183: that we want a class; where in general the var path may be taken.
0184: */
0185: synchronized public Object toObject(CallStack callstack,
0186: Interpreter interpreter, boolean forceClass)
0187: throws UtilEvalError {
0188: reset();
0189:
0190: Object obj = null;
0191: while (evalName != null)
0192: obj = consumeNextObjectField(callstack, interpreter,
0193: forceClass, false/*autoalloc*/);
0194:
0195: if (obj == null)
0196: throw new InterpreterError("null value in toObject()");
0197:
0198: return obj;
0199: }
0200:
0201: private Object completeRound(String lastEvalName,
0202: String nextEvalName, Object returnObject) {
0203: if (returnObject == null)
0204: throw new InterpreterError("lastEvalName = " + lastEvalName);
0205: this .lastEvalName = lastEvalName;
0206: this .evalName = nextEvalName;
0207: this .evalBaseObject = returnObject;
0208: return returnObject;
0209: }
0210:
0211: /**
0212: Get the next object by consuming one or more components of evalName.
0213: Often this consumes just one component, but if the name is a classname
0214: it will consume all of the components necessary to make the class
0215: identifier.
0216: */
0217: private Object consumeNextObjectField(CallStack callstack,
0218: Interpreter interpreter, boolean forceClass,
0219: boolean autoAllocateThis) throws UtilEvalError {
0220: /*
0221: Is it a simple variable name?
0222: Doing this first gives the correct Java precedence for vars
0223: vs. imported class names (at least in the simple case - see
0224: tests/precedence1.bsh). It should also speed things up a bit.
0225: */
0226: if ((evalBaseObject == null && !isCompound(evalName))
0227: && !forceClass) {
0228: Object obj = resolveThisFieldReference(callstack,
0229: namespace, interpreter, evalName, false);
0230:
0231: if (obj != Primitive.VOID)
0232: return completeRound(evalName, FINISHED, obj);
0233: }
0234:
0235: /*
0236: Is it a bsh script variable reference?
0237: If we're just starting the eval of name (no base object)
0238: or we're evaluating relative to a This type reference check.
0239: */
0240: String varName = prefix(evalName, 1);
0241: if ((evalBaseObject == null || evalBaseObject instanceof This)
0242: && !forceClass) {
0243: if (Interpreter.DEBUG)
0244: Interpreter.debug("trying to resolve variable: "
0245: + varName);
0246:
0247: Object obj;
0248: // switch namespace and special var visibility
0249: if (evalBaseObject == null) {
0250: obj = resolveThisFieldReference(callstack, namespace,
0251: interpreter, varName, false);
0252: } else {
0253: obj = resolveThisFieldReference(callstack,
0254: ((This) evalBaseObject).namespace, interpreter,
0255: varName, true);
0256: }
0257:
0258: if (obj != Primitive.VOID) {
0259: // Resolved the variable
0260: if (Interpreter.DEBUG)
0261: Interpreter.debug("resolved variable: " + varName
0262: + " in namespace: " + namespace);
0263:
0264: return completeRound(varName, suffix(evalName), obj);
0265: }
0266: }
0267:
0268: /*
0269: Is it a class name?
0270: If we're just starting eval of name try to make it, else fail.
0271: */
0272: if (evalBaseObject == null) {
0273: if (Interpreter.DEBUG)
0274: Interpreter.debug("trying class: " + evalName);
0275:
0276: /*
0277: Keep adding parts until we have a class
0278: */
0279: Class clas = null;
0280: int i = 1;
0281: String className = null;
0282: for (; i <= countParts(evalName); i++) {
0283: className = prefix(evalName, i);
0284: if ((clas = namespace.getClass(className)) != null)
0285: break;
0286: }
0287:
0288: if (clas != null) {
0289: return completeRound(className, suffix(evalName,
0290: countParts(evalName) - i), new ClassIdentifier(
0291: clas));
0292: }
0293: // not a class (or variable per above)
0294: if (Interpreter.DEBUG)
0295: Interpreter.debug("not a class, trying var prefix "
0296: + evalName);
0297: }
0298:
0299: // No variable or class found in 'this' type ref.
0300: // if autoAllocateThis then create one; a child 'this'.
0301: if ((evalBaseObject == null || evalBaseObject instanceof This)
0302: && !forceClass && autoAllocateThis) {
0303: NameSpace targetNameSpace = (evalBaseObject == null) ? namespace
0304: : ((This) evalBaseObject).namespace;
0305: Object obj = new NameSpace(targetNameSpace, "auto: "
0306: + varName).getThis(interpreter);
0307: targetNameSpace.setVariable(varName, obj, false);
0308: return completeRound(varName, suffix(evalName), obj);
0309: }
0310:
0311: /*
0312: If we didn't find a class or variable name (or prefix) above
0313: there are two possibilities:
0314:
0315: - If we are a simple name then we can pass as a void variable
0316: reference.
0317: - If we are compound then we must fail at this point.
0318: */
0319: if (evalBaseObject == null) {
0320: if (!isCompound(evalName)) {
0321: return completeRound(evalName, FINISHED, Primitive.VOID);
0322: } else
0323: throw new UtilEvalError("Class or variable not found: "
0324: + evalName);
0325: }
0326:
0327: /*
0328: --------------------------------------------------------
0329: After this point we're definitely evaluating relative to
0330: a base object.
0331: --------------------------------------------------------
0332: */
0333:
0334: /*
0335: Do some basic validity checks.
0336: */
0337:
0338: if (evalBaseObject == Primitive.NULL) // previous round produced null
0339: throw new UtilTargetError(new NullPointerException(
0340: "Null Pointer while evaluating: " + value));
0341:
0342: if (evalBaseObject == Primitive.VOID) // previous round produced void
0343: throw new UtilEvalError(
0344: "Undefined variable or class name while evaluating: "
0345: + value);
0346:
0347: if (evalBaseObject instanceof Primitive)
0348: throw new UtilEvalError(
0349: "Can't treat primitive like an object. "
0350: + "Error while evaluating: " + value);
0351:
0352: /*
0353: Resolve relative to a class type
0354: static field, inner class, ?
0355: */
0356: if (evalBaseObject instanceof ClassIdentifier) {
0357: Class clas = ((ClassIdentifier) evalBaseObject)
0358: .getTargetClass();
0359: String field = prefix(evalName, 1);
0360:
0361: // Class qualified 'this' reference from inner class.
0362: // e.g. 'MyOuterClass.this'
0363: if (field.equals("this")) {
0364: // find the enclosing class instance space of the class name
0365: NameSpace ns = namespace;
0366: while (ns != null) {
0367: // getClassInstance() throws exception if not there
0368: if (ns.classInstance != null
0369: && ns.classInstance.getClass() == clas)
0370: return completeRound(field, suffix(evalName),
0371: ns.classInstance);
0372: ns = ns.getParent();
0373: }
0374: throw new UtilEvalError(
0375: "Can't find enclosing 'this' instance of class: "
0376: + clas);
0377: }
0378:
0379: Object obj = null;
0380: // static field?
0381: try {
0382: if (Interpreter.DEBUG)
0383: Interpreter
0384: .debug("Name call to getStaticFieldValue, class: "
0385: + clas + ", field:" + field);
0386: obj = Reflect.getStaticFieldValue(clas, field);
0387: } catch (ReflectError e) {
0388: if (Interpreter.DEBUG)
0389: Interpreter.debug("field reflect error: " + e);
0390: }
0391:
0392: // inner class?
0393: if (obj == null) {
0394: String iclass = clas.getName() + "$" + field;
0395: Class c = namespace.getClass(iclass);
0396: if (c != null)
0397: obj = new ClassIdentifier(c);
0398: }
0399:
0400: if (obj == null)
0401: throw new UtilEvalError(
0402: "No static field or inner class: " + field
0403: + " of " + clas);
0404:
0405: return completeRound(field, suffix(evalName), obj);
0406: }
0407:
0408: /*
0409: If we've fallen through here we are no longer resolving to
0410: a class type.
0411: */
0412: if (forceClass)
0413: throw new UtilEvalError(value
0414: + " does not resolve to a class name.");
0415:
0416: /*
0417: Some kind of field access?
0418: */
0419:
0420: String field = prefix(evalName, 1);
0421:
0422: // length access on array?
0423: if (field.equals("length")
0424: && evalBaseObject.getClass().isArray()) {
0425: Object obj = new Primitive(Array.getLength(evalBaseObject));
0426: return completeRound(field, suffix(evalName), obj);
0427: }
0428:
0429: // Check for field on object
0430: // Note: could eliminate throwing the exception somehow
0431: try {
0432: Object obj = Reflect.getObjectFieldValue(evalBaseObject,
0433: field);
0434: return completeRound(field, suffix(evalName), obj);
0435: } catch (ReflectError e) { /* not a field */
0436: }
0437:
0438: // if we get here we have failed
0439: throw new UtilEvalError("Cannot access field: " + field
0440: + ", on object: " + evalBaseObject);
0441: }
0442:
0443: /**
0444: Resolve a variable relative to a This reference.
0445:
0446: This is the general variable resolution method, accomodating special
0447: fields from the This context. Together the namespace and interpreter
0448: comprise the This context. The callstack, if available allows for the
0449: this.caller construct.
0450: Optionally interpret special "magic" field names: e.g. interpreter.
0451: <p/>
0452:
0453: @param callstack may be null, but this is only legitimate in special
0454: cases where we are sure resolution will not involve this.caller.
0455:
0456: @param namespace the namespace of the this reference (should be the
0457: same as the top of the stack?
0458: */
0459: Object resolveThisFieldReference(CallStack callstack,
0460: NameSpace this NameSpace, Interpreter interpreter,
0461: String varName, boolean specialFieldsVisible)
0462: throws UtilEvalError {
0463: if (varName.equals("this")) {
0464: /*
0465: Somewhat of a hack. If the special fields are visible (we're
0466: operating relative to a 'this' type already) dissallow further
0467: .this references to prevent user from skipping to things like
0468: super.this.caller
0469: */
0470: if (specialFieldsVisible)
0471: throw new UtilEvalError(
0472: "Redundant to call .this on This type");
0473:
0474: // Allow getThis() to work through BlockNameSpace to the method
0475: // namespace
0476: // XXX re-eval this... do we need it?
0477: This ths = this NameSpace.getThis(interpreter);
0478: this NameSpace = ths.getNameSpace();
0479: Object result = ths;
0480:
0481: NameSpace classNameSpace = getClassNameSpace(this NameSpace);
0482: if (classNameSpace != null) {
0483: if (isCompound(evalName))
0484: result = classNameSpace.getThis(interpreter);
0485: else
0486: result = classNameSpace.getClassInstance();
0487: }
0488:
0489: return result;
0490: }
0491:
0492: /*
0493: Some duplication for "super". See notes for "this" above
0494: If we're in an enclsing class instance and have a superclass
0495: instance our super is the superclass instance.
0496: */
0497: if (varName.equals("super")) {
0498: //if ( specialFieldsVisible )
0499: //throw new UtilEvalError("Redundant to call .this on This type");
0500:
0501: // Allow getSuper() to through BlockNameSpace to the method's super
0502: This ths = this NameSpace.getSuper(interpreter);
0503: this NameSpace = ths.getNameSpace();
0504: // super is now the closure's super or class instance
0505:
0506: // XXXX re-evaluate this
0507: // can getSuper work by itself now?
0508: // If we're a class instance and the parent is also a class instance
0509: // then super means our parent.
0510: if (this NameSpace.getParent() != null
0511: && this NameSpace.getParent().isClass)
0512: ths = this NameSpace.getParent().getThis(interpreter);
0513:
0514: return ths;
0515: }
0516:
0517: Object obj = null;
0518:
0519: if (varName.equals("global"))
0520: obj = this NameSpace.getGlobal(interpreter);
0521:
0522: if (obj == null && specialFieldsVisible) {
0523: if (varName.equals("namespace"))
0524: obj = this NameSpace;
0525: else if (varName.equals("variables"))
0526: obj = this NameSpace.getVariableNames();
0527: else if (varName.equals("methods"))
0528: obj = this NameSpace.getMethodNames();
0529: else if (varName.equals("interpreter"))
0530: if (lastEvalName.equals("this"))
0531: obj = interpreter;
0532: else
0533: throw new UtilEvalError(
0534: "Can only call .interpreter on literal 'this'");
0535: }
0536:
0537: if (obj == null && specialFieldsVisible
0538: && varName.equals("caller")) {
0539: if (lastEvalName.equals("this")
0540: || lastEvalName.equals("caller")) {
0541: // get the previous context (see notes for this class)
0542: if (callstack == null)
0543: throw new InterpreterError("no callstack");
0544: obj = callstack.get(++callstackDepth).getThis(
0545: interpreter);
0546: } else
0547: throw new UtilEvalError(
0548: "Can only call .caller on literal 'this' or literal '.caller'");
0549:
0550: // early return
0551: return obj;
0552: }
0553:
0554: if (obj == null && specialFieldsVisible
0555: && varName.equals("callstack")) {
0556: if (lastEvalName.equals("this")) {
0557: // get the previous context (see notes for this class)
0558: if (callstack == null)
0559: throw new InterpreterError("no callstack");
0560: obj = callstack;
0561: } else
0562: throw new UtilEvalError(
0563: "Can only call .callstack on literal 'this'");
0564: }
0565:
0566: if (obj == null)
0567: obj = this NameSpace.getVariable(varName);
0568:
0569: if (obj == null)
0570: throw new InterpreterError("null this field ref:" + varName);
0571:
0572: return obj;
0573: }
0574:
0575: /**
0576: @return the enclosing class body namespace or null if not in a class.
0577: */
0578: static NameSpace getClassNameSpace(NameSpace this NameSpace) {
0579: // is a class instance
0580: //if ( thisNameSpace.classInstance != null )
0581: if (this NameSpace.isClass)
0582: return this NameSpace;
0583:
0584: if (this NameSpace.isMethod && this NameSpace.getParent() != null
0585: //&& thisNameSpace.getParent().classInstance != null
0586: && this NameSpace.getParent().isClass)
0587: return this NameSpace.getParent();
0588:
0589: return null;
0590: }
0591:
0592: /**
0593: Check the cache, else use toObject() to try to resolve to a class
0594: identifier.
0595:
0596: @throws ClassNotFoundException on class not found.
0597: @throws ClassPathException (type of EvalError) on special case of
0598: ambiguous unqualified name after super import.
0599: */
0600: synchronized public Class toClass() throws ClassNotFoundException,
0601: UtilEvalError {
0602: if (asClass != null)
0603: return asClass;
0604:
0605: reset();
0606:
0607: // "var" means untyped, return null class
0608: if (evalName.equals("var"))
0609: return asClass = null;
0610:
0611: /* Try straightforward class name first */
0612: Class clas = namespace.getClass(evalName);
0613:
0614: if (clas == null) {
0615: /*
0616: Try toObject() which knows how to work through inner classes
0617: and see what we end up with
0618: */
0619: Object obj = null;
0620: try {
0621: // Null interpreter and callstack references.
0622: // class only resolution should not require them.
0623: obj = toObject(null, null, true);
0624: } catch (UtilEvalError e) {
0625: }
0626: ; // couldn't resolve it
0627:
0628: if (obj instanceof ClassIdentifier)
0629: clas = ((ClassIdentifier) obj).getTargetClass();
0630: }
0631:
0632: if (clas == null)
0633: throw new ClassNotFoundException("Class: " + value
0634: + " not found in namespace");
0635:
0636: asClass = clas;
0637: return asClass;
0638: }
0639:
0640: /*
0641: */
0642: synchronized public LHS toLHS(CallStack callstack,
0643: Interpreter interpreter) throws UtilEvalError {
0644: // Should clean this up to a single return statement
0645: reset();
0646: LHS lhs;
0647:
0648: // Simple (non-compound) variable assignment e.g. x=5;
0649: if (!isCompound(evalName)) {
0650: if (evalName.equals("this"))
0651: throw new UtilEvalError("Can't assign to 'this'.");
0652:
0653: // Interpreter.debug("Simple var LHS...");
0654: lhs = new LHS(namespace, evalName, false/*bubble up if allowed*/);
0655: return lhs;
0656: }
0657:
0658: // Field e.g. foo.bar=5;
0659: Object obj = null;
0660: try {
0661: while (evalName != null && isCompound(evalName)) {
0662: obj = consumeNextObjectField(callstack, interpreter,
0663: false/*forcclass*/, true/*autoallocthis */);
0664: }
0665: } catch (UtilEvalError e) {
0666: throw new UtilEvalError("LHS evaluation: " + e.getMessage());
0667: }
0668:
0669: // Finished eval and its a class.
0670: if (evalName == null && obj instanceof ClassIdentifier)
0671: throw new UtilEvalError("Can't assign to class: " + value);
0672:
0673: if (obj == null)
0674: throw new UtilEvalError("Error in LHS: " + value);
0675:
0676: // e.g. this.x=5; or someThisType.x=5;
0677: if (obj instanceof This) {
0678: // dissallow assignment to magic fields
0679: if (evalName.equals("namespace")
0680: || evalName.equals("variables")
0681: || evalName.equals("methods")
0682: || evalName.equals("caller"))
0683: throw new UtilEvalError(
0684: "Can't assign to special variable: " + evalName);
0685:
0686: Interpreter.debug("found This reference evaluating LHS");
0687: /*
0688: If this was a literal "super" reference then we allow recursion
0689: in setting the variable to get the normal effect of finding the
0690: nearest definition starting at the super scope. On any other
0691: resolution qualified by a 'this' type reference we want to set
0692: the variable directly in that scope. e.g. this.x=5; or
0693: someThisType.x=5;
0694:
0695: In the old scoping rules super didn't do this.
0696: */
0697: boolean localVar = !lastEvalName.equals("super");
0698: return new LHS(((This) obj).namespace, evalName, localVar);
0699: }
0700:
0701: if (evalName != null) {
0702: try {
0703: if (obj instanceof ClassIdentifier) {
0704: Class clas = ((ClassIdentifier) obj)
0705: .getTargetClass();
0706: lhs = Reflect.getLHSStaticField(clas, evalName);
0707: return lhs;
0708: } else {
0709: lhs = Reflect.getLHSObjectField(obj, evalName);
0710: return lhs;
0711: }
0712: } catch (ReflectError e) {
0713: throw new UtilEvalError("Field access: " + e);
0714: }
0715: }
0716:
0717: throw new InterpreterError("Internal error in lhs...");
0718: }
0719:
0720: /**
0721: Invoke the method identified by this name.
0722: Performs caching of method resolution using SignatureKey.
0723: <p>
0724:
0725: Name contains a wholely unqualfied messy name; resolve it to
0726: ( object | static prefix ) + method name and invoke.
0727: <p>
0728:
0729: The interpreter is necessary to support 'this.interpreter' references
0730: in the called code. (e.g. debug());
0731: <p>
0732:
0733: <pre>
0734: Some cases:
0735:
0736: // dynamic
0737: local();
0738: myVariable.foo();
0739: myVariable.bar.blah.foo();
0740: // static
0741: java.lang.Integer.getInteger("foo");
0742: </pre>
0743: */
0744: public Object invokeMethod(Interpreter interpreter, Object[] args,
0745: CallStack callstack, SimpleNode callerInfo)
0746: throws UtilEvalError, EvalError, ReflectError,
0747: InvocationTargetException {
0748: String methodName = Name.suffix(value, 1);
0749: BshClassManager bcm = interpreter.getClassManager();
0750: NameSpace namespace = callstack.top();
0751:
0752: // Optimization - If classOfStaticMethod is set then we have already
0753: // been here and determined that this is a static method invocation.
0754: // Note: maybe factor this out with path below... clean up.
0755: if (classOfStaticMethod != null) {
0756: return Reflect.invokeStaticMethod(bcm, classOfStaticMethod,
0757: methodName, args);
0758: }
0759:
0760: if (!Name.isCompound(value))
0761: return invokeLocalMethod(interpreter, args, callstack,
0762: callerInfo);
0763:
0764: // Note: if we want methods declared inside blocks to be accessible via
0765: // this.methodname() inside the block we could handle it here as a
0766: // special case. See also resolveThisFieldReference() special handling
0767: // for BlockNameSpace case. They currently work via the direct name
0768: // e.g. methodName().
0769:
0770: String prefix = Name.prefix(value);
0771:
0772: // Superclass method invocation? (e.g. super.foo())
0773: if (prefix.equals("super") && Name.countParts(value) == 2) {
0774: // Allow getThis() to work through block namespaces first
0775: This ths = namespace.getThis(interpreter);
0776: NameSpace this NameSpace = ths.getNameSpace();
0777: NameSpace classNameSpace = getClassNameSpace(this NameSpace);
0778: if (classNameSpace != null) {
0779: Object instance = classNameSpace.getClassInstance();
0780: return ClassGenerator.getClassGenerator()
0781: .invokeSuperclassMethod(bcm, instance,
0782: methodName, args);
0783: }
0784: }
0785:
0786: // Find target object or class identifier
0787: Name targetName = namespace.getNameResolver(prefix);
0788: Object obj = targetName.toObject(callstack, interpreter);
0789:
0790: if (obj == Primitive.VOID)
0791: throw new UtilEvalError("Attempt to resolve method: "
0792: + methodName
0793: + "() on undefined variable or class name: "
0794: + targetName);
0795:
0796: // if we've got an object, resolve the method
0797: if (!(obj instanceof ClassIdentifier)) {
0798:
0799: if (obj instanceof Primitive) {
0800:
0801: if (obj == Primitive.NULL)
0802: throw new UtilTargetError(new NullPointerException(
0803: "Null Pointer in Method Invocation"));
0804:
0805: // some other primitive
0806: // should avoid calling methods on primitive, as we do
0807: // in Name (can't treat primitive like an object message)
0808: // but the hole is useful right now.
0809: if (Interpreter.DEBUG)
0810: interpreter
0811: .debug("Attempt to access method on primitive..."
0812: + " allowing bsh.Primitive to peek through for debugging");
0813: }
0814:
0815: // found an object and it's not an undefined variable
0816: return Reflect.invokeObjectMethod(obj, methodName, args,
0817: interpreter, callstack, callerInfo);
0818: }
0819:
0820: // It's a class
0821:
0822: // try static method
0823: if (Interpreter.DEBUG)
0824: Interpreter.debug("invokeMethod: trying static - "
0825: + targetName);
0826:
0827: Class clas = ((ClassIdentifier) obj).getTargetClass();
0828:
0829: // cache the fact that this is a static method invocation on this class
0830: classOfStaticMethod = clas;
0831:
0832: if (clas != null)
0833: return Reflect.invokeStaticMethod(bcm, clas, methodName,
0834: args);
0835:
0836: // return null; ???
0837: throw new UtilEvalError("invokeMethod: unknown target: "
0838: + targetName);
0839: }
0840:
0841: /**
0842: Invoke a locally declared method or a bsh command.
0843: If the method is not already declared in the namespace then try
0844: to load it as a resource from the imported command path (e.g.
0845: /bsh/commands)
0846: */
0847: /*
0848: Note: the bsh command code should probably not be here... we need to
0849: scope it by the namespace that imported the command... so it probably
0850: needs to be integrated into NameSpace.
0851: */
0852: private Object invokeLocalMethod(Interpreter interpreter,
0853: Object[] args, CallStack callstack, SimpleNode callerInfo)
0854: throws EvalError/*, ReflectError, InvocationTargetException*/
0855: {
0856: if (Interpreter.DEBUG)
0857: Interpreter.debug("invokeLocalMethod: " + value);
0858: if (interpreter == null)
0859: throw new InterpreterError(
0860: "invokeLocalMethod: interpreter = null");
0861:
0862: String commandName = value;
0863: Class[] argTypes = Types.getTypes(args);
0864:
0865: // Check for existing method
0866: BshMethod meth = null;
0867: try {
0868: meth = namespace.getMethod(commandName, argTypes);
0869: } catch (UtilEvalError e) {
0870: throw e.toEvalError("Local method invocation", callerInfo,
0871: callstack);
0872: }
0873:
0874: // If defined, invoke it
0875: if (meth != null)
0876: return meth
0877: .invoke(args, interpreter, callstack, callerInfo);
0878:
0879: BshClassManager bcm = interpreter.getClassManager();
0880:
0881: // Look for a BeanShell command
0882:
0883: Object commandObject;
0884: try {
0885: commandObject = namespace.getCommand(commandName, argTypes,
0886: interpreter);
0887: } catch (UtilEvalError e) {
0888: throw e.toEvalError("Error loading command: ", callerInfo,
0889: callstack);
0890: }
0891:
0892: // should try to print usage here if nothing found
0893: if (commandObject == null) {
0894: // Look for a default invoke() handler method in the namespace
0895: // Note: this code duplicates that in This.java... should it?
0896: // Call on 'This' can never be a command
0897: BshMethod invokeMethod = null;
0898: try {
0899: invokeMethod = namespace.getMethod("invoke",
0900: new Class[] { null, null });
0901: } catch (UtilEvalError e) {
0902: throw e.toEvalError("Local method invocation",
0903: callerInfo, callstack);
0904: }
0905:
0906: if (invokeMethod != null)
0907: return invokeMethod.invoke(new Object[] { commandName,
0908: args }, interpreter, callstack, callerInfo);
0909:
0910: throw new EvalError("Command not found: "
0911: + StringUtil.methodString(commandName, argTypes),
0912: callerInfo, callstack);
0913: }
0914:
0915: if (commandObject instanceof BshMethod)
0916: return ((BshMethod) commandObject).invoke(args,
0917: interpreter, callstack, callerInfo);
0918:
0919: if (commandObject instanceof Class)
0920: try {
0921: return Reflect.invokeCompiledCommand(
0922: ((Class) commandObject), args, interpreter,
0923: callstack);
0924: } catch (UtilEvalError e) {
0925: throw e.toEvalError(
0926: "Error invoking compiled command: ",
0927: callerInfo, callstack);
0928: }
0929:
0930: throw new InterpreterError("invalid command type");
0931: }
0932:
0933: /*
0934: private String getHelp( String name )
0935: throws UtilEvalError
0936: {
0937: try {
0938: // should check for null namespace here
0939: return get( "bsh.help."+name, null/interpreter/ );
0940: } catch ( Exception e ) {
0941: return "usage: "+name;
0942: }
0943: }
0944:
0945: private String getHelp( Class commandClass )
0946: throws UtilEvalError
0947: {
0948: try {
0949: return (String)Reflect.invokeStaticMethod(
0950: null/bcm/, commandClass, "usage", null );
0951: } catch( Exception e )
0952: return "usage: "+name;
0953: }
0954: }
0955: */
0956:
0957: // Static methods that operate on compound ('.' separated) names
0958: // I guess we could move these to StringUtil someday
0959: public static boolean isCompound(String value) {
0960: return value.indexOf('.') != -1;
0961: //return countParts(value) > 1;
0962: }
0963:
0964: static int countParts(String value) {
0965: if (value == null)
0966: return 0;
0967:
0968: int count = 0;
0969: int index = -1;
0970: while ((index = value.indexOf('.', index + 1)) != -1)
0971: count++;
0972: return count + 1;
0973: }
0974:
0975: static String prefix(String value) {
0976: if (!isCompound(value))
0977: return null;
0978:
0979: return prefix(value, countParts(value) - 1);
0980: }
0981:
0982: static String prefix(String value, int parts) {
0983: if (parts < 1)
0984: return null;
0985:
0986: int count = 0;
0987: int index = -1;
0988:
0989: while (((index = value.indexOf('.', index + 1)) != -1)
0990: && (++count < parts)) {
0991: ;
0992: }
0993:
0994: return (index == -1) ? value : value.substring(0, index);
0995: }
0996:
0997: static String suffix(String name) {
0998: if (!isCompound(name))
0999: return null;
1000:
1001: return suffix(name, countParts(name) - 1);
1002: }
1003:
1004: public static String suffix(String value, int parts) {
1005: if (parts < 1)
1006: return null;
1007:
1008: int count = 0;
1009: int index = value.length() + 1;
1010:
1011: while (((index = value.lastIndexOf('.', index - 1)) != -1)
1012: && (++count < parts))
1013: ;
1014:
1015: return (index == -1) ? value : value.substring(index + 1);
1016: }
1017:
1018: // end compound name routines
1019:
1020: public String toString() {
1021: return value;
1022: }
1023:
1024: }
|