001: /*****************************************************************************
002: * *
003: * This file is part of the BeanShell Java Scripting distribution. *
004: * Documentation and updates may be found at http://www.beanshell.org/ *
005: * *
006: * Sun Public License Notice: *
007: * *
008: * The contents of this file are subject to the Sun Public License Version *
009: * 1.0 (the "License"); you may not use this file except in compliance with *
010: * the License. A copy of the License is available at http://www.sun.com *
011: * *
012: * The Original Code is BeanShell. The Initial Developer of the Original *
013: * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright *
014: * (C) 2000. All Rights Reserved. *
015: * *
016: * GNU Public License Notice: *
017: * *
018: * Alternatively, the contents of this file may be used under the terms of *
019: * the GNU Lesser General Public License (the "LGPL"), in which case the *
020: * provisions of LGPL are applicable instead of those above. If you wish to *
021: * allow use of your version of this file only under the terms of the LGPL *
022: * and not to allow others to use your version of this file under the SPL, *
023: * indicate your decision by deleting the provisions above and replace *
024: * them with the notice and other provisions required by the LGPL. If you *
025: * do not delete the provisions above, a recipient may use your version of *
026: * this file under either the SPL or the LGPL. *
027: * *
028: * Patrick Niemeyer (pat@pat.net) *
029: * Author of Learning Java, O'Reilly & Associates *
030: * http://www.pat.net/~pat/ *
031: * *
032: *****************************************************************************/package bsh;
033:
034: import java.util.Hashtable;
035: import java.lang.reflect.Array;
036: import java.lang.reflect.InvocationTargetException;
037:
038: class BSHPrimarySuffix extends SimpleNode {
039: public static final int CLASS = 0, INDEX = 1, NAME = 2,
040: PROPERTY = 3;
041:
042: public int operation;
043: Object index;
044: public String field;
045:
046: BSHPrimarySuffix(int id) {
047: super (id);
048: }
049:
050: /*
051: Perform a suffix operation on the given object and return the
052: new value.
053: <p>
054:
055: obj will be a Node when suffix evaluation begins, allowing us to
056: interpret it contextually. (e.g. for .class) Thereafter it will be
057: an value object or LHS (as determined by toLHS).
058: <p>
059:
060: We must handle the toLHS case at each point here.
061: <p>
062: */
063: public Object doSuffix(Object obj, boolean toLHS,
064: CallStack callstack, Interpreter interpreter)
065: throws EvalError {
066: // Handle ".class" suffix operation
067: // Prefix must be a BSHType
068: if (operation == CLASS)
069: if (obj instanceof BSHType) {
070: if (toLHS)
071: throw new EvalError("Can't assign .class", this ,
072: callstack);
073: NameSpace namespace = callstack.top();
074: return ((BSHType) obj).getType(callstack, interpreter);
075: } else
076: throw new EvalError(
077: "Attempt to use .class suffix on non class.",
078: this , callstack);
079:
080: /*
081: Evaluate our prefix if it needs evaluating first.
082: If this is the first evaluation our prefix mayb be a Node
083: (directly from the PrimaryPrefix) - eval() it to an object.
084: If it's an LHS, resolve to a value.
085:
086: Note: The ambiguous name construct is now necessary where the node
087: may be an ambiguous name. If this becomes common we might want to
088: make a static method nodeToObject() or something. The point is
089: that we can't just eval() - we need to direct the evaluation to
090: the context sensitive type of result; namely object, class, etc.
091: */
092: if (obj instanceof SimpleNode)
093: if (obj instanceof BSHAmbiguousName)
094: obj = ((BSHAmbiguousName) obj).toObject(callstack,
095: interpreter);
096: else
097: obj = ((SimpleNode) obj).eval(callstack, interpreter);
098: else if (obj instanceof LHS)
099: try {
100: obj = ((LHS) obj).getValue();
101: } catch (UtilEvalError e) {
102: throw e.toEvalError(this , callstack);
103: }
104:
105: try {
106: switch (operation) {
107: case INDEX:
108: return doIndex(obj, toLHS, callstack, interpreter);
109:
110: case NAME:
111: return doName(obj, toLHS, callstack, interpreter);
112:
113: case PROPERTY:
114: return doProperty(toLHS, obj, callstack, interpreter);
115:
116: default:
117: throw new InterpreterError("Unknown suffix type");
118: }
119: } catch (ReflectError e) {
120: throw new EvalError("reflection error: " + e, this ,
121: callstack);
122: } catch (InvocationTargetException e) {
123: throw new TargetError("target exception", e
124: .getTargetException(), this , callstack, true);
125: }
126: }
127:
128: /*
129: Field access, .length on array, or a method invocation
130: Must handle toLHS case for each.
131: */
132: private Object doName(Object obj, boolean toLHS,
133: CallStack callstack, Interpreter interpreter)
134: throws EvalError, ReflectError, InvocationTargetException {
135: try {
136: // .length on array
137: if (field.equals("length") && obj.getClass().isArray())
138: if (toLHS)
139: throw new EvalError("Can't assign array length",
140: this , callstack);
141: else
142: return new Primitive(Array.getLength(obj));
143:
144: // field access
145: if (jjtGetNumChildren() == 0)
146: if (toLHS)
147: return Reflect.getLHSObjectField(obj, field);
148: else
149: return Reflect.getObjectFieldValue(obj, field);
150:
151: // Method invocation
152: // (LHS or non LHS evaluation can both encounter method calls)
153: Object[] oa = ((BSHArguments) jjtGetChild(0)).getArguments(
154: callstack, interpreter);
155:
156: // TODO:
157: // Note: this try/catch block is copied from BSHMethodInvocation
158: // we need to factor out this common functionality and make sure
159: // we handle all cases ... (e.g. property style access, etc.)
160: // maybe move this to Reflect ?
161: try {
162: return Reflect.invokeObjectMethod(obj, field, oa,
163: interpreter, callstack, this );
164: } catch (ReflectError e) {
165: throw new EvalError("Error in method invocation: "
166: + e.getMessage(), this , callstack);
167: } catch (InvocationTargetException e) {
168: String msg = "Method Invocation " + field;
169: Throwable te = e.getTargetException();
170:
171: /*
172: Try to squeltch the native code stack trace if the exception
173: was caused by a reflective call back into the bsh interpreter
174: (e.g. eval() or source()
175: */
176: boolean isNative = true;
177: if (te instanceof EvalError)
178: if (te instanceof TargetError)
179: isNative = ((TargetError) te).inNativeCode();
180: else
181: isNative = false;
182:
183: throw new TargetError(msg, te, this , callstack,
184: isNative);
185: }
186:
187: } catch (UtilEvalError e) {
188: throw e.toEvalError(this , callstack);
189: }
190: }
191:
192: /**
193: */
194: static int getIndexAux(Object obj, CallStack callstack,
195: Interpreter interpreter, SimpleNode callerInfo)
196: throws EvalError {
197: if (!obj.getClass().isArray())
198: throw new EvalError("Not an array", callerInfo, callstack);
199:
200: int index;
201: try {
202: Object indexVal = ((SimpleNode) callerInfo.jjtGetChild(0))
203: .eval(callstack, interpreter);
204: if (!(indexVal instanceof Primitive))
205: indexVal = Types.castObject(indexVal, Integer.TYPE,
206: Types.ASSIGNMENT);
207: index = ((Primitive) indexVal).intValue();
208: } catch (UtilEvalError e) {
209: Interpreter.debug("doIndex: " + e);
210: throw e.toEvalError(
211: "Arrays may only be indexed by integer types.",
212: callerInfo, callstack);
213: }
214:
215: return index;
216: }
217:
218: /**
219: array index.
220: Must handle toLHS case.
221: */
222: private Object doIndex(Object obj, boolean toLHS,
223: CallStack callstack, Interpreter interpreter)
224: throws EvalError, ReflectError {
225: int index = getIndexAux(obj, callstack, interpreter, this );
226: if (toLHS)
227: return new LHS(obj, index);
228: else
229: try {
230: return Reflect.getIndex(obj, index);
231: } catch (UtilEvalError e) {
232: throw e.toEvalError(this , callstack);
233: }
234: }
235:
236: /**
237: Property access.
238: Must handle toLHS case.
239: */
240: private Object doProperty(boolean toLHS, Object obj,
241: CallStack callstack, Interpreter interpreter)
242: throws EvalError {
243: if (obj == Primitive.VOID)
244: throw new EvalError(
245: "Attempt to access property on undefined variable or class name",
246: this , callstack);
247:
248: if (obj instanceof Primitive)
249: throw new EvalError(
250: "Attempt to access property on a primitive", this ,
251: callstack);
252:
253: Object value = ((SimpleNode) jjtGetChild(0)).eval(callstack,
254: interpreter);
255:
256: if (!(value instanceof String))
257: throw new EvalError(
258: "Property expression must be a String or identifier.",
259: this , callstack);
260:
261: if (toLHS)
262: return new LHS(obj, (String) value);
263:
264: // Property style access to Hashtable or Map
265: CollectionManager cm = CollectionManager.getCollectionManager();
266: if (cm.isMap(obj)) {
267: Object val = cm.getFromMap(obj, value/*key*/);
268: return (val == null ? val = Primitive.NULL : val);
269: }
270:
271: try {
272: return Reflect.getObjectProperty(obj, (String) value);
273: } catch (UtilEvalError e) {
274: throw e.toEvalError("Property: " + value, this , callstack);
275: } catch (ReflectError e) {
276: throw new EvalError("No such property: " + value, this,
277: callstack);
278: }
279: }
280: }
|