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