svobol13
svobol13

Reputation: 1960

Nashorn and 'with' context passed from Java

I am struggling with nashorn and with block. I would like to pass 'context' from java with HashMap and use it in my code. However, I am not able to get this working.

JS to be evaluated

with(ctx) {
    return a+b;
}

Java map to be "passed"

Map<Object, Object> ctx = new HashMap<>();
ctx.put("a", 5)
ctx.put("b", 5)

Below I prepared short class to demonstrate errors I am facing to.

public class Test {
    public static void main(String[] args) throws ScriptException {
        Map<Object, Object> ctx = new HashMap<>();
        ctx.put("foo", 5);
        eval("print('1st - :)'); ctx = {'foo':'bar'}; with(ctx) {print(foo);}", new HashMap<>());
        // No exception with 'with', o seems to be properly 'in context'..
        eval("print('2nd - :)'); var o = {}; print(o); with(Object.bindProperties(o, ctx)) { print(o); } print(o)", ctx);
        try {
            // ..But it is not
            eval("print('3rd - :('); var o = {}; with(Object.bindProperties(o, ctx)) {print(foo);}", ctx);
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            // 'with' failure - context was not event bound
            eval("print('4th - :('); with(ctx) {print(foo);}", ctx);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private static void eval(String code, Map<Object, Object> ctx) throws ScriptException {
        ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
        engine.getContext().setAttribute("ctx", ctx, ScriptContext.ENGINE_SCOPE);
        engine.eval(code);
    }
}

Thank You for any help.

Upvotes: 1

Views: 1168

Answers (1)

Holger
Holger

Reputation: 298263

When you say print(ctx.foo); it works because ctx is a special Java object implementing Map and it seems Nashorn handles this special case. But it doesn’t consider foo to be an actual property of the object. So when you use Object.bindProperties, it doesn’t transfer foo as a property. But the reason, your second example seems to work, is that it actually transferred the toString() method of the map as a function. So when printing the object o, you see the output of the Map’s toString() method as if all mappings were copied.

When you run the following program

Map<Object, Object> ctx = new HashMap<>();
ctx.put("foo", 5);
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.getContext().setAttribute("ctx", ctx, ScriptContext.ENGINE_SCOPE);
engine.eval("print(ctx.foo);"
   + "with(Object.bindProperties({}, ctx)) {"
   + " print(toString());"
   + " print(get('foo'));"
   + " print(foo); }");

you get

5
{foo=5}
5
Exception in thread "main" javax.script.ScriptException: ReferenceError: "foo" …

indicating that the methods have been transferred, but not the pseudo property foo. But the presence of the methods in the object opens a possibility for a work-around:

Map<Object, Object> ctx = new HashMap<>();
ctx.put("foo", 3);
ctx.put("bar", 7);
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
engine.getContext().setAttribute("ctx", ctx, ScriptContext.ENGINE_SCOPE);
engine.eval(
     "var o={ __noSuchProperty__: function(n) { return this.get(n); }  };"
   + "with(Object.bindProperties(o, ctx)) { print( foo + bar ); }");

This creates an object having the special Nashorn function __noSuchProperty__ which will be invoked for handling absent properties and calls the get(…) method we got from the Map in our case. Hence, print( foo + bar ); will print 10 in the above example.

Upvotes: 2

Related Questions