PageRenderTime 295ms CodeModel.GetById 266ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-3-pre5/bsh/BshMethod.java

#
Java | 413 lines | 223 code | 42 blank | 148 comment | 39 complexity | 7c91e3c673794b8e474abd781198753e MD5 | raw file
  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
 34package bsh;
 35
 36import java.lang.reflect.Method;
 37import java.lang.reflect.InvocationTargetException;
 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*/
 54public class BshMethod 
 55	implements java.io.Serializable 
 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 name;
 69	private Class creturnType;
 70
 71	// Arguments
 72	private String [] paramNames;
 73	private int numArgs;
 74	private Class [] cparamTypes;
 75
 76	// Scripted method body
 77	BSHBlock methodBody;
 78	// Java Method
 79	private Method javaMethod;
 80	private Object javaObject;
 81
 82	// End method components
 83
 84	BshMethod( 
 85		BSHMethodDeclaration method, 
 86		NameSpace declaringNameSpace, Modifiers modifiers ) 
 87	{
 88		this( method.name, method.returnType, method.paramsNode.getParamNames(),
 89			method.paramsNode.paramTypes, method.blockNode, declaringNameSpace,
 90			modifiers );
 91	}
 92
 93	BshMethod( 
 94		String name, Class returnType, String [] paramNames,
 95		Class [] paramTypes, BSHBlock methodBody, 
 96		NameSpace declaringNameSpace, Modifiers modifiers
 97	) {
 98		this.name = name;
 99		this.creturnType = returnType;
100		this.paramNames = paramNames;
101		if ( paramNames != null )
102			this.numArgs = paramNames.length;
103		this.cparamTypes = paramTypes;
104		this.methodBody = methodBody;
105		this.declaringNameSpace = declaringNameSpace;
106		this.modifiers = modifiers;
107	}
108
109	BshMethod( Method method, Object object )
110	{
111		this( method.getName(), method.getReturnType(), null/*paramNames*/,
112			method.getParameterTypes(), null/*method.block*/, 
113			null/*declaringNameSpace*/, null/*modifiers*/ );
114
115		this.javaMethod = method;
116		this.javaObject = object;
117	}
118
119
120	/**
121		Get the argument types of this method.
122		loosely typed (untyped) arguments will be represented by null argument
123		types.
124	*/
125	/*
126		Note: bshmethod needs to re-evaluate arg types here
127		This is broken.
128	*/
129	public Class [] getParameterTypes() { return cparamTypes; }
130	public String [] getParameterNames() { return paramNames; }
131
132	/**
133		Get the return type of the method.
134		@return Returns null for a loosely typed return value, 
135			Void.TYPE for a void return type, or the Class of the type.
136	*/
137	/*
138		Note: bshmethod needs to re-evaluate the method return type here.
139		This is broken.
140	*/
141	public Class getReturnType() { return creturnType; }
142
143	public Modifiers getModifiers() { return modifiers; }
144
145	public String getName() { return name; }
146
147	/**
148		Invoke the declared method with the specified arguments and interpreter
149		reference.  This is the simplest form of invoke() for BshMethod 
150		intended to be used in reflective style access to bsh scripts.
151	*/
152	public Object invoke( 
153		Object[] argValues, Interpreter interpreter ) 
154		throws EvalError 
155	{
156		return invoke( argValues, interpreter, null, null, false );
157	}
158
159	/**
160		Invoke the declared method with the specified arguments, interpreter
161		reference, and callstack.
162		<p/>
163		Note: this form of invoke() uses a null Node for the caller and a null
164		node for the CallStack.  This method is for scripts performing 
165		relective style access to scripted methods.
166	 */
167	public Object invoke( 
168		Object[] argValues, Interpreter interpreter, CallStack callstack ) 
169		throws EvalError 
170	{
171		return invoke( argValues, interpreter, callstack, null, false );
172	}
173
174	public Object invoke( 
175		Object[] argValues, Interpreter interpreter, CallStack callstack,
176			SimpleNode callerInfo ) 
177		throws EvalError 
178	{
179		return invoke( argValues, interpreter, callstack, callerInfo, false );
180	}
181
182	/**
183		Invoke the bsh method with the specified args, interpreter ref,
184		and callstack.
185		callerInfo is the node representing the method invocation
186		It is used primarily for debugging in order to provide access to the 
187		text of the construct that invoked the method through the namespace.
188		@param callerInfo is the BeanShell AST node representing the method 
189			invocation.  It is used to print the line number and text of 
190			errors in EvalError exceptions.  If the node is null here error
191			messages may not be able to point to the precise location and text
192			of the error.
193		@param callstack is the callstack.  If callstack is null a new one
194			will be created with the declaring namespace of the method on top
195			of the stack (i.e. it will look for purposes of the method 
196			invocation like the method call occurred in the declaring 
197			(enclosing) namespace in which the method is defined).
198		@param overrideNameSpace 
199			When true the method is executed in the namespace on the top of the
200			stack instead of creating its own local namespace.  This allows it
201			to be used in constructors.
202	*/
203	Object invoke( 
204		Object[] argValues, Interpreter interpreter, CallStack callstack,
205			SimpleNode callerInfo, boolean overrideNameSpace ) 
206		throws EvalError 
207	{
208		if ( javaMethod != null )
209			try {
210				return Reflect.invokeOnMethod( 
211					javaMethod, javaObject, argValues ); 
212			} catch ( ReflectError e ) {
213				throw new EvalError(
214					"Error invoking Java method: "+e, callerInfo, callstack );
215			} catch ( InvocationTargetException e2 ) {
216				throw new TargetError( 
217					"Exception invoking imported object method.", 
218					e2, callerInfo, callstack, true/*isNative*/ );
219			}
220
221		// is this a syncrhonized method?
222		if ( modifiers != null && modifiers.hasModifier("synchronized") )
223		{
224			// The lock is our declaring namespace's This reference
225			// (the method's 'super').  Or in the case of a class it's the
226			// class instance.
227			Object lock;
228			if ( declaringNameSpace.isClass )
229			{
230				try {
231					lock = declaringNameSpace.getClassInstance();
232				} catch ( UtilEvalError e ) {
233					throw new InterpreterError(
234						"Can't get class instance for synchronized method.");
235				}
236			} else
237				lock = declaringNameSpace.getThis(interpreter); // ???
238
239			synchronized( lock ) 
240			{
241				return invokeImpl( 
242					argValues, interpreter, callstack, 
243					callerInfo, overrideNameSpace );
244			}
245		} else
246			return invokeImpl( argValues, interpreter, callstack, callerInfo,
247				overrideNameSpace );
248	}
249
250	private Object invokeImpl( 
251		Object[] argValues, Interpreter interpreter, CallStack callstack,
252			SimpleNode callerInfo, boolean overrideNameSpace ) 
253		throws EvalError 
254	{
255		Class returnType = getReturnType();
256		Class [] paramTypes = getParameterTypes();
257
258		// If null callstack
259		if ( callstack == null )
260			callstack = new CallStack( declaringNameSpace );
261
262		if ( argValues == null )
263			argValues = new Object [] { };
264
265		// Cardinality (number of args) mismatch
266		if ( argValues.length != numArgs ) 
267		{
268		/*
269			// look for help string
270			try {
271				// should check for null namespace here
272				String help = 
273					(String)declaringNameSpace.get(
274					"bsh.help."+name, interpreter );
275
276				interpreter.println(help);
277				return Primitive.VOID;
278			} catch ( Exception e ) {
279				throw eval error
280			}
281		*/
282			throw new EvalError( 
283				"Wrong number of arguments for local method: " 
284				+ name, callerInfo, callstack );
285		}
286
287		// Make the local namespace for the method invocation
288		NameSpace localNameSpace;
289		if ( overrideNameSpace )
290			localNameSpace = callstack.top();
291		else
292		{
293			localNameSpace = new NameSpace( declaringNameSpace, name );
294			localNameSpace.isMethod = true;
295		}
296		// should we do this for both cases above?
297		localNameSpace.setNode( callerInfo );
298
299		// set the method parameters in the local namespace
300		for(int i=0; i<numArgs; i++)
301		{
302			// Set typed variable
303			if ( paramTypes[i] != null ) 
304			{
305				try {
306					argValues[i] = Types.getAssignableForm(
307						argValues[i], paramTypes[i] );
308				}
309				catch( UtilEvalError e) {
310					throw new EvalError(
311						"Invalid argument: " 
312						+ "`"+paramNames[i]+"'" + " for method: " 
313						+ name + " : " + 
314						e.getMessage(), callerInfo, callstack );
315				}
316				try {
317					localNameSpace.setTypedVariable( paramNames[i], 
318						paramTypes[i], argValues[i], null/*modifiers*/);
319				} catch ( UtilEvalError e2 ) {
320					throw e2.toEvalError( "Typed method parameter assignment", 
321						callerInfo, callstack  );
322				}
323			} 
324			// Set untyped variable
325			else  // untyped param
326			{
327				// getAssignable would catch this for typed param
328				if ( argValues[i] == Primitive.VOID)
329					throw new EvalError(
330						"Undefined variable or class name, parameter: " +
331						paramNames[i] + " to method: " 
332						+ name, callerInfo, callstack );
333				else
334					try {
335						localNameSpace.setLocalVariable(
336							paramNames[i], argValues[i],
337							interpreter.getStrictJava() );
338					} catch ( UtilEvalError e3 ) {
339						throw e3.toEvalError( callerInfo, callstack );
340					}
341			}
342		}
343
344		// Push the new namespace on the call stack
345		if ( !overrideNameSpace )
346			callstack.push( localNameSpace );
347
348		// Invoke the block, overriding namespace with localNameSpace
349		Object ret = methodBody.eval( 
350			callstack, interpreter, true/*override*/ );
351
352		// save the callstack including the called method, just for error mess
353		CallStack returnStack = callstack.copy();
354
355		// Get back to caller namespace
356		if ( !overrideNameSpace )
357			callstack.pop();
358
359		ReturnControl retControl = null;
360		if ( ret instanceof ReturnControl )
361		{
362			retControl = (ReturnControl)ret;
363
364			// Method body can only use 'return' statment type return control.
365			if ( retControl.kind == retControl.RETURN )
366				ret = ((ReturnControl)ret).value;
367			else 
368				// retControl.returnPoint is the Node of the return statement
369				throw new EvalError("'continue' or 'break' in method body", 
370					retControl.returnPoint, returnStack );
371
372			// Check for explicit return of value from void method type.
373			// retControl.returnPoint is the Node of the return statement
374			if ( returnType == Void.TYPE && ret != Primitive.VOID )
375				throw new EvalError( "Cannot return value from void method", 
376				retControl.returnPoint, returnStack);
377		}
378
379		if ( returnType != null )
380		{
381			// If return type void, return void as the value.
382			if ( returnType == Void.TYPE )
383				return Primitive.VOID;
384
385			// return type is a class
386			try {
387				ret = Types.getAssignableForm( ret, returnType );
388			} catch( UtilEvalError e ) 
389			{
390				// Point to return statement point if we had one.
391				// (else it was implicit return? What's the case here?)
392				SimpleNode node = callerInfo;
393				if ( retControl != null )
394					node = retControl.returnPoint;
395				throw e.toEvalError(
396					"Incorrect type returned from method: " 
397					+ name + e.getMessage(), node, callstack );
398			}
399		}
400
401		return ret;
402	}
403
404	public boolean hasModifier( String name ) {
405		return modifiers != null && modifiers.hasModifier(name);
406	}
407
408	public String toString() {
409		return "Scripted Method: "
410			+ StringUtil.methodString( name, getParameterTypes() ); 
411	}
412
413}