简体   繁体   中英

Java object hierarchy and JavaScript prototypes in Rhino

I have a simple Java class hierarchy that looks like so:

public abstract class Parent
{
  private String parentProperty = "parent property value";

  public String getParentProperty()
  {
    return parentProperty;
  }
}

public class Child
  extends Parent
{
  private String childProperty = "child property value";

  public String getChildProperty()
  {
    return childProperty;
  }
}

I have a Child instance that I want to expose in my JavaScript expressions. To make this happen, I created two host Scriptable classes that look like so:

public class ParentScriptable
  extends ScriptableObject
{
  private Parent parent;

  public ParentScriptable()
  {
    // no-arg constructor that seems to be required by Rhino's defineClass
  }

  public ParentScriptable( NativeJavaObject wrapper )
  {
    parent = (Parent) wrapper.unwrap();
  }

  @JSGetter
  public String getParentProperty()
  {
    return parent.getParentProperty();
  }  

  @Override
  public String getClassName()
  {
    return "Parent";
  }
}


public class ChildScriptable
  extends ScriptableObject
{
  private Child child;

  public ChildScriptable()
  {
    // no-arg constructor seems to be required by Rhino's defineClass
    setPrototype( new ParentScriptable() );
  }

  public ChildScriptable( NativeJavaObject wrapper )
  {
    child = (Child) wrapper.unwrap();    
    setPrototype( new ParentScriptable( wrapper ) );
  }

  @JSGetter
  public String getChildProperty()
  {
    return child.getChildProperty();
  }  

  @Override
  public String getClassName()
  {
    return "Child";
  }
}

This is how I register the two Scriptable classes with Rhino and expose a Child instance in the global scope that is used to evaluate my JavaScript expression:

Context cx = Context.enter();
Scriptable globalScope = cx.initStandardObjects();

// register host classes with in global scope
ScriptableObject.defineClass( globalScope, ParentScriptable.class );
ScriptableObject.defineClass( globalScope, ChildScriptable.class );

// expose a Child instance in the global scope as its 'child' property
globalScope.put( "child", globalScope, 
   cx.newObject( globalScope, "Child",  new Object[] { Context.toObject( new Child(), globalScope ) } ) );

// get value of "child.parentProperty"
Object jsResult = cx.evaluateString( globalScope, "child.parentProperty", "<expression>", 1, null );

Context.exit();

Without the "setPrototype" calls, I can access child.childProperty, but child.parentProperty is undefined. This is not surprising, however, I would expect setPrototype to fix this, but it broke it completely and I am now getting "TypeError: Cannot find default value for object." errors.

Any idea how to properly wire up Rhino JavaScript prototypes to make this work?

Answering my own question after a day spent with a debugger.

It turns out that Rhino can handle the Java inheritance and translate it onto the JavaScript prototype-based inheritance.

The changes I had to make are:

  1. Make ChildScriptable extend ParentScriptable.

  2. Remove setPrototype calls.

  3. Make "public ChildScriptable(NativeJavaObject wrapper)" constructor invoke super(wrapper).

  4. Use the following code to register the two scriptable classes. Note: the last boolean parameter instructs Rhino to perform the inheritance mapping.

    ScriptableObject.defineClass( globalScope, ParentScriptable.class, false, true ); // optional

    ScriptableObject.defineClass( globalScope, ChildScriptable.class, false, true );

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM