Comment puis-je passer une référence de méthode appropriée pour que Nashorn puisse l'exécuter?

J'essaie d'écrire une bibliothèque qui me permettra d'exécuter les règles JSON Logic via le moteur Nashorn Javascript.

Mon problème en ce moment concerne spécifiquement l'enveloppe JSObject que j'ai créée pour gérer les données en mouvement de Java / Kotlin dans le moteur de script.

Si un tableau est transmis tel que [true] il est enroulé et le script json-logique le recevra, voir qu'il s'agit d'un tableau et tenter d'exécuter le bit de code suivant:

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

Lorsque la fonction .map est appelée, Nashorn appellera getMember("map") sur mon objet en attendant de récupérer une fonction qu'il peut exécuter.

C'est là que je suis bloqué. Je n'ai pu trouver aucune sorte de syntaxe appropriée pour donner à Nashorn une méthode ou une référence de méthode qui peut être invoquée par elle comme récepteur de sa fonction de carte.

Le code est disponible ici: https://github.com/deinspanjer/json-logic-java Il existe des tests unitaires de base, y compris celui qui présente le problème, JavaJsonLogicTest.simpleApplyJEJO() . La ligne de code brisée est com/jsonlogic/JSObjectWrappers.kt:97 .

J'apprécierais beaucoup votre aide.

MISE À JOUR: Selon la réponse acceptée, voici la version Kotlin en cours du code:

 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 peut renvoyer tout script "callable". Ce pourrait être un autre JSObject qui renvoie 'true' pour isFunction ou un objet d'interface fonctionnelle Java. Un couple de simples exemples d'exemples Java ici:

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

L'exemple ci-dessus renvoie un JSObject callable pour la propriété "map". La "fonction" renvoyée utilise une fonction de rappel comme argument. Toutes les fonctions de script (et les objets) sont transmises en tant que JSObjects au code Java et le code "map" lance le premier argument sur JSObject pour appeler la fonction callback de script.

L'exemple ci-dessus modifié pour utiliser une interface fonctionnelle est le suivant:

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

J'espère que les exemples ci-dessus vous aideront à proposer la version de Kotlin pour votre scénario.