views:

157

answers:

3

Hi everyone,

Basically I'm trying to pass a javaScript function to a Java method to act as a callback to the script.

I can do it - sort of - but the object I receive is a sun.org.mozilla.javascript.internal.InterpretedFunction and I don't see a way to invoke it.

Any ideas?

Here's what I have so far:

var someNumber = 0;

function start() {
   // log is just an log4j instance added to the Bindings
   log.info("started....");
   someNumber = 20;

    // Test is a unit test object with this method on it (taking Object as a param).
    test.callFromRhino(junk);
}

function junk() {
    log.info("called back " + someNumber);
}
+2  A: 

sun.org.mozilla.javascript.internal.InterpretedFunction implements the interface sun.org.mozilla.javascript.Function. That interface has a method on it called call that takes:

  • a Context
  • a Scriptable to use as the scope
  • a Scriptable to use as the value of this within the function
  • an array of Objects that are the arguments to the function

So, what I suggest is that in java you cast the object you were passed as a sun.org.mozilla.javascript.Function and call call. The first two arguments can be whatever you used from java to start the script in the first place. The way you're using it there, the last two arguments can be null and new Object[0].

Daniel Martin
The sun.org.mozilla.javascript.Function is not available in the built-in Rhino engine in Java 6. I'll it with the downloadable rhino jar.
Dan Howard
Note that in the downloadable Rhino jar, the interface is called `org.mozilla.javascript.Function` - Sun renamed all the interfaces in the version they ship in their JDK.
Daniel Martin
+2  A: 

Implement an interface:

import javax.script.*;

public class CallBack {
  public void invoke(Runnable runnable) {
    runnable.run();
  }

  public static void main(String[] args) throws ScriptException {
    ScriptEngine js = new ScriptEngineManager().getEngineByExtension("js");
    js.getContext().setAttribute("callBack", new CallBack(),
        ScriptContext.ENGINE_SCOPE);
    js.eval("var impl = { run: function () { print('Hello, World!'); } };\n"
        + "var runnable = new java.lang.Runnable(impl);\n"
        + "callBack.invoke(runnable);\n");
  }
}
McDowell
I think the question was not how to invoke a Java method from Javascript - for which you have given an example, but how to pass a Javascript function to a Java method and invoke the function from Java code.
Christian Semrau
@Christian Semrau - the interface implementation should invoke the JavaScript function (where I call `print`)
McDowell
McDowell is correct. Thanks! The method callFromRhino can be defined like this and it works! public void callFromRhino(Runnable callback) { callback.run(); }
Dan Howard
Now I see. The JavaScript implements an interface (`Runnable` here) and calls the Java method which in turn calls back the script-implemented interface.
Christian Semrau
+1  A: 

The solution is actually to invoke it in another script. This sort of works:

import javax.script.*;

public class CallFunction {

    /**
     * @param args
     * @throws Exception oops!
     */
    public static void main(String[] args) throws Exception {
        ScriptEngine js = new ScriptEngineManager().getEngineByExtension("js");
        js.getContext().setAttribute("out", System.out, ScriptContext.ENGINE_SCOPE);
        Object a = js.eval(
                "out.println('Defining function a...');" +
                "function a() {out.println('hello from JavaScript!'); }" +
                "function foobar() {out.println('in foobar() definition');}" +    
                "out.println('Done!.');"
        );

        System.out.println(js.get("a")); // InterpretedFunction
        SimpleBindings bindings = new SimpleBindings();
        bindings.put("foobar",js.get("a"));
        js.eval("foobar();", bindings); // hello from JavaScript
        js.eval("foobar();"); // in foobar() definition
    }
}

When you get back the reference to a function, you need to ask the engine to execute that function for you. And although not pretty, asking js to eval() it for you with a specific set of bindings will actually do the job for you. You need to take care that the variables you're manipulating belong to the right scope; I guess it's easy to make mistakes here.

mogsie
Thanks interesting.
Dan Howard
You're welcome. I was struggling with the same problem and didn't find a satisfactory answer to your question, so I felt a working answer was warranted. Since the answer I've verified that this works really well, and it seemingly isn't hard to have callbacks calling java, calling functions with parameters. It's really cool! Makes me wonder if it's possible to port node.js to rhino. lol.
mogsie