18

I have an instance of this Java class accessible in my Javascript program

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;
  }
}

This is the code inside 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
);

The simple expression ctx.c(1) prints:

rhino:> 1.1 (java.lang.Double)

Now here is what happens with ctx.c(1) + ctx.c(2):

rhino:> 1.12.2 (java.lang.String)

And finally (ctx.c(1) + ctx.c(2)) * ctx.c(3):

rhino:> nan (java.lang.Double)

Rhino is performing string concatenation instead of number arithmetics! The following program works as expected instead:

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

Outputs:

rhino:> 10,89 (java.lang.Double)
Raffaele
  • 20,627
  • 6
  • 47
  • 86
lunicon
  • 1,690
  • 1
  • 15
  • 26
  • 20
    Oh my god, someone actually used the "java" and "javascript" tags together *correctly*! – user253751 May 12 '15 at 08:08
  • But why not just try `engine.eval("typeof ctx.c(1)")` and see what JavaScript thinks the type is? – user253751 May 12 '15 at 08:08
  • typeof ctx.c(1) = object – lunicon May 12 '15 at 08:13
  • @lunicon Nice solution! Please add it as an answer ([doesn't matter if you are the OP as well](http://blog.stackoverflow.com/2011/07/its-ok-to-ask-and-answer-your-own-questions/)) and accept it. – sp00m May 12 '15 at 16:00
  • @lunicon you may want to take a look at my new answer because it's easier to code and to maintain – Raffaele May 12 '15 at 21:41

2 Answers2

7

This is a strange feature of Rhino: a Java Number set with engine.put("one", new Double(1)) works as expected, while the result of a Java method depends on the return type declared by the method itself, which is read with the reflection API:

  • if it's a primitive, like double, it's converted to a Javascript number
  • otherwise it's handled like other host objects and the + means concatenation, either Object like in your sample as well as Double

You can configure this behavior with wrapFactory.setJavaPrimitiveWrap(false) on the WrapFactory in the current Context. This way the Rhino code can be kept in the bootstrap lines of your program and doesn't clutter ContentProvider (which I guess is some sort of configuration proxy)

From the live Javadoc of WrapFactory.isJavaPrimitiveWrap()

By default the method returns true to indicate that instances of String, Number, Boolean and Character should be wrapped as any other Java object and scripts can access any Java method available in these objects

So you can set this flag to false to indicate that Java Number's should be converted to Javascript numbers. It takes just two lines of code

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

Here is the Gist with the full code I used to test

Raffaele
  • 20,627
  • 6
  • 47
  • 86
1

I created a value wrapper:

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;
    }
}

and an edit function:

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

Now the expression works as expected. Thanks all.

Necreaux
  • 9,451
  • 7
  • 26
  • 43
lunicon
  • 1,690
  • 1
  • 15
  • 26