2017-05-27 17 views
2

Nashorn Javascriptエンジンを使用してJSONロジックルールを実行できるライブラリを作成しようとしています。Nashornが適切なメソッド参照を渡すことができますか?

私の問題は、Java/Kotlinからスクリプティングエンジンへのデータの移動を処理するために作成したJSObjectラッパーに関するものです。

配列が[true]などに渡された場合、それがラップされているとJSON-ロジックスクリプトは、それを受け取ることが配列であることがわかり、そして次のコードビットを実行しようとします。

if(Array.isArray(logic)) { 
    return logic.map(function(l) { 
    return jsonLogic.apply(l, data); 
    }); 
} 

.map関数が呼び出されると、Nashornは自分のオブジェクト上でgetMember("map")を呼び出して、実行可能な関数を返すことを期待します。

これは私が立ち往生している場所です。 Nashornにマップ関数の受信者として呼び出せるメソッドまたはメソッドの参照を与えるための適切な構文を見つけることができませんでした。

コードはここにあります:https://github.com/deinspanjer/json-logic-java 問題を示すものを含むいくつかの基本的なユニットテストがあります:JavaJsonLogicTest.simpleApplyJEJO()。 破損しているコード行はcom/jsonlogic/JSObjectWrappers.kt:97です。

ご協力いただきありがとうございます。

UPDATE:受け入れ答えに基づいて 、ここでのコードの作業Kotlinバージョンです:

package com.jsonlogic 

import jdk.nashorn.api.scripting.AbstractJSObject 
import jdk.nashorn.api.scripting.JSObject 
import java.util.function.Function 
import javax.script.ScriptEngineManager 

fun main(args: Array<String>) { 
    val m = ScriptEngineManager() 
    val e = m.getEngineByName("nashorn") 

    // The following JSObject wraps this list 
    val l = mutableListOf<Any>() 
    l.add("hello") 
    l.add("world") 
    l.add(true) 
    l.add(1) 

    val jsObj = object : AbstractJSObject() { 
     override fun getMember(name: String?): Any? { 
      if (name == "map") { 
       // return a functional interface object - nashorn will treat it like 
       // script function! 
       return Function { callback: JSObject -> 
        val res = l.map { 
         // call callback on each object and add the result to new list 
         callback.call(null, it) 
        } 

        // return fresh list as result of map (or this could be another wrapper) 
        res 
       } 
      } else { 
       // unknown property 
       return null 
      } 
     } 
    } 

    e.put("obj", jsObj) 
    // map each String to it's uppercase and print result of map 
    e.eval("print(obj.map(function(x) '\"'+x.toString()+'\"'))"); 
} 

答えて

3

JSObject.getMemberは "呼び出し可能" 任意のスクリプトを返すことができます。これは、isFunctionまたはJava機能インタフェースオブジェクトに対して「true」を返す別のJSObjectである可能性があります。

import javax.script.*; 
import jdk.nashorn.api.scripting.*; 
import java.util.*; 

public class Main { 
    public static void main(String[] args) throws Exception { 
     ScriptEngineManager m = new ScriptEngineManager(); 
     ScriptEngine e = m.getEngineByName("nashorn"); 

     // The following JSObject wraps this list 
     List<String> l = new ArrayList(); 
     l.add("hello"); 
     l.add("world"); 

     JSObject jsObj = new AbstractJSObject() { 
      @Override 
      public Object getMember(String name) { 
       // return a "function" object for "map" 
       if (name.equals("map")) { 
        return new AbstractJSObject() { 
         @Override 
         public Object call(Object thiz, Object... args) { 
          // first argument is the callback passed from script 
          JSObject callable = (JSObject)args[0]; 
          List<Object> res = new ArrayList<>(); 
          for (Object obj : l) { 
           // call callback on each object and add the result to new list 
           res.add(callable.call(null, obj)); 
          } 

          // return fresh list as result of map (or this could be another wrapper) 
          return res; 
         } 

         @Override 
         public boolean isFunction() { return true; } 
        }; 
       } else { 
        // unknown property 
        return null; 
       } 
      } 
     }; 

     e.put("obj", jsObj); 
     // map each String to it's uppercase and print result of map 
     e.eval("print(obj.map(function(x) x.toUpperCase()))"); 
    } 
} 

上記の例では、「map」プロパティの呼び出し可能なJSObjectを返します。返される "関数"自体は、引数としてコールバック関数を使用します。すべてのスクリプト関数(およびオブジェクト)はJSObjectsとしてJavaコードに渡されるので、 'map'コードは最初の引数をJSObjectにキャストしてスクリプトコールバック関数を呼び出します。

機能インタフェースを使用するように変更上記のサンプルは次のとおりです。

import javax.script.*; 
import jdk.nashorn.api.scripting.*; 
import java.util.*; 
import java.util.function.*; 

public class Main2 { 
    public static void main(String[] args) throws Exception { 
     ScriptEngineManager m = new ScriptEngineManager(); 
     ScriptEngine e = m.getEngineByName("nashorn"); 

     // The following JSObject wraps this list 
     List<String> l = new ArrayList(); 
     l.add("hello"); 
     l.add("world"); 

     JSObject jsObj = new AbstractJSObject() { 
      @Override 
      public Object getMember(String name) { 
       if (name.equals("map")) { 
        // return a functional interface object - nashorn will treat it like 
        // script function! 
        return (Function<JSObject, Object>)callback -> { 
         List<Object> res = new ArrayList<>(); 
         for (Object obj : l) { 
          // call callback on each object and add the result to new list 
          res.add(callback.call(null, obj)); 
         } 

         // return fresh list as result of map (or this could be another wrapper) 
         return res; 
        }; 
       } else { 
        // unknown property 
        return null; 
       } 
      } 
     }; 

     e.put("obj", jsObj); 
     // map each String to it's uppercase and print result of map 
     e.eval("print(obj.map(function(x) x.toUpperCase()))"); 
    } 
} 

は、上記のサンプルはあなたのシナリオでKotlinのバージョンを考え出すに役立つことを願っています。

+0

AbstractJSObject :: getMemberは文字列スイッチで実装できますか? publicメソッドgetMember(String name){switch(name){case "map":return(Function )コールバック - > {...};デフォルト:nullを返します。 }} – srborlongan

+0

Function の値は、List <>値に収集することによっても作成できます:callback - > l.stream()。map(obj - > callback.call(null、obj))。 Collectors.toList()); – srborlongan

+1

私は、提案の本質がはっきりと見られるように、ストリームなどを避けたがっていました。 –

関連する問題