KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > bsh > BshMethod


1 /*****************************************************************************
2  * *
3  * This file is part of the BeanShell Java Scripting distribution. *
4  * Documentation and updates may be found at http://www.beanshell.org/ *
5  * *
6  * Sun Public License Notice: *
7  * *
8  * The contents of this file are subject to the Sun Public License Version *
9  * 1.0 (the "License"); you may not use this file except in compliance with *
10  * the License. A copy of the License is available at http://www.sun.com *
11  * *
12  * The Original Code is BeanShell. The Initial Developer of the Original *
13  * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright *
14  * (C) 2000. All Rights Reserved. *
15  * *
16  * GNU Public License Notice: *
17  * *
18  * Alternatively, the contents of this file may be used under the terms of *
19  * the GNU Lesser General Public License (the "LGPL"), in which case the *
20  * provisions of LGPL are applicable instead of those above. If you wish to *
21  * allow use of your version of this file only under the terms of the LGPL *
22  * and not to allow others to use your version of this file under the SPL, *
23  * indicate your decision by deleting the provisions above and replace *
24  * them with the notice and other provisions required by the LGPL. If you *
25  * do not delete the provisions above, a recipient may use your version of *
26  * this file under either the SPL or the LGPL. *
27  * *
28  * Patrick Niemeyer (pat@pat.net) *
29  * Author of Learning Java, O'Reilly & Associates *
30  * http://www.pat.net/~pat/ *
31  * *
32  *****************************************************************************/

33
34 package bsh;
35
36 import java.lang.reflect.Method JavaDoc;
37 import java.lang.reflect.InvocationTargetException JavaDoc;
38
39 /**
40     This represents an instance of a bsh method declaration in a particular
41     namespace. This is a thin wrapper around the BSHMethodDeclaration
42     with a pointer to the declaring namespace.
43     <p>
44
45     When a method is located in a subordinate namespace or invoked from an
46     arbitrary namespace it must nontheless execute with its 'super' as the
47     context in which it was declared.
48     <p/>
49 */

50 /*
51     Note: this method incorrectly caches the method structure. It needs to
52     be cleared when the classloader changes.
53 */

54 public class BshMethod
55     implements java.io.Serializable JavaDoc
56 {
57     /*
58         This is the namespace in which the method is set.
59         It is a back-reference for the node, which needs to execute under this
60         namespace. It is not necessary to declare this transient, because
61         we can only be saved as part of our namespace anyway... (currently).
62     */

63     NameSpace declaringNameSpace;
64
65     // Begin Method components
66

67     Modifiers modifiers;
68     private String JavaDoc name;
69     private Class JavaDoc creturnType;
70
71     // Arguments
72
private String JavaDoc [] paramNames;
73     private int numArgs;
74     private Class JavaDoc [] cparamTypes;
75
76     // Scripted method body
77
BSHBlock methodBody;
78
79     // Java Method, for a BshObject that delegates to a real Java method
80
private Method JavaDoc javaMethod;
81     private Object JavaDoc javaObject;
82
83     // End method components
84

85     BshMethod(
86         BSHMethodDeclaration method,
87         NameSpace declaringNameSpace, Modifiers modifiers )
88     {
89         this( method.name, method.returnType, method.paramsNode.getParamNames(),
90             method.paramsNode.paramTypes, method.blockNode, declaringNameSpace,
91             modifiers );
92     }
93
94     BshMethod(
95         String JavaDoc name, Class JavaDoc returnType, String JavaDoc [] paramNames,
96         Class JavaDoc [] paramTypes, BSHBlock methodBody,
97         NameSpace declaringNameSpace, Modifiers modifiers
98     ) {
99         this.name = name;
100         this.creturnType = returnType;
101         this.paramNames = paramNames;
102         if ( paramNames != null )
103             this.numArgs = paramNames.length;
104         this.cparamTypes = paramTypes;
105         this.methodBody = methodBody;
106         this.declaringNameSpace = declaringNameSpace;
107         this.modifiers = modifiers;
108     }
109
110     /*
111         Create a BshMethod that delegates to a real Java method upon invocation.
112         This is used to represent imported object methods.
113     */

114     BshMethod( Method JavaDoc method, Object JavaDoc object )
115     {
116         this( method.getName(), method.getReturnType(), null/*paramNames*/,
117             method.getParameterTypes(), null/*method.block*/,
118             null/*declaringNameSpace*/, null/*modifiers*/ );
119
120         this.javaMethod = method;
121         this.javaObject = object;
122     }
123
124
125     /**
126         Get the argument types of this method.
127         loosely typed (untyped) arguments will be represented by null argument
128         types.
129     */

130     /*
131         Note: bshmethod needs to re-evaluate arg types here
132         This is broken.
133     */

134     public Class JavaDoc [] getParameterTypes() { return cparamTypes; }
135     public String JavaDoc [] getParameterNames() { return paramNames; }
136
137     /**
138         Get the return type of the method.
139         @return Returns null for a loosely typed return value,
140             Void.TYPE for a void return type, or the Class of the type.
141     */

142     /*
143         Note: bshmethod needs to re-evaluate the method return type here.
144         This is broken.
145     */

146     public Class JavaDoc getReturnType() { return creturnType; }
147
148     public Modifiers getModifiers() { return modifiers; }
149
150     public String JavaDoc getName() { return name; }
151
152     /**
153         Invoke the declared method with the specified arguments and interpreter
154         reference. This is the simplest form of invoke() for BshMethod
155         intended to be used in reflective style access to bsh scripts.
156     */

157     public Object JavaDoc invoke(
158         Object JavaDoc[] argValues, Interpreter interpreter )
159         throws EvalError
160     {
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 JavaDoc invoke(
182         Object JavaDoc[] argValues, Interpreter interpreter, CallStack callstack,
183             SimpleNode callerInfo )
184         throws EvalError
185     {
186         return invoke( argValues, interpreter, callstack, callerInfo, false );
187     }
188
189     /**
190         Invoke the bsh method with the specified args, interpreter ref,
191         and callstack.
192         callerInfo is the node representing the method invocation
193         It is used primarily for debugging in order to provide access to the
194         text of the construct that invoked the method through the namespace.
195         @param callerInfo is the BeanShell AST node representing the method
196             invocation. It is used to print the line number and text of
197             errors in EvalError exceptions. If the node is null here error
198             messages may not be able to point to the precise location and text
199             of the error.
200         @param callstack is the callstack. If callstack is null a new one
201             will be created with the declaring namespace of the method on top
202             of the stack (i.e. it will look for purposes of the method
203             invocation like the method call occurred in the declaring
204             (enclosing) namespace in which the method is defined).
205         @param overrideNameSpace
206             When true the method is executed in the namespace on the top of the
207             stack instead of creating its own local namespace. This allows it
208             to be used in constructors.
209     */

210     Object JavaDoc invoke(
211         Object JavaDoc[] argValues, Interpreter interpreter, CallStack callstack,
212             SimpleNode callerInfo, boolean overrideNameSpace )
213         throws EvalError
214     {
215         if ( argValues != null )
216             for (int i=0; i<argValues.length; i++)
217                 if ( argValues[i] == null )
218                     throw new Error JavaDoc("HERE!");
219
220         if ( javaMethod != null )
221             try {
222                 return Reflect.invokeMethod(
223                     javaMethod, javaObject, argValues );
224             } catch ( ReflectError e ) {
225                 throw new EvalError(
226                     "Error invoking Java method: "+e, callerInfo, callstack );
227             } catch ( InvocationTargetException JavaDoc e2 ) {
228                 throw new TargetError(
229                     "Exception invoking imported object method.",
230                     e2, callerInfo, callstack, true/*isNative*/ );
231             }
232
233         // is this a syncrhonized method?
234
if ( modifiers != null && modifiers.hasModifier("synchronized") )
235         {
236             // The lock is our declaring namespace's This reference
237
// (the method's 'super'). Or in the case of a class it's the
238
// class instance.
239
Object JavaDoc lock;
240             if ( declaringNameSpace.isClass )
241             {
242                 try {
243                     lock = declaringNameSpace.getClassInstance();
244                 } catch ( UtilEvalError e ) {
245                     throw new InterpreterError(
246                         "Can't get class instance for synchronized method.");
247                 }
248             } else
249                 lock = declaringNameSpace.getThis(interpreter); // ???
250

251             synchronized( lock )
252             {
253                 return invokeImpl(
254                     argValues, interpreter, callstack,
255                     callerInfo, overrideNameSpace );
256             }
257         } else
258             return invokeImpl( argValues, interpreter, callstack, callerInfo,
259                 overrideNameSpace );
260     }
261
262     private Object JavaDoc invokeImpl(
263         Object JavaDoc[] argValues, Interpreter interpreter, CallStack callstack,
264             SimpleNode callerInfo, boolean overrideNameSpace )
265         throws EvalError
266     {
267         Class JavaDoc returnType = getReturnType();
268         Class JavaDoc [] paramTypes = getParameterTypes();
269
270         // If null callstack
271
if ( callstack == null )
272             callstack = new CallStack( declaringNameSpace );
273
274         if ( argValues == null )
275             argValues = new Object JavaDoc [] { };
276
277         // Cardinality (number of args) mismatch
278
if ( argValues.length != numArgs )
279         {
280         /*
281             // look for help string
282             try {
283                 // should check for null namespace here
284                 String help =
285                     (String)declaringNameSpace.get(
286                     "bsh.help."+name, interpreter );
287
288                 interpreter.println(help);
289                 return Primitive.VOID;
290             } catch ( Exception e ) {
291                 throw eval error
292             }
293         */

294             throw new EvalError(
295                 "Wrong number of arguments for local method: "
296                 + name, callerInfo, callstack );
297         }
298
299         // Make the local namespace for the method invocation
300
NameSpace localNameSpace;
301         if ( overrideNameSpace )
302             localNameSpace = callstack.top();
303         else
304         {
305             localNameSpace = new NameSpace( declaringNameSpace, name );
306             localNameSpace.isMethod = true;
307         }
308         // should we do this for both cases above?
309
localNameSpace.setNode( callerInfo );
310
311         // set the method parameters in the local namespace
312
for(int i=0; i<numArgs; i++)
313         {
314             // Set typed variable
315
if ( paramTypes[i] != null )
316             {
317                 try {
318                     argValues[i] =
319                         //Types.getAssignableForm( argValues[i], paramTypes[i] );
320
Types.castObject( argValues[i], paramTypes[i], Types.ASSIGNMENT );
321                 }
322                 catch( UtilEvalError e) {
323                     throw new EvalError(
324                         "Invalid argument: "
325                         + "`"+paramNames[i]+"'" + " for method: "
326                         + name + " : " +
327                         e.getMessage(), callerInfo, callstack );
328                 }
329                 try {
330                     localNameSpace.setTypedVariable( paramNames[i],
331                         paramTypes[i], argValues[i], null/*modifiers*/);
332                 } catch ( UtilEvalError e2 ) {
333                     throw e2.toEvalError( "Typed method parameter assignment",
334                         callerInfo, callstack );
335                 }
336             }
337             // Set untyped variable
338
else // untyped param
339
{
340                 // getAssignable would catch this for typed param
341
if ( argValues[i] == Primitive.VOID)
342                     throw new EvalError(
343                         "Undefined variable or class name, parameter: " +
344                         paramNames[i] + " to method: "
345                         + name, callerInfo, callstack );
346                 else
347                     try {
348                         localNameSpace.setLocalVariable(
349                             paramNames[i], argValues[i],
350                             interpreter.getStrictJava() );
351                     } catch ( UtilEvalError e3 ) {
352                         throw e3.toEvalError( callerInfo, callstack );
353                     }
354             }
355         }
356
357         // Push the new namespace on the call stack
358
if ( !overrideNameSpace )
359             callstack.push( localNameSpace );
360
361         // Invoke the block, overriding namespace with localNameSpace
362
Object JavaDoc ret = methodBody.eval(
363             callstack, interpreter, true/*override*/ );
364
365         // save the callstack including the called method, just for error mess
366
CallStack returnStack = callstack.copy();
367
368         // Get back to caller namespace
369
if ( !overrideNameSpace )
370             callstack.pop();
371
372         ReturnControl retControl = null;
373         if ( ret instanceof ReturnControl )
374         {
375             retControl = (ReturnControl)ret;
376
377             // Method body can only use 'return' statment type return control.
378
if ( retControl.kind == retControl.RETURN )
379                 ret = ((ReturnControl)ret).value;
380             else
381                 // retControl.returnPoint is the Node of the return statement
382
throw new EvalError("'continue' or 'break' in method body",
383                     retControl.returnPoint, returnStack );
384
385             // Check for explicit return of value from void method type.
386
// retControl.returnPoint is the Node of the return statement
387
if ( returnType == Void.TYPE && ret != Primitive.VOID )
388                 throw new EvalError( "Cannot return value from void method",
389                 retControl.returnPoint, returnStack);
390         }
391
392         if ( returnType != null )
393         {
394             // If return type void, return void as the value.
395
if ( returnType == Void.TYPE )
396                 return Primitive.VOID;
397
398             // return type is a class
399
try {
400                 ret =
401                     // Types.getAssignableForm( ret, (Class)returnType );
402
Types.castObject( ret, returnType, Types.ASSIGNMENT );
403             } catch( UtilEvalError e )
404             {
405                 // Point to return statement point if we had one.
406
// (else it was implicit return? What's the case here?)
407
SimpleNode node = callerInfo;
408                 if ( retControl != null )
409                     node = retControl.returnPoint;
410                 throw e.toEvalError(
411                     "Incorrect type returned from method: "
412                     + name + e.getMessage(), node, callstack );
413             }
414         }
415
416         return ret;
417     }
418
419     public boolean hasModifier( String JavaDoc name ) {
420         return modifiers != null && modifiers.hasModifier(name);
421     }
422
423     public String JavaDoc toString() {
424         return "Scripted Method: "
425             + StringUtil.methodString( name, getParameterTypes() );
426     }
427
428 }
429
Popular Tags