I know you can work with Java arrays in Nashorn and there are plenty of examples of how to do this. The problem for me with the standard approach is that it makes the javascript code explicitly aware of it's runtime environment. Currently I have a solution that makes use of Rhino and it seamlessly converts between Java type and Native javascript types.
For Rhino I accomplished this by implementing org.mozilla.javascript.ContextFactory
and org.mozilla.javascript.WrapFActory
and setting WrapFactory
on the Context
when makeContext
is called. This WrapFactory implementation takes care of converting between Java arrays and Lists and Native javascript arrays and lists. It's also wroth mentioning that I had to get the Rhino source code from the JDK to get this approach to work.
I need to find a similar solution for Nashorn. Here is an example of what I am trying to accomplish.
public static void main(String args[]) {
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
ScriptEngine engine = factory.getScriptEngine();
try {
engine.eval("function print_array(arr) { print(arr); }");
engine.eval("function print_native() { print_array([1, 2, 3, 4]); }");
Invocable invocable = (Invocable) engine;
invocable.invokeFunction("print_array", new int[]{1, 2, 3, 4});
invocable.invokeFunction("print_array", Arrays.asList(1, 2, 3, 4));
invocable.invokeFunction("print_native");
} catch (ScriptException | NoSuchMethodException e) {
e.printStackTrace();
}
}
The output of this code is
[I@169e6180
[1, 2, 3, 4]
1,2,3,4
I am looking for a way to implement a ScriptObjectMirror, assuming that is even correct, that would make the output of those three invokeFunction
calls be the same.
I have tried using wrap
function on ScriptUtils
, but still the result is wrong
UPDATE
I tried to create a dynamic proxy of type Invocable
and do conversions in the InvocationHandler
. To create a NativeArray with Nashorn it seems you should use jdk.nashorn.internal.objects.Global.allocate
, but this always raises an exception.
Global.allocate(new int[] {1, 2, 3, 4})
Raises
Exception in thread "main" java.lang.NullPointerException
at jdk.nashorn.internal.objects.Global.instance(Global.java:491)
at jdk.nashorn.internal.objects.NativeArray.<init>(NativeArray.java:141)
at jdk.nashorn.internal.objects.Global.allocate(Global.java:1584)
I think you have to go the hard road down an implement a AbstractJSObject. I think a lot of Functions like getMember can be done via Refelction. But what would you do is somebody thinking it is a JS Array and try to extend the Prototype? Do you want to handle this too? In that case I would implement a JS Array as property in a list like wrapper class and delegate all set/add to a JS function updating the JS object.
Solution 1:
public static void main(String args[]) {
NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
ScriptEngine engine = factory.getScriptEngine();
try {
engine.eval("function print_array(arr) { print(arr); for(var i=0; i<arr.length; i++) {print(arr[i]);}}");
engine.eval("function print_native() { print_array([1, 2, 3, 4]); }");
engine.eval("function get_native() { return [1, 2, 3, 4]; }");
Invocable invocable = (Invocable) engine;
invocable.invokeFunction("print_array", new int[]{1, 2, 3, 4});
invocable.invokeFunction("print_array", Arrays.asList(1, 2, 3, 4));
invocable.invokeFunction("print_array", new Foo());
invocable.invokeFunction("print_native");
ScriptObjectMirror a = (ScriptObjectMirror) invocable.invokeFunction("get_native");
System.out.println(invocable.invokeFunction("get_native"));
} catch (Exception e) {
e.printStackTrace();
}
}
static class Foo extends AbstractJSObject {
Map<Integer, Object> arrayValues = new HashMap<>();
public Foo() {
arrayValues.put(0, 1);
arrayValues.put(1, 2);
arrayValues.put(2, 3);
}
@Override
public Object call(Object thiz, Object... args) {
System.out.println("call");
return super.call(thiz, args);
}
@Override
public Object newObject(Object... args) {
System.out.println("new Object");
return super.newObject(args);
}
@Override
public Object eval(String s) {
System.out.println("eval");
return super.eval(s);
}
@Override
public Object getMember(String name) {
System.out.println("getMember " + name);
return name.equals("length") ? arrayValues.size() : arrayValues.get(Integer.valueOf(name));
}
@Override
public Object getSlot(int index) {
//System.out.println("getSlot");
return arrayValues.get(index);
}
@Override
public boolean hasMember(String name) {
System.out.println("hasMember");
return super.hasMember(name);
}
@Override
public boolean hasSlot(int slot) {
System.out.println("hasSlot");
return super.hasSlot(slot);
}
@Override
public void removeMember(String name) {
System.out.println("removeMember");
super.removeMember(name);
}
@Override
public void setMember(String name, Object value) {
System.out.println("setMember");
super.setMember(name, value);
}
@Override
public void setSlot(int index, Object value) {
System.out.println("setSlot");
super.setSlot(index, value);
}
@Override
public Set<String> keySet() {
System.out.println("keySet");
return arrayValues.keySet().stream().map(k -> "" + k).collect(Collectors.toSet());
}
@Override
public Collection<Object> values() {
System.out.println("values");
return arrayValues.values();
}
@Override
public boolean isInstance(Object instance) {
System.out.println("isInstance");
return super.isInstance(instance);
}
@Override
public boolean isInstanceOf(Object clazz) {
System.out.println("isINstanceOf");
return super.isInstanceOf(clazz);
}
@Override
public String getClassName() {
System.out.println("getClassName");
return super.getClassName();
}
@Override
public boolean isFunction() {
return false;
}
@Override
public boolean isStrictFunction() {
return false;
}
@Override
public double toNumber() {
return super.toNumber();
}
@Override
public boolean isArray() {
return true;
}
@Override
public String toString() {
return arrayValues.values().toString();
}
}
Solution 2 would be (in pseudo code):
static class FooList implements List {
final ScriptObjectMirror wrapped;
public FooList(ScriptObjectMirror wrapped) {
this.wrapped = wrapped;
}
@Override
public int size() {
return engine.eval("get length of wrapped JS object");
}
... and so on ...
}
来源:https://stackoverflow.com/questions/30571711/seamlessly-pass-arrays-and-lists-to-and-from-nashorn