When we expose Java objects (or other language objects!) to a scripting language, we
may want flexibility. Most scripting languages on the Java platform support JavaBean conventions. For example, JavaScript allows property
style access for "getXXX" methods. Some language engines (for example, Groovy) treat java.util.Map's specially
to provide map.key_name style access. But, we may want more flexiblity in addition to bean conventions. For example, we may want java.sql.ResultSet to be accessed with
the natual obj.column_name syntax. But, instead of such ad-hoc special cases,
we may want to have a generic way.
Also, while jsr-223 has API support to call a specific script function or method, there is no way to "reflect" on script objects. For example, you can't find out the all methods and properties supported by a specific script object. There is no engine independent way to reflect script objects - you have to use Scriptable interface for Rhino , GroovyObject interface for Groovy and IRubyObject interface for JRuby and so on.
HotSpot Serviceability Agent (SA) is a core dump/hung process debugger for HotSpot JVM. It supports JavaScript based command line interface - much like dbx/gdb's shell-like scripting interface. I had used Mozilla Rhino API directly to implement that -- because it was done before the advent of jsr-223. I am porting this to use jsr-223 API. Previously, I had used Scriptable, ScriptableObject and Function interfaces from Mozilla Rhino. I had changed this to use the following interfaces:
/**
* Any Java object supporting this interface can be
* accessed from scripts with "simpler" access pattern.
* For example, a script engine may support natural
* property/field access syntax for the properties exposed
* via this interface. We use this interface so that we
* can dynamically add/delete/modify fields exposed to
* scripts. Also, script engines may expose this interface for
* it's own objects.
*/
public interface ScriptObject {
// special value to denote no-result -- so that
// null could be used as proper result value
public static final Object UNDEFINED = new Object();
// empty object array
public static final Object[] EMPTY_ARRAY = new Object[0];
/*
* Returns all property names supported by this object.
* Property "name" is either a String or an Integer".
*/
public Object[] getIds();
/**
* Get the value of the named property.
*/
public Object get(String name);
/**
* Get the value of the "indexed" property.
* Returns UNDEFINED if the property does not exist.
*/
public Object get(int index);
/**
* Set the value of the named property.
*/
public void put(String name, Object value);
/**
* Set the value of the indexed property.
*/
public void put(int index, Object value);
/**
* Returns whether the named property exists or not.
*/
public boolean has(String name);
/**
* Returns whether the indexed property exists or not.
*/
public boolean has(int index);
/**
* Deletes the named property. Returns true on success.
*/
public boolean delete(String name);
/**
* Deletes the indexed property. Returns true on success.
*/
public boolean delete(int index);
}
/**
* This interface is used to represent "function/method" valued
* properties in ScriptObjects.
*/
public interface Callable {
/**
* Call the underlying function passing the given
* arguments and return the result.
*/
public Object call(Object[] args) throws ScriptException;
}
Just to be sure: please note that I am thinking of ScriptObject and Callable as
two way interfaces. Script engines would expose it's objects (all or some of it's objects)
as ScriptObjects. Also, from Java code we can expose ScriptObject objects
to script engines and the engines will support special obj.field
and obj.method(...) syntax in the respective language.
Why can't we just use BeanUtils and avoid reinventing the wheel? For example, we can probably make use of BeanUtils API to expose objects to scripts and requires jsr-223 script engine implementers to treat DynaBeans specially to provide easier syntax. And optionally expose (some or all of) their own objects with DynaBean interface.
ScriptObject interface, we query
the ScriptObject itself to return it's properties by calling getIds method.
That returns the current snapshot of the properties of the object.
ScriptObject.get(String name) would return a Callable object
for method/function valued properties of the script object.
The ScriptObject and Callable interfaces could help in inter scripting language communication as well -- there
is no need to specify a strongly typed Java interface to communicate b/w two
dynamically typed languages (using say, Invocable.getInterface()
). For example, you can access Groovy object from a JRuby script and
vice versa and both sides could use natural syntax to access ScriptObjects.Please
let me know your comments. We can probably try this out with script engines @
scripting.dev.java.net