PageRenderTime 111ms CodeModel.GetById 40ms app.highlight 34ms RepoModel.GetById 17ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/bsh/BshMethod.java

#
Java | 428 lines | 222 code | 43 blank | 163 comment | 44 complexity | 09b7209ae06575a680ee725e8d7e355c 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 org.gjt.sp.jedit.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
 79	// Java Method, for a BshObject that delegates to a real Java method
 80	private Method javaMethod;
 81	private Object 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 name, Class returnType, String [] paramNames,
 96		Class [] 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 method, Object 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 [] getParameterTypes() { return cparamTypes; }
135	public String [] 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 getReturnType() { return creturnType; }
147
148	public Modifiers getModifiers() { return modifiers; }
149
150	public String 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 invoke( 
158		Object[] 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 invoke( 
182		Object[] 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 invoke( 
211		Object[] 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("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 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 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 invokeImpl( 
263		Object[] argValues, Interpreter interpreter, CallStack callstack,
264			SimpleNode callerInfo, boolean overrideNameSpace ) 
265		throws EvalError 
266	{
267		Class returnType = getReturnType();
268		Class [] 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 [] { };
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 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 name ) {
420		return modifiers != null && modifiers.hasModifier(name);
421	}
422
423	public String toString() {
424		return "Scripted Method: "
425			+ StringUtil.methodString( name, getParameterTypes() ); 
426	}
427
428}