Reputation: 1960
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
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