2002/03/21 01:19:33
[org.ibex.core.git] / src / org / mozilla / javascript / ScriptableObject.java
diff --git a/src/org/mozilla/javascript/ScriptableObject.java b/src/org/mozilla/javascript/ScriptableObject.java
new file mode 100644 (file)
index 0000000..7c2bf9c
--- /dev/null
@@ -0,0 +1,1807 @@
+/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-\r
+ *\r
+ * The contents of this file are subject to the Netscape Public\r
+ * License Version 1.1 (the "License"); you may not use this file\r
+ * except in compliance with the License. You may obtain a copy of\r
+ * the License at http://www.mozilla.org/NPL/\r
+ *\r
+ * Software distributed under the License is distributed on an "AS\r
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr\r
+ * implied. See the License for the specific language governing\r
+ * rights and limitations under the License.\r
+ *\r
+ * The Original Code is Rhino code, released\r
+ * May 6, 1999.\r
+ *\r
+ * The Initial Developer of the Original Code is Netscape\r
+ * Communications Corporation.  Portions created by Netscape are\r
+ * Copyright (C) 1997-1999 Netscape Communications Corporation. All\r
+ * Rights Reserved.\r
+ *\r
+ * Contributor(s): \r
+ * Norris Boyd\r
+ * Igor Bukanov\r
+ * Roger Lawrence\r
+ *\r
+ * Alternatively, the contents of this file may be used under the\r
+ * terms of the GNU Public License (the "GPL"), in which case the\r
+ * provisions of the GPL are applicable instead of those above.\r
+ * If you wish to allow use of your version of this file only\r
+ * under the terms of the GPL and not to allow others to use your\r
+ * version of this file under the NPL, indicate your decision by\r
+ * deleting the provisions above and replace them with the notice\r
+ * and other provisions required by the GPL.  If you do not delete\r
+ * the provisions above, a recipient may use your version of this\r
+ * file under either the NPL or the GPL.\r
+ */\r
+\r
+// API class\r
+\r
+package org.mozilla.javascript;\r
+\r
+import java.lang.reflect.*;\r
+import java.util.Hashtable;\r
+\r
+/**\r
+ * This is the default implementation of the Scriptable interface. This\r
+ * class provides convenient default behavior that makes it easier to\r
+ * define host objects.\r
+ * <p>\r
+ * Various properties and methods of JavaScript objects can be conveniently\r
+ * defined using methods of ScriptableObject.\r
+ * <p>\r
+ * Classes extending ScriptableObject must define the getClassName method.\r
+ *\r
+ * @see org.mozilla.javascript.Scriptable\r
+ * @author Norris Boyd\r
+ */\r
+\r
+public abstract class ScriptableObject implements Scriptable {\r
+\r
+    /**\r
+     * The empty property attribute.\r
+     *\r
+     * Used by getAttributes() and setAttributes().\r
+     *\r
+     * @see org.mozilla.javascript.ScriptableObject#getAttributes\r
+     * @see org.mozilla.javascript.ScriptableObject#setAttributes\r
+     */\r
+    public static final int EMPTY =     0x00;\r
+\r
+    /**\r
+     * Property attribute indicating assignment to this property is ignored.\r
+     *\r
+     * @see org.mozilla.javascript.ScriptableObject#put\r
+     * @see org.mozilla.javascript.ScriptableObject#getAttributes\r
+     * @see org.mozilla.javascript.ScriptableObject#setAttributes\r
+     */\r
+    public static final int READONLY =  0x01;\r
+\r
+    /**\r
+     * Property attribute indicating property is not enumerated.\r
+     *\r
+     * Only enumerated properties will be returned by getIds().\r
+     *\r
+     * @see org.mozilla.javascript.ScriptableObject#getIds\r
+     * @see org.mozilla.javascript.ScriptableObject#getAttributes\r
+     * @see org.mozilla.javascript.ScriptableObject#setAttributes\r
+     */\r
+    public static final int DONTENUM =  0x02;\r
+\r
+    /**\r
+     * Property attribute indicating property cannot be deleted.\r
+     *\r
+     * @see org.mozilla.javascript.ScriptableObject#delete\r
+     * @see org.mozilla.javascript.ScriptableObject#getAttributes\r
+     * @see org.mozilla.javascript.ScriptableObject#setAttributes\r
+     */\r
+    public static final int PERMANENT = 0x04;\r
+\r
+    /**\r
+     * Return the name of the class.\r
+     *\r
+     * This is typically the same name as the constructor.\r
+     * Classes extending ScriptableObject must implement this abstract\r
+     * method.\r
+     */\r
+    public abstract String getClassName();\r
+\r
+    /**\r
+     * Returns true if the named property is defined.\r
+     *\r
+     * @param name the name of the property\r
+     * @param start the object in which the lookup began\r
+     * @return true if and only if the property was found in the object\r
+     */\r
+    public boolean has(String name, Scriptable start) {\r
+        return getSlot(name, name.hashCode(), false) != null;\r
+    }\r
+\r
+    /**\r
+     * Returns true if the property index is defined.\r
+     *\r
+     * @param index the numeric index for the property\r
+     * @param start the object in which the lookup began\r
+     * @return true if and only if the property was found in the object\r
+     */\r
+    public boolean has(int index, Scriptable start) {\r
+        return getSlot(null, index, false) != null;\r
+    }\r
+\r
+    /**\r
+     * Returns the value of the named property or NOT_FOUND.\r
+     *\r
+     * If the property was created using defineProperty, the\r
+     * appropriate getter method is called.\r
+     *\r
+     * @param name the name of the property\r
+     * @param start the object in which the lookup began\r
+     * @return the value of the property (may be null), or NOT_FOUND\r
+     */\r
+    public Object get(String name, Scriptable start) {\r
+        Slot slot = lastAccess; // Get local copy\r
+        if (name == slot.stringKey) {\r
+            if (slot.wasDeleted == 0) { return slot.value; }\r
+        } \r
+        int hashCode = name.hashCode();\r
+        slot = getSlot(name, hashCode, false);\r
+        if (slot == null)\r
+            return Scriptable.NOT_FOUND;\r
+        if ((slot.flags & Slot.HAS_GETTER) != 0) {\r
+            GetterSlot getterSlot = (GetterSlot) slot;\r
+            try {\r
+                if (getterSlot.delegateTo == null) {\r
+                    // Walk the prototype chain to find an appropriate\r
+                    // object to invoke the getter on.\r
+                    Class clazz = getterSlot.getter.getDeclaringClass();\r
+                    while (!clazz.isInstance(start)) {\r
+                        start = start.getPrototype();\r
+                        if (start == null) {\r
+                            start = this;\r
+                            break;\r
+                        }\r
+                    }\r
+                    return getterSlot.getter.invoke(start, ScriptRuntime.emptyArgs);\r
+                }\r
+                Object[] args = { this };\r
+                return getterSlot.getter.invoke(getterSlot.delegateTo, args);\r
+            }\r
+            catch (InvocationTargetException e) {\r
+                throw WrappedException.wrapException(e);\r
+            }\r
+            catch (IllegalAccessException e) {\r
+                throw WrappedException.wrapException(e);\r
+            }\r
+        }\r
+        // Here stringKey.equals(name) holds, but it can be that \r
+        // slot.stringKey != name. To make last name cache work, need\r
+        // to change the key\r
+        slot.stringKey = name;\r
+\r
+        // Update cache. \r
+        lastAccess = slot;\r
+        return slot.value;\r
+    }\r
+\r
+    /**\r
+     * Returns the value of the indexed property or NOT_FOUND.\r
+     *\r
+     * @param index the numeric index for the property\r
+     * @param start the object in which the lookup began\r
+     * @return the value of the property (may be null), or NOT_FOUND\r
+     */\r
+    public Object get(int index, Scriptable start) {\r
+        Slot slot = getSlot(null, index, false);\r
+        if (slot == null)\r
+            return Scriptable.NOT_FOUND;\r
+        return slot.value;\r
+    }\r
+    \r
+    /**\r
+     * Sets the value of the named property, creating it if need be.\r
+     *\r
+     * If the property was created using defineProperty, the\r
+     * appropriate setter method is called. <p>\r
+     *\r
+     * If the property's attributes include READONLY, no action is\r
+     * taken.\r
+     * This method will actually set the property in the start\r
+     * object.\r
+     *\r
+     * @param name the name of the property\r
+     * @param start the object whose property is being set\r
+     * @param value value to set the property to\r
+     */\r
+    public void put(String name, Scriptable start, Object value) {\r
+        int hash = name.hashCode();\r
+        Slot slot = getSlot(name, hash, false);\r
+        if (slot == null) {\r
+            if (start != this) {\r
+                start.put(name, start, value);\r
+                return;\r
+            }\r
+            slot = getSlotToSet(name, hash, false);\r
+        }\r
+        if ((slot.attributes & ScriptableObject.READONLY) != 0)\r
+            return;\r
+        if ((slot.flags & Slot.HAS_SETTER) != 0) {\r
+            GetterSlot getterSlot = (GetterSlot) slot;\r
+            try {\r
+                Class pTypes[] = getterSlot.setter.getParameterTypes();\r
+                Class desired = pTypes[pTypes.length - 1];\r
+                Object actualArg\r
+                        = FunctionObject.convertArg(start, value, desired);\r
+                if (getterSlot.delegateTo == null) {\r
+                    // Walk the prototype chain to find an appropriate\r
+                    // object to invoke the setter on.\r
+                    Object[] arg = { actualArg };\r
+                    Class clazz = getterSlot.setter.getDeclaringClass();\r
+                    while (!clazz.isInstance(start)) {\r
+                        start = start.getPrototype();\r
+                        if (start == null) {\r
+                            start = this;\r
+                            break;\r
+                        }\r
+                    }\r
+                    Object v = getterSlot.setter.invoke(start, arg);\r
+                    if (getterSlot.setterReturnsValue) {\r
+                        slot.value = v;\r
+                        if (!(v instanceof Method))\r
+                            slot.flags = 0;\r
+                    }\r
+                    return;\r
+                }\r
+                Object[] args = { this, actualArg };\r
+                Object v = getterSlot.setter.invoke(getterSlot.delegateTo, args);\r
+                if (getterSlot.setterReturnsValue) {\r
+                    slot.value = v;\r
+                    if (!(v instanceof Method))\r
+                        slot.flags = 0;\r
+                }\r
+                return;\r
+            }\r
+            catch (InvocationTargetException e) {\r
+                throw WrappedException.wrapException(e);\r
+            }\r
+            catch (IllegalAccessException e) {\r
+                throw WrappedException.wrapException(e);\r
+            }\r
+        }\r
+        if (this == start) {\r
+            slot.value = value;\r
+            // Make cache work\r
+            slot.stringKey = name;\r
+            lastAccess = slot;\r
+        } else {\r
+            start.put(name, start, value);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Sets the value of the indexed property, creating it if need be.\r
+     *\r
+     * @param index the numeric index for the property\r
+     * @param start the object whose property is being set\r
+     * @param value value to set the property to\r
+     */\r
+    public void put(int index, Scriptable start, Object value) {\r
+        Slot slot = getSlot(null, index, false);\r
+        if (slot == null) {\r
+            if (start != this) {\r
+                start.put(index, start, value);\r
+                return;\r
+            }\r
+            slot = getSlotToSet(null, index, false);\r
+        }\r
+        if ((slot.attributes & ScriptableObject.READONLY) != 0)\r
+            return;\r
+        if (this == start) {\r
+            slot.value = value;\r
+        } else {\r
+            start.put(index, start, value);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Removes a named property from the object.\r
+     *\r
+     * If the property is not found, or it has the PERMANENT attribute,\r
+     * no action is taken.\r
+     *\r
+     * @param name the name of the property\r
+     */\r
+    public void delete(String name) {\r
+        removeSlot(name, name.hashCode());\r
+    }\r
+\r
+    /**\r
+     * Removes the indexed property from the object.\r
+     *\r
+     * If the property is not found, or it has the PERMANENT attribute,\r
+     * no action is taken.\r
+     *\r
+     * @param index the numeric index for the property\r
+     */\r
+    public void delete(int index) {\r
+        removeSlot(null, index);\r
+    }\r
+\r
+    /**\r
+     * Get the attributes of a named property.\r
+     *\r
+     * The property is specified by <code>name</code>\r
+     * as defined for <code>has</code>.<p>\r
+     *\r
+     * @param name the identifier for the property\r
+     * @param start the object in which the lookup began\r
+     * @return the bitset of attributes\r
+     * @exception PropertyException if the named property\r
+     *            is not found\r
+     * @see org.mozilla.javascript.ScriptableObject#has\r
+     * @see org.mozilla.javascript.ScriptableObject#READONLY\r
+     * @see org.mozilla.javascript.ScriptableObject#DONTENUM\r
+     * @see org.mozilla.javascript.ScriptableObject#PERMANENT\r
+     * @see org.mozilla.javascript.ScriptableObject#EMPTY\r
+     */\r
+    public int getAttributes(String name, Scriptable start)\r
+        throws PropertyException\r
+    {\r
+        Slot slot = getSlot(name, name.hashCode(), false);\r
+        if (slot == null) {\r
+            throw PropertyException.withMessage0("msg.prop.not.found");\r
+        }\r
+        return slot.attributes;\r
+    }\r
+\r
+    /**\r
+     * Get the attributes of an indexed property.\r
+     *\r
+     * @param index the numeric index for the property\r
+     * @param start the object in which the lookup began\r
+     * @exception PropertyException if the indexed property\r
+     *            is not found\r
+     * @return the bitset of attributes\r
+     * @see org.mozilla.javascript.ScriptableObject#has\r
+     * @see org.mozilla.javascript.ScriptableObject#READONLY\r
+     * @see org.mozilla.javascript.ScriptableObject#DONTENUM\r
+     * @see org.mozilla.javascript.ScriptableObject#PERMANENT\r
+     * @see org.mozilla.javascript.ScriptableObject#EMPTY\r
+     */\r
+    public int getAttributes(int index, Scriptable start)\r
+        throws PropertyException\r
+    {\r
+        Slot slot = getSlot(null, index, false);\r
+        if (slot == null) {\r
+            throw PropertyException.withMessage0("msg.prop.not.found");\r
+        }\r
+        return slot.attributes;\r
+    }\r
+\r
+    /**\r
+     * Set the attributes of a named property.\r
+     *\r
+     * The property is specified by <code>name</code>\r
+     * as defined for <code>has</code>.<p>\r
+     *\r
+     * The possible attributes are READONLY, DONTENUM,\r
+     * and PERMANENT. Combinations of attributes\r
+     * are expressed by the bitwise OR of attributes.\r
+     * EMPTY is the state of no attributes set. Any unused\r
+     * bits are reserved for future use.\r
+     *\r
+     * @param name the name of the property\r
+     * @param start the object in which the lookup began\r
+     * @param attributes the bitset of attributes\r
+     * @exception PropertyException if the named property\r
+     *            is not found\r
+     * @see org.mozilla.javascript.Scriptable#has\r
+     * @see org.mozilla.javascript.ScriptableObject#READONLY\r
+     * @see org.mozilla.javascript.ScriptableObject#DONTENUM\r
+     * @see org.mozilla.javascript.ScriptableObject#PERMANENT\r
+     * @see org.mozilla.javascript.ScriptableObject#EMPTY\r
+     */\r
+    public void setAttributes(String name, Scriptable start,\r
+                              int attributes)\r
+        throws PropertyException\r
+    {\r
+        final int mask = READONLY | DONTENUM | PERMANENT;\r
+        attributes &= mask; // mask out unused bits\r
+        Slot slot = getSlot(name, name.hashCode(), false);\r
+        if (slot == null) {\r
+            throw PropertyException.withMessage0("msg.prop.not.found");\r
+        }\r
+        slot.attributes = (short) attributes;\r
+    }\r
+\r
+    /**\r
+     * Set the attributes of an indexed property.\r
+     *\r
+     * @param index the numeric index for the property\r
+     * @param start the object in which the lookup began\r
+     * @param attributes the bitset of attributes\r
+     * @exception PropertyException if the indexed property\r
+     *            is not found\r
+     * @see org.mozilla.javascript.Scriptable#has\r
+     * @see org.mozilla.javascript.ScriptableObject#READONLY\r
+     * @see org.mozilla.javascript.ScriptableObject#DONTENUM\r
+     * @see org.mozilla.javascript.ScriptableObject#PERMANENT\r
+     * @see org.mozilla.javascript.ScriptableObject#EMPTY\r
+     */\r
+    public void setAttributes(int index, Scriptable start,\r
+                              int attributes)\r
+        throws PropertyException\r
+    {\r
+        Slot slot = getSlot(null, index, false);\r
+        if (slot == null) {\r
+            throw PropertyException.withMessage0("msg.prop.not.found");\r
+        }\r
+        slot.attributes = (short) attributes;\r
+    }\r
+\r
+    /**\r
+     * Returns the prototype of the object.\r
+     */\r
+    public Scriptable getPrototype() {\r
+        return prototype;\r
+    }\r
+\r
+    /**\r
+     * Sets the prototype of the object.\r
+     */\r
+    public void setPrototype(Scriptable m) {\r
+        prototype = m;\r
+    }\r
+\r
+    /**\r
+     * Returns the parent (enclosing) scope of the object.\r
+     */\r
+    public Scriptable getParentScope() {\r
+        return parent;\r
+    }\r
+\r
+    /**\r
+     * Sets the parent (enclosing) scope of the object.\r
+     */\r
+    public void setParentScope(Scriptable m) {\r
+        parent = m;\r
+    }\r
+\r
+    /**\r
+     * Returns an array of ids for the properties of the object.\r
+     *\r
+     * <p>Any properties with the attribute DONTENUM are not listed. <p>\r
+     *\r
+     * @return an array of java.lang.Objects with an entry for every\r
+     * listed property. Properties accessed via an integer index will \r
+     * have a corresponding\r
+     * Integer entry in the returned array. Properties accessed by\r
+     * a String will have a String entry in the returned array.\r
+     */\r
+    public Object[] getIds() {\r
+        return getIds(false);\r
+    }\r
+    \r
+    /**\r
+     * Returns an array of ids for the properties of the object.\r
+     *\r
+     * <p>All properties, even those with attribute DONTENUM, are listed. <p>\r
+     *\r
+     * @return an array of java.lang.Objects with an entry for every\r
+     * listed property. Properties accessed via an integer index will \r
+     * have a corresponding\r
+     * Integer entry in the returned array. Properties accessed by\r
+     * a String will have a String entry in the returned array.\r
+     */\r
+    public Object[] getAllIds() {\r
+        return getIds(true);\r
+    }\r
+    \r
+    /**\r
+     * Implements the [[DefaultValue]] internal method.\r
+     *\r
+     * <p>Note that the toPrimitive conversion is a no-op for\r
+     * every type other than Object, for which [[DefaultValue]]\r
+     * is called. See ECMA 9.1.<p>\r
+     *\r
+     * A <code>hint</code> of null means "no hint".\r
+     *\r
+     * @param typeHint the type hint\r
+     * @return the default value for the object\r
+     *\r
+     * See ECMA 8.6.2.6.\r
+     */\r
+    public Object getDefaultValue(Class typeHint) {\r
+        Object val;\r
+        Context cx = null;\r
+        try {\r
+            for (int i=0; i < 2; i++) {\r
+                if (typeHint == ScriptRuntime.StringClass ? i == 0 : i == 1) {\r
+                    Object v = getProperty(this, "toString");\r
+                    if (!(v instanceof Function))\r
+                        continue;\r
+                    Function fun = (Function) v;\r
+                    if (cx == null)\r
+                        cx = Context.getContext();\r
+                    val = fun.call(cx, fun.getParentScope(), this,\r
+                                   ScriptRuntime.emptyArgs);\r
+                } else {\r
+                    String hint;\r
+                    if (typeHint == null)\r
+                        hint = "undefined";\r
+                    else if (typeHint == ScriptRuntime.StringClass)\r
+                        hint = "string";\r
+                    else if (typeHint == ScriptRuntime.ScriptableClass)\r
+                        hint = "object";\r
+                    else if (typeHint == ScriptRuntime.FunctionClass)\r
+                        hint = "function";\r
+                    else if (typeHint == ScriptRuntime.BooleanClass || \r
+                                                typeHint == Boolean.TYPE)\r
+                        hint = "boolean";\r
+                    else if (typeHint == ScriptRuntime.NumberClass ||\r
+                             typeHint == ScriptRuntime.ByteClass || \r
+                             typeHint == Byte.TYPE ||\r
+                             typeHint == ScriptRuntime.ShortClass || \r
+                             typeHint == Short.TYPE ||\r
+                             typeHint == ScriptRuntime.IntegerClass || \r
+                             typeHint == Integer.TYPE ||\r
+                             typeHint == ScriptRuntime.FloatClass || \r
+                             typeHint == Float.TYPE ||\r
+                             typeHint == ScriptRuntime.DoubleClass || \r
+                             typeHint == Double.TYPE)\r
+                        hint = "number";\r
+                    else {\r
+                        throw Context.reportRuntimeError1(\r
+                            "msg.invalid.type", typeHint.toString());\r
+                    }\r
+                    Object v = getProperty(this, "valueOf");\r
+                    if (!(v instanceof Function))\r
+                        continue;\r
+                    Function fun = (Function) v;\r
+                    Object[] args = { hint };\r
+                    if (cx == null)\r
+                        cx = Context.getContext();\r
+                    val = fun.call(cx, fun.getParentScope(), this, args);\r
+                }\r
+                if (val != null && (val == Undefined.instance ||\r
+                                    !(val instanceof Scriptable) ||\r
+                                    typeHint == Scriptable.class ||\r
+                                    typeHint == Function.class))\r
+                {\r
+                    return val;\r
+                }\r
+                if (val instanceof NativeJavaObject) {\r
+                    // Let a wrapped java.lang.String pass for a primitive \r
+                    // string.\r
+                    Object u = ((Wrapper) val).unwrap();\r
+                    if (u instanceof String)\r
+                        return u;\r
+                }\r
+            }\r
+            // fall through to error \r
+        }\r
+        catch (JavaScriptException jse) {\r
+            // fall through to error \r
+        }\r
+        Object arg = (typeHint == null) ? "undefined" : typeHint.toString();\r
+        throw NativeGlobal.typeError1("msg.default.value", arg, this);\r
+    }\r
+\r
+    /**\r
+     * Implements the instanceof operator.\r
+     *\r
+     * <p>This operator has been proposed to ECMA.\r
+     *\r
+     * @param instance The value that appeared on the LHS of the instanceof\r
+     *              operator\r
+     * @return true if "this" appears in value's prototype chain\r
+     *\r
+     */\r
+    public boolean hasInstance(Scriptable instance) {\r
+        // Default for JS objects (other than Function) is to do prototype\r
+        // chasing.  This will be overridden in NativeFunction and non-JS objects.\r
+\r
+        return ScriptRuntime.jsDelegatesTo(instance, this);\r
+    }\r
+\r
+    /**\r
+     * Defines JavaScript objects from a Java class that implements Scriptable.\r
+     *\r
+     * If the given class has a method\r
+     * <pre>\r
+     * static void init(Context cx, Scriptable scope, boolean sealed);</pre>\r
+     *\r
+     * or its compatibility form \r
+     * <pre>\r
+     * static void init(Scriptable scope);</pre>\r
+     *\r
+     * then it is invoked and no further initialization is done.<p>\r
+     *\r
+     * However, if no such a method is found, then the class's constructors and\r
+     * methods are used to initialize a class in the following manner.<p>\r
+     *\r
+     * First, the zero-parameter constructor of the class is called to\r
+     * create the prototype. If no such constructor exists,\r
+     * a ClassDefinitionException is thrown. <p>\r
+     *\r
+     * Next, all methods are scanned for special prefixes that indicate that they\r
+     * have special meaning for defining JavaScript objects.\r
+     * These special prefixes are\r
+     * <ul>\r
+     * <li><code>jsFunction_</code> for a JavaScript function\r
+     * <li><code>jsStaticFunction_</code> for a JavaScript function that \r
+     *           is a property of the constructor\r
+     * <li><code>jsGet_</code> for a getter of a JavaScript property\r
+     * <li><code>jsSet_</code> for a setter of a JavaScript property\r
+     * <li><code>jsConstructor</code> for a JavaScript function that \r
+     *           is the constructor\r
+     * </ul><p>\r
+     *\r
+     * If the method's name begins with "jsFunction_", a JavaScript function \r
+     * is created with a name formed from the rest of the Java method name \r
+     * following "jsFunction_". So a Java method named "jsFunction_foo" will\r
+     * define a JavaScript method "foo". Calling this JavaScript function \r
+     * will cause the Java method to be called. The parameters of the method\r
+     * must be of number and types as defined by the FunctionObject class.\r
+     * The JavaScript function is then added as a property\r
+     * of the prototype. <p>\r
+     * \r
+     * If the method's name begins with "jsStaticFunction_", it is handled\r
+     * similarly except that the resulting JavaScript function is added as a \r
+     * property of the constructor object. The Java method must be static.\r
+     * \r
+     * If the method's name begins with "jsGet_" or "jsSet_", the method is\r
+     * considered to define a property. Accesses to the defined property\r
+     * will result in calls to these getter and setter methods. If no\r
+     * setter is defined, the property is defined as READONLY.<p>\r
+     *\r
+     * If the method's name is "jsConstructor", the method is\r
+     * considered to define the body of the constructor. Only one \r
+     * method of this name may be defined. \r
+     * If no method is found that can serve as constructor, a Java\r
+     * constructor will be selected to serve as the JavaScript\r
+     * constructor in the following manner. If the class has only one\r
+     * Java constructor, that constructor is used to define\r
+     * the JavaScript constructor. If the the class has two constructors,\r
+     * one must be the zero-argument constructor (otherwise an\r
+     * ClassDefinitionException would have already been thrown\r
+     * when the prototype was to be created). In this case\r
+     * the Java constructor with one or more parameters will be used\r
+     * to define the JavaScript constructor. If the class has three\r
+     * or more constructors, an ClassDefinitionException\r
+     * will be thrown.<p>\r
+     *\r
+     * Finally, if there is a method\r
+     * <pre>\r
+     * static void finishInit(Scriptable scope, FunctionObject constructor,\r
+     *                        Scriptable prototype)</pre>\r
+     *\r
+     * it will be called to finish any initialization. The <code>scope</code>\r
+     * argument will be passed, along with the newly created constructor and\r
+     * the newly created prototype.<p>\r
+     *\r
+     * @param scope The scope in which to define the constructor\r
+     * @param clazz The Java class to use to define the JavaScript objects\r
+     *              and properties\r
+     * @exception IllegalAccessException if access is not available\r
+     *            to a reflected class member\r
+     * @exception InstantiationException if unable to instantiate\r
+     *            the named class\r
+     * @exception InvocationTargetException if an exception is thrown\r
+     *            during execution of methods of the named class\r
+     * @exception ClassDefinitionException if an appropriate\r
+     *            constructor cannot be found to create the prototype\r
+     * @exception PropertyException if getter and setter\r
+     *            methods do not conform to the requirements of the\r
+     *            defineProperty method\r
+     * @see org.mozilla.javascript.Function\r
+     * @see org.mozilla.javascript.FunctionObject\r
+     * @see org.mozilla.javascript.ScriptableObject#READONLY\r
+     * @see org.mozilla.javascript.ScriptableObject#defineProperty\r
+     */\r
+    public static void defineClass(Scriptable scope, Class clazz)\r
+        throws IllegalAccessException, InstantiationException,\r
+               InvocationTargetException, ClassDefinitionException,\r
+               PropertyException\r
+    {\r
+        defineClass(scope, clazz, false);\r
+    }\r
+    \r
+    /**\r
+     * Defines JavaScript objects from a Java class, optionally \r
+     * allowing sealing.\r
+     *\r
+     * Similar to <code>defineClass(Scriptable scope, Class clazz)</code>\r
+     * except that sealing is allowed. An object that is sealed cannot have \r
+     * properties added or removed. Note that sealing is not allowed in\r
+     * the current ECMA/ISO language specification, but is likely for\r
+     * the next version.\r
+     * \r
+     * @param scope The scope in which to define the constructor\r
+     * @param clazz The Java class to use to define the JavaScript objects\r
+     *              and properties. The class must implement Scriptable.\r
+     * @param sealed whether or not to create sealed standard objects that\r
+     *               cannot be modified. \r
+     * @exception IllegalAccessException if access is not available\r
+     *            to a reflected class member\r
+     * @exception InstantiationException if unable to instantiate\r
+     *            the named class\r
+     * @exception InvocationTargetException if an exception is thrown\r
+     *            during execution of methods of the named class\r
+     * @exception ClassDefinitionException if an appropriate\r
+     *            constructor cannot be found to create the prototype\r
+     * @exception PropertyException if getter and setter\r
+     *            methods do not conform to the requirements of the\r
+     *            defineProperty method\r
+     * @since 1.4R3\r
+     */\r
+    public static void defineClass(Scriptable scope, Class clazz, \r
+                                   boolean sealed)\r
+        throws IllegalAccessException, InstantiationException,\r
+               InvocationTargetException, ClassDefinitionException,\r
+               PropertyException\r
+    {\r
+        Method[] methods = FunctionObject.getMethodList(clazz);\r
+        for (int i=0; i < methods.length; i++) {\r
+            Method method = methods[i];\r
+            if (!method.getName().equals("init"))\r
+                continue;\r
+            Class[] parmTypes = method.getParameterTypes();\r
+            if (parmTypes.length == 3 &&\r
+                parmTypes[0] == ContextClass &&\r
+                parmTypes[1] == ScriptRuntime.ScriptableClass &&\r
+                parmTypes[2] == Boolean.TYPE &&\r
+                Modifier.isStatic(method.getModifiers()))\r
+            {\r
+                Object args[] = { Context.getContext(), scope, \r
+                                  sealed ? Boolean.TRUE : Boolean.FALSE };\r
+                method.invoke(null, args);\r
+                return;\r
+            }\r
+            if (parmTypes.length == 1 &&\r
+                parmTypes[0] == ScriptRuntime.ScriptableClass &&\r
+                Modifier.isStatic(method.getModifiers()))\r
+            {\r
+                Object args[] = { scope };\r
+                method.invoke(null, args);\r
+                return;\r
+            }\r
+            \r
+        }\r
+\r
+        // If we got here, there isn't an "init" method with the right\r
+        // parameter types.\r
+        Hashtable exclusionList = getExclusionList();\r
+\r
+        Constructor[] ctors = clazz.getConstructors();\r
+        Constructor protoCtor = null;\r
+        for (int i=0; i < ctors.length; i++) {\r
+            if (ctors[i].getParameterTypes().length == 0) {\r
+                protoCtor = ctors[i];\r
+                break;\r
+            }\r
+        }\r
+        if (protoCtor == null) {\r
+            throw new ClassDefinitionException(\r
+                    Context.getMessage1("msg.zero.arg.ctor", clazz.getName()));\r
+        }\r
+\r
+        Scriptable proto = (Scriptable) \r
+                        protoCtor.newInstance(ScriptRuntime.emptyArgs);\r
+        proto.setPrototype(getObjectPrototype(scope));\r
+        String className = proto.getClassName();\r
+\r
+        // Find out whether there are any methods that begin with\r
+        // "js". If so, then only methods that begin with special\r
+        // prefixes will be defined as JavaScript entities.\r
+        // The prefixes "js_" and "jsProperty_" are deprecated.\r
+        final String genericPrefix = "js_";\r
+        final String functionPrefix = "jsFunction_";\r
+        final String staticFunctionPrefix = "jsStaticFunction_";\r
+        final String propertyPrefix = "jsProperty_";\r
+        final String getterPrefix = "jsGet_";\r
+        final String setterPrefix = "jsSet_";\r
+        final String ctorName = "jsConstructor";\r
+\r
+        boolean hasPrefix = false;\r
+        Method[] ctorMeths = FunctionObject.findMethods(clazz, ctorName);\r
+        Member ctorMember = null;\r
+        if (ctorMeths != null) {\r
+            if (ctorMeths.length > 1) {\r
+                throw new ClassDefinitionException(\r
+                    Context.getMessage2("msg.multiple.ctors", \r
+                                        ctorMeths[0], ctorMeths[1]));\r
+            }\r
+            ctorMember = ctorMeths[0];\r
+            hasPrefix = true;\r
+        }\r
+\r
+        // Deprecated: look for functions with the same name as the class\r
+        // and consider them constructors.\r
+        for (int i=0; i < methods.length; i++) {\r
+            String name = methods[i].getName();\r
+            String prefix = null;\r
+            if (!name.startsWith("js")) // common start to all prefixes\r
+                prefix = null;\r
+            else if (name.startsWith(genericPrefix))\r
+                prefix = genericPrefix;\r
+            else if (name.startsWith(functionPrefix))\r
+                prefix = functionPrefix;\r
+            else if (name.startsWith(staticFunctionPrefix))\r
+                prefix = staticFunctionPrefix;\r
+            else if (name.startsWith(propertyPrefix))\r
+                prefix = propertyPrefix;\r
+            else if (name.startsWith(getterPrefix))\r
+                prefix = getterPrefix;\r
+            else if (name.startsWith(setterPrefix))\r
+                prefix = setterPrefix;\r
+            if (prefix != null) {\r
+                hasPrefix = true;\r
+                name = name.substring(prefix.length());\r
+            }\r
+            if (name.equals(className)) {\r
+                if (ctorMember != null) {\r
+                    throw new ClassDefinitionException(\r
+                        Context.getMessage2("msg.multiple.ctors", \r
+                                            ctorMember, methods[i]));\r
+                }\r
+                ctorMember = methods[i];\r
+            }\r
+        }\r
+\r
+        if (ctorMember == null) {\r
+            if (ctors.length == 1) {\r
+                ctorMember = ctors[0];\r
+            } else if (ctors.length == 2) {\r
+                if (ctors[0].getParameterTypes().length == 0)\r
+                    ctorMember = ctors[1];\r
+                else if (ctors[1].getParameterTypes().length == 0)\r
+                    ctorMember = ctors[0];\r
+            }\r
+            if (ctorMember == null) {\r
+                throw new ClassDefinitionException(\r
+                    Context.getMessage1("msg.ctor.multiple.parms",\r
+                                        clazz.getName()));\r
+            }\r
+        }\r
+\r
+        FunctionObject ctor = new FunctionObject(className, ctorMember, scope);\r
+        if (ctor.isVarArgsMethod()) {\r
+            throw Context.reportRuntimeError1\r
+                ("msg.varargs.ctor", ctorMember.getName());\r
+        }\r
+        ctor.addAsConstructor(scope, proto);\r
+\r
+        if (!hasPrefix && exclusionList == null)\r
+            exclusionList = getExclusionList();\r
+        Method finishInit = null;\r
+        for (int i=0; i < methods.length; i++) {\r
+            if (!hasPrefix && methods[i].getDeclaringClass() != clazz)\r
+                continue;\r
+            String name = methods[i].getName();\r
+            if (name.equals("finishInit")) {\r
+                Class[] parmTypes = methods[i].getParameterTypes();\r
+                if (parmTypes.length == 3 &&\r
+                    parmTypes[0] == ScriptRuntime.ScriptableClass &&\r
+                    parmTypes[1] == FunctionObject.class &&\r
+                    parmTypes[2] == ScriptRuntime.ScriptableClass &&\r
+                    Modifier.isStatic(methods[i].getModifiers()))\r
+                {\r
+                    finishInit = methods[i];\r
+                    continue;\r
+                }\r
+            }\r
+            // ignore any compiler generated methods.\r
+            if (name.indexOf('$') != -1)\r
+                continue;\r
+            if (name.equals(ctorName))\r
+                continue;\r
+            String prefix = null;\r
+            if (hasPrefix) {\r
+                if (name.startsWith(genericPrefix)) {\r
+                    prefix = genericPrefix;\r
+                } else if (name.startsWith(functionPrefix)) {\r
+                    prefix = functionPrefix;\r
+                } else if (name.startsWith(staticFunctionPrefix)) {\r
+                    prefix = staticFunctionPrefix;\r
+                    if (!Modifier.isStatic(methods[i].getModifiers())) {\r
+                        throw new ClassDefinitionException(\r
+                            "jsStaticFunction must be used with static method.");\r
+                    }\r
+                } else if (name.startsWith(propertyPrefix)) {\r
+                    prefix = propertyPrefix;\r
+                } else if (name.startsWith(getterPrefix)) {\r
+                    prefix = getterPrefix;\r
+                } else if (name.startsWith(setterPrefix)) {\r
+                    prefix = setterPrefix;\r
+                } else {\r
+                    continue;\r
+                }\r
+                name = name.substring(prefix.length());\r
+            } else if (exclusionList.get(name) != null)\r
+                continue;\r
+            if (methods[i] == ctorMember) {\r
+                continue;\r
+            }\r
+            if (prefix != null && prefix.equals(setterPrefix))\r
+                continue;   // deal with set when we see get\r
+            if (prefix != null && prefix.equals(getterPrefix)) {\r
+                if (!(proto instanceof ScriptableObject)) {\r
+                    throw PropertyException.withMessage2\r
+                        ("msg.extend.scriptable",                                                         proto.getClass().toString(), name);\r
+                }\r
+                Method[] setter = FunctionObject.findMethods(\r
+                                    clazz,\r
+                                    setterPrefix + name);\r
+                if (setter != null && setter.length != 1) {\r
+                    throw PropertyException.withMessage2\r
+                        ("msg.no.overload", name, clazz.getName());\r
+                }\r
+                int attr = ScriptableObject.PERMANENT |\r
+                           ScriptableObject.DONTENUM  |\r
+                           (setter != null ? 0\r
+                                           : ScriptableObject.READONLY);\r
+                Method m = setter == null ? null : setter[0];\r
+                ((ScriptableObject) proto).defineProperty(name, null,\r
+                                                          methods[i], m,\r
+                                                          attr);\r
+                continue;\r
+            }\r
+            if ((name.startsWith("get") || name.startsWith("set")) &&\r
+                name.length() > 3 &&\r
+                !(hasPrefix && (prefix.equals(functionPrefix) ||\r
+                                prefix.equals(staticFunctionPrefix))))\r
+            {\r
+                if (!(proto instanceof ScriptableObject)) {\r
+                    throw PropertyException.withMessage2\r
+                        ("msg.extend.scriptable",\r
+                         proto.getClass().toString(), name);\r
+                }\r
+                if (name.startsWith("set"))\r
+                    continue;   // deal with set when we see get\r
+                StringBuffer buf = new StringBuffer();\r
+                char c = name.charAt(3);\r
+                buf.append(Character.toLowerCase(c));\r
+                if (name.length() > 4)\r
+                    buf.append(name.substring(4));\r
+                String propertyName = buf.toString();\r
+                buf.setCharAt(0, c);\r
+                buf.insert(0, "set");\r
+                String setterName = buf.toString();\r
+                Method[] setter = FunctionObject.findMethods(\r
+                                    clazz,\r
+                                    hasPrefix ? genericPrefix + setterName\r
+                                              : setterName);\r
+                if (setter != null && setter.length != 1) {\r
+                    throw PropertyException.withMessage2\r
+                        ("msg.no.overload", name, clazz.getName());\r
+                }\r
+                if (setter == null && hasPrefix)\r
+                    setter = FunctionObject.findMethods(\r
+                                clazz,\r
+                                propertyPrefix + setterName);\r
+                int attr = ScriptableObject.PERMANENT |\r
+                           ScriptableObject.DONTENUM  |\r
+                           (setter != null ? 0\r
+                                           : ScriptableObject.READONLY);\r
+                Method m = setter == null ? null : setter[0];\r
+                ((ScriptableObject) proto).defineProperty(propertyName, null,\r
+                                                          methods[i], m,\r
+                                                          attr);\r
+                continue;\r
+            }\r
+            FunctionObject f = new FunctionObject(name, methods[i], proto);\r
+            if (f.isVarArgsConstructor()) {\r
+                throw Context.reportRuntimeError1\r
+                    ("msg.varargs.fun", ctorMember.getName());\r
+            }\r
+            Scriptable dest = prefix == staticFunctionPrefix\r
+                              ? ctor\r
+                              : proto;\r
+            defineProperty(dest, name, f, DONTENUM);\r
+            if (sealed) {\r
+                f.sealObject();\r
+                f.addPropertyAttribute(READONLY);\r
+            }\r
+        }\r
+\r
+        if (finishInit != null) {\r
+            // call user code to complete the initialization\r
+            Object[] finishArgs = { scope, ctor, proto };\r
+            finishInit.invoke(null, finishArgs);\r
+        }\r
+        \r
+        if (sealed) {\r
+            ctor.sealObject();\r
+            ctor.addPropertyAttribute(READONLY);\r
+            if (proto instanceof ScriptableObject) {\r
+                ((ScriptableObject) proto).sealObject();\r
+                ((ScriptableObject) proto).addPropertyAttribute(READONLY);\r
+            }\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Define a JavaScript property.\r
+     *\r
+     * Creates the property with an initial value and sets its attributes.\r
+     *\r
+     * @param propertyName the name of the property to define.\r
+     * @param value the initial value of the property\r
+     * @param attributes the attributes of the JavaScript property\r
+     * @see org.mozilla.javascript.Scriptable#put\r
+     */\r
+    public void defineProperty(String propertyName, Object value,\r
+                               int attributes)\r
+    {\r
+        put(propertyName, this, value);\r
+        try {\r
+            setAttributes(propertyName, this, attributes);\r
+        }\r
+        catch (PropertyException e) {\r
+            throw new RuntimeException("Cannot create property");\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Utility method to add properties to arbitrary Scriptable object.\r
+     * If destination is instance of ScriptableObject, calls \r
+     * defineProperty there, otherwise calls put in destination \r
+     * ignoring attributes\r
+     */\r
+    public static void defineProperty(Scriptable destination, \r
+                                      String propertyName, Object value,\r
+                                      int attributes)\r
+    {\r
+        if (destination instanceof ScriptableObject) {\r
+            ScriptableObject obj = (ScriptableObject)destination;\r
+            obj.defineProperty(propertyName, value, attributes);\r
+        }\r
+        else {\r
+            destination.put(propertyName, destination, value);\r
+        }\r
+    }\r
\r
+    /**\r
+     * Define a JavaScript property with getter and setter side effects.\r
+     *\r
+     * If the setter is not found, the attribute READONLY is added to\r
+     * the given attributes. <p>\r
+     *\r
+     * The getter must be a method with zero parameters, and the setter, if\r
+     * found, must be a method with one parameter.<p>\r
+     *\r
+     * @param propertyName the name of the property to define. This name\r
+     *                    also affects the name of the setter and getter\r
+     *                    to search for. If the propertyId is "foo", then\r
+     *                    <code>clazz</code> will be searched for "getFoo"\r
+     *                    and "setFoo" methods.\r
+     * @param clazz the Java class to search for the getter and setter\r
+     * @param attributes the attributes of the JavaScript property\r
+     * @exception PropertyException if multiple methods\r
+     *            are found for the getter or setter, or if the getter\r
+     *            or setter do not conform to the forms described in\r
+     *            <code>defineProperty(String, Object, Method, Method,\r
+     *            int)</code>\r
+     * @see org.mozilla.javascript.Scriptable#put\r
+     */\r
+    public void defineProperty(String propertyName, Class clazz,\r
+                               int attributes)\r
+        throws PropertyException\r
+    {\r
+        StringBuffer buf = new StringBuffer(propertyName);\r
+        buf.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));\r
+        String s = buf.toString();\r
+        Method[] getter = FunctionObject.findMethods(clazz, "get" + s);\r
+        Method[] setter = FunctionObject.findMethods(clazz, "set" + s);\r
+        if (setter == null)\r
+            attributes |= ScriptableObject.READONLY;\r
+        if (getter.length != 1 || (setter != null && setter.length != 1)) {\r
+            throw PropertyException.withMessage2\r
+                ("msg.no.overload", propertyName, clazz.getName());\r
+        }\r
+        defineProperty(propertyName, null, getter[0],\r
+                       setter == null ? null : setter[0], attributes);\r
+    }\r
+\r
+    /**\r
+     * Define a JavaScript property.\r
+     *\r
+     * Use this method only if you wish to define getters and setters for\r
+     * a given property in a ScriptableObject. To create a property without\r
+     * special getter or setter side effects, use\r
+     * <code>defineProperty(String,int)</code>.\r
+     *\r
+     * If <code>setter</code> is null, the attribute READONLY is added to\r
+     * the given attributes.<p>\r
+     *\r
+     * Several forms of getters or setters are allowed. In all cases the\r
+     * type of the value parameter can be any one of the following types: \r
+     * Object, String, boolean, Scriptable, byte, short, int, long, float,\r
+     * or double. The runtime will perform appropriate conversions based\r
+     * upon the type of the parameter (see description in FunctionObject).\r
+     * The first forms are nonstatic methods of the class referred to\r
+     * by 'this':\r
+     * <pre>\r
+     * Object getFoo();\r
+     * void setFoo(SomeType value);</pre>\r
+     * Next are static methods that may be of any class; the object whose\r
+     * property is being accessed is passed in as an extra argument:\r
+     * <pre>\r
+     * static Object getFoo(ScriptableObject obj);\r
+     * static void setFoo(ScriptableObject obj, SomeType value);</pre>\r
+     * Finally, it is possible to delegate to another object entirely using\r
+     * the <code>delegateTo</code> parameter. In this case the methods are\r
+     * nonstatic methods of the class delegated to, and the object whose\r
+     * property is being accessed is passed in as an extra argument:\r
+     * <pre>\r
+     * Object getFoo(ScriptableObject obj);\r
+     * void setFoo(ScriptableObject obj, SomeType value);</pre>\r
+     *\r
+     * @param propertyName the name of the property to define.\r
+     * @param delegateTo an object to call the getter and setter methods on,\r
+     *                   or null, depending on the form used above.\r
+     * @param getter the method to invoke to get the value of the property\r
+     * @param setter the method to invoke to set the value of the property\r
+     * @param attributes the attributes of the JavaScript property\r
+     * @exception PropertyException if the getter or setter\r
+     *            do not conform to the forms specified above\r
+     */\r
+    public void defineProperty(String propertyName, Object delegateTo,\r
+                               Method getter, Method setter, int attributes)\r
+        throws PropertyException\r
+    {\r
+        int flags = Slot.HAS_GETTER;\r
+        if (delegateTo == null && (Modifier.isStatic(getter.getModifiers())))\r
+            delegateTo = HAS_STATIC_ACCESSORS;\r
+        Class[] parmTypes = getter.getParameterTypes();\r
+        if (parmTypes.length != 0) {\r
+            if (parmTypes.length != 1 ||\r
+                parmTypes[0] != ScriptableObject.class)\r
+            {\r
+                throw PropertyException.withMessage1\r
+                    ("msg.bad.getter.parms", getter.toString());\r
+            }\r
+        } else if (delegateTo != null) {\r
+            throw PropertyException.withMessage1\r
+                ("msg.obj.getter.parms", getter.toString());\r
+        }\r
+        if (setter != null) {\r
+            flags |= Slot.HAS_SETTER;\r
+            if ((delegateTo == HAS_STATIC_ACCESSORS) !=\r
+                (Modifier.isStatic(setter.getModifiers())))\r
+            {\r
+                throw PropertyException.withMessage0("msg.getter.static");\r
+            }\r
+            parmTypes = setter.getParameterTypes();\r
+            if (parmTypes.length == 2) {\r
+                if (parmTypes[0] != ScriptableObject.class) {\r
+                    throw PropertyException.withMessage0("msg.setter2.parms");\r
+                }\r
+                if (delegateTo == null) {\r
+                    throw PropertyException.withMessage1\r
+                        ("msg.setter1.parms", setter.toString());\r
+                }\r
+            } else if (parmTypes.length == 1) {\r
+                if (delegateTo != null) {\r
+                    throw PropertyException.withMessage1\r
+                        ("msg.setter2.expected", setter.toString());\r
+                }\r
+            } else {\r
+                throw PropertyException.withMessage0("msg.setter.parms");\r
+            }\r
+        }\r
+        GetterSlot slot = (GetterSlot)getSlotToSet(propertyName,\r
+                                                   propertyName.hashCode(),\r
+                                                   true);\r
+        slot.delegateTo = delegateTo;\r
+        slot.getter = getter;\r
+        slot.setter = setter;\r
+        slot.setterReturnsValue = setter != null && setter.getReturnType() != Void.TYPE;\r
+        slot.value = null;\r
+        slot.attributes = (short) attributes;\r
+        slot.flags = (byte)flags;\r
+    }\r
+\r
+    /**\r
+     * Search for names in a class, adding the resulting methods\r
+     * as properties.\r
+     *\r
+     * <p> Uses reflection to find the methods of the given names. Then\r
+     * FunctionObjects are constructed from the methods found, and\r
+     * are added to this object as properties with the given names.\r
+     *\r
+     * @param names the names of the Methods to add as function properties\r
+     * @param clazz the class to search for the Methods\r
+     * @param attributes the attributes of the new properties\r
+     * @exception PropertyException if any of the names\r
+     *            has no corresponding method or more than one corresponding\r
+     *            method in the class\r
+     * @see org.mozilla.javascript.FunctionObject\r
+     */\r
+    public void defineFunctionProperties(String[] names, Class clazz,\r
+                                         int attributes)\r
+        throws PropertyException\r
+    {\r
+        for (int i=0; i < names.length; i++) {\r
+            String name = names[i];\r
+            Method[] m = FunctionObject.findMethods(clazz, name);\r
+            if (m == null) {\r
+                throw PropertyException.withMessage2\r
+                    ("msg.method.not.found", name, clazz.getName());\r
+            }\r
+            if (m.length > 1) {\r
+                throw PropertyException.withMessage2\r
+                    ("msg.no.overload", name, clazz.getName());\r
+            }\r
+            FunctionObject f = new FunctionObject(name, m[0], this);\r
+            defineProperty(name, f, attributes);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Get the Object.prototype property.\r
+     * See ECMA 15.2.4.\r
+     */\r
+    public static Scriptable getObjectPrototype(Scriptable scope) {\r
+        return getClassPrototype(scope, "Object");\r
+    }\r
+\r
+    /**\r
+     * Get the Function.prototype property.\r
+     * See ECMA 15.3.4.\r
+     */\r
+    public static Scriptable getFunctionPrototype(Scriptable scope) {\r
+        return getClassPrototype(scope, "Function");\r
+    }\r
+\r
+    /**\r
+     * Get the prototype for the named class.\r
+     *\r
+     * For example, <code>getClassPrototype(s, "Date")</code> will first\r
+     * walk up the parent chain to find the outermost scope, then will\r
+     * search that scope for the Date constructor, and then will\r
+     * return Date.prototype. If any of the lookups fail, or\r
+     * the prototype is not a JavaScript object, then null will\r
+     * be returned.\r
+     *\r
+     * @param scope an object in the scope chain\r
+     * @param className the name of the constructor\r
+     * @return the prototype for the named class, or null if it\r
+     *         cannot be found.\r
+     */\r
+    public static Scriptable getClassPrototype(Scriptable scope,\r
+                                               String className)\r
+    {\r
+        scope = getTopLevelScope(scope);\r
+        Object ctor = ScriptRuntime.getTopLevelProp(scope, className);\r
+        if (ctor == NOT_FOUND || !(ctor instanceof Scriptable))\r
+            return null;\r
+        Scriptable ctorObj = (Scriptable) ctor;\r
+        if (!ctorObj.has("prototype", ctorObj))\r
+            return null;\r
+        Object proto = ctorObj.get("prototype", ctorObj);\r
+        if (!(proto instanceof Scriptable))\r
+            return null;\r
+        return (Scriptable) proto;\r
+    }\r
+\r
+    /**\r
+     * Get the global scope.\r
+     *\r
+     * <p>Walks the parent scope chain to find an object with a null\r
+     * parent scope (the global object).\r
+     *\r
+     * @param obj a JavaScript object\r
+     * @return the corresponding global scope\r
+     */\r
+    public static Scriptable getTopLevelScope(Scriptable obj) {\r
+        Scriptable next = obj;\r
+        do {\r
+            obj = next;\r
+            next = obj.getParentScope();\r
+        } while (next != null);\r
+        return obj;\r
+    }\r
+    \r
+    /**\r
+     * Seal this object.\r
+     * \r
+     * A sealed object may not have properties added or removed. Once\r
+     * an object is sealed it may not be unsealed.\r
+     * \r
+     * @since 1.4R3\r
+     */\r
+    public void sealObject() {\r
+        count = -1;\r
+    }\r
+    \r
+    /**\r
+     * Return true if this object is sealed.\r
+     * \r
+     * It is an error to attempt to add or remove properties to \r
+     * a sealed object.\r
+     * \r
+     * @return true if sealed, false otherwise.\r
+     * @since 1.4R3\r
+     */\r
+    public boolean isSealed() {\r
+        return count == -1;\r
+    }\r
+\r
+    /**\r
+     * Gets a named property from an object or any object in its prototype chain.\r
+     * <p>\r
+     * Searches the prototype chain for a property named <code>name</code>.\r
+     * <p>\r
+     * @param obj a JavaScript object \r
+     * @param name a property name \r
+     * @return the value of a property with name <code>name</code> found in \r
+     *         <code>obj</code> or any object in its prototype chain, or \r
+     *         <code>Scriptable.NOT_FOUND</code> if not found\r
+     * @since 1.5R2\r
+     */\r
+    public static Object getProperty(Scriptable obj, String name) {\r
+        Scriptable start = obj;\r
+        Object result;\r
+        do {\r
+            result = obj.get(name, start);\r
+            if (result != Scriptable.NOT_FOUND)\r
+                break;\r
+            obj = obj.getPrototype();\r
+        } while (obj != null);\r
+        return result;\r
+    }\r
+    \r
+    /**\r
+     * Gets an indexed property from an object or any object in its prototype chain.\r
+     * <p>\r
+     * Searches the prototype chain for a property with integral index \r
+     * <code>index</code>. Note that if you wish to look for properties with numerical\r
+     * but non-integral indicies, you should use getProperty(Scriptable,String) with\r
+     * the string value of the index.\r
+     * <p>\r
+     * @param obj a JavaScript object \r
+     * @param index an integral index \r
+     * @return the value of a property with index <code>index</code> found in \r
+     *         <code>obj</code> or any object in its prototype chain, or \r
+     *         <code>Scriptable.NOT_FOUND</code> if not found\r
+     * @since 1.5R2\r
+     */\r
+    public static Object getProperty(Scriptable obj, int index) {\r
+        Scriptable start = obj;\r
+        Object result;\r
+        do {\r
+            result = obj.get(index, start);\r
+            if (result != Scriptable.NOT_FOUND)\r
+                break;\r
+            obj = obj.getPrototype();\r
+        } while (obj != null);\r
+        return result;\r
+    }\r
+    \r
+    /**\r
+     * Returns whether a named property is defined in an object or any object \r
+     * in its prototype chain.\r
+     * <p>\r
+     * Searches the prototype chain for a property named <code>name</code>.\r
+     * <p>\r
+     * @param obj a JavaScript object \r
+     * @param name a property name \r
+     * @return the true if property was found\r
+     * @since 1.5R2\r
+     */\r
+    public static boolean hasProperty(Scriptable obj, String name) {\r
+        Scriptable start = obj;\r
+        do {\r
+            if (obj.has(name, start))\r
+                return true;\r
+            obj = obj.getPrototype();\r
+        } while (obj != null);\r
+        return false;\r
+    }\r
+    \r
+    /**\r
+     * Returns whether an indexed property is defined in an object or any object \r
+     * in its prototype chain.\r
+     * <p>\r
+     * Searches the prototype chain for a property with index <code>index</code>.\r
+     * <p>\r
+     * @param obj a JavaScript object \r
+     * @param index a property index \r
+     * @return the true if property was found\r
+     * @since 1.5R2\r
+     */\r
+    public static boolean hasProperty(Scriptable obj, int index) {\r
+        Scriptable start = obj;\r
+        do {\r
+            if (obj.has(index, start))\r
+                return true;\r
+            obj = obj.getPrototype();\r
+        } while (obj != null);\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * Puts a named property in an object or in an object in its prototype chain.\r
+     * <p>\r
+     * Seaches for the named property in the prototype chain. If it is found,\r
+     * the value of the property is changed. If it is not found, a new\r
+     * property is added in <code>obj</code>.\r
+     * @param obj a JavaScript object \r
+     * @param name a property name\r
+     * @param value any JavaScript value accepted by Scriptable.put \r
+     * @since 1.5R2\r
+     */\r
+    public static void putProperty(Scriptable obj, String name, Object value) {\r
+        Scriptable base = getBase(obj, name);\r
+        if (base == null)\r
+            base = obj;\r
+        base.put(name, obj, value);\r
+    }\r
+\r
+    /**\r
+     * Puts an indexed property in an object or in an object in its prototype chain.\r
+     * <p>\r
+     * Seaches for the indexed property in the prototype chain. If it is found,\r
+     * the value of the property is changed. If it is not found, a new\r
+     * property is added in <code>obj</code>.\r
+     * @param obj a JavaScript object \r
+     * @param index a property index\r
+     * @param value any JavaScript value accepted by Scriptable.put \r
+     * @since 1.5R2\r
+     */\r
+    public static void putProperty(Scriptable obj, int index, Object value) {\r
+        Scriptable base = getBase(obj, index);\r
+        if (base == null)\r
+            base = obj;\r
+        base.put(index, obj, value);\r
+    }\r
+\r
+    /**\r
+     * Removes the property from an object or its prototype chain.\r
+     * <p>\r
+     * Searches for a property with <code>name</code> in obj or\r
+     * its prototype chain. If it is found, the object's delete\r
+     * method is called. \r
+     * @param obj a JavaScript object\r
+     * @param name a property name\r
+     * @return true if the property doesn't exist or was successfully removed\r
+     * @since 1.5R2\r
+     */\r
+    public static boolean deleteProperty(Scriptable obj, String name) {\r
+        Scriptable base = getBase(obj, name);\r
+        if (base == null)\r
+            return true;\r
+        base.delete(name);\r
+        return base.get(name, obj) == NOT_FOUND;\r
+    }\r
+                \r
+    /**\r
+     * Removes the property from an object or its prototype chain.\r
+     * <p>\r
+     * Searches for a property with <code>index</code> in obj or\r
+     * its prototype chain. If it is found, the object's delete\r
+     * method is called. \r
+     * @param obj a JavaScript object\r
+     * @param index a property index\r
+     * @return true if the property doesn't exist or was successfully removed\r
+     * @since 1.5R2\r
+     */\r
+    public static boolean deleteProperty(Scriptable obj, int index) {\r
+        Scriptable base = getBase(obj, index);\r
+        if (base == null)\r
+            return true;\r
+        base.delete(index);\r
+        return base.get(index, obj) == NOT_FOUND;\r
+    }\r
+    \r
+    /**\r
+     * Returns an array of all ids from an object and its prototypes.\r
+     * <p>\r
+     * @param obj a JavaScript object\r
+     * @return an array of all ids from all object in the prototype chain.\r
+     *         If a given id occurs multiple times in the prototype chain,\r
+     *         it will occur only once in this list.\r
+     * @since 1.5R2\r
+     */\r
+    public static Object[] getPropertyIds(Scriptable obj) {\r
+        Hashtable h = new Hashtable();  // JDK1.2: use HashSet\r
+        while (obj != null) {\r
+            Object[] ids = obj.getIds();\r
+            for (int i=0; i < ids.length; i++) {\r
+                h.put(ids[i], ids[i]);\r
+            }\r
+            obj = (Scriptable)obj.getPrototype();\r
+        }\r
+        Object[] result = new Object[h.size()];\r
+        java.util.Enumeration e = h.elements();\r
+        int n = 0;\r
+        while (e.hasMoreElements()) {\r
+            result[n++] = e.nextElement();\r
+        }\r
+        return result;\r
+    }\r
+    \r
+    /**\r
+     * Call a method of an object.\r
+     * <p>\r
+     * @param obj the JavaScript object\r
+     * @param methodName the name of the function property\r
+     * @param args the arguments for the call\r
+     * @exception JavaScriptException thrown if there were errors in the call\r
+     */\r
+    public static Object callMethod(Scriptable obj, String methodName, \r
+                                    Object[] args)\r
+        throws JavaScriptException\r
+    {\r
+        Context cx = Context.enter();\r
+        try {\r
+            Object fun = getProperty(obj, methodName);\r
+            if (fun == NOT_FOUND)\r
+                fun = Undefined.instance;\r
+            return ScriptRuntime.call(cx, fun, obj, args, getTopLevelScope(obj));\r
+        } finally {\r
+          Context.exit();\r
+        }\r
+    }\r
+                \r
+    private static Scriptable getBase(Scriptable obj, String s) {\r
+        Scriptable m = obj;\r
+        while (m != null) {\r
+            if (m.has(s, obj))\r
+                return m;\r
+            m = m.getPrototype();\r
+        }\r
+        return null;\r
+    }\r
+\r
+    private static Scriptable getBase(Scriptable obj, int index) {\r
+        Scriptable m = obj;\r
+        while (m != null) {\r
+            if (m.has(index, obj))\r
+                return m;\r
+            m = m.getPrototype();\r
+        }\r
+        return null;\r
+    }\r
+    \r
+    /**\r
+     * Adds a property attribute to all properties.\r
+     */\r
+    synchronized void addPropertyAttribute(int attribute) {\r
+        if (slots == null)\r
+            return;\r
+        for (int i=0; i < slots.length; i++) {\r
+            Slot slot = slots[i];\r
+            if (slot == null || slot == REMOVED)\r
+                continue;\r
+            if ((slot.flags & slot.HAS_SETTER) != 0 && attribute == READONLY)\r
+                continue;\r
+            slot.attributes |= attribute;\r
+        }\r
+    }\r
+    \r
+    private Slot getSlot(String id, int index, boolean shouldDelete) {\r
+        Slot[] slots = this.slots;\r
+        if (slots == null)\r
+            return null;\r
+        int start = (index & 0x7fffffff) % slots.length;\r
+        int i = start;\r
+        do {\r
+            Slot slot = slots[i];\r
+            if (slot == null)\r
+                return null;\r
+            if (slot != REMOVED && slot.intKey == index && \r
+                (slot.stringKey == id || (id != null && \r
+                                          id.equals(slot.stringKey))))\r
+            {\r
+                if (shouldDelete) {\r
+                    if ((slot.attributes & PERMANENT) == 0) {\r
+                        // Mark the slot as removed to handle a case when\r
+                        // another thread manages to put just removed slot\r
+                        // into lastAccess cache.\r
+                        slot.wasDeleted = (byte)1;\r
+                        slots[i] = REMOVED;\r
+                        count--;\r
+                        if (slot == lastAccess)\r
+                            lastAccess = REMOVED;\r
+                    }\r
+                }\r
+                return slot;\r
+            }\r
+            if (++i == slots.length)\r
+                i = 0;\r
+        } while (i != start);\r
+        return null;\r
+    }\r
+\r
+    private Slot getSlotToSet(String id, int index, boolean getterSlot) {\r
+        if (slots == null)\r
+            slots = new Slot[5];\r
+        int start = (index & 0x7fffffff) % slots.length;\r
+        boolean sawRemoved = false;\r
+        int i = start;\r
+        do {\r
+            Slot slot = slots[i];\r
+            if (slot == null) {\r
+                return addSlot(id, index, getterSlot);\r
+            }\r
+            if (slot == REMOVED) {\r
+                sawRemoved = true;\r
+            } else if (slot.intKey == index && \r
+                       (slot.stringKey == id || \r
+                        (id != null && id.equals(slot.stringKey))))\r
+            {\r
+                return slot;\r
+            }\r
+            if (++i == slots.length)\r
+                i = 0;\r
+        } while (i != start);\r
+        if (sawRemoved) {\r
+            // Table could be full, but with some REMOVED elements. \r
+            // Call to addSlot will use a slot currently taken by \r
+            // a REMOVED.\r
+            return addSlot(id, index, getterSlot);\r
+        }\r
+        throw new RuntimeException("Hashtable internal error");\r
+    }\r
+\r
+    /**\r
+     * Add a new slot to the hash table.\r
+     *\r
+     * This method must be synchronized since it is altering the hash\r
+     * table itself. Note that we search again for the slot to set\r
+     * since another thread could have added the given property or\r
+     * caused the table to grow while this thread was searching.\r
+     */\r
+    private synchronized Slot addSlot(String id, int index, boolean getterSlot)\r
+    {\r
+        if (count == -1)\r
+            throw Context.reportRuntimeError0("msg.add.sealed");\r
+        int start = (index & 0x7fffffff) % slots.length;\r
+        int i = start;\r
+        do {\r
+            Slot slot = slots[i];\r
+            if (slot == null || slot == REMOVED) {\r
+                if ((4 * (count+1)) > (3 * slots.length)) {\r
+                    grow();\r
+                    return getSlotToSet(id, index, getterSlot);\r
+                }\r
+                slot = getterSlot ? new GetterSlot() : new Slot();\r
+                slot.stringKey = id;\r
+                slot.intKey = index;\r
+                slots[i] = slot;\r
+                count++;\r
+                return slot;\r
+            }\r
+            if (slot.intKey == index && \r
+                (slot.stringKey == id || (id != null && \r
+                                          id.equals(slot.stringKey)))) \r
+            {\r
+                return slot;\r
+            }\r
+            if (++i == slots.length)\r
+                i = 0;\r
+        } while (i != start);\r
+        throw new RuntimeException("Hashtable internal error");\r
+    }\r
+\r
+    /**\r
+     * Remove a slot from the hash table.\r
+     *\r
+     * This method must be synchronized since it is altering the hash\r
+     * table itself. We might be able to optimize this more, but\r
+     * deletes are not common.\r
+     */\r
+    private synchronized void removeSlot(String name, int index) {\r
+        if (count == -1)\r
+            throw Context.reportRuntimeError0("msg.remove.sealed");\r
+        getSlot(name, index, true);\r
+    }\r
+\r
+    /**\r
+     * Grow the hash table to accommodate new entries.\r
+     *\r
+     * Note that by assigning the new array back at the end we\r
+     * can continue reading the array from other threads.\r
+     */\r
+    private synchronized void grow() {\r
+        Slot[] newSlots = new Slot[slots.length*2 + 1];\r
+        for (int j=slots.length-1; j >= 0 ; j--) {\r
+            Slot slot = slots[j];\r
+            if (slot == null || slot == REMOVED)\r
+                continue;\r
+            int k = (slot.intKey & 0x7fffffff) % newSlots.length;\r
+            while (newSlots[k] != null)\r
+                if (++k == newSlots.length)\r
+                    k = 0;\r
+            // The end of the "synchronized" statement will cause the memory\r
+            // writes to be propagated on a multiprocessor machine. We want\r
+            // to make sure that the new table is prepared to be read.\r
+            // XXX causes the 'this' pointer to be null in calling stack frames\r
+            // on the MS JVM\r
+            //synchronized (slot) { }\r
+            newSlots[k] = slot;\r
+        }\r
+        slots = newSlots;\r
+    }\r
+\r
+    private static Hashtable getExclusionList() {\r
+        if (exclusionList != null)\r
+            return exclusionList;\r
+        Hashtable result = new Hashtable(17);\r
+        Method[] methods = ScriptRuntime.FunctionClass.getMethods();\r
+        for (int i=0; i < methods.length; i++) {\r
+            result.put(methods[i].getName(), Boolean.TRUE);\r
+        }\r
+        exclusionList = result;\r
+        return result;\r
+    }\r
+        \r
+    Object[] getIds(boolean getAll) {\r
+        Slot[] s = slots;\r
+        Object[] a = ScriptRuntime.emptyArgs;\r
+        if (s == null)\r
+            return a;\r
+        int c = 0;\r
+        for (int i=0; i < s.length; i++) {\r
+            Slot slot = s[i];\r
+            if (slot == null || slot == REMOVED)\r
+                continue;\r
+            if (getAll || (slot.attributes & DONTENUM) == 0) {\r
+                if (c == 0)\r
+                    a = new Object[s.length - i];\r
+                a[c++] = slot.stringKey != null\r
+                             ? (Object) slot.stringKey\r
+                             : new Integer(slot.intKey);\r
+            }\r
+        }\r
+        if (c == a.length)\r
+            return a;\r
+        Object[] result = new Object[c];\r
+        System.arraycopy(a, 0, result, 0, c);\r
+        return result;\r
+    }\r
+\r
+    \r
+    /**\r
+     * The prototype of this object.\r
+     */\r
+    protected Scriptable prototype;\r
+    \r
+    /**\r
+     * The parent scope of this object.\r
+     */\r
+    protected Scriptable parent;\r
+\r
+    private static final Object HAS_STATIC_ACCESSORS = Void.TYPE;\r
+    private static final Slot REMOVED = new Slot();\r
+    private static Hashtable exclusionList = null;\r
+    \r
+    private Slot[] slots;\r
+    private int count;\r
+\r
+    // cache; may be removed for smaller memory footprint\r
+    private Slot lastAccess = REMOVED;\r
+\r
+    private static class Slot {\r
+        static final int HAS_GETTER  = 0x01;\r
+        static final int HAS_SETTER  = 0x02;\r
+        \r
+        int intKey;\r
+        String stringKey;\r
+        Object value;\r
+        short attributes;\r
+        byte flags;\r
+        byte wasDeleted;\r
+    }\r
+\r
+    private static class GetterSlot extends Slot {\r
+        Object delegateTo;  // OPT: merge with "value"\r
+        Method getter;\r
+        Method setter;\r
+        boolean setterReturnsValue;\r
+    }\r
+\r
+    private static final Class ContextClass = Context.class;\r
+}\r