001: /*****************************************************************************
002: * *
003: * This file is part of the BeanShell Java Scripting distribution. *
004: * Documentation and updates may be found at http://www.beanshell.org/ *
005: * *
006: * Sun Public License Notice: *
007: * *
008: * The contents of this file are subject to the Sun Public License Version *
009: * 1.0 (the "License"); you may not use this file except in compliance with *
010: * the License. A copy of the License is available at http://www.sun.com *
011: * *
012: * The Original Code is BeanShell. The Initial Developer of the Original *
013: * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright *
014: * (C) 2000. All Rights Reserved. *
015: * *
016: * GNU Public License Notice: *
017: * *
018: * Alternatively, the contents of this file may be used under the terms of *
019: * the GNU Lesser General Public License (the "LGPL"), in which case the *
020: * provisions of LGPL are applicable instead of those above. If you wish to *
021: * allow use of your version of this file only under the terms of the LGPL *
022: * and not to allow others to use your version of this file under the SPL, *
023: * indicate your decision by deleting the provisions above and replace *
024: * them with the notice and other provisions required by the LGPL. If you *
025: * do not delete the provisions above, a recipient may use your version of *
026: * this file under either the SPL or the LGPL. *
027: * *
028: * Patrick Niemeyer (pat@pat.net) *
029: * Author of Learning Java, O'Reilly & Associates *
030: * http://www.pat.net/~pat/ *
031: * *
032: *****************************************************************************/package bsh;
033:
034: import java.lang.reflect.Method;
035: import java.lang.reflect.InvocationTargetException;
036:
037: /**
038: This represents an instance of a bsh method declaration in a particular
039: namespace. This is a thin wrapper around the BSHMethodDeclaration
040: with a pointer to the declaring namespace.
041: <p>
042:
043: When a method is located in a subordinate namespace or invoked from an
044: arbitrary namespace it must nontheless execute with its 'super' as the
045: context in which it was declared.
046: <p/>
047: */
048: /*
049: Note: this method incorrectly caches the method structure. It needs to
050: be cleared when the classloader changes.
051: */
052: public class BshMethod implements java.io.Serializable {
053: /*
054: This is the namespace in which the method is set.
055: It is a back-reference for the node, which needs to execute under this
056: namespace. It is not necessary to declare this transient, because
057: we can only be saved as part of our namespace anyway... (currently).
058: */
059: NameSpace declaringNameSpace;
060:
061: // Begin Method components
062:
063: Modifiers modifiers;
064: private String name;
065: private Class creturnType;
066:
067: // Arguments
068: private String[] paramNames;
069: private int numArgs;
070: private Class[] cparamTypes;
071:
072: // Scripted method body
073: BSHBlock methodBody;
074:
075: // Java Method, for a BshObject that delegates to a real Java method
076: private Method javaMethod;
077: private Object javaObject;
078:
079: // End method components
080:
081: BshMethod(BSHMethodDeclaration method,
082: NameSpace declaringNameSpace, Modifiers modifiers) {
083: this (method.name, method.returnType, method.paramsNode
084: .getParamNames(), method.paramsNode.paramTypes,
085: method.blockNode, declaringNameSpace, modifiers);
086: }
087:
088: BshMethod(String name, Class returnType, String[] paramNames,
089: Class[] paramTypes, BSHBlock methodBody,
090: NameSpace declaringNameSpace, Modifiers modifiers) {
091: this .name = name;
092: this .creturnType = returnType;
093: this .paramNames = paramNames;
094: if (paramNames != null)
095: this .numArgs = paramNames.length;
096: this .cparamTypes = paramTypes;
097: this .methodBody = methodBody;
098: this .declaringNameSpace = declaringNameSpace;
099: this .modifiers = modifiers;
100: }
101:
102: /*
103: Create a BshMethod that delegates to a real Java method upon invocation.
104: This is used to represent imported object methods.
105: */
106: BshMethod(Method method, Object object) {
107: this (method.getName(), method.getReturnType(),
108: null/*paramNames*/, method.getParameterTypes(),
109: null/*method.block*/, null/*declaringNameSpace*/,
110: null/*modifiers*/);
111:
112: this .javaMethod = method;
113: this .javaObject = object;
114: }
115:
116: /**
117: Get the argument types of this method.
118: loosely typed (untyped) arguments will be represented by null argument
119: types.
120: */
121: /*
122: Note: bshmethod needs to re-evaluate arg types here
123: This is broken.
124: */
125: public Class[] getParameterTypes() {
126: return cparamTypes;
127: }
128:
129: public String[] getParameterNames() {
130: return paramNames;
131: }
132:
133: /**
134: Get the return type of the method.
135: @return Returns null for a loosely typed return value,
136: Void.TYPE for a void return type, or the Class of the type.
137: */
138: /*
139: Note: bshmethod needs to re-evaluate the method return type here.
140: This is broken.
141: */
142: public Class getReturnType() {
143: return creturnType;
144: }
145:
146: public Modifiers getModifiers() {
147: return modifiers;
148: }
149:
150: public String getName() {
151: return name;
152: }
153:
154: /**
155: Invoke the declared method with the specified arguments and interpreter
156: reference. This is the simplest form of invoke() for BshMethod
157: intended to be used in reflective style access to bsh scripts.
158: */
159: public Object invoke(Object[] argValues, Interpreter interpreter)
160: throws EvalError {
161: return invoke(argValues, interpreter, null, null, false);
162: }
163:
164: /**
165: Invoke the bsh method with the specified args, interpreter ref,
166: and callstack.
167: callerInfo is the node representing the method invocation
168: It is used primarily for debugging in order to provide access to the
169: text of the construct that invoked the method through the namespace.
170: @param callerInfo is the BeanShell AST node representing the method
171: invocation. It is used to print the line number and text of
172: errors in EvalError exceptions. If the node is null here error
173: messages may not be able to point to the precise location and text
174: of the error.
175: @param callstack is the callstack. If callstack is null a new one
176: will be created with the declaring namespace of the method on top
177: of the stack (i.e. it will look for purposes of the method
178: invocation like the method call occurred in the declaring
179: (enclosing) namespace in which the method is defined).
180: */
181: public Object invoke(Object[] argValues, Interpreter interpreter,
182: CallStack callstack, SimpleNode callerInfo)
183: throws EvalError {
184: return invoke(argValues, interpreter, callstack, callerInfo,
185: false);
186: }
187:
188: /**
189: Invoke the bsh method with the specified args, interpreter ref,
190: and callstack.
191: callerInfo is the node representing the method invocation
192: It is used primarily for debugging in order to provide access to the
193: text of the construct that invoked the method through the namespace.
194: @param callerInfo is the BeanShell AST node representing the method
195: invocation. It is used to print the line number and text of
196: errors in EvalError exceptions. If the node is null here error
197: messages may not be able to point to the precise location and text
198: of the error.
199: @param callstack is the callstack. If callstack is null a new one
200: will be created with the declaring namespace of the method on top
201: of the stack (i.e. it will look for purposes of the method
202: invocation like the method call occurred in the declaring
203: (enclosing) namespace in which the method is defined).
204: @param overrideNameSpace
205: When true the method is executed in the namespace on the top of the
206: stack instead of creating its own local namespace. This allows it
207: to be used in constructors.
208: */
209: Object invoke(Object[] argValues, Interpreter interpreter,
210: CallStack callstack, SimpleNode callerInfo,
211: boolean overrideNameSpace) throws EvalError {
212: if (argValues != null)
213: for (int i = 0; i < argValues.length; i++)
214: if (argValues[i] == null)
215: throw new Error("HERE!");
216:
217: if (javaMethod != null)
218: try {
219: return Reflect.invokeMethod(javaMethod, javaObject,
220: argValues);
221: } catch (ReflectError e) {
222: throw new EvalError("Error invoking Java method: " + e,
223: callerInfo, callstack);
224: } catch (InvocationTargetException e2) {
225: throw new TargetError(
226: "Exception invoking imported object method.",
227: e2, callerInfo, callstack, true/*isNative*/);
228: }
229:
230: // is this a syncrhonized method?
231: if (modifiers != null && modifiers.hasModifier("synchronized")) {
232: // The lock is our declaring namespace's This reference
233: // (the method's 'super'). Or in the case of a class it's the
234: // class instance.
235: Object lock;
236: if (declaringNameSpace.isClass) {
237: try {
238: lock = declaringNameSpace.getClassInstance();
239: } catch (UtilEvalError e) {
240: throw new InterpreterError(
241: "Can't get class instance for synchronized method.");
242: }
243: } else
244: lock = declaringNameSpace.getThis(interpreter); // ???
245:
246: synchronized (lock) {
247: return invokeImpl(argValues, interpreter, callstack,
248: callerInfo, overrideNameSpace);
249: }
250: } else
251: return invokeImpl(argValues, interpreter, callstack,
252: callerInfo, overrideNameSpace);
253: }
254:
255: private Object invokeImpl(Object[] argValues,
256: Interpreter interpreter, CallStack callstack,
257: SimpleNode callerInfo, boolean overrideNameSpace)
258: throws EvalError {
259: Class returnType = getReturnType();
260: Class[] paramTypes = getParameterTypes();
261:
262: // If null callstack
263: if (callstack == null)
264: callstack = new CallStack(declaringNameSpace);
265:
266: if (argValues == null)
267: argValues = new Object[] {};
268:
269: // Cardinality (number of args) mismatch
270: if (argValues.length != numArgs) {
271: /*
272: // look for help string
273: try {
274: // should check for null namespace here
275: String help =
276: (String)declaringNameSpace.get(
277: "bsh.help."+name, interpreter );
278:
279: interpreter.println(help);
280: return Primitive.VOID;
281: } catch ( Exception e ) {
282: throw eval error
283: }
284: */
285: throw new EvalError(
286: "Wrong number of arguments for local method: "
287: + name, callerInfo, callstack);
288: }
289:
290: // Make the local namespace for the method invocation
291: NameSpace localNameSpace;
292: if (overrideNameSpace)
293: localNameSpace = callstack.top();
294: else {
295: localNameSpace = new NameSpace(declaringNameSpace, name);
296: localNameSpace.isMethod = true;
297: }
298: // should we do this for both cases above?
299: localNameSpace.setNode(callerInfo);
300:
301: // set the method parameters in the local namespace
302: for (int i = 0; i < numArgs; i++) {
303: // Set typed variable
304: if (paramTypes[i] != null) {
305: try {
306: argValues[i] =
307: //Types.getAssignableForm( argValues[i], paramTypes[i] );
308: Types.castObject(argValues[i], paramTypes[i],
309: Types.ASSIGNMENT);
310: } catch (UtilEvalError e) {
311: throw new EvalError("Invalid argument: " + "`"
312: + paramNames[i] + "'" + " for method: "
313: + name + " : " + e.getMessage(),
314: callerInfo, callstack);
315: }
316: try {
317: localNameSpace
318: .setTypedVariable(paramNames[i],
319: paramTypes[i], argValues[i], null/*modifiers*/);
320: } catch (UtilEvalError e2) {
321: throw e2.toEvalError(
322: "Typed method parameter assignment",
323: callerInfo, callstack);
324: }
325: }
326: // Set untyped variable
327: else // untyped param
328: {
329: // getAssignable would catch this for typed param
330: if (argValues[i] == Primitive.VOID)
331: throw new EvalError(
332: "Undefined variable or class name, parameter: "
333: + paramNames[i] + " to method: "
334: + name, callerInfo, callstack);
335: else
336: try {
337: localNameSpace.setLocalVariable(paramNames[i],
338: argValues[i], interpreter
339: .getStrictJava());
340: } catch (UtilEvalError e3) {
341: throw e3.toEvalError(callerInfo, callstack);
342: }
343: }
344: }
345:
346: // Push the new namespace on the call stack
347: if (!overrideNameSpace)
348: callstack.push(localNameSpace);
349:
350: // Invoke the block, overriding namespace with localNameSpace
351: Object ret = methodBody
352: .eval(callstack, interpreter, true/*override*/);
353:
354: // save the callstack including the called method, just for error mess
355: CallStack returnStack = callstack.copy();
356:
357: // Get back to caller namespace
358: if (!overrideNameSpace)
359: callstack.pop();
360:
361: ReturnControl retControl = null;
362: if (ret instanceof ReturnControl) {
363: retControl = (ReturnControl) ret;
364:
365: // Method body can only use 'return' statment type return control.
366: if (retControl.kind == retControl.RETURN)
367: ret = ((ReturnControl) ret).value;
368: else
369: // retControl.returnPoint is the Node of the return statement
370: throw new EvalError(
371: "'continue' or 'break' in method body",
372: retControl.returnPoint, returnStack);
373:
374: // Check for explicit return of value from void method type.
375: // retControl.returnPoint is the Node of the return statement
376: if (returnType == Void.TYPE && ret != Primitive.VOID)
377: throw new EvalError(
378: "Cannot return value from void method",
379: retControl.returnPoint, returnStack);
380: }
381:
382: if (returnType != null) {
383: // If return type void, return void as the value.
384: if (returnType == Void.TYPE)
385: return Primitive.VOID;
386:
387: // return type is a class
388: try {
389: ret =
390: // Types.getAssignableForm( ret, (Class)returnType );
391: Types.castObject(ret, returnType, Types.ASSIGNMENT);
392: } catch (UtilEvalError e) {
393: // Point to return statement point if we had one.
394: // (else it was implicit return? What's the case here?)
395: SimpleNode node = callerInfo;
396: if (retControl != null)
397: node = retControl.returnPoint;
398: throw e.toEvalError(
399: "Incorrect type returned from method: " + name
400: + e.getMessage(), node, callstack);
401: }
402: }
403:
404: return ret;
405: }
406:
407: public boolean hasModifier(String name) {
408: return modifiers != null && modifiers.hasModifier(name);
409: }
410:
411: public String toString() {
412: return "Scripted Method: "
413: + StringUtil.methodString(name, getParameterTypes());
414: }
415:
416: }
|