X

Sundararajan's Weblog

  • Java
    March 21, 2007

Script Beans?

Guest Author

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:





ScriptObject

/\*\*
\* 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);
}





Callable.java

/\*\*
\* 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.

  • beanutils has the notion of DynaClass.
    Every DynaBean is
    queried to return it's DynaClass and from the DynaClass we get all properties supported.
    I think we can (and should?) avoid this when we have to deal with multiple
    languages. We would like to make minimal assumptions about objects -- allow prototype/delegation
    languages like JavaScript (where there is no notion of class) and allow for
    dynamically changing metaclass (like in Groovy, JRuby). So, in the above 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.
  • Also, beanutils API seems to concentrate only on "properties" only. There is
    nothing mentioned about "methods". (Did I miss anything in BeanUtils API that deals methods?).
    We have "Callable" - which is wrapper for any script method/function.
    ScriptObject.get(String name) would return a Callable object
    for method/function valued properties of the script object.
  • beanutils brings much more than what we need - we want to add just enough
    abstractions.

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

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.