Функция связывания Java и Javascript с массивами в качестве параметра

Я пытаюсь добиться следующего. Приложение Java должно запускать javascript через ScriptEngineManager/ScriptEngine, и javascript должен иметь возможность вызывать функции из приложения Java. Это прекрасно работает, пока туда и обратно передается только простая переменная. Но я не могу заставить его передавать массивы. Вот мой код:

private static class test implements Function<Integer[], Integer[]> {
    @Override
    public Integer[] apply(Integer[] msg) {
        for(int i = 0; i < msg.length; i++) System.out.println(msg[i]);     
        Integer[] a = {1, 2, 3};           
        return a;
    }
}

public static void main(String[] args) throws ScriptException {
    String ps = "var e = [0x04, 0x05, 0x06]; var a = send(e); for(i in a) print(i);";
    ScriptEngineManager sm = new ScriptEngineManager();
    ScriptEngine eng = sm.getEngineByName("JavaScript");
    Bindings mbind = eng.createBindings();
    mbind.put("send", new test());
    eng.setBindings(mbind, ScriptContext.ENGINE_SCOPE);
    Object t = eng.eval(ps);
}

Я получаю сообщение об ошибке:

Исключение в потоке "main" java.lang.ClassCastException: jdk.nashorn.api.scripting.ScriptObjectMirror не может быть приведен к [Ljava.lang.Integer; в scripttest$test.apply(scripttest.java:1) в jdk.nashorn.internal.scripts.Script$\^eval_.:program(:1) в jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java :637) в jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:494) в jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:393) в jdk.nashorn.api.scripting. NashornScriptEngine.evalImpl(NashornScriptEngine.java:446) в jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:403) в jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:399) в jdk .nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:155) в javax.script.AbstractScriptEngine.eval(неизвестный источник) в scripttest.main(scripttest.java:26)

Кто-нибудь знает, как передавать массивы?

Спасибо! С уважением


person Fooble    schedule 24.10.2018    source источник
comment
Обратите внимание, что когда ваш Javascript пытается напечатать результат, он фактически выполняет итерацию по полям a, а не по элементам - for(... in ...) - это синтаксис для итерации по полям объекта, поэтому, если a действительно является массивом из 3 элементов, вывод будет 0\n1\n2\n независимо от содержимого массива. Синтаксис Javascript для итерации массива — for(... of ...).   -  person Guss    schedule 28.03.2019


Ответы (1)


Рефлексия массивов представляет собой небольшую проблему в Java (примитивные типы и все такое барахло), поэтому у движков сценариев JSR-223 с ними проблемы. Я попробовал тот же код с новой GraalVM, и она страдает от той же проблемы — движок отказывается от поиска хорошего сопоставления и просто пытается отправить свое собственное представление значения массива (Nashorn использует ScriptObjectMirror, а GraalVM использует PolyglotMap).

Если вы хотите, чтобы ваша хост-функция выглядела так, как будто она принимает «настоящий массив» (реализация гостевого массива), ваша хост-функция должна будет принять базовую внутреннюю реализацию ScriptEngine и научиться работать с ней. Согласно этому ответу SO и документы ScriptObjectMirror API вы сможете использовать метод obj.to(int[].class) для извлечения внутреннего целочисленного массива из прокси-объекта.

Основным недостатком этого подхода является то, что ваша хост-функция должна поддерживать каждое конкретное внутреннее представление каждого конкретного ScriptEngine, который вы, возможно, захотите поддерживать.

Поскольку движок Nashorn Javascript устарел в текущих JDK, вы можете также захотеть поддерживать GraalVM, в случае чего ваша реализация может выглядеть примерно так:

private static class test implements Function<Object, Integer[]> {
    @Override
    public Integer[] apply(Object inp) {
        int[] msg;
        if (inp instanceof ScriptObjectMirror)
            msg = ((ScriptObjectMirror)inp).to(int[].class);
        else if (inp instanceof AbstractMap)
            msg = Value.asValue(inp).as(int[].class);
        else
            return null;
        for (int i = 0; i < msg.length; i++)
            System.out.println(msg[i]);
        Integer[] a = { 1, 2, 3 };
        return a;
    }
}
person Guss    schedule 28.03.2019
comment
Кстати, что касается AbstractMap, к сожалению, внутренние типы движка GraalVM Polyglot не могут быть обнаружены с помощью instanceof, потому что ни один из них не является общедоступным классом. В реализации, которая поддерживает только GraalVM, я предполагаю, что вы должны просто Value.asValue() получать любые входные данные, а затем использовать Value API, чтобы получить их оттуда, но я почти уверен, что Value не понравятся прокси-объекты Nashorn. - person Guss; 28.03.2019