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)
Ответы
Ответ 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);
Теперь выражение работает так, как ожидалось. Спасибо всем.