我怎样才能传递一个正确的方法引用以便 Nashorn 可以执行它?

How can I pass a proper method reference in so Nashorn can execute it?

我正在尝试编写一个库,让我通过 Nashorn Javascript 引擎执行 JSON 逻辑规则。

我现在的问题是我创建的 JSObject 包装器,用于处理将数据从 Java/Kotlin 移动到脚本引擎。

如果传入一个数组,例如[true],它被包装并且json-逻辑脚本将接收它,看到它是一个数组,并尝试运行以下代码:

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

.map 函数被调用时,Nashorn 将在我的对象上调用 getMember("map"),期望得到一个可以执行的函数。

这就是我卡住的地方。我一直无法找到任何类型的正确语法来为 Nashorn 提供一个方法或方法引用,使其可以作为其 map 函数的接收者进行调用。

代码可在此处获得:https://github.com/deinspanjer/json-logic-java 有一些基本的单元测试,包括出现问题的单元测试 JavaJsonLogicTest.simpleApplyJEJO()。 被破坏的代码行是 com/jsonlogic/JSObjectWrappers.kt:97.

非常感谢您的帮助。

更新: 根据已接受的答案,这里是代码的工作 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()+'\"'))");
}

JSObject.getMember 可以 return 任何脚本 "callable"。这可能是另一个 JSObject returns 'true' for isFunction 或 Java 功能接口对象。这里有几个简单的 Java 示例程序:

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

上面的示例 return 是 "map" 属性 的可调用 JSObject。 returned "function" 本身使用回调函数作为参数。所有脚本函数(和对象)都作为 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 版本。