From 89bda83e0570ab87c6e449f5955613d5385e90b3 Mon Sep 17 00:00:00 2001 From: "alexanders@b2ef00c0-3703-41da-baef-cfe82387ac0c" Date: Wed, 3 Feb 2010 00:50:41 +0000 Subject: removed obsolete svn folder from hg tree --HG-- extra : convert_revision : svn%3Ab2ef00c0-3703-41da-baef-cfe82387ac0c/trunk%408 --- .../org/mozilla/javascript/ScriptableObject.java | 2428 ++++++++++++++++++++ 1 file changed, 2428 insertions(+) create mode 100644 infrastructure/rhino1_7R1/src/org/mozilla/javascript/ScriptableObject.java (limited to 'infrastructure/rhino1_7R1/src/org/mozilla/javascript/ScriptableObject.java') diff --git a/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ScriptableObject.java b/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ScriptableObject.java new file mode 100644 index 0000000..53de1fc --- /dev/null +++ b/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ScriptableObject.java @@ -0,0 +1,2428 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Bob Jervis + * Roger Lawrence + * Steve Weiss + * + * Alternatively, the contents of this file may be used under the terms of + * the GNU General Public License Version 2 or later (the "GPL"), in which + * case the provisions of the GPL are applicable instead of those above. If + * you wish to allow use of your version of this file only under the terms of + * the GPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replacing + * them with the notice and other provisions required by the GPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the GPL. + * + * ***** END LICENSE BLOCK ***** */ + +// API class + +package org.mozilla.javascript; + +import java.lang.reflect.*; +import java.util.Hashtable; +import java.io.*; +import org.mozilla.javascript.debug.DebuggableObject; + +/** + * This is the default implementation of the Scriptable interface. This + * class provides convenient default behavior that makes it easier to + * define host objects. + *

+ * Various properties and methods of JavaScript objects can be conveniently + * defined using methods of ScriptableObject. + *

+ * Classes extending ScriptableObject must define the getClassName method. + * + * @see org.mozilla.javascript.Scriptable + * @author Norris Boyd + */ + +public abstract class ScriptableObject implements Scriptable, Serializable, + DebuggableObject, + ConstProperties +{ + + /** + * The empty property attribute. + * + * Used by getAttributes() and setAttributes(). + * + * @see org.mozilla.javascript.ScriptableObject#getAttributes(String) + * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int) + */ + public static final int EMPTY = 0x00; + + /** + * Property attribute indicating assignment to this property is ignored. + * + * @see org.mozilla.javascript.ScriptableObject + * #put(String, Scriptable, Object) + * @see org.mozilla.javascript.ScriptableObject#getAttributes(String) + * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int) + */ + public static final int READONLY = 0x01; + + /** + * Property attribute indicating property is not enumerated. + * + * Only enumerated properties will be returned by getIds(). + * + * @see org.mozilla.javascript.ScriptableObject#getIds() + * @see org.mozilla.javascript.ScriptableObject#getAttributes(String) + * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int) + */ + public static final int DONTENUM = 0x02; + + /** + * Property attribute indicating property cannot be deleted. + * + * @see org.mozilla.javascript.ScriptableObject#delete(String) + * @see org.mozilla.javascript.ScriptableObject#getAttributes(String) + * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int) + */ + public static final int PERMANENT = 0x04; + + /** + * Property attribute indicating that this is a const property that has not + * been assigned yet. The first 'const' assignment to the property will + * clear this bit. + */ + public static final int UNINITIALIZED_CONST = 0x08; + + public static final int CONST = PERMANENT|READONLY|UNINITIALIZED_CONST; + /** + * The prototype of this object. + */ + private Scriptable prototypeObject; + + /** + * The parent scope of this object. + */ + private Scriptable parentScopeObject; + + private static final Slot REMOVED = new Slot(null, 0, READONLY); + + static { + REMOVED.wasDeleted = 1; + } + + private transient Slot[] slots; + // If count >= 0, it gives number of keys or if count < 0, + // it indicates sealed object where ~count gives number of keys + private int count; + + // cache; may be removed for smaller memory footprint + private transient Slot lastAccess = REMOVED; + + // associated values are not serialized + private transient volatile Hashtable associatedValues; + + private static final int SLOT_QUERY = 1; + private static final int SLOT_MODIFY = 2; + private static final int SLOT_REMOVE = 3; + private static final int SLOT_MODIFY_GETTER_SETTER = 4; + private static final int SLOT_MODIFY_CONST = 5; + + private static class Slot implements Serializable + { + static final long serialVersionUID = -3539051633409902634L; + + String name; // This can change due to caching + int indexOrHash; + private volatile short attributes; + transient volatile byte wasDeleted; + volatile Object value; + transient volatile Slot next; + + Slot(String name, int indexOrHash, int attributes) + { + this.name = name; + this.indexOrHash = indexOrHash; + this.attributes = (short)attributes; + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + if (name != null) { + indexOrHash = name.hashCode(); + } + } + + final int getAttributes() + { + return attributes; + } + + final synchronized void setAttributes(int value) + { + checkValidAttributes(value); + attributes = (short)value; + } + + final void checkNotReadonly() + { + if ((attributes & READONLY) != 0) { + String str = (name != null ? name + : Integer.toString(indexOrHash)); + throw Context.reportRuntimeError1("msg.modify.readonly", str); + } + } + + } + + private static final class GetterSlot extends Slot + { + static final long serialVersionUID = -4900574849788797588L; + + Object getter; + Object setter; + + GetterSlot(String name, int indexOrHash, int attributes) + { + super(name, indexOrHash, attributes); + } + } + + static void checkValidAttributes(int attributes) + { + final int mask = READONLY | DONTENUM | PERMANENT | UNINITIALIZED_CONST; + if ((attributes & ~mask) != 0) { + throw new IllegalArgumentException(String.valueOf(attributes)); + } + } + + public ScriptableObject() + { + } + + public ScriptableObject(Scriptable scope, Scriptable prototype) + { + if (scope == null) + throw new IllegalArgumentException(); + + parentScopeObject = scope; + prototypeObject = prototype; + } + + /** + * Return the name of the class. + * + * This is typically the same name as the constructor. + * Classes extending ScriptableObject must implement this abstract + * method. + */ + public abstract String getClassName(); + + /** + * Returns true if the named property is defined. + * + * @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) + { + return null != getSlot(name, 0, SLOT_QUERY); + } + + /** + * Returns true if the property index is defined. + * + * @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) + { + return null != getSlot(null, index, SLOT_QUERY); + } + + /** + * Returns the value of the named property or NOT_FOUND. + * + * If the property was created using defineProperty, the + * appropriate getter method is called. + * + * @param name the name of 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(String name, Scriptable start) + { + return getImpl(name, 0, start); + } + + /** + * Returns the value of the indexed property or NOT_FOUND. + * + * @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) + { + return getImpl(null, index, start); + } + + /** + * Sets the value of the named property, creating it if need be. + * + * If the property was created using defineProperty, the + * appropriate setter method is called.

+ * + * If the property's attributes include READONLY, no action is + * taken. + * This method will actually set the property in the start + * object. + * + * @param name the name of the property + * @param start the object whose property is being set + * @param value value to set the property to + */ + public void put(String name, Scriptable start, Object value) + { + if (putImpl(name, 0, start, value, EMPTY)) + return; + + if (start == this) throw Kit.codeBug(); + start.put(name, start, value); + } + + /** + * Sets the value of the indexed property, creating it if need be. + * + * @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) + { + if (putImpl(null, index, start, value, EMPTY)) + return; + + if (start == this) throw Kit.codeBug(); + start.put(index, start, value); + } + + /** + * Removes a named property from the object. + * + * If the property is not found, or it has the PERMANENT attribute, + * no action is taken. + * + * @param name the name of the property + */ + public void delete(String name) + { + checkNotSealed(name, 0); + accessSlot(name, 0, SLOT_REMOVE); + } + + /** + * Removes the indexed property from the object. + * + * If the property is not found, or it has the PERMANENT attribute, + * no action is taken. + * + * @param index the numeric index for the property + */ + public void delete(int index) + { + checkNotSealed(null, index); + accessSlot(null, index, SLOT_REMOVE); + } + + /** + * Sets the value of the named const property, creating it if need be. + * + * If the property was created using defineProperty, the + * appropriate setter method is called.

+ * + * If the property's attributes include READONLY, no action is + * taken. + * This method will actually set the property in the start + * object. + * + * @param name the name of the property + * @param start the object whose property is being set + * @param value value to set the property to + */ + public void putConst(String name, Scriptable start, Object value) + { + if (putImpl(name, 0, start, value, READONLY)) + return; + + if (start == this) throw Kit.codeBug(); + if (start instanceof ConstProperties) + ((ConstProperties)start).putConst(name, start, value); + else + start.put(name, start, value); + } + + public void defineConst(String name, Scriptable start) + { + if (putImpl(name, 0, start, Undefined.instance, UNINITIALIZED_CONST)) + return; + + if (start == this) throw Kit.codeBug(); + if (start instanceof ConstProperties) + ((ConstProperties)start).defineConst(name, start); + } + /** + * Returns true if the named property is defined as a const on this object. + * @param name + * @return true if the named property is defined as a const, false + * otherwise. + */ + public boolean isConst(String name) + { + Slot slot = getSlot(name, 0, SLOT_QUERY); + if (slot == null) { + return false; + } + return (slot.getAttributes() & (PERMANENT|READONLY)) == + (PERMANENT|READONLY); + + } + /** + * @deprecated Use {@link #getAttributes(String name)}. The engine always + * ignored the start argument. + */ + public final int getAttributes(String name, Scriptable start) + { + return getAttributes(name); + } + + /** + * @deprecated Use {@link #getAttributes(int index)}. The engine always + * ignored the start argument. + */ + public final int getAttributes(int index, Scriptable start) + { + return getAttributes(index); + } + + /** + * @deprecated Use {@link #setAttributes(String name, int attributes)}. + * The engine always ignored the start argument. + */ + public final void setAttributes(String name, Scriptable start, + int attributes) + { + setAttributes(name, attributes); + } + + /** + * @deprecated Use {@link #setAttributes(int index, int attributes)}. + * The engine always ignored the start argument. + */ + public void setAttributes(int index, Scriptable start, + int attributes) + { + setAttributes(index, attributes); + } + + /** + * Get the attributes of a named property. + * + * The property is specified by name + * as defined for has.

+ * + * @param name the identifier for the property + * @return the bitset of attributes + * @exception EvaluatorException if the named property is not found + * @see org.mozilla.javascript.ScriptableObject#has(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#READONLY + * @see org.mozilla.javascript.ScriptableObject#DONTENUM + * @see org.mozilla.javascript.ScriptableObject#PERMANENT + * @see org.mozilla.javascript.ScriptableObject#EMPTY + */ + public int getAttributes(String name) + { + return findAttributeSlot(name, 0, SLOT_QUERY).getAttributes(); + } + + /** + * Get the attributes of an indexed property. + * + * @param index the numeric index for the property + * @exception EvaluatorException if the named property is not found + * is not found + * @return the bitset of attributes + * @see org.mozilla.javascript.ScriptableObject#has(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#READONLY + * @see org.mozilla.javascript.ScriptableObject#DONTENUM + * @see org.mozilla.javascript.ScriptableObject#PERMANENT + * @see org.mozilla.javascript.ScriptableObject#EMPTY + */ + public int getAttributes(int index) + { + return findAttributeSlot(null, index, SLOT_QUERY).getAttributes(); + } + + /** + * Set the attributes of a named property. + * + * The property is specified by name + * as defined for has.

+ * + * The possible attributes are READONLY, DONTENUM, + * and PERMANENT. Combinations of attributes + * are expressed by the bitwise OR of attributes. + * EMPTY is the state of no attributes set. Any unused + * bits are reserved for future use. + * + * @param name the name of the property + * @param attributes the bitset of attributes + * @exception EvaluatorException if the named property is not found + * @see org.mozilla.javascript.Scriptable#has(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#READONLY + * @see org.mozilla.javascript.ScriptableObject#DONTENUM + * @see org.mozilla.javascript.ScriptableObject#PERMANENT + * @see org.mozilla.javascript.ScriptableObject#EMPTY + */ + public void setAttributes(String name, int attributes) + { + checkNotSealed(name, 0); + findAttributeSlot(name, 0, SLOT_MODIFY).setAttributes(attributes); + } + + /** + * Set the attributes of an indexed property. + * + * @param index the numeric index for the property + * @param attributes the bitset of attributes + * @exception EvaluatorException if the named property is not found + * @see org.mozilla.javascript.Scriptable#has(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#READONLY + * @see org.mozilla.javascript.ScriptableObject#DONTENUM + * @see org.mozilla.javascript.ScriptableObject#PERMANENT + * @see org.mozilla.javascript.ScriptableObject#EMPTY + */ + public void setAttributes(int index, int attributes) + { + checkNotSealed(null, index); + findAttributeSlot(null, index, SLOT_MODIFY).setAttributes(attributes); + } + + /** + * XXX: write docs. + */ + public void setGetterOrSetter(String name, int index, + Callable getterOrSeter, boolean isSetter) + { + if (name != null && index != 0) + throw new IllegalArgumentException(name); + + checkNotSealed(name, index); + GetterSlot gslot = (GetterSlot)getSlot(name, index, + SLOT_MODIFY_GETTER_SETTER); + gslot.checkNotReadonly(); + if (isSetter) { + gslot.setter = getterOrSeter; + } else { + gslot.getter = getterOrSeter; + } + gslot.value = Undefined.instance; + } + + /** + * Get the getter or setter for a given property. Used by __lookupGetter__ + * and __lookupSetter__. + * + * @param name Name of the object. If nonnull, index must be 0. + * @param index Index of the object. If nonzero, name must be null. + * @param isSetter If true, return the setter, otherwise return the getter. + * @exception IllegalArgumentException if both name and index are nonnull + * and nonzero respectively. + * @return Null if the property does not exist. Otherwise returns either + * the getter or the setter for the property, depending on + * the value of isSetter (may be undefined if unset). + */ + public Object getGetterOrSetter(String name, int index, boolean isSetter) + { + if (name != null && index != 0) + throw new IllegalArgumentException(name); + Slot slot = getSlot(name, index, SLOT_QUERY); + if (slot == null) + return null; + if (slot instanceof GetterSlot) { + GetterSlot gslot = (GetterSlot)slot; + Object result = isSetter ? gslot.setter : gslot.getter; + return result != null ? result : Undefined.instance; + } else + return Undefined.instance; + } + + /** + * Returns whether a property is a getter or a setter + * @param name property name + * @param index property index + * @param setter true to check for a setter, false for a getter + * @return whether the property is a getter or a setter + */ + protected boolean isGetterOrSetter(String name, int index, boolean setter) { + Slot slot = getSlot(name, index, SLOT_QUERY); + if (slot instanceof GetterSlot) { + if (setter && ((GetterSlot)slot).setter != null) return true; + if (!setter && ((GetterSlot)slot).getter != null) return true; + } + return false; + } + + void addLazilyInitializedValue(String name, int index, + LazilyLoadedCtor init, int attributes) + { + if (name != null && index != 0) + throw new IllegalArgumentException(name); + checkNotSealed(name, index); + GetterSlot gslot = (GetterSlot)getSlot(name, index, + SLOT_MODIFY_GETTER_SETTER); + gslot.setAttributes(attributes); + gslot.getter = null; + gslot.setter = null; + gslot.value = init; + } + + /** + * Returns the prototype of the object. + */ + public Scriptable getPrototype() + { + return prototypeObject; + } + + /** + * Sets the prototype of the object. + */ + public void setPrototype(Scriptable m) + { + prototypeObject = m; + } + + /** + * Returns the parent (enclosing) scope of the object. + */ + public Scriptable getParentScope() + { + return parentScopeObject; + } + + /** + * Sets the parent (enclosing) scope of the object. + */ + public void setParentScope(Scriptable m) + { + parentScopeObject = m; + } + + /** + * Returns an array of ids for the properties of the object. + * + *

Any properties with the attribute DONTENUM are not listed.

+ * + * @return an array of java.lang.Objects with an entry for every + * listed property. Properties accessed via an integer index will + * have a corresponding + * Integer entry in the returned array. Properties accessed by + * a String will have a String entry in the returned array. + */ + public Object[] getIds() { + return getIds(false); + } + + /** + * Returns an array of ids for the properties of the object. + * + *

All properties, even those with attribute DONTENUM, are listed.

+ * + * @return an array of java.lang.Objects with an entry for every + * listed property. Properties accessed via an integer index will + * have a corresponding + * Integer entry in the returned array. Properties accessed by + * a String will have a String entry in the returned array. + */ + public Object[] getAllIds() { + return getIds(true); + } + + /** + * Implements the [[DefaultValue]] internal method. + * + *

Note that the toPrimitive conversion is a no-op for + * every type other than Object, for which [[DefaultValue]] + * is called. See ECMA 9.1.

+ * + * A hint of null means "no hint". + * + * @param typeHint the type hint + * @return the default value for the object + * + * See ECMA 8.6.2.6. + */ + public Object getDefaultValue(Class typeHint) + { + return getDefaultValue(this, typeHint); + } + + public static Object getDefaultValue(Scriptable object, Class typeHint) + { + Context cx = null; + for (int i=0; i < 2; i++) { + boolean tryToString; + if (typeHint == ScriptRuntime.StringClass) { + tryToString = (i == 0); + } else { + tryToString = (i == 1); + } + + String methodName; + Object[] args; + if (tryToString) { + methodName = "toString"; + args = ScriptRuntime.emptyArgs; + } else { + methodName = "valueOf"; + args = new Object[1]; + String hint; + if (typeHint == null) { + hint = "undefined"; + } else if (typeHint == ScriptRuntime.StringClass) { + hint = "string"; + } else if (typeHint == ScriptRuntime.ScriptableClass) { + hint = "object"; + } else if (typeHint == ScriptRuntime.FunctionClass) { + hint = "function"; + } else if (typeHint == ScriptRuntime.BooleanClass + || typeHint == Boolean.TYPE) + { + hint = "boolean"; + } else if (typeHint == ScriptRuntime.NumberClass || + typeHint == ScriptRuntime.ByteClass || + typeHint == Byte.TYPE || + typeHint == ScriptRuntime.ShortClass || + typeHint == Short.TYPE || + typeHint == ScriptRuntime.IntegerClass || + typeHint == Integer.TYPE || + typeHint == ScriptRuntime.FloatClass || + typeHint == Float.TYPE || + typeHint == ScriptRuntime.DoubleClass || + typeHint == Double.TYPE) + { + hint = "number"; + } else { + throw Context.reportRuntimeError1( + "msg.invalid.type", typeHint.toString()); + } + args[0] = hint; + } + Object v = getProperty(object, methodName); + if (!(v instanceof Function)) + continue; + Function fun = (Function) v; + if (cx == null) + cx = Context.getContext(); + v = fun.call(cx, fun.getParentScope(), object, args); + if (v != null) { + if (!(v instanceof Scriptable)) { + return v; + } + if (typeHint == ScriptRuntime.ScriptableClass + || typeHint == ScriptRuntime.FunctionClass) + { + return v; + } + if (tryToString && v instanceof Wrapper) { + // Let a wrapped java.lang.String pass for a primitive + // string. + Object u = ((Wrapper)v).unwrap(); + if (u instanceof String) + return u; + } + } + } + // fall through to error + String arg = (typeHint == null) ? "undefined" : typeHint.getName(); + throw ScriptRuntime.typeError1("msg.default.value", arg); + } + + /** + * Implements the instanceof operator. + * + *

This operator has been proposed to ECMA. + * + * @param instance The value that appeared on the LHS of the instanceof + * operator + * @return true if "this" appears in value's prototype chain + * + */ + public boolean hasInstance(Scriptable instance) { + // Default for JS objects (other than Function) is to do prototype + // chasing. This will be overridden in NativeFunction and non-JS + // objects. + + return ScriptRuntime.jsDelegatesTo(instance, this); + } + + /** + * Emulate the SpiderMonkey (and Firefox) feature of allowing + * custom objects to avoid detection by normal "object detection" + * code patterns. This is used to implement document.all. + * See https://bugzilla.mozilla.org/show_bug.cgi?id=412247. + * This is an analog to JOF_DETECTING from SpiderMonkey; see + * https://bugzilla.mozilla.org/show_bug.cgi?id=248549. + * Other than this special case, embeddings should return false. + * @return true if this object should avoid object detection + * @since 1.7R1 + */ + public boolean avoidObjectDetection() { + return false; + } + + /** + * Custom == operator. + * Must return {@link Scriptable#NOT_FOUND} if this object does not + * have custom equality operator for the given value, + * Boolean.TRUE if this object is equivalent to value, + * Boolean.FALSE if this object is not equivalent to + * value. + *

+ * The default implementation returns Boolean.TRUE + * if this == value or {@link Scriptable#NOT_FOUND} otherwise. + * It indicates that by default custom equality is available only if + * value is this in which case true is returned. + */ + protected Object equivalentValues(Object value) + { + return (this == value) ? Boolean.TRUE : Scriptable.NOT_FOUND; + } + + /** + * Defines JavaScript objects from a Java class that implements Scriptable. + * + * If the given class has a method + *

+     * static void init(Context cx, Scriptable scope, boolean sealed);
+ * + * or its compatibility form + *
+     * static void init(Scriptable scope);
+ * + * then it is invoked and no further initialization is done.

+ * + * However, if no such a method is found, then the class's constructors and + * methods are used to initialize a class in the following manner.

+ * + * First, the zero-parameter constructor of the class is called to + * create the prototype. If no such constructor exists, + * a {@link EvaluatorException} is thrown.

+ * + * Next, all methods are scanned for special prefixes that indicate that they + * have special meaning for defining JavaScript objects. + * These special prefixes are + *

+ * + * If the method's name begins with "jsFunction_", a JavaScript function + * is created with a name formed from the rest of the Java method name + * following "jsFunction_". So a Java method named "jsFunction_foo" will + * define a JavaScript method "foo". Calling this JavaScript function + * will cause the Java method to be called. The parameters of the method + * must be of number and types as defined by the FunctionObject class. + * The JavaScript function is then added as a property + * of the prototype.

+ * + * If the method's name begins with "jsStaticFunction_", it is handled + * similarly except that the resulting JavaScript function is added as a + * property of the constructor object. The Java method must be static. + * + * If the method's name begins with "jsGet_" or "jsSet_", the method is + * considered to define a property. Accesses to the defined property + * will result in calls to these getter and setter methods. If no + * setter is defined, the property is defined as READONLY.

+ * + * If the method's name is "jsConstructor", the method is + * considered to define the body of the constructor. Only one + * method of this name may be defined. + * If no method is found that can serve as constructor, a Java + * constructor will be selected to serve as the JavaScript + * constructor in the following manner. If the class has only one + * Java constructor, that constructor is used to define + * the JavaScript constructor. If the the class has two constructors, + * one must be the zero-argument constructor (otherwise an + * {@link EvaluatorException} would have already been thrown + * when the prototype was to be created). In this case + * the Java constructor with one or more parameters will be used + * to define the JavaScript constructor. If the class has three + * or more constructors, an {@link EvaluatorException} + * will be thrown.

+ * + * Finally, if there is a method + *

+     * static void finishInit(Scriptable scope, FunctionObject constructor,
+     *                        Scriptable prototype)
+ * + * it will be called to finish any initialization. The scope + * argument will be passed, along with the newly created constructor and + * the newly created prototype.

+ * + * @param scope The scope in which to define the constructor. + * @param clazz The Java class to use to define the JavaScript objects + * and properties. + * @exception IllegalAccessException if access is not available + * to a reflected class member + * @exception InstantiationException if unable to instantiate + * the named class + * @exception InvocationTargetException if an exception is thrown + * during execution of methods of the named class + * @see org.mozilla.javascript.Function + * @see org.mozilla.javascript.FunctionObject + * @see org.mozilla.javascript.ScriptableObject#READONLY + * @see org.mozilla.javascript.ScriptableObject + * #defineProperty(String, Class, int) + */ + public static void defineClass(Scriptable scope, Class clazz) + throws IllegalAccessException, InstantiationException, + InvocationTargetException + { + defineClass(scope, clazz, false, false); + } + + /** + * Defines JavaScript objects from a Java class, optionally + * allowing sealing. + * + * Similar to defineClass(Scriptable scope, Class clazz) + * except that sealing is allowed. An object that is sealed cannot have + * properties added or removed. Note that sealing is not allowed in + * the current ECMA/ISO language specification, but is likely for + * the next version. + * + * @param scope The scope in which to define the constructor. + * @param clazz The Java class to use to define the JavaScript objects + * and properties. The class must implement Scriptable. + * @param sealed Whether or not to create sealed standard objects that + * cannot be modified. + * @exception IllegalAccessException if access is not available + * to a reflected class member + * @exception InstantiationException if unable to instantiate + * the named class + * @exception InvocationTargetException if an exception is thrown + * during execution of methods of the named class + * @since 1.4R3 + */ + public static void defineClass(Scriptable scope, Class clazz, + boolean sealed) + throws IllegalAccessException, InstantiationException, + InvocationTargetException + { + defineClass(scope, clazz, sealed, false); + } + + /** + * Defines JavaScript objects from a Java class, optionally + * allowing sealing and mapping of Java inheritance to JavaScript + * prototype-based inheritance. + * + * Similar to defineClass(Scriptable scope, Class clazz) + * except that sealing and inheritance mapping are allowed. An object + * that is sealed cannot have properties added or removed. Note that + * sealing is not allowed in the current ECMA/ISO language specification, + * but is likely for the next version. + * + * @param scope The scope in which to define the constructor. + * @param clazz The Java class to use to define the JavaScript objects + * and properties. The class must implement Scriptable. + * @param sealed Whether or not to create sealed standard objects that + * cannot be modified. + * @param mapInheritance Whether or not to map Java inheritance to + * JavaScript prototype-based inheritance. + * @return the class name for the prototype of the specified class + * @exception IllegalAccessException if access is not available + * to a reflected class member + * @exception InstantiationException if unable to instantiate + * the named class + * @exception InvocationTargetException if an exception is thrown + * during execution of methods of the named class + * @since 1.6R2 + */ + public static String defineClass(Scriptable scope, Class clazz, + boolean sealed, boolean mapInheritance) + throws IllegalAccessException, InstantiationException, + InvocationTargetException + { + BaseFunction ctor = buildClassCtor(scope, clazz, sealed, + mapInheritance); + if (ctor == null) + return null; + String name = ctor.getClassPrototype().getClassName(); + defineProperty(scope, name, ctor, ScriptableObject.DONTENUM); + return name; + } + + static BaseFunction buildClassCtor(Scriptable scope, Class clazz, + boolean sealed, + boolean mapInheritance) + throws IllegalAccessException, InstantiationException, + InvocationTargetException + { + Method[] methods = FunctionObject.getMethodList(clazz); + for (int i=0; i < methods.length; i++) { + Method method = methods[i]; + if (!method.getName().equals("init")) + continue; + Class[] parmTypes = method.getParameterTypes(); + if (parmTypes.length == 3 && + parmTypes[0] == ScriptRuntime.ContextClass && + parmTypes[1] == ScriptRuntime.ScriptableClass && + parmTypes[2] == Boolean.TYPE && + Modifier.isStatic(method.getModifiers())) + { + Object args[] = { Context.getContext(), scope, + sealed ? Boolean.TRUE : Boolean.FALSE }; + method.invoke(null, args); + return null; + } + if (parmTypes.length == 1 && + parmTypes[0] == ScriptRuntime.ScriptableClass && + Modifier.isStatic(method.getModifiers())) + { + Object args[] = { scope }; + method.invoke(null, args); + return null; + } + + } + + // If we got here, there isn't an "init" method with the right + // parameter types. + + Constructor[] ctors = clazz.getConstructors(); + Constructor protoCtor = null; + for (int i=0; i < ctors.length; i++) { + if (ctors[i].getParameterTypes().length == 0) { + protoCtor = ctors[i]; + break; + } + } + if (protoCtor == null) { + throw Context.reportRuntimeError1( + "msg.zero.arg.ctor", clazz.getName()); + } + + Scriptable proto = (Scriptable) protoCtor.newInstance(ScriptRuntime.emptyArgs); + String className = proto.getClassName(); + + // Set the prototype's prototype, trying to map Java inheritance to JS + // prototype-based inheritance if requested to do so. + Scriptable superProto = null; + if (mapInheritance) { + Class superClass = clazz.getSuperclass(); + if (ScriptRuntime.ScriptableClass.isAssignableFrom(superClass) + && !Modifier.isAbstract(superClass.getModifiers())) { + String name = ScriptableObject.defineClass(scope, superClass, sealed, mapInheritance); + if (name != null) { + superProto = ScriptableObject.getClassPrototype(scope, name); + } + } + } + if (superProto == null) { + superProto = ScriptableObject.getObjectPrototype(scope); + } + proto.setPrototype(superProto); + + // Find out whether there are any methods that begin with + // "js". If so, then only methods that begin with special + // prefixes will be defined as JavaScript entities. + final String functionPrefix = "jsFunction_"; + final String staticFunctionPrefix = "jsStaticFunction_"; + final String getterPrefix = "jsGet_"; + final String setterPrefix = "jsSet_"; + final String ctorName = "jsConstructor"; + + Member ctorMember = FunctionObject.findSingleMethod(methods, ctorName); + + if (ctorMember == null) { + if (ctors.length == 1) { + ctorMember = ctors[0]; + } else if (ctors.length == 2) { + if (ctors[0].getParameterTypes().length == 0) + ctorMember = ctors[1]; + else if (ctors[1].getParameterTypes().length == 0) + ctorMember = ctors[0]; + } + if (ctorMember == null) { + throw Context.reportRuntimeError1( + "msg.ctor.multiple.parms", clazz.getName()); + } + } + + FunctionObject ctor = new FunctionObject(className, ctorMember, scope); + if (ctor.isVarArgsMethod()) { + throw Context.reportRuntimeError1 + ("msg.varargs.ctor", ctorMember.getName()); + } + ctor.initAsConstructor(scope, proto); + + Method finishInit = null; + for (int i=0; i < methods.length; i++) { + if (methods[i] == ctorMember) { + continue; + } + String name = methods[i].getName(); + if (name.equals("finishInit")) { + Class[] parmTypes = methods[i].getParameterTypes(); + if (parmTypes.length == 3 && + parmTypes[0] == ScriptRuntime.ScriptableClass && + parmTypes[1] == FunctionObject.class && + parmTypes[2] == ScriptRuntime.ScriptableClass && + Modifier.isStatic(methods[i].getModifiers())) + { + finishInit = methods[i]; + continue; + } + } + // ignore any compiler generated methods. + if (name.indexOf('$') != -1) + continue; + if (name.equals(ctorName)) + continue; + + String prefix = null; + if (name.startsWith(functionPrefix)) { + prefix = functionPrefix; + } else if (name.startsWith(staticFunctionPrefix)) { + prefix = staticFunctionPrefix; + if (!Modifier.isStatic(methods[i].getModifiers())) { + throw Context.reportRuntimeError( + "jsStaticFunction must be used with static method."); + } + } else if (name.startsWith(getterPrefix)) { + prefix = getterPrefix; + } else if (name.startsWith(setterPrefix)) { + prefix = setterPrefix; + } else { + continue; + } + name = name.substring(prefix.length()); + if (prefix == setterPrefix) + continue; // deal with set when we see get + if (prefix == getterPrefix) { + if (!(proto instanceof ScriptableObject)) { + throw Context.reportRuntimeError2( + "msg.extend.scriptable", + proto.getClass().toString(), name); + } + Method setter = FunctionObject.findSingleMethod( + methods, + setterPrefix + name); + int attr = ScriptableObject.PERMANENT | + ScriptableObject.DONTENUM | + (setter != null ? 0 + : ScriptableObject.READONLY); + ((ScriptableObject) proto).defineProperty(name, null, + methods[i], setter, + attr); + continue; + } + + FunctionObject f = new FunctionObject(name, methods[i], proto); + if (f.isVarArgsConstructor()) { + throw Context.reportRuntimeError1 + ("msg.varargs.fun", ctorMember.getName()); + } + Scriptable dest = prefix == staticFunctionPrefix + ? ctor + : proto; + defineProperty(dest, name, f, DONTENUM); + if (sealed) { + f.sealObject(); + } + } + + // Call user code to complete initialization if necessary. + if (finishInit != null) { + Object[] finishArgs = { scope, ctor, proto }; + finishInit.invoke(null, finishArgs); + } + + // Seal the object if necessary. + if (sealed) { + ctor.sealObject(); + if (proto instanceof ScriptableObject) { + ((ScriptableObject) proto).sealObject(); + } + } + + return ctor; + } + + /** + * Define a JavaScript property. + * + * Creates the property with an initial value and sets its attributes. + * + * @param propertyName the name of the property to define. + * @param value the initial value of the property + * @param attributes the attributes of the JavaScript property + * @see org.mozilla.javascript.Scriptable#put(String, Scriptable, Object) + */ + public void defineProperty(String propertyName, Object value, + int attributes) + { + checkNotSealed(propertyName, 0); + put(propertyName, this, value); + setAttributes(propertyName, attributes); + } + + /** + * Utility method to add properties to arbitrary Scriptable object. + * If destination is instance of ScriptableObject, calls + * defineProperty there, otherwise calls put in destination + * ignoring attributes + */ + public static void defineProperty(Scriptable destination, + String propertyName, Object value, + int attributes) + { + if (!(destination instanceof ScriptableObject)) { + destination.put(propertyName, destination, value); + return; + } + ScriptableObject so = (ScriptableObject)destination; + so.defineProperty(propertyName, value, attributes); + } + + /** + * Utility method to add properties to arbitrary Scriptable object. + * If destination is instance of ScriptableObject, calls + * defineProperty there, otherwise calls put in destination + * ignoring attributes + */ + public static void defineConstProperty(Scriptable destination, + String propertyName) + { + if (destination instanceof ConstProperties) { + ConstProperties cp = (ConstProperties)destination; + cp.defineConst(propertyName, destination); + } else + defineProperty(destination, propertyName, Undefined.instance, CONST); + } + + /** + * Define a JavaScript property with getter and setter side effects. + * + * If the setter is not found, the attribute READONLY is added to + * the given attributes.

+ * + * The getter must be a method with zero parameters, and the setter, if + * found, must be a method with one parameter.

+ * + * @param propertyName the name of the property to define. This name + * also affects the name of the setter and getter + * to search for. If the propertyId is "foo", then + * clazz will be searched for "getFoo" + * and "setFoo" methods. + * @param clazz the Java class to search for the getter and setter + * @param attributes the attributes of the JavaScript property + * @see org.mozilla.javascript.Scriptable#put(String, Scriptable, Object) + */ + public void defineProperty(String propertyName, Class clazz, + int attributes) + { + int length = propertyName.length(); + if (length == 0) throw new IllegalArgumentException(); + char[] buf = new char[3 + length]; + propertyName.getChars(0, length, buf, 3); + buf[3] = Character.toUpperCase(buf[3]); + buf[0] = 'g'; + buf[1] = 'e'; + buf[2] = 't'; + String getterName = new String(buf); + buf[0] = 's'; + String setterName = new String(buf); + + Method[] methods = FunctionObject.getMethodList(clazz); + Method getter = FunctionObject.findSingleMethod(methods, getterName); + Method setter = FunctionObject.findSingleMethod(methods, setterName); + if (setter == null) + attributes |= ScriptableObject.READONLY; + defineProperty(propertyName, null, getter, + setter == null ? null : setter, attributes); + } + + /** + * Define a JavaScript property. + * + * Use this method only if you wish to define getters and setters for + * a given property in a ScriptableObject. To create a property without + * special getter or setter side effects, use + * defineProperty(String,int). + * + * If setter is null, the attribute READONLY is added to + * the given attributes.

+ * + * Several forms of getters or setters are allowed. In all cases the + * type of the value parameter can be any one of the following types: + * Object, String, boolean, Scriptable, byte, short, int, long, float, + * or double. The runtime will perform appropriate conversions based + * upon the type of the parameter (see description in FunctionObject). + * The first forms are nonstatic methods of the class referred to + * by 'this': + *

+     * Object getFoo();
+     * void setFoo(SomeType value);
+ * Next are static methods that may be of any class; the object whose + * property is being accessed is passed in as an extra argument: + *
+     * static Object getFoo(Scriptable obj);
+     * static void setFoo(Scriptable obj, SomeType value);
+ * Finally, it is possible to delegate to another object entirely using + * the delegateTo parameter. In this case the methods are + * nonstatic methods of the class delegated to, and the object whose + * property is being accessed is passed in as an extra argument: + *
+     * Object getFoo(Scriptable obj);
+     * void setFoo(Scriptable obj, SomeType value);
+ * + * @param propertyName the name of the property to define. + * @param delegateTo an object to call the getter and setter methods on, + * or null, depending on the form used above. + * @param getter the method to invoke to get the value of the property + * @param setter the method to invoke to set the value of the property + * @param attributes the attributes of the JavaScript property + */ + public void defineProperty(String propertyName, Object delegateTo, + Method getter, Method setter, int attributes) + { + MemberBox getterBox = null; + if (getter != null) { + getterBox = new MemberBox(getter); + + boolean delegatedForm; + if (!Modifier.isStatic(getter.getModifiers())) { + delegatedForm = (delegateTo != null); + getterBox.delegateTo = delegateTo; + } else { + delegatedForm = true; + // Ignore delegateTo for static getter but store + // non-null delegateTo indicator. + getterBox.delegateTo = Void.TYPE; + } + + String errorId = null; + Class[] parmTypes = getter.getParameterTypes(); + if (parmTypes.length == 0) { + if (delegatedForm) { + errorId = "msg.obj.getter.parms"; + } + } else if (parmTypes.length == 1) { + Object argType = parmTypes[0]; + // Allow ScriptableObject for compatibility + if (!(argType == ScriptRuntime.ScriptableClass || + argType == ScriptRuntime.ScriptableObjectClass)) + { + errorId = "msg.bad.getter.parms"; + } else if (!delegatedForm) { + errorId = "msg.bad.getter.parms"; + } + } else { + errorId = "msg.bad.getter.parms"; + } + if (errorId != null) { + throw Context.reportRuntimeError1(errorId, getter.toString()); + } + } + + MemberBox setterBox = null; + if (setter != null) { + if (setter.getReturnType() != Void.TYPE) + throw Context.reportRuntimeError1("msg.setter.return", + setter.toString()); + + setterBox = new MemberBox(setter); + + boolean delegatedForm; + if (!Modifier.isStatic(setter.getModifiers())) { + delegatedForm = (delegateTo != null); + setterBox.delegateTo = delegateTo; + } else { + delegatedForm = true; + // Ignore delegateTo for static setter but store + // non-null delegateTo indicator. + setterBox.delegateTo = Void.TYPE; + } + + String errorId = null; + Class[] parmTypes = setter.getParameterTypes(); + if (parmTypes.length == 1) { + if (delegatedForm) { + errorId = "msg.setter2.expected"; + } + } else if (parmTypes.length == 2) { + Object argType = parmTypes[0]; + // Allow ScriptableObject for compatibility + if (!(argType == ScriptRuntime.ScriptableClass || + argType == ScriptRuntime.ScriptableObjectClass)) + { + errorId = "msg.setter2.parms"; + } else if (!delegatedForm) { + errorId = "msg.setter1.parms"; + } + } else { + errorId = "msg.setter.parms"; + } + if (errorId != null) { + throw Context.reportRuntimeError1(errorId, setter.toString()); + } + } + + GetterSlot gslot = (GetterSlot)getSlot(propertyName, 0, + SLOT_MODIFY_GETTER_SETTER); + gslot.setAttributes(attributes); + gslot.getter = getterBox; + gslot.setter = setterBox; + } + + /** + * Search for names in a class, adding the resulting methods + * as properties. + * + *

Uses reflection to find the methods of the given names. Then + * FunctionObjects are constructed from the methods found, and + * are added to this object as properties with the given names. + * + * @param names the names of the Methods to add as function properties + * @param clazz the class to search for the Methods + * @param attributes the attributes of the new properties + * @see org.mozilla.javascript.FunctionObject + */ + public void defineFunctionProperties(String[] names, Class clazz, + int attributes) + { + Method[] methods = FunctionObject.getMethodList(clazz); + for (int i=0; i < names.length; i++) { + String name = names[i]; + Method m = FunctionObject.findSingleMethod(methods, name); + if (m == null) { + throw Context.reportRuntimeError2( + "msg.method.not.found", name, clazz.getName()); + } + FunctionObject f = new FunctionObject(name, m, this); + defineProperty(name, f, attributes); + } + } + + /** + * Get the Object.prototype property. + * See ECMA 15.2.4. + */ + public static Scriptable getObjectPrototype(Scriptable scope) { + return getClassPrototype(scope, "Object"); + } + + /** + * Get the Function.prototype property. + * See ECMA 15.3.4. + */ + public static Scriptable getFunctionPrototype(Scriptable scope) { + return getClassPrototype(scope, "Function"); + } + + /** + * Get the prototype for the named class. + * + * For example, getClassPrototype(s, "Date") will first + * walk up the parent chain to find the outermost scope, then will + * search that scope for the Date constructor, and then will + * return Date.prototype. If any of the lookups fail, or + * the prototype is not a JavaScript object, then null will + * be returned. + * + * @param scope an object in the scope chain + * @param className the name of the constructor + * @return the prototype for the named class, or null if it + * cannot be found. + */ + public static Scriptable getClassPrototype(Scriptable scope, + String className) + { + scope = getTopLevelScope(scope); + Object ctor = getProperty(scope, className); + Object proto; + if (ctor instanceof BaseFunction) { + proto = ((BaseFunction)ctor).getPrototypeProperty(); + } else if (ctor instanceof Scriptable) { + Scriptable ctorObj = (Scriptable)ctor; + proto = ctorObj.get("prototype", ctorObj); + } else { + return null; + } + if (proto instanceof Scriptable) { + return (Scriptable)proto; + } + return null; + } + + /** + * Get the global scope. + * + *

Walks the parent scope chain to find an object with a null + * parent scope (the global object). + * + * @param obj a JavaScript object + * @return the corresponding global scope + */ + public static Scriptable getTopLevelScope(Scriptable obj) + { + for (;;) { + Scriptable parent = obj.getParentScope(); + if (parent == null) { + return obj; + } + obj = parent; + } + } + + // APPJET + public static Scriptable getVeryTopLevelScope(Scriptable obj) { + return ScriptRuntime.getLibraryScopeOrNull(obj); + } + + /** + * Seal this object. + * + * A sealed object may not have properties added or removed. Once + * an object is sealed it may not be unsealed. + * + * @since 1.4R3 + */ + public synchronized void sealObject() { + if (count >= 0) { + count = ~count; + } + } + + /** + * Return true if this object is sealed. + * + * It is an error to attempt to add or remove properties to + * a sealed object. + * + * @return true if sealed, false otherwise. + * @since 1.4R3 + */ + public final boolean isSealed() { + return count < 0; + } + + private void checkNotSealed(String name, int index) + { + if (!isSealed()) + return; + + String str = (name != null) ? name : Integer.toString(index); + throw Context.reportRuntimeError1("msg.modify.sealed", str); + } + + /** + * Gets a named property from an object or any object in its prototype chain. + *

+ * Searches the prototype chain for a property named name. + *

+ * @param obj a JavaScript object + * @param name a property name + * @return the value of a property with name name found in + * obj or any object in its prototype chain, or + * Scriptable.NOT_FOUND if not found + * @since 1.5R2 + */ + public static Object getProperty(Scriptable obj, String name) + { + Scriptable start = obj; + Object result; + do { + result = obj.get(name, start); + if (result != Scriptable.NOT_FOUND) + break; + obj = obj.getPrototype(); + } while (obj != null); + return result; + } + + /** + * Gets an indexed property from an object or any object in its prototype chain. + *

+ * Searches the prototype chain for a property with integral index + * index. Note that if you wish to look for properties with numerical + * but non-integral indicies, you should use getProperty(Scriptable,String) with + * the string value of the index. + *

+ * @param obj a JavaScript object + * @param index an integral index + * @return the value of a property with index index found in + * obj or any object in its prototype chain, or + * Scriptable.NOT_FOUND if not found + * @since 1.5R2 + */ + public static Object getProperty(Scriptable obj, int index) + { + Scriptable start = obj; + Object result; + do { + result = obj.get(index, start); + if (result != Scriptable.NOT_FOUND) + break; + obj = obj.getPrototype(); + } while (obj != null); + return result; + } + + /** + * Returns whether a named property is defined in an object or any object + * in its prototype chain. + *

+ * Searches the prototype chain for a property named name. + *

+ * @param obj a JavaScript object + * @param name a property name + * @return the true if property was found + * @since 1.5R2 + */ + public static boolean hasProperty(Scriptable obj, String name) + { + return null != getBase(obj, name); + } + + /** + * If hasProperty(obj, name) would return true, then if the property that + * was found is compatible with the new property, this method just returns. + * If the property is not compatible, then an exception is thrown. + * + * A property redefinition is incompatible if the first definition was a + * const declaration or if this one is. They are compatible only if neither + * was const. + */ + public static void redefineProperty(Scriptable obj, String name, + boolean isConst) + { + Scriptable base = getBase(obj, name); + if (base == null) + return; + if (base instanceof ConstProperties) { + ConstProperties cp = (ConstProperties)base; + + if (cp.isConst(name)) + throw Context.reportRuntimeError1("msg.const.redecl", name); + } + if (isConst) + throw Context.reportRuntimeError1("msg.var.redecl", name); + } + /** + * Returns whether an indexed property is defined in an object or any object + * in its prototype chain. + *

+ * Searches the prototype chain for a property with index index. + *

+ * @param obj a JavaScript object + * @param index a property index + * @return the true if property was found + * @since 1.5R2 + */ + public static boolean hasProperty(Scriptable obj, int index) + { + return null != getBase(obj, index); + } + + /** + * Puts a named property in an object or in an object in its prototype chain. + *

+ * Searches for the named property in the prototype chain. If it is found, + * the value of the property in obj is changed through a call + * to {@link Scriptable#put(String, Scriptable, Object)} on the + * prototype passing obj as the start argument. + * This allows the prototype to veto the property setting in case the + * prototype defines the property with [[ReadOnly]] attribute. If the + * property is not found, it is added in obj. + * @param obj a JavaScript object + * @param name a property name + * @param value any JavaScript value accepted by Scriptable.put + * @since 1.5R2 + */ + public static void putProperty(Scriptable obj, String name, Object value) + { + Scriptable base = getBase(obj, name); + if (base == null) + base = obj; + base.put(name, obj, value); + } + + /** + * Puts a named property in an object or in an object in its prototype chain. + *

+ * Searches for the named property in the prototype chain. If it is found, + * the value of the property in obj is changed through a call + * to {@link Scriptable#put(String, Scriptable, Object)} on the + * prototype passing obj as the start argument. + * This allows the prototype to veto the property setting in case the + * prototype defines the property with [[ReadOnly]] attribute. If the + * property is not found, it is added in obj. + * @param obj a JavaScript object + * @param name a property name + * @param value any JavaScript value accepted by Scriptable.put + * @since 1.5R2 + */ + public static void putConstProperty(Scriptable obj, String name, Object value) + { + Scriptable base = getBase(obj, name); + if (base == null) + base = obj; + if (base instanceof ConstProperties) + ((ConstProperties)base).putConst(name, obj, value); + } + + /** + * Puts an indexed property in an object or in an object in its prototype chain. + *

+ * Searches for the indexed property in the prototype chain. If it is found, + * the value of the property in obj is changed through a call + * to {@link Scriptable#put(int, Scriptable, Object)} on the prototype + * passing obj as the start argument. This allows + * the prototype to veto the property setting in case the prototype defines + * the property with [[ReadOnly]] attribute. If the property is not found, + * it is added in obj. + * @param obj a JavaScript object + * @param index a property index + * @param value any JavaScript value accepted by Scriptable.put + * @since 1.5R2 + */ + public static void putProperty(Scriptable obj, int index, Object value) + { + Scriptable base = getBase(obj, index); + if (base == null) + base = obj; + base.put(index, obj, value); + } + + /** + * Removes the property from an object or its prototype chain. + *

+ * Searches for a property with name in obj or + * its prototype chain. If it is found, the object's delete + * method is called. + * @param obj a JavaScript object + * @param name a property name + * @return true if the property doesn't exist or was successfully removed + * @since 1.5R2 + */ + public static boolean deleteProperty(Scriptable obj, String name) + { + Scriptable base = getBase(obj, name); + if (base == null) + return true; + base.delete(name); + return !base.has(name, obj); + } + + /** + * Removes the property from an object or its prototype chain. + *

+ * Searches for a property with index in obj or + * its prototype chain. If it is found, the object's delete + * method is called. + * @param obj a JavaScript object + * @param index a property index + * @return true if the property doesn't exist or was successfully removed + * @since 1.5R2 + */ + public static boolean deleteProperty(Scriptable obj, int index) + { + Scriptable base = getBase(obj, index); + if (base == null) + return true; + base.delete(index); + return !base.has(index, obj); + } + + /** + * Returns an array of all ids from an object and its prototypes. + *

+ * @param obj a JavaScript object + * @return an array of all ids from all object in the prototype chain. + * If a given id occurs multiple times in the prototype chain, + * it will occur only once in this list. + * @since 1.5R2 + */ + public static Object[] getPropertyIds(Scriptable obj) + { + if (obj == null) { + return ScriptRuntime.emptyArgs; + } + Object[] result = obj.getIds(); + ObjToIntMap map = null; + for (;;) { + obj = obj.getPrototype(); + if (obj == null) { + break; + } + Object[] ids = obj.getIds(); + if (ids.length == 0) { + continue; + } + if (map == null) { + if (result.length == 0) { + result = ids; + continue; + } + map = new ObjToIntMap(result.length + ids.length); + for (int i = 0; i != result.length; ++i) { + map.intern(result[i]); + } + result = null; // Allow to GC the result + } + for (int i = 0; i != ids.length; ++i) { + map.intern(ids[i]); + } + } + if (map != null) { + result = map.getKeys(); + } + return result; + } + + /** + * Call a method of an object. + * @param obj the JavaScript object + * @param methodName the name of the function property + * @param args the arguments for the call + * + * @see Context#getCurrentContext() + */ + public static Object callMethod(Scriptable obj, String methodName, + Object[] args) + { + return callMethod(null, obj, methodName, args); + } + + /** + * Call a method of an object. + * @param cx the Context object associated with the current thread. + * @param obj the JavaScript object + * @param methodName the name of the function property + * @param args the arguments for the call + */ + public static Object callMethod(Context cx, Scriptable obj, + String methodName, + Object[] args) + { + Object funObj = getProperty(obj, methodName); + if (!(funObj instanceof Function)) { + throw ScriptRuntime.notFunctionError(obj, methodName); + } + Function fun = (Function)funObj; + // XXX: What should be the scope when calling funObj? + // The following favor scope stored in the object on the assumption + // that is more useful especially under dynamic scope setup. + // An alternative is to check for dynamic scope flag + // and use ScriptableObject.getTopLevelScope(fun) if the flag is not + // set. But that require access to Context and messy code + // so for now it is not checked. + Scriptable scope = ScriptableObject.getTopLevelScope(obj); + if (cx != null) { + return fun.call(cx, scope, obj, args); + } else { + return Context.call(null, fun, scope, obj, args); + } + } + + private static Scriptable getBase(Scriptable obj, String name) + { + do { + if (obj.has(name, obj)) + break; + obj = obj.getPrototype(); + } while(obj != null); + return obj; + } + + private static Scriptable getBase(Scriptable obj, int index) + { + do { + if (obj.has(index, obj)) + break; + obj = obj.getPrototype(); + } while(obj != null); + return obj; + } + + /** + * Get arbitrary application-specific value associated with this object. + * @param key key object to select particular value. + * @see #associateValue(Object key, Object value) + */ + public final Object getAssociatedValue(Object key) + { + Hashtable h = associatedValues; + if (h == null) + return null; + return h.get(key); + } + + /** + * Get arbitrary application-specific value associated with the top scope + * of the given scope. + * The method first calls {@link #getTopLevelScope(Scriptable scope)} + * and then searches the prototype chain of the top scope for the first + * object containing the associated value with the given key. + * + * @param scope the starting scope. + * @param key key object to select particular value. + * @see #getAssociatedValue(Object key) + */ + public static Object getTopScopeValue(Scriptable scope, Object key) + { + scope = ScriptableObject.getTopLevelScope(scope); + for (;;) { + if (scope instanceof ScriptableObject) { + ScriptableObject so = (ScriptableObject)scope; + Object value = so.getAssociatedValue(key); + if (value != null) { + return value; + } + } + scope = scope.getPrototype(); + if (scope == null) { + return null; + } + } + } + + /** + * Associate arbitrary application-specific value with this object. + * Value can only be associated with the given object and key only once. + * The method ignores any subsequent attempts to change the already + * associated value. + *

The associated values are not serialized. + * @param key key object to select particular value. + * @param value the value to associate + * @return the passed value if the method is called first time for the + * given key or old value for any subsequent calls. + * @see #getAssociatedValue(Object key) + */ + public final Object associateValue(Object key, Object value) + { + if (value == null) throw new IllegalArgumentException(); + Hashtable h = associatedValues; + if (h == null) { + synchronized (this) { + h = associatedValues; + if (h == null) { + h = new Hashtable(); + associatedValues = h; + } + } + } + return Kit.initHash(h, key, value); + } + + private Object getImpl(String name, int index, Scriptable start) + { + Slot slot = getSlot(name, index, SLOT_QUERY); + if (slot == null) { + return Scriptable.NOT_FOUND; + } + if (!(slot instanceof GetterSlot)) { + return slot.value; + } + Object getterObj = ((GetterSlot)slot).getter; + if (getterObj != null) { + if (getterObj instanceof MemberBox) { + MemberBox nativeGetter = (MemberBox)getterObj; + Object getterThis; + Object[] args; + if (nativeGetter.delegateTo == null) { + getterThis = start; + args = ScriptRuntime.emptyArgs; + } else { + getterThis = nativeGetter.delegateTo; + args = new Object[] { start }; + } + return nativeGetter.invoke(getterThis, args); + } else { + Function f = (Function)getterObj; + Context cx = Context.getContext(); + return f.call(cx, f.getParentScope(), start, + ScriptRuntime.emptyArgs); + } + } + Object value = slot.value; + if (value instanceof LazilyLoadedCtor) { + LazilyLoadedCtor initializer = (LazilyLoadedCtor)value; + try { + initializer.init(); + } finally { + value = initializer.getValue(); + slot.value = value; + } + } + return value; + } + + /** + * + * @param name + * @param index + * @param start + * @param value + * @param constFlag EMPTY means normal put. UNINITIALIZED_CONST means + * defineConstProperty. READONLY means const initialization expression. + * @return false if this != start and no slot was found. true if this == start + * or this != start and a READONLY slot was found. + */ + private boolean putImpl(String name, int index, Scriptable start, + Object value, int constFlag) + { + Slot slot; + if (this != start) { + slot = getSlot(name, index, SLOT_QUERY); + if (slot == null) { + return false; + } + } else { + checkNotSealed(name, index); + // either const hoisted declaration or initialization + if (constFlag != EMPTY) { + slot = getSlot(name, index, SLOT_MODIFY_CONST); + int attr = slot.getAttributes(); + if ((attr & READONLY) == 0) + throw Context.reportRuntimeError1("msg.var.redecl", name); + if ((attr & UNINITIALIZED_CONST) != 0) { + slot.value = value; + // clear the bit on const initialization + if (constFlag != UNINITIALIZED_CONST) + slot.setAttributes(attr & ~UNINITIALIZED_CONST); + } + return true; + } + slot = getSlot(name, index, SLOT_MODIFY); + } + if ((slot.getAttributes() & READONLY) != 0) + return true; + if (slot instanceof GetterSlot) { + Object setterObj = ((GetterSlot)slot).setter; + if (setterObj != null) { + Context cx = Context.getContext(); + if (setterObj instanceof MemberBox) { + MemberBox nativeSetter = (MemberBox)setterObj; + Class pTypes[] = nativeSetter.argTypes; + // XXX: cache tag since it is already calculated in + // defineProperty ? + Class valueType = pTypes[pTypes.length - 1]; + int tag = FunctionObject.getTypeTag(valueType); + Object actualArg = FunctionObject.convertArg(cx, start, + value, tag); + Object setterThis; + Object[] args; + if (nativeSetter.delegateTo == null) { + setterThis = start; + args = new Object[] { actualArg }; + } else { + setterThis = nativeSetter.delegateTo; + args = new Object[] { start, actualArg }; + } + nativeSetter.invoke(setterThis, args); + } else { + Function f = (Function)setterObj; + f.call(cx, f.getParentScope(), start, + new Object[] { value }); + } + return true; + } + } + if (this == start) { + slot.value = value; + return true; + } else { + return false; + } + } + + private Slot findAttributeSlot(String name, int index, int accessType) + { + Slot slot = getSlot(name, index, accessType); + if (slot == null) { + String str = (name != null ? name : Integer.toString(index)); + throw Context.reportRuntimeError1("msg.prop.not.found", str); + } + return slot; + } + + /** + * Locate the slot with given name or index. + * + * @param name property name or null if slot holds spare array index. + * @param index index or 0 if slot holds property name. + */ + private Slot getSlot(String name, int index, int accessType) + { + Slot slot; + + // Query last access cache and check that it was not deleted. + lastAccessCheck: + { + slot = lastAccess; + if (name != null) { + if (name != slot.name) + break lastAccessCheck; + // No String.equals here as successful slot search update + // name object with fresh reference of the same string. + } else { + if (slot.name != null || index != slot.indexOrHash) + break lastAccessCheck; + } + + if (slot.wasDeleted != 0) + break lastAccessCheck; + + if (accessType == SLOT_MODIFY_GETTER_SETTER && + !(slot instanceof GetterSlot)) + break lastAccessCheck; + + return slot; + } + + slot = accessSlot(name, index, accessType); + if (slot != null) { + // Update the cache + lastAccess = slot; + } + return slot; + } + + private Slot accessSlot(String name, int index, int accessType) + { + int indexOrHash = (name != null ? name.hashCode() : index); + + if (accessType == SLOT_QUERY || + accessType == SLOT_MODIFY || + accessType == SLOT_MODIFY_CONST || + accessType == SLOT_MODIFY_GETTER_SETTER) + { + // Check the hashtable without using synchronization + + Slot[] slotsLocalRef = slots; // Get stable local reference + if (slotsLocalRef == null) { + if (accessType == SLOT_QUERY) + return null; + } else { + int tableSize = slotsLocalRef.length; + int slotIndex = getSlotIndex(tableSize, indexOrHash); + Slot slot = slotsLocalRef[slotIndex]; + while (slot != null) { + String sname = slot.name; + if (sname != null) { + if (sname == name) + break; + if (name != null && indexOrHash == slot.indexOrHash) { + if (name.equals(sname)) { + // This will avoid calling String.equals when + // slot is accessed with same string object + // next time. + slot.name = name; + break; + } + } + } else if (name == null && + indexOrHash == slot.indexOrHash) { + break; + } + slot = slot.next; + } + if (accessType == SLOT_QUERY) { + return slot; + } else if (accessType == SLOT_MODIFY) { + if (slot != null) + return slot; + } else if (accessType == SLOT_MODIFY_GETTER_SETTER) { + if (slot instanceof GetterSlot) + return slot; + } else if (accessType == SLOT_MODIFY_CONST) { + if (slot != null) + return slot; + } + } + + // A new slot has to be inserted or the old has to be replaced + // by GetterSlot. Time to synchronize. + + synchronized (this) { + // Refresh local ref if another thread triggered grow + slotsLocalRef = slots; + int insertPos; + if (count == 0) { + // Always throw away old slots if any on empty insert + slotsLocalRef = new Slot[5]; + slots = slotsLocalRef; + insertPos = getSlotIndex(slotsLocalRef.length, indexOrHash); + } else { + int tableSize = slotsLocalRef.length; + insertPos = getSlotIndex(tableSize, indexOrHash); + Slot prev = slotsLocalRef[insertPos]; + Slot slot = prev; + while (slot != null) { + if (slot.indexOrHash == indexOrHash && + (slot.name == name || + (name != null && name.equals(slot.name)))) + { + break; + } + prev = slot; + slot = slot.next; + } + + if (slot != null) { + // Another thread just added a slot with same + // name/index before this one entered synchronized + // block. This is a race in application code and + // probably indicates bug there. But for the hashtable + // implementation it is harmless with the only + // complication is the need to replace the added slot + // if we need GetterSlot and the old one is not. + if (accessType == SLOT_MODIFY_GETTER_SETTER && + !(slot instanceof GetterSlot)) + { + GetterSlot newSlot = new GetterSlot(name, indexOrHash, + slot.getAttributes()); + newSlot.value = slot.value; + newSlot.next = slot.next; + if (prev == slot) { + slotsLocalRef[insertPos] = newSlot; + } else { + prev.next = newSlot; + } + slot.wasDeleted = (byte)1; + if (slot == lastAccess) { + lastAccess = REMOVED; + } + slot = newSlot; + } else if (accessType == SLOT_MODIFY_CONST) { + return null; + } + return slot; + } + + // Check if the table is not too full before inserting. + if (4 * (count + 1) > 3 * slotsLocalRef.length) { + slotsLocalRef = new Slot[slotsLocalRef.length * 2 + 1]; + copyTable(slots, slotsLocalRef, count); + slots = slotsLocalRef; + insertPos = getSlotIndex(slotsLocalRef.length, + indexOrHash); + } + } + + Slot newSlot = (accessType == SLOT_MODIFY_GETTER_SETTER + ? new GetterSlot(name, indexOrHash, 0) + : new Slot(name, indexOrHash, 0)); + if (accessType == SLOT_MODIFY_CONST) + newSlot.setAttributes(CONST); + ++count; + addKnownAbsentSlot(slotsLocalRef, newSlot, insertPos); + return newSlot; + } + + } else if (accessType == SLOT_REMOVE) { + synchronized (this) { + Slot[] slotsLocalRef = slots; + if (count != 0) { + int tableSize = slots.length; + int slotIndex = getSlotIndex(tableSize, indexOrHash); + Slot prev = slotsLocalRef[slotIndex]; + Slot slot = prev; + while (slot != null) { + if (slot.indexOrHash == indexOrHash && + (slot.name == name || + (name != null && name.equals(slot.name)))) + { + break; + } + prev = slot; + slot = slot.next; + } + if (slot != null && (slot.getAttributes() & PERMANENT) == 0) { + count--; + if (prev == slot) { + slotsLocalRef[slotIndex] = slot.next; + } else { + prev.next = slot.next; + } + // Mark the slot as removed to handle a case when + // another thread manages to put just removed slot + // into lastAccess cache. + slot.wasDeleted = (byte)1; + if (slot == lastAccess) { + lastAccess = REMOVED; + } + } + } + } + return null; + + } else { + throw Kit.codeBug(); + } + } + + private static int getSlotIndex(int tableSize, int indexOrHash) + { + return (indexOrHash & 0x7fffffff) % tableSize; + } + + // Must be inside synchronized (this) + private static void copyTable(Slot[] slots, Slot[] newSlots, int count) + { + if (count == 0) throw Kit.codeBug(); + + int tableSize = newSlots.length; + int i = slots.length; + for (;;) { + --i; + Slot slot = slots[i]; + while (slot != null) { + int insertPos = getSlotIndex(tableSize, slot.indexOrHash); + Slot next = slot.next; + addKnownAbsentSlot(newSlots, slot, insertPos); + slot.next = null; + slot = next; + if (--count == 0) + return; + } + } + } + + /** + * Add slot with keys that are known to absent from the table. + * This is an optimization to use when inserting into empty table, + * after table growth or during deserialization. + */ + private static void addKnownAbsentSlot(Slot[] slots, Slot slot, int insertPos) + { + if (slots[insertPos] == null) { + slots[insertPos] = slot; + } else { + Slot prev = slots[insertPos]; + while (prev.next != null) { + prev = prev.next; + } + prev.next = slot; + } + } + + Object[] getIds(boolean getAll) { + Slot[] s = slots; + Object[] a = ScriptRuntime.emptyArgs; + if (s == null) + return a; + int c = 0; + for (int i=0; i < s.length; i++) { + Slot slot = s[i]; + while (slot != null) { + if (getAll || (slot.getAttributes() & DONTENUM) == 0) { + if (c == 0) + a = new Object[s.length]; + a[c++] = (slot.name != null ? (Object) slot.name + : new Integer(slot.indexOrHash)); + } + slot = slot.next; + } + } + if (c == a.length) + return a; + Object[] result = new Object[c]; + System.arraycopy(a, 0, result, 0, c); + return result; + } + + private synchronized void writeObject(ObjectOutputStream out) + throws IOException + { + out.defaultWriteObject(); + int objectsCount = count; + if (objectsCount < 0) { + // "this" was sealed + objectsCount = ~objectsCount; + } + if (objectsCount == 0) { + out.writeInt(0); + } else { + out.writeInt(slots.length); + for (int i = 0; i < slots.length; ++i) { + Slot slot = slots[i]; + while (slot != null) { + out.writeObject(slot); + slot = slot.next; + if (--objectsCount == 0) + return; + } + } + } + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + lastAccess = REMOVED; + + int tableSize = in.readInt(); + if (tableSize != 0) { + slots = new Slot[tableSize]; + int objectsCount = count; + if (objectsCount < 0) { + // "this" was sealed + objectsCount = ~objectsCount; + } + for (int i = 0; i != objectsCount; ++i) { + Slot slot = (Slot)in.readObject(); + int slotIndex = getSlotIndex(tableSize, slot.indexOrHash); + addKnownAbsentSlot(slots, slot, slotIndex); + } + } + } + +} -- cgit v1.2.3