changeset 556:2d4c8fa8a5f4

8024615: Refactor ScriptObjectMirror and JSObject to support external JSObject implementations Reviewed-by: jlaskey, hannesw
author sundar
date Wed, 11 Sep 2013 20:49:28 +0530
parents badf750dda21
children 66db7354e7e2
files src/jdk/nashorn/api/scripting/JSObject.java src/jdk/nashorn/api/scripting/NashornScriptEngine.java src/jdk/nashorn/api/scripting/ScriptObjectMirror.java src/jdk/nashorn/internal/ir/IdentNode.java src/jdk/nashorn/internal/lookup/MethodHandleFactory.java src/jdk/nashorn/internal/objects/NativeArray.java src/jdk/nashorn/internal/objects/NativeFunction.java src/jdk/nashorn/internal/runtime/ScriptRuntime.java src/jdk/nashorn/internal/runtime/arrays/ArrayLikeIterator.java src/jdk/nashorn/internal/runtime/arrays/IteratorAction.java src/jdk/nashorn/internal/runtime/arrays/JSObjectIterator.java src/jdk/nashorn/internal/runtime/arrays/ReverseJSObjectIterator.java src/jdk/nashorn/internal/runtime/arrays/ReverseScriptObjectMirrorIterator.java src/jdk/nashorn/internal/runtime/arrays/ScriptObjectMirrorIterator.java src/jdk/nashorn/internal/runtime/linker/Bootstrap.java src/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java test/src/jdk/nashorn/api/scripting/PluggableJSObjectTest.java test/src/jdk/nashorn/api/scripting/ScriptObjectMirrorTest.java
diffstat 18 files changed, 772 insertions(+), 311 deletions(-) [+]
line wrap: on
line diff
--- a/src/jdk/nashorn/api/scripting/JSObject.java	Wed Sep 11 10:27:25 2013 +0200
+++ b/src/jdk/nashorn/api/scripting/JSObject.java	Wed Sep 11 20:49:28 2013 +0530
@@ -25,73 +25,210 @@
 
 package jdk.nashorn.api.scripting;
 
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+
 /**
- * netscape.javascript.JSObject-like interface for nashorn script objects.
+ * This is the base class for nashorn ScriptObjectMirror class.
+ *
+ * This class can also be subclassed by an arbitrary Java class. Nashorn will
+ * treat objects of such classes just like nashorn script objects. Usual nashorn
+ * operations like obj[i], obj.foo, obj.func(), delete obj.foo will be glued
+ * to appropriate method call of this class.
  */
 public abstract class JSObject {
     /**
-     * Call a JavaScript function
+     * Call this object as a JavaScript function. This is equivalent to
+     * 'func.apply(thiz, args)' in JavaScript.
      *
-     * @param functionName name of function
+     * @param thiz 'this' object to be passed to the function
      * @param args arguments to method
      * @return result of call
      */
-    public abstract Object call(String functionName, Object... args);
+    public Object call(Object thiz, Object... args) {
+        throw new UnsupportedOperationException("call");
+    }
 
     /**
-     * Call a JavaScript method as a constructor. This is equivalent to
-     * calling new obj.Method(arg1, arg2...) in JavaScript.
+     * Call this 'constructor' JavaScript function to create a new object.
+     * This is equivalent to 'new func(arg1, arg2...)' in JavaScript.
      *
-     * @param functionName name of function
      * @param args arguments to method
      * @return result of constructor call
      */
-    public abstract Object newObject(String functionName, Object... args);
+    public Object newObject(Object... args) {
+        throw new UnsupportedOperationException("newObject");
+    }
 
     /**
-     * Evaluate a JavaScript expression
+     * Evaluate a JavaScript expression.
      *
      * @param s JavaScript expression to evaluate
      * @return evaluation result
      */
-    public abstract Object eval(String s);
+    public Object eval(String s) {
+        throw new UnsupportedOperationException("eval");
+    }
 
     /**
-     * Retrieves a named member of a JavaScript object.
+     * Call a JavaScript function member of this object.
+     *
+     * @param name name of the member function to call
+     * @param args arguments to be passed to the member function
+     * @return result of call
+     */
+    public Object callMember(String name, Object... args) {
+        throw new UnsupportedOperationException("call");
+    }
+
+    /**
+     * Retrieves a named member of this JavaScript object.
      *
      * @param name of member
      * @return member
      */
-    public abstract Object getMember(String name);
+    public Object getMember(String name) {
+        return null;
+    }
 
     /**
-     * Retrieves an indexed member of a JavaScript object.
+     * Retrieves an indexed member of this JavaScript object.
      *
-     * @param index index of member slot
+     * @param index index slot to retrieve
      * @return member
      */
-    public abstract Object getSlot(int index);
+    public Object getSlot(int index) {
+        return null;
+    }
 
     /**
-     * Remove a named member from a JavaScript object
+     * Does this object have a named member?
      *
      * @param name name of member
+     * @return true if this object has a member of the given name
      */
-    public abstract void removeMember(String name);
+    public boolean hasMember(String name) {
+        return false;
+    }
 
     /**
-     * Set a named member in a JavaScript object
+     * Does this object have a indexed property?
      *
-     * @param name  name of member
-     * @param value value of member
+     * @param slot index to check
+     * @return true if this object has a slot
      */
-    public abstract void setMember(String name, Object value);
+    public boolean hasSlot(int slot) {
+        return false;
+    }
 
     /**
-     * Set an indexed member in a JavaScript object
+     * Remove a named member from this JavaScript object
      *
-     * @param index index of member slot
-     * @param value value of member
+     * @param name name of the member
      */
-    public abstract void setSlot(int index, Object value);
+    public void removeMember(String name) {
+    }
+
+    /**
+     * Set a named member in this JavaScript object
+     *
+     * @param name  name of the member
+     * @param value value of the member
+     */
+    public void setMember(String name, Object value) {
+    }
+
+    /**
+     * Set an indexed member in this JavaScript object
+     *
+     * @param index index of the member slot
+     * @param value value of the member
+     */
+    public void setSlot(int index, Object value) {
+    }
+
+    // property and value iteration
+
+    /**
+     * Returns the set of all property names of this object.
+     *
+     * @return set of property names
+     */
+    @SuppressWarnings("unchecked")
+    public Set<String> keySet() {
+        return Collections.EMPTY_SET;
+    }
+
+    /**
+     * Returns the set of all property values of this object.
+     *
+     * @return set of property values.
+     */
+    @SuppressWarnings("unchecked")
+    public Collection<Object> values() {
+        return Collections.EMPTY_SET;
+    }
+
+    // JavaScript instanceof check
+
+    /**
+     * Checking whether the given object is an instance of 'this' object.
+     *
+     * @param instance instace to check
+     * @return true if the given 'instance' is an instance of this 'function' object
+     */
+    public boolean isInstance(final Object instance) {
+        return false;
+    }
+
+    /**
+     * Checking whether this object is an instance of the given 'clazz' object.
+     *
+     * @param clazz clazz to check
+     * @return true if this object is an instance of the given 'clazz'
+     */
+    public boolean isInstanceOf(final Object clazz) {
+        if (clazz instanceof JSObject) {
+            return ((JSObject)clazz).isInstance(this);
+        }
+
+        return false;
+    }
+
+    /**
+     * ECMA [[Class]] property
+     *
+     * @return ECMA [[Class]] property value of this object
+     */
+    public String getClassName() {
+        return getClass().getName();
+    }
+
+    /**
+     * Is this a function object?
+     *
+     * @return if this mirror wraps a ECMAScript function instance
+     */
+    public boolean isFunction() {
+        return false;
+    }
+
+    /**
+     * Is this a 'use strict' function object?
+     *
+     * @return true if this mirror represents a ECMAScript 'use strict' function
+     */
+    public boolean isStrictFunction() {
+        return false;
+    }
+
+    /**
+     * Is this an array object?
+     *
+     * @return if this mirror wraps a ECMAScript array object
+     */
+    public boolean isArray() {
+        return false;
+    }
 }
--- a/src/jdk/nashorn/api/scripting/NashornScriptEngine.java	Wed Sep 11 10:27:25 2013 +0200
+++ b/src/jdk/nashorn/api/scripting/NashornScriptEngine.java	Wed Sep 11 20:49:28 2013 +0530
@@ -494,7 +494,7 @@
 
         if (selfMirror != null) {
             try {
-                return ScriptObjectMirror.translateUndefined(selfMirror.call(name, args));
+                return ScriptObjectMirror.translateUndefined(selfMirror.callMember(name, args));
             } catch (final Exception e) {
                 final Throwable cause = e.getCause();
                 if (cause instanceof NoSuchMethodException) {
--- a/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java	Wed Sep 11 10:27:25 2013 +0200
+++ b/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java	Wed Sep 11 20:49:28 2013 +0530
@@ -48,9 +48,7 @@
 import jdk.nashorn.internal.runtime.ScriptRuntime;
 
 /**
- * Mirror object that wraps a given ScriptObject instance. User can
- * access ScriptObject via the javax.script.Bindings interface or
- * netscape.javascript.JSObject interface.
+ * Mirror object that wraps a given Nashorn Script object.
  */
 public final class ScriptObjectMirror extends JSObject implements Bindings {
     private static AccessControlContext getContextAccCtxt() {
@@ -90,8 +88,9 @@
     }
 
     // JSObject methods
+
     @Override
-    public Object call(final String functionName, final Object... args) {
+    public Object call(final Object thiz, final Object... args) {
         final ScriptObject oldGlobal = Context.getGlobal();
         final boolean globalChanged = (oldGlobal != global);
 
@@ -100,15 +99,13 @@
                 Context.setGlobal(global);
             }
 
-            final Object val = functionName == null? sobj : sobj.get(functionName);
-            if (val instanceof ScriptFunction) {
+            if (sobj instanceof ScriptFunction) {
                 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
-                return wrap(ScriptRuntime.apply((ScriptFunction)val, sobj, unwrapArray(modArgs, global)), global);
-            } else if (val instanceof ScriptObjectMirror && ((ScriptObjectMirror)val).isFunction()) {
-                return ((ScriptObjectMirror)val).call(null, args);
+                final Object self = globalChanged? wrap(thiz, oldGlobal) : thiz;
+                return wrap(ScriptRuntime.apply((ScriptFunction)sobj, unwrap(self, global), unwrapArray(modArgs, global)), global);
             }
 
-            throw new NoSuchMethodException("No such function " + ((functionName != null)? functionName : ""));
+            throw new RuntimeException("not a function: " + toString());
         } catch (final RuntimeException | Error e) {
             throw e;
         } catch (final Throwable t) {
@@ -121,7 +118,7 @@
     }
 
     @Override
-    public Object newObject(final String functionName, final Object... args) {
+    public Object newObject(final Object... args) {
         final ScriptObject oldGlobal = Context.getGlobal();
         final boolean globalChanged = (oldGlobal != global);
 
@@ -130,15 +127,12 @@
                 Context.setGlobal(global);
             }
 
-            final Object val = functionName == null? sobj : sobj.get(functionName);
-            if (val instanceof ScriptFunction) {
+            if (sobj instanceof ScriptFunction) {
                 final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
-                return wrap(ScriptRuntime.construct((ScriptFunction)val, unwrapArray(modArgs, global)), global);
-            } else if (val instanceof ScriptObjectMirror && ((ScriptObjectMirror)val).isFunction()) {
-                return ((ScriptObjectMirror)val).newObject(null, args);
+                return wrap(ScriptRuntime.construct((ScriptFunction)sobj, unwrapArray(modArgs, global)), global);
             }
 
-            throw new RuntimeException("not a constructor " + ((functionName != null)? functionName : ""));
+            throw new RuntimeException("not a constructor: " + toString());
         } catch (final RuntimeException | Error e) {
             throw e;
         } catch (final Throwable t) {
@@ -168,7 +162,39 @@
     }
 
     @Override
+    public Object callMember(final String functionName, final Object... args) {
+        functionName.getClass(); // null check
+        final ScriptObject oldGlobal = Context.getGlobal();
+        final boolean globalChanged = (oldGlobal != global);
+
+        try {
+            if (globalChanged) {
+                Context.setGlobal(global);
+            }
+
+            final Object val = sobj.get(functionName);
+            if (val instanceof ScriptFunction) {
+                final Object[] modArgs = globalChanged? wrapArray(args, oldGlobal) : args;
+                return wrap(ScriptRuntime.apply((ScriptFunction)val, sobj, unwrapArray(modArgs, global)), global);
+            } else if (val instanceof JSObject && ((JSObject)val).isFunction()) {
+                return ((JSObject)val).call(sobj, args);
+            }
+
+            throw new NoSuchMethodException("No such function " + functionName);
+        } catch (final RuntimeException | Error e) {
+            throw e;
+        } catch (final Throwable t) {
+            throw new RuntimeException(t);
+        } finally {
+            if (globalChanged) {
+                Context.setGlobal(oldGlobal);
+            }
+        }
+    }
+
+    @Override
     public Object getMember(final String name) {
+        name.getClass();
         return inGlobal(new Callable<Object>() {
             @Override public Object call() {
                 return wrap(sobj.get(name), global);
@@ -186,12 +212,33 @@
     }
 
     @Override
+    public boolean hasMember(final String name) {
+        name.getClass();
+        return inGlobal(new Callable<Boolean>() {
+            @Override public Boolean call() {
+                return sobj.has(name);
+            }
+        });
+    }
+
+    @Override
+    public boolean hasSlot(final int slot) {
+        return inGlobal(new Callable<Boolean>() {
+            @Override public Boolean call() {
+                return sobj.has(slot);
+            }
+        });
+    }
+
+    @Override
     public void removeMember(final String name) {
+        name.getClass();
         remove(name);
     }
 
     @Override
     public void setMember(final String name, final Object value) {
+        name.getClass();
         put(name, value);
     }
 
@@ -205,6 +252,45 @@
         });
     }
 
+    @Override
+    public boolean isInstance(final Object obj) {
+        if (! (obj instanceof ScriptObjectMirror)) {
+            return false;
+        }
+
+        final ScriptObjectMirror instance = (ScriptObjectMirror)obj;
+        // if not belongs to my global scope, return false
+        if (global != instance.global) {
+            return false;
+        }
+
+        return inGlobal(new Callable<Boolean>() {
+            @Override public Boolean call() {
+                return sobj.isInstance(instance.sobj);
+            }
+        });
+    }
+
+    @Override
+    public String getClassName() {
+        return sobj.getClassName();
+    }
+
+    @Override
+    public boolean isFunction() {
+        return sobj instanceof ScriptFunction;
+    }
+
+    @Override
+    public boolean isStrictFunction() {
+        return isFunction() && ((ScriptFunction)sobj).isStrict();
+    }
+
+    @Override
+    public boolean isArray() {
+        return sobj.isArray();
+    }
+
     // javax.script.Bindings methods
 
     @Override
@@ -392,15 +478,6 @@
     }
 
     /**
-     * ECMA [[Class]] property
-     *
-     * @return ECMA [[Class]] property value of this object
-     */
-    public String getClassName() {
-        return sobj.getClassName();
-    }
-
-    /**
      * ECMA 8.12.1 [[GetOwnProperty]] (P)
      *
      * @param key property key
@@ -506,55 +583,6 @@
         });
     }
 
-    // ECMAScript instanceof check
-
-    /**
-     * Checking whether a script object is an instance of another by
-     * walking the proto chain
-     *
-     * @param instance instace to check
-     * @return true if 'instance' is an instance of this object
-     */
-    public boolean isInstance(final ScriptObjectMirror instance) {
-        // if not belongs to my global scope, return false
-        if (instance == null || global != instance.global) {
-            return false;
-        }
-
-        return inGlobal(new Callable<Boolean>() {
-            @Override public Boolean call() {
-                return sobj.isInstance(instance.sobj);
-            }
-        });
-    }
-
-    /**
-     * is this a function object?
-     *
-     * @return if this mirror wraps a ECMAScript function instance
-     */
-    public boolean isFunction() {
-        return sobj instanceof ScriptFunction;
-    }
-
-    /**
-     * is this a 'use strict' function object?
-     *
-     * @return true if this mirror represents a ECMAScript 'use strict' function
-     */
-    public boolean isStrictFunction() {
-        return isFunction() && ((ScriptFunction)sobj).isStrict();
-    }
-
-    /**
-     * is this an array object?
-     *
-     * @return if this mirror wraps a ECMAScript array object
-     */
-    public boolean isArray() {
-        return sobj.isArray();
-    }
-
     /**
      * Utility to check if given object is ECMAScript undefined value
      *
--- a/src/jdk/nashorn/internal/ir/IdentNode.java	Wed Sep 11 10:27:25 2013 +0200
+++ b/src/jdk/nashorn/internal/ir/IdentNode.java	Wed Sep 11 20:49:28 2013 +0530
@@ -168,6 +168,7 @@
      *     return 3;
      *   }
      * }
+     * </pre>
      *
      * @return true if can have callsite type
      */
--- a/src/jdk/nashorn/internal/lookup/MethodHandleFactory.java	Wed Sep 11 10:27:25 2013 +0200
+++ b/src/jdk/nashorn/internal/lookup/MethodHandleFactory.java	Wed Sep 11 20:49:28 2013 +0530
@@ -565,7 +565,7 @@
 
         @Override
         public MethodHandle asSpreader(final MethodHandle handle, final Class<?> arrayType, final int arrayLength) {
-            final MethodHandle mh = super.asCollector(handle, arrayType, arrayLength);
+            final MethodHandle mh = super.asSpreader(handle, arrayType, arrayLength);
             return debug(mh, "asSpreader", handle, arrayType, arrayLength);
         }
 
--- a/src/jdk/nashorn/internal/objects/NativeArray.java	Wed Sep 11 10:27:25 2013 +0200
+++ b/src/jdk/nashorn/internal/objects/NativeArray.java	Wed Sep 11 20:49:28 2013 +0530
@@ -41,7 +41,7 @@
 import java.util.List;
 import java.util.concurrent.Callable;
 
-import jdk.nashorn.api.scripting.ScriptObjectMirror;
+import jdk.nashorn.api.scripting.JSObject;
 import jdk.nashorn.internal.objects.annotations.Attribute;
 import jdk.nashorn.internal.objects.annotations.Constructor;
 import jdk.nashorn.internal.objects.annotations.Function;
@@ -374,7 +374,7 @@
     public static Object isArray(final Object self, final Object arg) {
         return isArray(arg) || (arg == Global.instance().getArrayPrototype())
                 || (arg instanceof NativeRegExpExecResult)
-                || (arg instanceof ScriptObjectMirror && ((ScriptObjectMirror)arg).isArray());
+                || (arg instanceof JSObject && ((JSObject)arg).isArray());
     }
 
     /**
--- a/src/jdk/nashorn/internal/objects/NativeFunction.java	Wed Sep 11 10:27:25 2013 +0200
+++ b/src/jdk/nashorn/internal/objects/NativeFunction.java	Wed Sep 11 20:49:28 2013 +0530
@@ -30,7 +30,7 @@
 
 import java.util.List;
 
-import jdk.nashorn.api.scripting.ScriptObjectMirror;
+import jdk.nashorn.api.scripting.JSObject;
 import jdk.nashorn.internal.objects.annotations.Attribute;
 import jdk.nashorn.internal.objects.annotations.Constructor;
 import jdk.nashorn.internal.objects.annotations.Function;
@@ -88,7 +88,7 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE)
     public static Object apply(final Object self, final Object thiz, final Object array) {
-        if (!(self instanceof ScriptFunction)) {
+        if (!(self instanceof ScriptFunction) && !(self instanceof JSObject)) {
             throw typeError("not.a.function", ScriptRuntime.safeToString(self));
         }
 
@@ -111,21 +111,27 @@
             list.toArray(args = new Object[list.size()]);
         } else if (array == null || array == UNDEFINED) {
             args = ScriptRuntime.EMPTY_ARRAY;
-        } else if (array instanceof ScriptObjectMirror) {
-            // look for array-like ScriptObjectMirror object
-            final ScriptObjectMirror mirror = (ScriptObjectMirror)array;
-            final Object       len  = mirror.containsKey("length")? mirror.getMember("length") : Integer.valueOf(0);
+        } else if (array instanceof JSObject) {
+            // look for array-like JSObject object
+            final JSObject jsObj = (JSObject)array;
+            final Object       len  = jsObj.hasMember("length")? jsObj.getMember("length") : Integer.valueOf(0);
             final int n = (int)JSType.toUint32(len);
 
             args = new Object[n];
             for (int i = 0; i < args.length; i++) {
-                args[i] = mirror.containsKey(i)? mirror.getSlot(i) : UNDEFINED;
+                args[i] = jsObj.hasSlot(i)? jsObj.getSlot(i) : UNDEFINED;
             }
         } else {
             throw typeError("function.apply.expects.array");
         }
 
-        return ScriptRuntime.apply((ScriptFunction)self, thiz, args);
+        if (self instanceof ScriptFunction) {
+            return ScriptRuntime.apply((ScriptFunction)self, thiz, args);
+        } else if (self instanceof JSObject) {
+            return ((JSObject)self).call(thiz, args);
+        }
+
+        throw new AssertionError("should not reach here");
     }
 
     /**
@@ -137,7 +143,7 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
     public static Object call(final Object self, final Object... args) {
-        if (!(self instanceof ScriptFunction)) {
+        if (!(self instanceof ScriptFunction) && !(self instanceof JSObject)) {
             throw typeError("not.a.function", ScriptRuntime.safeToString(self));
         }
 
@@ -151,7 +157,13 @@
             arguments = ScriptRuntime.EMPTY_ARRAY;
         }
 
-        return ScriptRuntime.apply((ScriptFunction)self, thiz, arguments);
+        if (self instanceof ScriptFunction) {
+            return ScriptRuntime.apply((ScriptFunction)self, thiz, arguments);
+        } else if (self instanceof JSObject) {
+            return ((JSObject)self).call(thiz, arguments);
+        }
+
+        throw new AssertionError("should not reach here");
     }
 
     /**
--- a/src/jdk/nashorn/internal/runtime/ScriptRuntime.java	Wed Sep 11 10:27:25 2013 +0200
+++ b/src/jdk/nashorn/internal/runtime/ScriptRuntime.java	Wed Sep 11 20:49:28 2013 +0530
@@ -43,6 +43,7 @@
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import jdk.internal.dynalink.beans.StaticClass;
+import jdk.nashorn.api.scripting.JSObject;
 import jdk.nashorn.api.scripting.ScriptObjectMirror;
 import jdk.nashorn.internal.codegen.CompilerConstants.Call;
 import jdk.nashorn.internal.ir.debug.JSONWriter;
@@ -190,8 +191,8 @@
         case FUNCTION:
             if (self instanceof ScriptObject) {
                 className = ((ScriptObject)self).getClassName();
-            } else if (self instanceof ScriptObjectMirror) {
-                className = ((ScriptObjectMirror)self).getClassName();
+            } else if (self instanceof JSObject) {
+                className = ((JSObject)self).getClassName();
             } else {
                 className = self.getClass().getName();
             }
@@ -245,8 +246,8 @@
             return new RangeIterator(Array.getLength(obj));
         }
 
-        if (obj instanceof ScriptObjectMirror) {
-            return ((ScriptObjectMirror)obj).keySet().iterator();
+        if (obj instanceof JSObject) {
+            return ((JSObject)obj).keySet().iterator();
         }
 
         if (obj instanceof List) {
@@ -323,8 +324,8 @@
             };
         }
 
-        if (obj instanceof ScriptObjectMirror) {
-            return ((ScriptObjectMirror)obj).values().iterator();
+        if (obj instanceof JSObject) {
+            return ((JSObject)obj).values().iterator();
         }
 
         if (obj instanceof Map) {
@@ -571,8 +572,8 @@
                 throw typeError("cant.get.property", safeToString(property), "null");
             } else if (JSType.isPrimitive(obj)) {
                 obj = ((ScriptObject)JSType.toScriptObject(obj)).get(property);
-            } else if (obj instanceof ScriptObjectMirror) {
-                obj = ((ScriptObjectMirror)obj).getMember(property.toString());
+            } else if (obj instanceof JSObject) {
+                obj = ((JSObject)obj).getMember(property.toString());
             } else {
                 obj = UNDEFINED;
             }
@@ -624,6 +625,11 @@
             return ((ScriptObject) JSType.toScriptObject(obj)).delete(property, Boolean.TRUE.equals(strict));
         }
 
+        if (obj instanceof JSObject) {
+            ((JSObject)obj).removeMember(Objects.toString(property));
+            return true;
+        }
+
         // if object is not reference type, vacuously delete is successful.
         return true;
     }
@@ -815,6 +821,10 @@
                 return ((ScriptObject)obj).has(property);
             }
 
+            if (obj instanceof JSObject) {
+                return ((JSObject)obj).hasMember(Objects.toString(property));
+            }
+
             return false;
         }
 
@@ -841,11 +851,13 @@
             return ((StaticClass)clazz).getRepresentedClass().isInstance(obj);
         }
 
-        if (clazz instanceof ScriptObjectMirror) {
-            if (obj instanceof ScriptObjectMirror) {
-                return ((ScriptObjectMirror)clazz).isInstance((ScriptObjectMirror)obj);
-            }
-            return false;
+        if (clazz instanceof JSObject) {
+            return ((JSObject)clazz).isInstance(obj);
+        }
+
+        // provide for reverse hook
+        if (obj instanceof JSObject) {
+            return ((JSObject)obj).isInstanceOf(clazz);
         }
 
         throw typeError("instanceof.on.non.object");
--- a/src/jdk/nashorn/internal/runtime/arrays/ArrayLikeIterator.java	Wed Sep 11 10:27:25 2013 +0200
+++ b/src/jdk/nashorn/internal/runtime/arrays/ArrayLikeIterator.java	Wed Sep 11 20:49:28 2013 +0530
@@ -27,7 +27,7 @@
 
 import java.util.Iterator;
 import java.util.List;
-import jdk.nashorn.api.scripting.ScriptObjectMirror;
+import jdk.nashorn.api.scripting.JSObject;
 import jdk.nashorn.internal.runtime.JSType;
 import jdk.nashorn.internal.runtime.ScriptObject;
 
@@ -127,8 +127,8 @@
             return new ScriptObjectIterator((ScriptObject)obj, includeUndefined);
         }
 
-        if (obj instanceof ScriptObjectMirror) {
-            return new ScriptObjectMirrorIterator((ScriptObjectMirror)obj, includeUndefined);
+        if (obj instanceof JSObject) {
+            return new JSObjectIterator((JSObject)obj, includeUndefined);
         }
 
         if (obj instanceof List) {
@@ -160,8 +160,8 @@
             return new ReverseScriptObjectIterator((ScriptObject)obj, includeUndefined);
         }
 
-        if (obj instanceof ScriptObjectMirror) {
-            return new ReverseScriptObjectMirrorIterator((ScriptObjectMirror)obj, includeUndefined);
+        if (obj instanceof JSObject) {
+            return new ReverseJSObjectIterator((JSObject)obj, includeUndefined);
         }
 
         if (obj instanceof List) {
--- a/src/jdk/nashorn/internal/runtime/arrays/IteratorAction.java	Wed Sep 11 10:27:25 2013 +0200
+++ b/src/jdk/nashorn/internal/runtime/arrays/IteratorAction.java	Wed Sep 11 20:49:28 2013 +0530
@@ -27,7 +27,7 @@
 
 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
 
-import jdk.nashorn.api.scripting.ScriptObjectMirror;
+import jdk.nashorn.api.scripting.JSObject;
 import jdk.nashorn.internal.runtime.Context;
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptRuntime;
@@ -101,9 +101,9 @@
         final boolean strict;
         if (callbackfn instanceof ScriptFunction) {
             strict = ((ScriptFunction)callbackfn).isStrict();
-        } else if (callbackfn instanceof ScriptObjectMirror &&
-            ((ScriptObjectMirror)callbackfn).isFunction()) {
-            strict = ((ScriptObjectMirror)callbackfn).isStrictFunction();
+        } else if (callbackfn instanceof JSObject &&
+            ((JSObject)callbackfn).isFunction()) {
+            strict = ((JSObject)callbackfn).isStrictFunction();
         } else if (Bootstrap.isDynamicMethod(callbackfn) || Bootstrap.isFunctionalInterfaceObject(callbackfn)) {
             strict = false;
         } else {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk/nashorn/internal/runtime/arrays/JSObjectIterator.java	Wed Sep 11 20:49:28 2013 +0530
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.internal.runtime.arrays;
+
+import java.util.NoSuchElementException;
+import jdk.nashorn.api.scripting.JSObject;
+import jdk.nashorn.internal.runtime.JSType;
+
+/**
+ * Iterator over a ScriptObjectMirror
+ */
+class JSObjectIterator extends ArrayLikeIterator<Object> {
+
+    protected final JSObject obj;
+    private final long length;
+
+    JSObjectIterator(final JSObject obj, final boolean includeUndefined) {
+        super(includeUndefined);
+        this.obj    = obj;
+        this.length = JSType.toUint32(obj.hasMember("length")? obj.getMember("length") : 0);
+        this.index  = 0;
+    }
+
+    protected boolean indexInArray() {
+        return index < length;
+    }
+
+    @Override
+    public long getLength() {
+        return length;
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (length == 0L) {
+            return false; //return empty string if toUint32(length) == 0
+        }
+
+        while (indexInArray()) {
+            if (obj.hasSlot((int)index) || includeUndefined) {
+                break;
+            }
+            bumpIndex();
+        }
+
+        return indexInArray();
+    }
+
+    @Override
+    public Object next() {
+        if (indexInArray()) {
+            return obj.getSlot((int)bumpIndex());
+        }
+
+        throw new NoSuchElementException();
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk/nashorn/internal/runtime/arrays/ReverseJSObjectIterator.java	Wed Sep 11 20:49:28 2013 +0530
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.internal.runtime.arrays;
+
+import jdk.nashorn.api.scripting.JSObject;
+import jdk.nashorn.internal.runtime.JSType;
+
+/**
+ * Reverse iterator over a ScriptObjectMirror
+ */
+final class ReverseJSObjectIterator extends JSObjectIterator {
+
+    ReverseJSObjectIterator(final JSObject obj, final boolean includeUndefined) {
+        super(obj, includeUndefined);
+        this.index = JSType.toUint32(obj.hasMember("length")? obj.getMember("length") : 0) - 1;
+    }
+
+    @Override
+    public boolean isReverse() {
+        return true;
+    }
+
+    @Override
+    protected boolean indexInArray() {
+        return index >= 0;
+    }
+
+    @Override
+    protected long bumpIndex() {
+        return index--;
+    }
+}
+
--- a/src/jdk/nashorn/internal/runtime/arrays/ReverseScriptObjectMirrorIterator.java	Wed Sep 11 10:27:25 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.nashorn.internal.runtime.arrays;
-
-import jdk.nashorn.api.scripting.ScriptObjectMirror;
-import jdk.nashorn.internal.runtime.JSType;
-
-/**
- * Reverse iterator over a ScriptObjectMirror
- */
-final class ReverseScriptObjectMirrorIterator extends ScriptObjectMirrorIterator {
-
-    ReverseScriptObjectMirrorIterator(final ScriptObjectMirror obj, final boolean includeUndefined) {
-        super(obj, includeUndefined);
-        this.index = JSType.toUint32(obj.containsKey("length")? obj.getMember("length") : 0) - 1;
-    }
-
-    @Override
-    public boolean isReverse() {
-        return true;
-    }
-
-    @Override
-    protected boolean indexInArray() {
-        return index >= 0;
-    }
-
-    @Override
-    protected long bumpIndex() {
-        return index--;
-    }
-}
-
--- a/src/jdk/nashorn/internal/runtime/arrays/ScriptObjectMirrorIterator.java	Wed Sep 11 10:27:25 2013 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-/*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.nashorn.internal.runtime.arrays;
-
-import java.util.NoSuchElementException;
-import jdk.nashorn.api.scripting.ScriptObjectMirror;
-import jdk.nashorn.internal.runtime.JSType;
-
-/**
- * Iterator over a ScriptObjectMirror
- */
-class ScriptObjectMirrorIterator extends ArrayLikeIterator<Object> {
-
-    protected final ScriptObjectMirror obj;
-    private final long length;
-
-    ScriptObjectMirrorIterator(final ScriptObjectMirror obj, final boolean includeUndefined) {
-        super(includeUndefined);
-        this.obj    = obj;
-        this.length = JSType.toUint32(obj.containsKey("length")? obj.getMember("length") : 0);
-        this.index  = 0;
-    }
-
-    protected boolean indexInArray() {
-        return index < length;
-    }
-
-    @Override
-    public long getLength() {
-        return length;
-    }
-
-    @Override
-    public boolean hasNext() {
-        if (length == 0L) {
-            return false; //return empty string if toUint32(length) == 0
-        }
-
-        while (indexInArray()) {
-            if (obj.containsKey(index) || includeUndefined) {
-                break;
-            }
-            bumpIndex();
-        }
-
-        return indexInArray();
-    }
-
-    @Override
-    public Object next() {
-        if (indexInArray()) {
-            return obj.get(bumpIndex());
-        }
-
-        throw new NoSuchElementException();
-    }
-}
-
--- a/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java	Wed Sep 11 10:27:25 2013 +0200
+++ b/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java	Wed Sep 11 20:49:28 2013 +0530
@@ -39,7 +39,7 @@
 import jdk.internal.dynalink.beans.StaticClass;
 import jdk.internal.dynalink.linker.GuardedInvocation;
 import jdk.internal.dynalink.linker.LinkerServices;
-import jdk.nashorn.api.scripting.ScriptObjectMirror;
+import jdk.nashorn.api.scripting.JSObject;
 import jdk.nashorn.internal.codegen.CompilerConstants.Call;
 import jdk.nashorn.internal.codegen.RuntimeCallSite;
 import jdk.nashorn.internal.runtime.JSType;
@@ -87,7 +87,7 @@
         }
 
         return obj instanceof ScriptFunction ||
-            ((obj instanceof ScriptObjectMirror) && ((ScriptObjectMirror)obj).isFunction()) ||
+            ((obj instanceof JSObject) && ((JSObject)obj).isFunction()) ||
             isDynamicMethod(obj) ||
             isFunctionalInterfaceObject(obj) ||
             obj instanceof StaticClass;
--- a/src/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java	Wed Sep 11 10:27:25 2013 +0200
+++ b/src/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java	Wed Sep 11 20:49:28 2013 +0530
@@ -30,7 +30,6 @@
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
-import java.util.Objects;
 import jdk.internal.dynalink.CallSiteDescriptor;
 import jdk.internal.dynalink.linker.GuardedInvocation;
 import jdk.internal.dynalink.linker.LinkRequest;
@@ -88,8 +87,9 @@
             case "setElem":
                 return c > 2 ? findSetMethod(desc) : findSetIndexMethod();
             case "call":
+                return findCallMethod(desc, operator);
             case "callMethod":
-                return findCallMethod(desc, operator);
+                return findCallMethodMethod(desc, operator);
             case "new":
                 return findNewMethod(desc);
             default:
@@ -98,33 +98,37 @@
     }
 
     private static GuardedInvocation findGetMethod(final CallSiteDescriptor desc) {
-        final MethodHandle getter = MH.insertArguments(JSOBJECT_GET, 1, desc.getNameToken(2));
+        final MethodHandle getter = MH.insertArguments(JSOBJECT_GETMEMBER, 1, desc.getNameToken(2));
         return new GuardedInvocation(getter, null, IS_JSOBJECT_GUARD);
     }
 
     private static GuardedInvocation findGetIndexMethod() {
-        return new GuardedInvocation(JSOBJECT_GET, null, IS_JSOBJECT_GUARD);
+        return new GuardedInvocation(JSOBJECTLINKER_GET, null, IS_JSOBJECT_GUARD);
     }
 
     private static GuardedInvocation findSetMethod(final CallSiteDescriptor desc) {
-        final MethodHandle getter = MH.insertArguments(JSOBJECT_PUT, 1, desc.getNameToken(2));
+        final MethodHandle getter = MH.insertArguments(JSOBJECT_SETMEMBER, 1, desc.getNameToken(2));
         return new GuardedInvocation(getter, null, IS_JSOBJECT_GUARD);
     }
 
     private static GuardedInvocation findSetIndexMethod() {
-        return new GuardedInvocation(JSOBJECT_PUT, null, IS_JSOBJECT_GUARD);
+        return new GuardedInvocation(JSOBJECTLINKER_PUT, null, IS_JSOBJECT_GUARD);
     }
 
-    private static GuardedInvocation findCallMethod(final CallSiteDescriptor desc, final String operator) {
-        // if operator is "call", then 'self' is a JSObject function object already. Use 'call' as the method name
-        final String methodName = "callMethod".equals(operator)? desc.getNameToken(2) : "call";
-        MethodHandle func = MH.insertArguments(JSOBJECT_CALL, 1, methodName);
+    private static GuardedInvocation findCallMethodMethod(final CallSiteDescriptor desc, final String operator) {
+        final String methodName = desc.getNameToken(2);
+        MethodHandle func = MH.insertArguments(JSOBJECT_CALLMEMBER, 1, methodName);
         func = MH.asCollector(func, Object[].class, desc.getMethodType().parameterCount() - 1);
         return new GuardedInvocation(func, null, IS_JSOBJECT_GUARD);
     }
 
+    private static GuardedInvocation findCallMethod(final CallSiteDescriptor desc, final String operator) {
+        final MethodHandle func = MH.asCollector(JSOBJECT_CALL, Object[].class, desc.getMethodType().parameterCount() - 2);
+        return new GuardedInvocation(func, null, IS_JSOBJECT_GUARD);
+    }
+
     private static GuardedInvocation findNewMethod(final CallSiteDescriptor desc) {
-        MethodHandle func = MH.asCollector(JSOBJECT_NEW, Object[].class, desc.getMethodType().parameterCount() - 1);
+        final MethodHandle func = MH.asCollector(JSOBJECT_NEW, Object[].class, desc.getMethodType().parameterCount() - 1);
         return new GuardedInvocation(func, null, IS_JSOBJECT_GUARD);
     }
 
@@ -135,36 +139,30 @@
 
     @SuppressWarnings("unused")
     private static Object get(final Object jsobj, final Object key) {
-        if (key instanceof String) {
-            return ((JSObject)jsobj).getMember((String)key);
+        if (key instanceof Integer) {
+            return ((JSObject)jsobj).getSlot((int)(Integer)key);
         } else if (key instanceof Number) {
             final int index = getIndex((Number)key);
             if (index > -1) {
                 return ((JSObject)jsobj).getSlot(index);
             }
+        } else if (key instanceof String) {
+            return ((JSObject)jsobj).getMember((String)key);
         }
         return null;
     }
 
     @SuppressWarnings("unused")
     private static void put(final Object jsobj, final Object key, final Object value) {
-        if (key instanceof String) {
-            ((JSObject)jsobj).setMember((String)key, value);
+        if (key instanceof Integer) {
+            ((JSObject)jsobj).setSlot((int)(Integer)key, value);
         } else if (key instanceof Number) {
             ((JSObject)jsobj).setSlot(getIndex((Number)key), value);
+        } else if (key instanceof String) {
+            ((JSObject)jsobj).setMember((String)key, value);
         }
     }
 
-    @SuppressWarnings("unused")
-    private static Object call(final Object jsobj, final Object method, final Object... args) {
-        return ((JSObject)jsobj).call(Objects.toString(method), args);
-    }
-
-    @SuppressWarnings("unused")
-    private static Object newObject(final Object jsobj, final Object... args) {
-        return ((JSObject)jsobj).newObject(null, args);
-    }
-
     private static int getIndex(final Number n) {
         final double value = n.doubleValue();
         return JSType.isRepresentableAsInt(value) ? (int)value : -1;
@@ -172,11 +170,17 @@
 
     private static final MethodHandleFunctionality MH = MethodHandleFactory.getFunctionality();
 
-    private static final MethodHandle IS_JSOBJECT_GUARD = findOwnMH("isJSObject", boolean.class, Object.class);
-    private static final MethodHandle JSOBJECT_GET = findOwnMH("get", Object.class, Object.class, Object.class);
-    private static final MethodHandle JSOBJECT_PUT = findOwnMH("put", Void.TYPE, Object.class, Object.class, Object.class);
-    private static final MethodHandle JSOBJECT_CALL = findOwnMH("call", Object.class, Object.class, Object.class, Object[].class);
-    private static final MethodHandle JSOBJECT_NEW = findOwnMH("newObject", Object.class, Object.class, Object[].class);
+    // method handles of the current class
+    private static final MethodHandle IS_JSOBJECT_GUARD  = findOwnMH("isJSObject", boolean.class, Object.class);
+    private static final MethodHandle JSOBJECTLINKER_GET = findOwnMH("get", Object.class, Object.class, Object.class);
+    private static final MethodHandle JSOBJECTLINKER_PUT = findOwnMH("put", Void.TYPE, Object.class, Object.class, Object.class);
+
+    // method handles of JSObject class
+    private static final MethodHandle JSOBJECT_GETMEMBER  = findJSObjectMH("getMember", Object.class, String.class);
+    private static final MethodHandle JSOBJECT_SETMEMBER  = findJSObjectMH("setMember", Void.TYPE, String.class, Object.class);
+    private static final MethodHandle JSOBJECT_CALLMEMBER = findJSObjectMH("callMember", Object.class, String.class, Object[].class);
+    private static final MethodHandle JSOBJECT_CALL       = findJSObjectMH("call", Object.class, Object.class, Object[].class);
+    private static final MethodHandle JSOBJECT_NEW        = findJSObjectMH("newObject", Object.class, Object[].class);
 
     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
         final Class<?>   own = JSObjectLinker.class;
@@ -187,4 +191,14 @@
             return MH.findVirtual(MethodHandles.lookup(), own, name, mt);
         }
     }
+
+    private static MethodHandle findJSObjectMH(final String name, final Class<?> rtype, final Class<?>... types) {
+        final Class<?>   own = JSObject.class;
+        final MethodType mt  = MH.type(rtype, types);
+        try {
+            return MH.findVirtual(MethodHandles.publicLookup(), own, name, mt);
+        } catch (final MethodHandleFactory.LookupException e) {
+            return MH.findVirtual(MethodHandles.lookup(), own, name, mt);
+        }
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/src/jdk/nashorn/api/scripting/PluggableJSObjectTest.java	Wed Sep 11 20:49:28 2013 +0530
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.api.scripting;
+
+import java.nio.IntBuffer;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Set;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.fail;
+import org.testng.annotations.Test;
+
+/**
+ * Tests for pluggable external impls. of jdk.nashorn.api.scripting.JSObject.
+ *
+ * JDK-8024615: Refactor ScriptObjectMirror and JSObject to support external
+ * JSObject implementations.
+ */
+public class PluggableJSObjectTest {
+    public static class MapWrapperObject extends JSObject {
+        private final HashMap<String, Object> map = new HashMap<>();
+
+        public HashMap<String, Object> getMap() {
+            return map;
+        }
+
+        @Override
+        public Object getMember(String name) {
+            return map.get(name);
+        }
+
+        @Override
+        public void setMember(String name, Object value) {
+            map.put(name, value);
+        }
+
+        @Override
+        public boolean hasMember(String name) {
+            return map.containsKey(name);
+        }
+
+        @Override
+        public void removeMember(String name) {
+            map.remove(name);
+        }
+
+        @Override
+        public Set<String> keySet() {
+            return map.keySet();
+        }
+
+        @Override
+        public Collection<Object> values() {
+            return map.values();
+        }
+    }
+
+    @Test
+    // Named property access on a JSObject
+    public void namedAccessTest() {
+        final ScriptEngineManager m = new ScriptEngineManager();
+        final ScriptEngine e = m.getEngineByName("nashorn");
+        try {
+            final MapWrapperObject obj = new MapWrapperObject();
+            e.put("obj", obj);
+            obj.getMap().put("foo", "bar");
+
+            // property-like access on MapWrapperObject objects
+            assertEquals(e.eval("obj.foo"), "bar");
+            e.eval("obj.foo = 'hello'");
+            assertEquals(e.eval("'foo' in obj"), Boolean.TRUE);
+            assertEquals(e.eval("obj.foo"), "hello");
+            assertEquals(obj.getMap().get("foo"), "hello");
+            e.eval("delete obj.foo");
+            assertFalse(obj.getMap().containsKey("foo"));
+            assertEquals(e.eval("'foo' in obj"), Boolean.FALSE);
+        } catch (final Exception exp) {
+            exp.printStackTrace();
+            fail(exp.getMessage());
+        }
+    }
+
+    public static class BufferObject extends JSObject {
+        private final IntBuffer buf;
+
+        public BufferObject(int size) {
+            buf = IntBuffer.allocate(size);
+        }
+
+        public IntBuffer getBuffer() {
+            return buf;
+        }
+
+        @Override
+        public Object getMember(String name) {
+            return name.equals("length")? buf.capacity() : null;
+        }
+
+        @Override
+        public boolean hasSlot(int i) {
+            return i > -1 && i < buf.capacity();
+        }
+
+        @Override
+        public Object getSlot(int i) {
+            return buf.get(i);
+        }
+
+        @Override
+        public void setSlot(int i, Object value) {
+            buf.put(i, ((Number)value).intValue());
+        }
+
+        @Override
+        public boolean isArray() {
+            return true;
+        }
+    }
+
+    @Test
+    // array-like indexed access for a JSObject
+    public void indexedAccessTest() {
+        final ScriptEngineManager m = new ScriptEngineManager();
+        final ScriptEngine e = m.getEngineByName("nashorn");
+        try {
+            final BufferObject buf = new BufferObject(2);
+            e.put("buf", buf);
+
+            // array-like access on BufferObject objects
+            assertEquals(e.eval("buf.length"), buf.getBuffer().capacity());
+            e.eval("buf[0] = 23");
+            assertEquals(buf.getBuffer().get(0), 23);
+            assertEquals(e.eval("buf[0]"), 23);
+            assertEquals(e.eval("buf[1]"), 0);
+            buf.getBuffer().put(1, 42);
+            assertEquals(e.eval("buf[1]"), 42);
+            assertEquals(e.eval("Array.isArray(buf)"), Boolean.TRUE);
+        } catch (final Exception exp) {
+            exp.printStackTrace();
+            fail(exp.getMessage());
+        }
+    }
+
+    public static class Adder extends JSObject {
+        @Override
+        public Object call(Object thiz, Object... args) {
+            double res = 0.0;
+            for (Object arg : args) {
+                res += ((Number)arg).doubleValue();
+            }
+            return res;
+        }
+
+        @Override
+        public boolean isFunction() {
+            return true;
+        }
+    }
+
+    @Test
+    // a callable JSObject
+    public void callableJSObjectTest() {
+        final ScriptEngineManager m = new ScriptEngineManager();
+        final ScriptEngine e = m.getEngineByName("nashorn");
+        try {
+            e.put("sum", new Adder());
+            // check callability of Adder objects
+            assertEquals(e.eval("typeof sum"), "function");
+            assertEquals(((Number)e.eval("sum(1, 2, 3, 4, 5)")).intValue(), 15);
+        } catch (final Exception exp) {
+            exp.printStackTrace();
+            fail(exp.getMessage());
+        }
+    }
+
+    public static class Factory extends JSObject {
+        @Override
+        public Object newObject(Object... args) {
+            return new HashMap<Object, Object>();
+        }
+
+        @Override
+        public boolean isFunction() {
+            return true;
+        }
+    }
+
+    @Test
+    // a factory JSObject
+    public void factoryJSObjectTest() {
+        final ScriptEngineManager m = new ScriptEngineManager();
+        final ScriptEngine e = m.getEngineByName("nashorn");
+        try {
+            e.put("Factory", new Factory());
+
+            // check new on Factory
+            assertEquals(e.eval("typeof Factory"), "function");
+            assertEquals(e.eval("typeof new Factory()"), "object");
+            assertEquals(e.eval("(new Factory()) instanceof java.util.Map"), Boolean.TRUE);
+        } catch (final Exception exp) {
+            exp.printStackTrace();
+            fail(exp.getMessage());
+        }
+    }
+
+    @Test
+    // iteration tests
+    public void iteratingJSObjectTest() {
+        final ScriptEngineManager m = new ScriptEngineManager();
+        final ScriptEngine e = m.getEngineByName("nashorn");
+        try {
+            final MapWrapperObject obj = new MapWrapperObject();
+            obj.setMember("foo", "hello");
+            obj.setMember("bar", "world");
+            e.put("obj", obj);
+
+            // check for..in
+            Object val = e.eval("var str = ''; for (i in obj) str += i; str");
+            assertEquals(val.toString(), "foobar");
+
+            // check for..each..in
+            val = e.eval("var str = ''; for each (i in obj) str += i; str");
+            assertEquals(val.toString(), "helloworld");
+        } catch (final Exception exp) {
+            exp.printStackTrace();
+            fail(exp.getMessage());
+        }
+    }
+}
--- a/test/src/jdk/nashorn/api/scripting/ScriptObjectMirrorTest.java	Wed Sep 11 10:27:25 2013 +0200
+++ b/test/src/jdk/nashorn/api/scripting/ScriptObjectMirrorTest.java	Wed Sep 11 20:49:28 2013 +0530
@@ -140,8 +140,8 @@
                 fail("obj[1] != 'world'");
             }
 
-            if (!obj.call("func", new Object[0]).equals("hello")) {
-                fail("obj.call('func') != 'hello'");
+            if (!obj.callMember("func", new Object[0]).equals("hello")) {
+                fail("obj.func() != 'hello'");
             }
 
             // try setting properties
@@ -210,8 +210,8 @@
 
         e.eval("function func() {}");
         e2.put("foo", e.get("func"));
-        final Object e2global = e2.eval("this");
-        final Object newObj = ((ScriptObjectMirror) e2global).newObject("foo");
+        final ScriptObjectMirror e2global = (ScriptObjectMirror)e2.eval("this");
+        final Object newObj = ((ScriptObjectMirror)e2global.getMember("foo")).newObject();
         assertTrue(newObj instanceof ScriptObjectMirror);
     }
 
@@ -223,8 +223,8 @@
 
         e.eval("function func() {}");
         e2.put("func", e.get("func"));
-        final Object e2obj = e2.eval("({ foo: func })");
-        final Object newObj = ((ScriptObjectMirror) e2obj).newObject("foo");
+        final ScriptObjectMirror e2obj = (ScriptObjectMirror)e2.eval("({ foo: func })");
+        final Object newObj = ((ScriptObjectMirror)e2obj.getMember("foo")).newObject();
         assertTrue(newObj instanceof ScriptObjectMirror);
     }
 }