NishM
NishM

Reputation: 1726

Nashorn runtime exception while printing JSObject implementations

I have a bean that implements JSObject ( & Map ) interface as shown below. I have removed some overridden methods to make it easy to read.

package test.nashorn;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import jdk.nashorn.api.scripting.JSObject;

public class JSBean implements JSObject, Map<String,Object>{

    /**
     * The current values for this object.
     */
    private HashMap<String, Object> values = new HashMap<>();

    @Override
    public String toString() {
        System.out.println("ToString");
        Set<Entry<String,Object>> entries = values.entrySet();
        StringBuilder sb = new StringBuilder();

        for(Entry<String,Object> entry:entries){
            sb.append(entry.getKey()+ " "+(String)entry.getValue());
        }
        System.out.println("Completed ToString");
        return sb.toString();
    }
    @Override
    public boolean hasMember(String name) {
        return has(name);
    }

    // get the value of that named property
    @Override
    public Object getMember(String name) {

        return get(name);

    }

    // get the value of that named property
    @Override
    public void setMember(String name,Object value) {

         put(name,value);

    }

    public Object get(String name) {

        System.out.println("JAVA Get is called."+name);
         System.out.println("Called for this"+name+" and returned"
         +":"+values.get(name));

        return values.get(name);
    }

    @Override
    public Object put(String name, Object value) {
        System.out.println("JAVA Put is called. Input name: " + name + "\n Input values: " + value);

        return values.put(name, value);

    }

    public boolean has(String name) {
        System.out.println("JAVA Has is called. Input name: " + name);

        return values.containsKey(name);

    }



    public JSBean() {
        // TODO Auto-generated constructor stub
    }

    @Override
    public Object call(Object arg0, Object... arg1) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Object eval(String arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public String getClassName() {
        // TODO Auto-generated method stub
        return null;
    }


    @Override
    public Object getSlot(int arg0) {
        // TODO Auto-generated method stub
        return null;
    }


    @Override
    public boolean hasSlot(int arg0) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean isArray() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean isFunction() {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean isInstance(Object arg0) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean isInstanceOf(Object arg0) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean isStrictFunction() {
        // TODO Auto-generated method stub
        return false;
    }



}

When i run the test shown below

@Test
public void testDefaultValMethod(){

    JSBean bean = new JSBean();

    bean.setMember("hello", " Sport ");

    //Add stuff to engine.
    engine.put("jsBean", bean);

    String source = "(function(){\n"
            + "print(jsBean);"
            + "} )();";

    Object obj=null;
    try {
        obj = engine.eval(source);
    } catch (ScriptException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    System.out.println("Returned : " + String.valueOf(obj));

}

I see the below error in the console. Ideally Nashorn should have directly called the toString() method of the bean to get the String implementation. Not sure what is going wrong here. I did try adding a call to 'toString()' explicitly in the getMember() method call but that did not fix the problem.

JAVA Put is called. Input name: hello
 Input values:  Sport 
JAVA Get is called.toString
Called for thistoString and returned:null
JAVA Get is called.valueOf
Called for thisvalueOf and returned:null
javax.script.ScriptException: TypeError: cannot.get.default.string in <eval> at line number 2
    at jdk.nashorn.api.scripting.NashornScriptEngine.throwAsScriptException(NashornScriptEngine.java:467)
    at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:451)
    at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:403)
    at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:399)
    at jdk.nashorn.api.scripting.NashornScriptEngine.eval(NashornScriptEngine.java:155)
    at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:264)
    at test.nashorn.NashornTest.testDefaultValMethod(NashornTest.java:386)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: <eval>:2 TypeError: cannot.get.default.string
    at jdk.nashorn.internal.runtime.JSType.toPrimitive(JSType.java:514)
    at jdk.nashorn.internal.runtime.JSType.toPrimitive(JSType.java:480)
    at jdk.nashorn.internal.runtime.JSType.toStringImpl(JSType.java:1391)
    at jdk.nashorn.internal.runtime.JSType.toString(JSType.java:589)
    at jdk.nashorn.internal.objects.Global.printImpl(Global.java:2782)
    at jdk.nashorn.internal.objects.Global.println(Global.java:1497)
    at jdk.nashorn.internal.scripts.Script$Recompilation$1$11$\^eval\_.L:1(<eval>:2)
    at jdk.nashorn.internal.scripts.Script$\^eval\_.:program(<eval>:1)
    at jdk.nashorn.internal.runtime.ScriptFunctionData.invoke(ScriptFunctionData.java:640)
    at jdk.nashorn.internal.runtime.ScriptFunction.invoke(ScriptFunction.java:228)
    at jdk.nashorn.internal.runtime.ScriptRuntime.apply(ScriptRuntime.java:393)
    at jdk.nashorn.api.scripting.NashornScriptEngine.evalImpl(NashornScriptEngine.java:446)
    ... 29 more
Caused by: java.lang.UnsupportedOperationException: cannot.get.default.string
    at jdk.nashorn.api.scripting.DefaultValueImpl.getDefaultValue(DefaultValueImpl.java:53)
    at jdk.nashorn.api.scripting.AbstractJSObject.getDefaultValue(AbstractJSObject.java:289)
    at jdk.nashorn.internal.runtime.JSType.toPrimitive(JSType.java:512)
    ... 40 more
Returned : null

Upvotes: 0

Views: 1404

Answers (1)

A. Sundararajan
A. Sundararajan

Reputation: 4405

print or "toString" conversions from scripts call "toString" method on the script object. Any property access (including function valued property) on a JSObject is routed to getMember method. So, to make "valueOf" or "toString" you've to implement appropriate getMember in your JSObject subtype.

Example:

import jdk.nashorn.api.scripting.*;
import javax.script.*;

public class Main {

  static class MyJSObject extends AbstractJSObject {
    @Override
    public Object getMember(String name) {
      if (name.equals("toString")) {
          // return a "function" object for "toString" property
          return new AbstractJSObject() {
              @Override
              public boolean isFunction() {
                  return true;
              }

              @Override
              public Object call(Object self, Object...args) {
                  return self.toString();
              }
          };
      }
      return null; // other properties here
    }

    @Override 
    public String toString() {
        return "my js object";
    }
  }

  public static void main(String[] a) throws Exception {
    ScriptEngineManager m = new ScriptEngineManager();
    ScriptEngine e = m.getEngineByName("nashorn");
    e.put("myObj", new MyJSObject());
    e.eval("print(myObj)");
  }
}

Alternatively, you can also override

Object getDefaultValue(final Class<?> hint) throws UnsupportedOperationException

method in your AbstractJSObject subclass.

import jdk.nashorn.api.scripting.*;
import javax.script.*;

public class Main2 {

  static class MyJSObject extends AbstractJSObject {
    @Override
    public Object getDefaultValue(Class<?> hint) {
        if (hint == String.class) {
            return toString();
        }
        throw new UnsupportedOperationException("no conversion for " + hint);
    }

    @Override 
    public String toString() {
        return "my js object";
    }
  }

  public static void main(String[] a) throws Exception {
    ScriptEngineManager m = new ScriptEngineManager();
    ScriptEngine e = m.getEngineByName("nashorn");
    e.put("myObj", new MyJSObject());
    e.eval("print(myObj)");
  }
}

Upvotes: 2

Related Questions