Index: src/org/mozilla/javascript/ScriptableObject.java
===================================================================
RCS file: /cvsroot/mozilla/js/rhino/src/org/mozilla/javascript/ScriptableObject.java,v
retrieving revision 1.137
diff -u -r1.137 ScriptableObject.java
--- src/org/mozilla/javascript/ScriptableObject.java	7 Aug 2008 20:59:49 -0000	1.137
+++ src/org/mozilla/javascript/ScriptableObject.java	30 Oct 2008 15:17:31 -0000
@@ -155,6 +155,21 @@
     private static final int SLOT_MODIFY_GETTER_SETTER = 4;
     private static final int SLOT_MODIFY_CONST = 5;
 
+    // Used to indicate a failed MOP call.
+    private static final Object MOP_CALL_FAILED = new Object();
+
+    private Scriptable metaObject;
+
+    // Guard to protect against infinite recursion with meta object invocation
+    private ThreadLocal<Boolean> disableMop = new ThreadLocal<Boolean>() {
+        protected Boolean initialValue() {
+            return Boolean.FALSE;
+        }
+    };
+
+	// Hack to allow activation of the feature from scripts for testing    
+    public static boolean metaEnable = true;
+
     private static class Slot implements Serializable
     {
         private static final long serialVersionUID = -6090581677123995491L;
@@ -248,26 +263,97 @@
     public abstract String getClassName();
 
     /**
+     * Gets the meta object for the specified object.
+     */
+    protected Scriptable getMetaObject(Scriptable object)
+    {
+        if (!metaEnable)
+            return null;
+        if (!disableMop.get()) {
+            Scriptable meta;
+            Scriptable obj = this;
+            do {
+                meta = ((ScriptableObject) obj).metaObject;
+                obj = obj.getPrototype();
+            } while (meta == null && obj != null && obj instanceof ScriptableObject);
+            // We want to disable MOP for the meta object itself
+            if (meta != null)
+                meta.put("__meta__", meta, null);
+            return meta;
+        }  else {
+        }
+        return null;
+    }
+    
+    /**
+     * Call the function on the specified meta object, if the function
+     * is specified by the meta object.
+     * 
+     * @param meta  Meta object which may have the function.
+     * @param name  Function name to call.
+     * @param start  Scope of the meta object, used to disable the meta object.
+     * @param args  Arguments to pass to the function.
+     * 
+     * @return  Result of the call, or MOP_CALL_FAILED.
+     */
+    protected Object callMetaFunction(Scriptable meta, String name,
+            Scriptable start, Object[] args)
+    {
+        Object value = MOP_CALL_FAILED;
+        Context cx = Context.getCurrentContext();
+        if (cx != null) {
+            Object obj = meta.get(name, meta);
+            if (obj != Scriptable.NOT_FOUND && obj instanceof Function) {
+                try {
+                    disableMop.set(true);
+                    value = ((Function) obj).call(cx, start, start, args);
+                } finally {
+                    disableMop.set(false);
+                }
+            }
+        }
+        return value;
+    }
+    
+    /**
      * Returns true if the named property is defined.
-     *
+     * 
+     * This looks for a meta object, specified by the __meta__ property. If one
+     * is found, __meta__.has(thisObj,property) will be called.
+     * 
      * @param name the name of the property
      * @param start the object in which the lookup began
      * @return true if and only if the property was found in the object
      */
-    public boolean has(String name, Scriptable start)
-    {
+    public boolean has(String name, Scriptable start) {
+        if (metaEnable && "__meta__".equals(name))
+            return metaObject != null;
+        Scriptable meta = this.getMetaObject(start);
+        if (meta != null) {
+            Object value = this.callMetaFunction(meta, "has", start,
+                    new Object[] { name });
+            if (value != MOP_CALL_FAILED)
+                return ScriptRuntime.toBoolean(value);
+        }
         return null != getSlot(name, 0, SLOT_QUERY);
     }
 
     /**
      * Returns true if the property index is defined.
+     * Looks for __meta__.has.  If not found, works normally.
      *
      * @param index the numeric index for the property
      * @param start the object in which the lookup began
      * @return true if and only if the property was found in the object
      */
-    public boolean has(int index, Scriptable start)
-    {
+    public boolean has(int index, Scriptable start) {
+        Scriptable meta = this.getMetaObject(start);
+        if (meta != null) {
+            Object value = this.callMetaFunction(meta, "has", start,
+                    new Object[] { index });
+            if (value != MOP_CALL_FAILED)
+                return ScriptRuntime.toBoolean(value);
+        }
         return null != getSlot(null, index, SLOT_QUERY);
     }
 
@@ -276,6 +362,8 @@
      *
      * If the property was created using defineProperty, the
      * appropriate getter method is called.
+     * This checks for the existence of a __meta__ property with a get method.
+     * Otherwise, this method will work normally.
      *
      * @param name the name of the property
      * @param start the object in which the lookup began
@@ -283,18 +371,36 @@
      */
     public Object get(String name, Scriptable start)
     {
+        if (metaEnable && "__meta__".equals(name))
+            return metaObject;
+        Scriptable meta = this.getMetaObject(start);
+        if (meta != null) {
+            Object value = this.callMetaFunction(meta, "get", start,
+                    new Object[] { name });
+            if (value != MOP_CALL_FAILED)
+                return value;
+        }
         return getImpl(name, 0, start);
     }
 
     /**
      * Returns the value of the indexed property or NOT_FOUND.
+     * This checks for the existence of a __meta__ property with a get method.
+     * Otherwise, this method will work normally.
      *
      * @param index the numeric index for the property
      * @param start the object in which the lookup began
      * @return the value of the property (may be null), or NOT_FOUND
      */
-    public Object get(int index, Scriptable start)
-    {
+    public Object get(int index, Scriptable start) {
+        Scriptable meta = this.getMetaObject(start);
+        if (meta != null)
+        {
+            Object value = this.callMetaFunction(meta, "get", start,
+                    new Object[] { index });
+            if (value != MOP_CALL_FAILED)
+                return value;
+        }
         return getImpl(null, index, start);
     }
 
@@ -308,6 +414,8 @@
      * taken.
      * This method will actually set the property in the start
      * object.
+     * 
+     * Looks for __meta__.set.  Otherwise, works normally.
      *
      * @param name the name of the property
      * @param start the object whose property is being set
@@ -315,22 +423,44 @@
      */
     public void put(String name, Scriptable start, Object value)
     {
-        if (putImpl(name, 0, start, value, EMPTY))
-            return;
+        if (metaEnable && "__meta__".equals(name)) {
+            if (value instanceof Scriptable)
+                metaObject = (Scriptable) value;
+        } else {
+            Scriptable meta = this.getMetaObject(start);
+            if (meta != null) {
+                Object result = this.callMetaFunction(meta, "set", start,
+                        new Object[] { name, value });
+                if (result != MOP_CALL_FAILED)
+                    return;
+            }
+            if (putImpl(name, 0, start, value, EMPTY))
+                return;
 
-        if (start == this) throw Kit.codeBug();
-        start.put(name, start, value);
+            if (start == this) throw Kit.codeBug();
+            start.put(name, start, value);
+        }
     }
 
     /**
      * Sets the value of the indexed property, creating it if need be.
      *
+     * Looks for __meta__.set.  Otherwise, works normally.
+     * 
      * @param index the numeric index for the property
      * @param start the object whose property is being set
      * @param value value to set the property to
      */
     public void put(int index, Scriptable start, Object value)
     {
+        Scriptable meta = this.getMetaObject(start);
+        if (meta != null)
+        {
+            Object result = this.callMetaFunction(meta, "set", start,
+            		new Object[] { index, value });
+            if (result != MOP_CALL_FAILED)
+                return;
+        }
         if (putImpl(null, index, start, value, EMPTY))
             return;
 
@@ -343,13 +473,27 @@
      *
      * If the property is not found, or it has the PERMANENT attribute,
      * no action is taken.
+     * 
+     * Looks for __meta__.remove().  If not found, works normally.  
      *
      * @param name the name of the property
      */
     public void delete(String name)
     {
-        checkNotSealed(name, 0);
-        accessSlot(name, 0, SLOT_REMOVE);
+        if (metaEnable && "__meta__".equals(name)) {
+            metaObject = null;
+        } else {
+            Scriptable meta = this.getMetaObject(this);
+            if (meta != null)
+            {
+                Object result = this.callMetaFunction(meta, "remove", this,
+                        new Object[] { name });
+                if (result != MOP_CALL_FAILED)
+                    return;
+            }
+            checkNotSealed(name, 0);
+            accessSlot(name, 0, SLOT_REMOVE);
+        }
     }
 
     /**
@@ -357,11 +501,21 @@
      *
      * If the property is not found, or it has the PERMANENT attribute,
      * no action is taken.
+     * 
+     * Looks for __meta__.remove().  If not found, works normally.
      *
      * @param index the numeric index for the property
      */
     public void delete(int index)
     {
+        Scriptable start = this;
+        Scriptable meta = this.getMetaObject(start);
+        if (meta != null) {
+            Object result = this.callMetaFunction(meta, "remove", start,
+                    new Object[] { index });
+            if (result != MOP_CALL_FAILED)
+                return;
+        }
         checkNotSealed(null, index);
         accessSlot(null, index, SLOT_REMOVE);
     }
@@ -628,6 +782,7 @@
 
     /**
      * Sets the prototype of the object.
+     * @return 
      */
     public void setPrototype(Scriptable m)
     {
@@ -651,7 +806,8 @@
     }
 
     /**
-     * Returns an array of ids for the properties of the object.
+     * Returns an array of ids for the properties of the object, deferring to
+     * __meta__.getIds(thisObj), if that exists.
      *
      * <p>Any properties with the attribute DONTENUM are not listed. <p>
      *
@@ -661,7 +817,30 @@
      * Integer entry in the returned array. Properties accessed by
      * a String will have a String entry in the returned array.
      */
-    public Object[] getIds() {
+    public Object[] getIds()
+    {
+        Scriptable start = this;
+        Scriptable meta = this.getMetaObject(start);
+        
+        if (meta != null)
+        {
+            Object result = this.callMetaFunction(meta, "getIds", start,
+                    new Object[] {}); // TODO: null ok too?
+            if (result != MOP_CALL_FAILED) {
+                if (result instanceof NativeArray) {
+                    NativeArray na = (NativeArray) result;
+                    Object[] naIds = na.getIds();
+                    Object[] ids = new Object[naIds.length];
+                    for (int i=0; i< naIds.length; i++)
+                        ids[i] = na.get(i, na);
+                    return ids;
+                }
+                else {
+                    throw new IllegalArgumentException("Did not receive an array of ids.  Got a "
+                            + result.getClass().getName());
+                }
+            }
+        }
         return getIds(false);
     }
 
