Подтвердить что ты не робот

Rhino: номера Java не ведут себя как номера Javascript

У меня есть экземпляр этого класса Java, доступный в моей программе Javascript

public class ContentProvider {
  public Object c(int n) {
    switch (n) {
      case 1: return 1.1;
      case 2: return 2.2;
      case 3: return 3.3;
      case 4: return "4";
      case 5: return new java.util.Date();
    }
    return null;
  }
}

Это код внутри main():

ScriptEngineManager mgr = new ScriptEngineManager();
ScriptEngine engine = mgr.getEngineByName("JavaScript");
engine.put("ctx", new ContentProvider());

res = engine.eval("ctx.c(1)");

System.out.printf("rhino:> %s (%s)%n"
        , res
        , res != null ? res.getClass().getName() : null
);

Простое выражение ctx.c(1) печатает:

rhino:> 1.1 (java.lang.Double)

Теперь вот что происходит с ctx.c(1) + ctx.c(2):

rhino:> 1.12.2 (java.lang.String)

И наконец (ctx.c(1) + ctx.c(2)) * ctx.c(3):

rhino:> nan (java.lang.Double)

Rhino выполняет конкатенацию строк вместо числовой арифметики! Следующая программа работает как ожидалось:

engine.put("a", 1.1);
engine.put("b", 2.2);
engine.put("c", 3.3);
res = engine.eval("(a + b) * c");

Выходы:

rhino:> 10,89 (java.lang.Double)
4b9b3361

Ответ 1

Это странная особенность Rhino: набор Java Number с engine.put("one", new Double(1)) работает так, как ожидалось, в то время как результат метода Java зависит от типа возврата, объявленного самим методом, который считывается с API отражения

  • если это примитив, например double, он преобразуется в номер Javascript
  • иначе он обрабатывается как другие объекты хоста, а + означает конкатенацию, либо Object как в вашем примере, так и double

Вы можете настроить это поведение с помощью wrapFactory.setJavaPrimitiveWrap(false) на WrapFactory в текущем Context. Таким образом, код Rhino может храниться в строках бутстрапа вашей программы и не мешает ContentProvider (который, я думаю, является своего рода конфигурационным прокси)

Из живого Javadoc WrapFactory.isJavaPrimitiveWrap()

По умолчанию метод возвращает true, чтобы указать, что экземпляры String, Number, Boolean и Character должны быть обернуты как любые другие Объект Java и скрипты могут получить доступ к любому Java-методу, доступному в этих Объекты

Таким образом, вы можете установить этот флаг в false, чтобы указать, что Java Number следует преобразовать в номера Javascript. Требуется всего две строки кода

Context ctx = Context.enter();
ctx.getWrapFactory().setJavaPrimitiveWrap(false);

Вот Gist с полным кодом, который я использовал для тестирования

Ответ 2

Я создал обертку значений:

public static class JSValue extends sun.org.mozilla.javascript.internal.ScriptableObject
{
    Object value;

    public JSValue(Object value) {
        this.value = value;
    }

    public String getClassName() {
        return value != null? value.getClass().getName(): null;
    }

    @Override
    public Object getDefaultValue(Class typeHint) {
        if (typeHint == null || Number.class.isAssignableFrom(typeHint)) {
            if (value instanceof Number)
                return ((Number) value).doubleValue();
        }

        return toString();
    }

    @Override
    public String toString() {
        return value != null? value.toString(): null;
    }
}

и функцию редактирования:

  public static class ContentProvider {
    public Object c(int n) {
    ... return new JSValue(1.1);

Теперь выражение работает так, как ожидалось. Спасибо всем.