2016-03-04 12 views
5

Ich habe ein Java 7-Programm, das Tausende von Objekten (Komponenten) mit vielen Parametern lädt (gespeichert in Map) und verschiedene Rhino-Skripte für diese Objekte ausführt, um andere abgeleitete Parameter zu berechnen, die gespeichert werden zurück im Objekt Map. Bevor jedes Skript ausgeführt wird, wird ein Objekt Scope erstellt, das von der Objektzuordnung unterstützt wird, die als JavaScript-Bereich für die Dauer des Skripts verwendet wird.Erfassen von Nashorns globalen Variablen

Als einfaches Beispiel erstellt die folgenden ein HashMap mit a = 10 und b = 20, und führt das Skript c = a + b, die gespeichert zurück in der Karte in c = 30.0 führt. Obwohl das Skript aussieht, als ob es eine globale Variable c erstellt, erfasst das Scope Objekt es und speichert es in der Karte; ein anderes Skript mit einem anderen Scope Objekt ausgeführt wird diese Variable nicht sehen:

public class Rhino { 

    public static void main(String[] args) throws ScriptException { 
     Context cx = Context.enter(); 
     Scriptable root_scope = cx.initStandardObjects(); 

     Map<String, Object> map = new HashMap<>(); 
     map.put("a", 10); 
     map.put("b", 20); 

     Scope scope = new Scope(root_scope, map); 
     cx.evaluateString(scope, "c = a + b", "<expr>", 0, null); 
     System.out.println(map); // --> {b=20, c=30.0, a=10} 

     Context.exit(); 
    } 

    static class Scope extends ScriptableObject { 

     private Map<String, Object> map; 

     public Scope(Scriptable parent, Map<String, Object> map) { 
      setParentScope(parent); 
      this.map = map; 
     } 

     @Override 
     public boolean has(String key, Scriptable start) { 
      return true; 
     } 

     @Override 
     public Object get(String key, Scriptable start) { 
      if (map.containsKey(key)) 
       return map.get(key); 
      return Scriptable.NOT_FOUND; 
     } 

     @Override 
     public void put(String key, Scriptable start, Object value) { 
      map.put(key, value); 
     } 

     @Override 
     public String getClassName() { 
      return "MapScope"; 
     } 
    } 
} 

Das obige Skript gibt {b=20, c=30.0, a=10}, die Variable c zeigt hat im Map gespeichert.

Jetzt muss ich dieses Java 8 migrieren und Nashorn verwenden. Ich finde jedoch, dass Nashorn immer globale Variablen in einem speziellen "nashorn.global" Objekt speichert. Tatsächlich scheint es, alle Bindungen als schreibgeschützt zu behandeln, und Versuche, eine vorhandene Variable zu ändern, führen stattdessen zu einer neuen globalen Variablen, die die vorhandene Bindung überschattet.

public class Nashorn { 

    private final static ScriptEngineManager MANAGER = new ScriptEngineManager(); 

    public static void main(String[] args) throws ScriptException { 
     new Nashorn().testBindingsAsArgument(); 
     new Nashorn().testScopeBindings("ENGINE_SCOPE", ScriptContext.ENGINE_SCOPE); 
     new Nashorn().testScopeBindings("GLOBAL_SCOPE", ScriptContext.GLOBAL_SCOPE); 
    } 

    private ScriptEngine engine = MANAGER.getEngineByName("nashorn"); 
    private Map<String, Object> map = new HashMap<>(); 
    private Bindings bindings = new SimpleBindings(map); 

    private Nashorn() { 
     map.put("a", 10); 
     map.put("b", 20); 
    } 

    private void testBindingsAsArgument() throws ScriptException { 
     System.out.println("Bindings as argument:"); 
     engine.eval("c = a + b; a += b", bindings); 
     System.out.println("map = " + map); 
     System.out.println("eval('c', bindings) = " + engine.eval("c", bindings)); 
     System.out.println("eval('a', bindings) = " + engine.eval("a", bindings)); 
    } 

    private void testScopeBindings(String scope_name, int scope) throws ScriptException { 
     System.out.println("\n" + scope_name + ":"); 
     engine.getContext().setBindings(bindings, scope); 
     engine.eval("c = a + b; a += b"); 
     System.out.println("map = " + map); 
     System.out.println("eval('c') = " + engine.eval("c")); 
     System.out.println("eval('a') = " + engine.eval("a")); 
    } 
} 

Ausgang:

Bindings as argument: 
map = {a=10, b=20, nashorn.global=[object global]} 
eval('c', bindings) = 30.0 
eval('a', bindings) = 30.0 

ENGINE_SCOPE: 
map = {a=10, b=20, nashorn.global=[object global]} 
eval('c') = 30.0 
eval('a') = 30.0 

GLOBAL_SCOPE: 
map = {a=10, b=20} 
eval('c') = 30.0 
eval('a') = 30.0 

Die eval Ausgangsleitungen zeigen die Ergebnisse korrekt berechnet und gespeichert werden, aber die map Ausgangsleitungen zeigt die Ergebnisse werden nicht gespeichert werden, wo ich sie sein wollen.

Dies ist aus verschiedenen Gründen nicht akzeptabel. Die einzelnen Objekte erhalten die berechneten Parameter nicht in ihrem eigenen lokalen Speicher zurück. Variablen aus anderen Skripten, die auf anderen Objekten ausgeführt werden, werden von vorherigen Skriptausführungen übernommen, die logische Fehler verbergen könnten (ein Skript könnte versehentlich einen undefinierten Variablennamen verwenden, aber wenn dieser Name tatsächlich von einem früheren Skript verwendet wurde, könnte der alte Wert für den Papierkorb) verwendet werden anstelle von ReferenceError generiert werden, den Fehler zu verstecken).

Nach der engine.eval() mit map.put("c", engine.get("c")) würde das Ergebnis dorthin verschieben, wo ich es brauche, aber mit einem beliebigen Skript, ich weiß nicht, was alle Variablennamen sein würden, also ist keine Option.

Also die Frage: Gibt es eh die Erfassung von globalen Variablen zu erfassen und diese stattdessen in einem Java-Objekt unter Kontrolle der Anwendung zu speichern, zB das ursprüngliche Binding-Objekt ??

Antwort

3

Ich habe eine Lösung, die zu funktionieren scheint, aber es ist eindeutig ein Hack.

Testprogramm:

public class Nashorn { 
    public static void main(String[] args) throws ScriptException { 
     ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); 

     Map<String, Object> map = new HashMap<>(); 
     map.put("a", 10); 
     map.put("b", 20); 

     try (GlobalMap globals = new GlobalMap(map)) { 
      engine.eval("c = a + b; a += b;", globals); 
     } 

     System.out.println("map = " + map); 
    } 
} 

Das Prüfprogramm Ausgänge map = {a=30.0, b=20, c=30.0} nach Wunsch.

Die GlobalMap fängt das Speichern des globalen Nashorn-Objekts unter dem Schlüssel "nashorn.global" ab, damit es nicht in der Karte gespeichert wird.Wenn die GlobalMap geschlossen ist, beseitigt alle neue globale Variablen aus dem Nashorn globalen Objekt und speichert sie in der Originalkarte:

public class GlobalMap extends SimpleBindings implements Closeable { 

    private final static String NASHORN_GLOBAL = "nashorn.global"; 
    private Bindings global; 
    private Set<String> original_keys; 

    public GlobalMap(Map<String, Object> map) { 
     super(map); 
    } 

    @Override 
    public Object put(String key, Object value) { 
     if (key.equals(NASHORN_GLOBAL) && value instanceof Bindings) { 
      global = (Bindings) value; 
      original_keys = new HashSet<>(global.keySet()); 
      return null; 
     } 
     return super.put(key, value); 
    } 

    @Override 
    public Object get(Object key) { 
     return key.equals(NASHORN_GLOBAL) ? global : super.get(key); 
    } 

    @Override 
    public void close() { 
     if (global != null) { 
      Set<String> keys = new HashSet<>(global.keySet()); 
      keys.removeAll(original_keys); 
      for (String key : keys) 
       put(key, global.remove(key)); 
     } 
    } 
} 

Ich hoffe nach wie vor, eine Lösung zu finden, wo der aktuelle Bereich auf ein Map<String,Object> eingestellt werden könnte oder Bindings Objekt, und alle neuen Variablen, die vom Skript erstellt wurden, werden direkt in diesem Objekt gespeichert.

+0

Es ist so dumm, dass wir darauf zurückgreifen müssen, die Nashorn-Engine ist so stur wie ihre Schöpfer ich wette ... – grimmeld

+2

Das Aufrufen des Ausdrucks in einem IIFE tut auch den Trick (Sie müssen 'var' Schlüsselwort für die Deklaration verwenden Variablen). Weiß jemand, ob diese Methode irgendwelche Vorbehalte hat? – faizan

+0

@faizan Danke für den Vorschlag. Die Verwendung eines IIFE mit 'var c = ...' verhindert, dass 'c' im globalen Gültigkeitsbereich gespeichert wird. Dies hilft dabei, den Code zu isolieren und verhindert, dass andere Skripte versehentlich Werte sehen, die in anderen Skripten berechnet wurden. Aber das hilft nicht bei dem ursprünglichen Problem: die berechneten Werte, die in diesen Variablen gespeichert sind, in der 'Map' zu erfassen, die vom Java-Programm bereitgestellt wird, das diese JavaScript-Code-Snippits ausführt, um später in anderen Teilen des Programms verwendet zu werden. – AJNeufeld

Verwandte Themen