Skip to content

Instantly share code, notes, and snippets.

@joelso
Last active August 29, 2015 14:21
Show Gist options
  • Save joelso/95425f83cf9f4b2eec5c to your computer and use it in GitHub Desktop.
Save joelso/95425f83cf9f4b2eec5c to your computer and use it in GitHub Desktop.
Creating an JS object and invoking a method, using Rhino

JSEngine.java

// ...

public Object run(String script) {
   return rhinoContext.evaluateString(sharedJsScope, script, "A label here", 1, null);
}

public Object invokeFn(Scriptable o, String fn, Object... args) {
   Function function = (Function) ScriptableObject.getProperty(o, fn);
   return function.call(this.rhinoContext, this.sharedJsScope, o, args);
}

public Date jsToDate(Object o) {
  return (Date) this.rhinoContext.jsToJava(o, Date.class);
}

// ...

Car.java

// Create a reference to the car object with all its protoypes and state
// Note that by assigning it to an object (without "var") it's returning the object
// as a scriptable, otherwise it would return undefined. 
Scriptable car = (Scriptable) jsEngine.run("car = new Car()", "");

// Invoke an instance method on car
double numDoors = (double) jsEngine.invokeFn(car, "getDoors");

// Get a property on the car
Date created = jsEngine.jsToDate(car.get("created", car));

System.out.println("Number of doors: " + numDoors);
System.out.println("Car was created: " + created);

car.js

function Car() {
   this.created = new Date();
}

Car.prototype.getDoors = function() {
  return 4;
}
@joelso
Copy link
Author

joelso commented May 22, 2015

@snezdoliy

@sergii-frost
Copy link

@joelso big thanks!
Now I see where problem is in our code.
From beginning I was not thinking about need to save states and get direct access to js objects, but instead made everything JSON-based and stateless. We have Java native JSON-based representations of js models,

When running script, we have logics like:

Object res = rhinoContext.evaluateString(...); // <-- this gives us NativeObject, which is Scriptable
//BUT then we do this: 
return NativeJSON.stringify(rhinoContext, sharedJsScope, res, null, null);

The last return kills all referencing, and we have just String representation (type JSON) for the object we got from javascript.

And I had methods like:

    public <T>T run(String script, Class<T> type) {
        Object result = run(script);
        T parsed = null;
        if (result != null && result instanceof String) {
            parsed = fromJsonString((String)result, type);
        }

        return parsed;
    }

I continue digging into code, how to make it support real classes, and vital question for me if Scriptable or ScriptableObject support serialisation somehow. As when we implement "Flow", we need to transfer our Scriptable object from one flow view to another, as it should be mutable on any step (e.g. when user wants to change any property on it or invoke any method).

@sergii-frost
Copy link

I will just keep this gist as storage of thoughts, sorry :)
Here is how JS code is represented in Rhino:
javascript:

function Car() {
   this.created = new Date();
   this.doors = 4;
   this.owner = {"firstName":"Sven", "lastName":"Svensson"};
}

Rhino:

NativeObject "car" -> " size = 3"
|_ NativeDate "created" -> 
|_ Double "doors" -> 4.0
|_ NativeObject "owner" -> " size = 2"
   |_ String "firstName" -> "Sven"
   |_ String "lastName" -> "Svensson"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment