2015-06-01 7 views
9

Ich weiß, dass Sie mit Java-Arrays in Nashorn arbeiten können und es gibt viele Beispiele dafür, wie Sie dies tun. Das Problem für mich mit dem Standardansatz ist, dass es den JavaScript-Code explizit auf seine Laufzeitumgebung aufmerksam macht. Derzeit habe ich eine Lösung, die Rhino verwendet und es nahtlos zwischen Java-Typ und Native-Javascript-Typen konvertiert.Nahtlos Pass Arrays und Listen zu und von Nashorn

Für Rhino erreichte ich dies durch org.mozilla.javascript.ContextFactory und org.mozilla.javascript.WrapFActory Implementierung und WrapFactory auf dem Context Einstellung, wenn makeContext genannt wird. Diese WrapFactory-Implementierung kümmert sich um die Konvertierung zwischen Java-Arrays und Listen und nativen Javascript-Arrays und -Listen. Es wurde auch erwähnt, dass ich den Rhino-Quellcode vom JDK bekommen musste, damit dieser Ansatz funktioniert.

Ich muss eine ähnliche Lösung für Nashorn finden. Hier ist ein Beispiel für das, was ich erreichen möchte.

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(); 
    } 
} 

Der Ausgang dieses Codes ist

[I @ 169e6180

[1, 2, 3, 4]

1,2,3,4

Ich bin auf der Suche nach einer Möglichkeit, einen ScriptObjectMirror zu implementieren, vorausgesetzt, dass sogar richtig ist, dass die Ausgabe machen würde von diesen drei invokeFunction Anrufe gleich sein.

Ich habe versucht, mit wrap Funktion auf ScriptUtils, aber immer noch das Ergebnis ist falsch

UPDATE

Ich habe versucht, einen dynamischen Proxy-Typ zu schaffen Invocable und Konvertierungen in der InvocationHandler. Um ein NativeArray mit Nashorn zu erstellen, sollten Sie jdk.nashorn.internal.objects.Global.allocate verwenden, was jedoch immer eine Ausnahme auslöst.

Global.allocate(new int[] {1, 2, 3, 4}) 

Lösen

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) 
+0

Ihre 'Arrays.asList()' Anruf erscheint eine Liste mit einem Element des Typs zu schaffen, 'int []' anstatt was du willst. Probieren Sie stattdessen "Arrays.asList (1,2,3,4)". – biziclop

+0

ah, danke :) Das macht die Dinge ein wenig besser. Ich werde die Frage aktualisieren – Leon

+0

Ist nicht das Hauptproblem hier, dass JS-Arrays nicht eigentlich Arrays im Java-Sinne sind?Sie sind mehr wie Karten mit einer "int" -Taste. – biziclop

Antwort

1

Ich glaube, Sie auf den harten Weg zu gehen haben eine AbstractJSObject eines Gerät nach unten. Ich denke, viele Funktionen wie getMember können über Refelction ausgeführt werden. Aber was würden Sie tun, wenn jemand denkt, dass es ein JS-Array ist und versuchen, den Prototyp zu erweitern? Willst du damit auch umgehen? In diesem Fall würde ich ein JS-Array als Eigenschaft in einer Liste wie Wrapper-Klasse implementieren und alle set/add an eine JS-Funktion delegieren, die das JS-Objekt aktualisiert.

Lösung 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(); 
     } 
    } 

Lösung 2 würde (in Pseudo-Code) sein:

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 ... 
    }