Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add Java-implemented function into Nashorn's global scope

Tags:

nashorn

I'm trying to migrate/update my project to use Nashorn from Rhino. I have some global utility functions implemented in Java and added into global scope of the target script engine, typical example is log(message).

In Rhino it is implemented via

public static class LogFunction extends org.mozilla.javascript.BaseFunction {
    @Override
    public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
        ...
    }
}

whose instance added into target scope. What has to be done in case of Nashorn? I can't find how standalone function can be implemented for Nashorn.

like image 466
Xtra Coder Avatar asked Feb 08 '23 17:02

Xtra Coder


2 Answers

You can easily implement script functions in Java. You just implement any @FunctionalInterface (https://docs.oracle.com/javase/8/docs/api/java/lang/FunctionalInterface.html) interface using a lambda and expose the same as a global variable by calling ScriptEngine.put (https://docs.oracle.com/javase/8/docs/api/javax/script/ScriptEngine.html#put-java.lang.String-java.lang.Object-) method. The following example implements two such script 'functions' implemented in Java code.

import javax.script.*;
import java.util.function.*;
import java.util.Random;

public class Main {
  public static void main(String[] args) throws Exception {
     ScriptEngineManager m = new ScriptEngineManager();
     ScriptEngine e = m.getEngineByName("nashorn");

     // expose 'log' function - any @FunctionInterface Java
     // object can be exposed as 'function'
     e.put("log", (Consumer<String>)System.out::println);

     Random r = new Random();
     // expose 'next gaussian' as script global function
     e.put("gaussian", (Supplier<Double>)r::nextGaussian);

     // call functions implemented in Java!
     e.eval("log('hello')");
     e.eval("print(gaussian())");
     e.eval("print(gaussian())");
  }
}
like image 176
A. Sundararajan Avatar answered Feb 11 '23 07:02

A. Sundararajan


Some time after asking the question I googled once again and found this post: http://mail.openjdk.java.net/pipermail/nashorn-dev/2013-December/002520.html

*) Implement any @FunctionalInterface interface in JDK (or your own @FunctionalInterface) and pass/put object of the same in a javax.script.Bindings or even global scope. Script can access these as though these are functions.

*) Implement jdk.nashorn.api.scripting.JSObject in your class and implement "call" method on it. Again, nashorn's flexible dynalink based linker will treat your JSObject impl. as though it is a function. This can also be used to implement "constructor" (newObject method) in Java code and so on.

I decided to go with JSObject implementation and my code looks more Rhino-like and closer to my original code than approach recommended in Sundararajan's answer. Not sure if there any performance difference between them.

import jdk.nashorn.api.scripting.AbstractJSObject;

public static class PrintFunction extends AbstractJSObject {

    public PrintFunction() {
    }

    @Override
    public boolean isFunction() {
        return true;
    }

    @Override
    public Object call(Object thiz, Object... args) {
        ... do something ...

        return null;
    }
}

...

void onInitScriptObjects(Bindings scope) {
    scope.put("print", new PrintFunction());
}
like image 32
Xtra Coder Avatar answered Feb 11 '23 09:02

Xtra Coder