bsh.This Maven / Gradle / Ivy
/*
* #%L
* The AIBench Shell Plugin
* %%
* Copyright (C) 2006 - 2017 Daniel Glez-Peña and Florentino Fdez-Riverola
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
/*****************************************************************************
* *
* This file is part of the BeanShell Java Scripting distribution. *
* Documentation and updates may be found at http://www.beanshell.org/ *
* *
* Sun Public License Notice: *
* *
* The contents of this file are subject to the Sun Public License Version *
* 1.0 (the "License"); you may not use this file except in compliance with *
* the License. A copy of the License is available at http://www.sun.com *
* *
* The Original Code is BeanShell. The Initial Developer of the Original *
* Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright *
* (C) 2000. All Rights Reserved. *
* *
* GNU Public License Notice: *
* *
* Alternatively, the contents of this file may be used under the terms of *
* the GNU Lesser General Public License (the "LGPL"), in which case the *
* provisions of LGPL are applicable instead of those above. If you wish to *
* allow use of your version of this file only under the terms of the LGPL *
* and not to allow others to use your version of this file under the SPL, *
* indicate your decision by deleting the provisions above and replace *
* them with the notice and other provisions required by the LGPL. If you *
* do not delete the provisions above, a recipient may use your version of *
* this file under either the SPL or the LGPL. *
* *
* Patrick Niemeyer ([email protected]) *
* Author of Learning Java, O'Reilly & Associates *
* http://www.pat.net/~pat/ *
* *
*****************************************************************************/
package bsh;
/**
* 'This' is the type of bsh scripted objects. A 'This' object is a bsh scripted
* object context. It holds a namespace reference and implements event listeners
* and various other interfaces.
*
* This holds a reference to the declaring interpreter for callbacks from
* outside of bsh.
*/
public class This implements java.io.Serializable, Runnable {
/**
* The namespace that this This reference wraps.
*/
NameSpace namespace;
/**
* This is the interpreter running when the This ref was created. It's used
* as a default interpreter for callback through the This where there is no
* current interpreter instance e.g. interface proxy or event call backs
* from outside of bsh.
*/
transient Interpreter declaringInterpreter;
/**
* getThis() is a factory for bsh.This type references. The capabilities of
* ".this" references in bsh are version dependent up until jdk1.3. The
* version dependence was to support different default interface
* implementations. i.e. different sets of listener interfaces which
* scripted objects were capable of implementing. In jdk1.3 the reflection
* proxy mechanism was introduced which allowed us to implement arbitrary
* interfaces. This is fantastic.
*
* A This object is a thin layer over a namespace, comprising a bsh object
* context. We create it here only if needed for the namespace.
*
* Note: this method could be considered slow because of the way it
* dynamically factories objects. However I've also done tests where I
* hard-code the factory to return JThis and see no change in the rough test
* suite time. This references are also cached in NameSpace.
*
* @param namespace the namespace associated.
* @param declaringInterpreter the interpreter.
* @return a new This instance.
*/
static This getThis(NameSpace namespace, Interpreter declaringInterpreter) {
try {
Class c;
if (Capabilities.canGenerateInterfaces())
c = Class.forName("bsh.XThis");
else if (Capabilities.haveSwing())
c = Class.forName("bsh.JThis");
else
return new This(namespace, declaringInterpreter);
return (This) Reflect.constructObject(
c, new Object[] {
namespace, declaringInterpreter
}
);
} catch (Exception e) {
throw new InterpreterError("internal error 1 in This: " + e);
}
}
/**
* Returns a version of this scripted object implementing the specified
* interface.
*
* @param clas the interface to be implemented.
* @return a version of this scripted object implementing the specified
* interface.
* @throws UtilEvalError if an error occurs during evaluation.
*/
/*
* If this type of This implements it directly return this, else try
* complain that we don't have the proxy mechanism.
*/
public Object getInterface(Class clas) throws UtilEvalError {
if (clas.isInstance(this))
return this;
else
throw new UtilEvalError("Dynamic proxy mechanism not available. " + "Cannot construct interface type: " + clas);
}
/**
* Get a version of this scripted object implementing the specified
* interfaces.
*
* @param ca the interfaces to be implemented.
* @return a version of this scripted object implementing the specified
* interface.
* @throws UtilEvalError if an error occurs during evaluation.
*/
public Object getInterface(Class[] ca) throws UtilEvalError {
for (int i = 0; i < ca.length; i++)
if (!(ca[i].isInstance(this)))
throw new UtilEvalError("Dynamic proxy mechanism not available. " + "Cannot construct interface type: " + ca[i]);
return this;
}
/*
* I wish protected access were limited to children and not also package
* scope... I want this to be a singleton implemented by various children.
*/
protected This(NameSpace namespace, Interpreter declaringInterpreter) {
this.namespace = namespace;
this.declaringInterpreter = declaringInterpreter;
// initCallStack( namespace );
}
public NameSpace getNameSpace() {
return namespace;
}
public String toString() {
return "'this' reference to Bsh object: " + namespace;
}
public void run() {
try {
invokeMethod("run", new Object[0]);
} catch (EvalError e) {
declaringInterpreter.error("Exception in runnable:" + e);
}
}
/**
* Invoke specified method as from outside java code, using the declaring
* interpreter and current namespace. The call stack will indicate that the
* method is being invoked from outside of bsh in native java code. Note:
* you must still wrap/unwrap args/return values using
* Primitive/Primitive.unwrap() for use outside of BeanShell.
*
* @param name the name of the method to invoke
* @param args the parameters of the method to invoke.
* @return the result of the invocation.
* @throws EvalError if an error occurs during evaluation.
* @see bsh.Primitive
*/
public Object invokeMethod(String name, Object[] args) throws EvalError {
// null callstack, one will be created for us
return invokeMethod(name, args, null/* declaringInterpreter */, null, null, false/* declaredOnly */);
}
/**
* Invoke a method in this namespace with the specified args, interpreter
* reference, callstack, and caller info.
*
*
* Note: If you use this method outside of the bsh package and wish to use
* variables with primitive values you will have to wrap them using
* bsh.Primitive. Consider using This getInterface() to make a true Java
* interface for invoking your scripted methods.
*
*
* This method also implements the default object protocol of toString(),
* hashCode() and equals() and the invoke() meta-method handling as a last
* resort.
*
*
* Note: The invoke() meta-method will not catch the Object protocol methods
* (toString(), hashCode()...). If you want to override them you have to
* script them directly.
*
*
* @param methodName
* the name of the method to invoke.
* @param args
* the arguments of the method.
* @param interpreter
* the interpreter.
* @param callstack
* the call stack related. If callStack is null a new CallStack
* will be created and initialized with this namespace.
* @param callerInfo
* information of the caller.
* @param declaredOnly
* if {@code true} then only methods declared directly in the
* namespace will be visible - no inherited or imported methods
* will be visible.
* @return the invocation result.
* @throws EvalError
* if an error occurs during evaluation.
* @see bsh.Primitive
* @see bsh.This#invokeMethod(String, Object[], Interpreter, CallStack,
* SimpleNode, boolean)
*/
/*
* invokeMethod() here is generally used by outside code to callback into
* the bsh interpreter. e.g. when we are acting as an interface for a
* scripted listener, etc. In this case there is no real call stack so we
* make a default one starting with the special JAVACODE namespace and our
* namespace as the next.
*/
public Object invokeMethod(String methodName, Object[] args, Interpreter interpreter, CallStack callstack, SimpleNode callerInfo, boolean declaredOnly)
throws EvalError {
/*
* Wrap nulls. This is a bit of a cludge to address a deficiency in the
* class generator whereby it does not wrap nulls on method delegate.
* See Class Generator.java. If we fix that then we can remove this.
* (just have to generate the code there.)
*/
if (args != null) {
Object[] oa = new Object[args.length];
for (int i = 0; i < args.length; i++)
oa[i] = (args[i] == null ? Primitive.NULL : args[i]);
args = oa;
}
if (interpreter == null)
interpreter = declaringInterpreter;
if (callstack == null)
callstack = new CallStack(namespace);
if (callerInfo == null)
callerInfo = SimpleNode.JAVACODE;
// Find the bsh method
Class[] types = Types.getTypes(args);
BshMethod bshMethod = null;
try {
bshMethod = namespace.getMethod(methodName, types, declaredOnly);
} catch (UtilEvalError e) {
// leave null
}
if (bshMethod != null)
return bshMethod.invoke(args, interpreter, callstack, callerInfo);
/*
* No scripted method of that name. Implement the required part of the
* Object protocol: public int hashCode(); public boolean
* equals(java.lang.Object); public java.lang.String toString(); if
* these were not handled by scripted methods we must provide a default
* impl.
*/
// a default toString() that shows the interfaces we implement
if (methodName.equals("toString"))
return toString();
// a default hashCode()
if (methodName.equals("hashCode"))
return new Integer(this.hashCode());
// a default equals() testing for equality with the This
// reference
if (methodName.equals("equals")) {
Object obj = args[0];
return new Boolean(this == obj);
}
// Look for a default invoke() handler method in the namespace
// Note: this code duplicates that in NameSpace getCommand()
// is that ok?
try {
bshMethod = namespace.getMethod(
"invoke", new Class[] {
null, null
}
);
} catch (UtilEvalError e) { /* leave null */
}
// Call script "invoke( String methodName, Object [] args );
if (bshMethod != null)
return bshMethod.invoke(
new Object[] {
methodName, args
}, interpreter, callstack, callerInfo
);
throw new EvalError(
"Method " + StringUtil.methodString(methodName, types) + " not found in bsh scripted object: " + namespace.getName(), callerInfo, callstack
);
}
/**
* Bind a This reference to a parent's namespace with the specified
* declaring interpreter. Also re-init the callstack. It's necessary to bind
* a This reference before it can be used after deserialization. This is
* used by the bsh load() command.
*
*
* This is a static utility method because it's used by a bsh command bind()
* and the interpreter doesn't currently allow access to direct methods of
* This objects (small hack).
*
* @param ths a reference to This.
* @param namespace the namespace associted.
* @param declaringInterpreter the interpreter.
*/
public static void bind(This ths, NameSpace namespace, Interpreter declaringInterpreter) {
ths.namespace.setParent(namespace);
ths.declaringInterpreter = declaringInterpreter;
}
/**
* Allow invocations of these method names on This type objects. Don't give
* bsh.This a chance to override their behavior.
*
*
* If the method is passed here the invocation will actually happen on the
* bsh.This object via the regular reflective method invocation mechanism.
* If not, then the method is evaluated by bsh.This itself as a scripted
* method call.
*
* @param name the name of a method.
* @return {@code true} if the method is exposed. {@code false} otherwise.
*/
static boolean isExposedThisMethod(String name) {
return name.equals("getClass") || name.equals("invokeMethod") || name.equals("getInterface")
// These are necessary to let us test
// synchronization from scripts
|| name.equals("wait") || name.equals("notify") || name.equals("notifyAll");
}
}