Search code examples
javascriptjavarhinonashorn

Migrating a Rhino Scriptable bean to Nashorn


I have a Scriptable bean as shown below

package test.rhino;

import java.util.HashMap;

import org.mozilla.javascript.Scriptable;

public class SomeBean implements Scriptable {

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

    /**
     * 
     */
    public SomeBean() {
        System.out.println("SomeBean();");
    }

    /*
     * @see org.mozilla.javascript.Scriptable#getClassName()
     */
    @Override
    public String getClassName() {
        return "SomeBean";
    }

    /*
     * @see org.mozilla.javascript.Scriptable#get(java.lang.String,
     * org.mozilla.javascript.Scriptable)
     */
    @Override
    public Object get(String name, Scriptable start) {

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

        return values.get(name);
    }

    /*
     * @see org.mozilla.javascript.Scriptable#put(java.lang.String,
     * org.mozilla.javascript.Scriptable, java.lang.Object)
     */
    @Override
    public void put(String name, Scriptable start, Object value) {
        System.out.println("Put is called. Input name: " + name + "\n Input values: " + value);

        values.put(name, value);

    }

    @Override
    public Object get(int index, Scriptable start) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public boolean has(String name, Scriptable start) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public boolean has(int index, Scriptable start) {
        // TODO Auto-generated method stub
        return false;
    }

    @Override
    public void put(int index, Scriptable start, Object value) {
        // TODO Auto-generated method stub

    }

    @Override
    public void delete(String name) {
        // TODO Auto-generated method stub

    }

    @Override
    public void delete(int index) {
        // TODO Auto-generated method stub

    }

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

    @Override
    public void setPrototype(Scriptable prototype) {
        // TODO Auto-generated method stub

    }

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

    @Override
    public void setParentScope(Scriptable parent) {
        // TODO Auto-generated method stub

    }

    @Override
    public Object[] getIds() {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Object getDefaultValue(Class<?> hint) {
        // TODO Auto-generated method stub
        return null;
    }

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

}

In Rhino, using javascript i can access the keys in underlying HashMap as the bean's properties.

var bean = new SomeBean();"
                + "bean.nomen = 'John Doe';\n"
                + "bean.nomen2 = bean.nomen + ' is cool';

The output shows the get and put being called the key and value being added and accessed from the HashMap 'values' . This way i can also add additional functionality to get() and put() method definition.

While porting this bean to Nashorn, i could not find a way to have the same functionality. Nashorn does expose the HashMap in Javascript and allows us to add entries but for me, this behavior should be accessible from the bean instance through a common setter or getter so that i can do more stuff while values are added and retrieved from the HashMap.

So it should work like below :-

Assume bean = new SomeBean(); 
bean.name = 'John Doe' // Adds name and John Doe to the HashMap
print(bean.name) // Retrieves John Doe. 

Is there a way to accomplish this in Nashorn?. I am aware that public class variables are available to my bean instance but that doesn't give me the same functionality as shown above. I do not want to access the HashMap directly either.

Thanks.


Solution

  • After a lot of digging, I was able to resolve this by extending my bean from 'AbstractJSObject' class. This class had proxy get, set and has methods that will be invoked when we try to access/change the object properties with a dot operator.

    The changed class looks like below.

    package test.nashorn;
    
    import java.util.HashMap;
    
    import jdk.nashorn.api.scripting.AbstractJSObject;
    
    public class NSomeOtherBean extends AbstractJSObject {
    
        /**
         * The current values for this object.
         */
        private HashMap<String, Object> values = new HashMap<>();
    
        public NSomeOtherBean() {
            System.out.println("Constructor called.");
        }
    
        // do you have a property of that given name?
        @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.");
            // System.out.println("Called for this"+name+" and returned
            // :"+values.get(name));
    
            return values.get(name);
        }
    
        public void put(String name, Object value) {
            System.out.println("JAVA Put is called. Input name: " + name + "\n Input values: " + value);
    
            values.put(name, value);
    
        }
    
        public boolean has(String name) {
            System.out.println("JAVA Has is called. Input name: " + name);
    
            return values.containsKey(name);
    
        }
    }