All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mozilla.javascript.ScriptableObject Maven / Gradle / Ivy

Go to download

Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically embedded into Java applications to provide scripting to end users.

There is a newer version: 1.7R2
Show newest version
/* -*- 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
 *   Roger Lawrence
 *
 * 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 { /** * The empty property attribute. * * Used by getAttributes() and setAttributes(). * * @see org.mozilla.javascript.ScriptableObject#getAttributes * @see org.mozilla.javascript.ScriptableObject#setAttributes */ public static final int EMPTY = 0x00; /** * Property attribute indicating assignment to this property is ignored. * * @see org.mozilla.javascript.ScriptableObject#put * @see org.mozilla.javascript.ScriptableObject#getAttributes * @see org.mozilla.javascript.ScriptableObject#setAttributes */ 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 * @see org.mozilla.javascript.ScriptableObject#setAttributes */ public static final int DONTENUM = 0x02; /** * Property attribute indicating property cannot be deleted. * * @see org.mozilla.javascript.ScriptableObject#delete * @see org.mozilla.javascript.ScriptableObject#getAttributes * @see org.mozilla.javascript.ScriptableObject#setAttributes */ public static final int PERMANENT = 0x04; static void checkValidAttributes(int attributes) { final int mask = READONLY | DONTENUM | PERMANENT; 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 != getNamedSlot(name); } /** * 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); } /** * 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) { Slot slot = getNamedSlot(name); if (slot == null) { return Scriptable.NOT_FOUND; } if (slot instanceof GetterSlot) { GetterSlot gslot = (GetterSlot)slot; if (gslot.getter != null) { return getByGetter(gslot, start); } } return slot.value; } /** * 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) { Slot slot = getSlot(null, index); if (slot == null) { return Scriptable.NOT_FOUND; } return slot.value; } /** * 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) { Slot slot = lastAccess; // Get local copy if (name != slot.stringKey || slot.wasDeleted != 0) { int hash = name.hashCode(); slot = getSlot(name, hash); if (slot == null) { if (start != this) { start.put(name, start, value); return; } slot = addSlot(name, hash, null); } // Note: cache is not updated in put } if (start == this && isSealed()) { throw Context.reportRuntimeError1("msg.modify.sealed", name); } if ((slot.attributes & ScriptableObject.READONLY) != 0) { return; } if (slot instanceof GetterSlot) { GetterSlot gslot = (GetterSlot)slot; if (gslot.setter != null) { setBySetter(gslot, start, value); } return; } if (this == start) { slot.value = value; } else { 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) { Slot slot = getSlot(null, index); if (slot == null) { if (start != this) { start.put(index, start, value); return; } slot = addSlot(null, index, null); } if (start == this && isSealed()) { throw Context.reportRuntimeError1("msg.modify.sealed", Integer.toString(index)); } if ((slot.attributes & ScriptableObject.READONLY) != 0) { return; } if (this == start) { slot.value = value; } else { 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) { removeSlot(name, name.hashCode()); } /** * 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) { removeSlot(null, index); } /** * @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 * @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) { Slot slot = getNamedSlot(name); if (slot == null) { throw Context.reportRuntimeError1("msg.prop.not.found", name); } return slot.attributes; } /** * 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 * @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) { Slot slot = getSlot(null, index); if (slot == null) { throw Context.reportRuntimeError1("msg.prop.not.found", String.valueOf(index)); } return slot.attributes; } /** * 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 * @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) { checkValidAttributes(attributes); Slot slot = getNamedSlot(name); if (slot == null) { throw Context.reportRuntimeError1("msg.prop.not.found", name); } slot.attributes = (short) 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 * @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) { checkValidAttributes(attributes); Slot slot = getSlot(null, index); if (slot == null) { throw Context.reportRuntimeError1("msg.prop.not.found", String.valueOf(index)); } slot.attributes = (short) attributes; } /** * 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) { 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(this, methodName); if (!(v instanceof Function)) continue; Function fun = (Function) v; if (cx == null) cx = Context.getContext(); v = fun.call(cx, fun.getParentScope(), this, 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); } /** * 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 *

    *
  • jsFunction_ for a JavaScript function *
  • jsStaticFunction_ for a JavaScript function that * is a property of the constructor *
  • jsGet_ for a getter of a JavaScript property *
  • jsSet_ for a setter of a JavaScript property *
  • jsConstructor for a JavaScript function that * is the constructor *

* * 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 */ 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 { 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)) { 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.addAsConstructor(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 className; } /** * 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 */ public void defineProperty(String propertyName, Object value, int attributes) { 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); } /** * 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 */ 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(ScriptableObject obj);
     * static void setFoo(ScriptableObject 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(ScriptableObject obj);
     * void setFoo(ScriptableObject 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) { if (delegateTo == null && (Modifier.isStatic(getter.getModifiers()))) delegateTo = HAS_STATIC_ACCESSORS; Class[] parmTypes = getter.getParameterTypes(); if (parmTypes.length != 0) { if (parmTypes.length != 1 || parmTypes[0] != ScriptRuntime.ScriptableObjectClass) { throw Context.reportRuntimeError1( "msg.bad.getter.parms", getter.toString()); } } else if (delegateTo != null) { throw Context.reportRuntimeError1( "msg.obj.getter.parms", getter.toString()); } if (setter != null) { if ((delegateTo == HAS_STATIC_ACCESSORS) != (Modifier.isStatic(setter.getModifiers()))) { throw Context.reportRuntimeError0("msg.getter.static"); } parmTypes = setter.getParameterTypes(); if (parmTypes.length == 2) { if (parmTypes[0] != ScriptRuntime.ScriptableObjectClass) { throw Context.reportRuntimeError0("msg.setter2.parms"); } if (delegateTo == null) { throw Context.reportRuntimeError1( "msg.setter1.parms", setter.toString()); } } else if (parmTypes.length == 1) { if (delegateTo != null) { throw Context.reportRuntimeError1( "msg.setter2.expected", setter.toString()); } } else { throw Context.reportRuntimeError0("msg.setter.parms"); } Class setterType = parmTypes[parmTypes.length - 1]; int setterTypeTag = FunctionObject.getTypeTag(setterType); if (setterTypeTag == FunctionObject.JAVA_UNSUPPORTED_TYPE) { throw Context.reportRuntimeError2( "msg.setter2.expected", setterType.getName(), setter.toString()); } } GetterSlot gslot = new GetterSlot(); gslot.delegateTo = delegateTo; gslot.getter = new MemberBox(getter); if (setter != null) { gslot.setter = new MemberBox(setter); } gslot.attributes = (short) attributes; Slot inserted = addSlot(propertyName, propertyName.hashCode(), gslot); if (inserted != gslot) { throw new RuntimeException("Property already exists"); } } /** * 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; } } /** * 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 = -1 - 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; } /** * 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); } /** * 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. *

* Seaches 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 an indexed property in an object or in an object in its prototype chain. *

* Seaches 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 serilized. * @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 getByGetter(GetterSlot slot, Scriptable start) { Object getterThis; Object[] args; if (slot.delegateTo == null) { if (start != this) { // Walk the prototype chain to find an appropriate // object to invoke the getter on. Class clazz = slot.getter.getDeclaringClass(); while (!clazz.isInstance(start)) { start = start.getPrototype(); if (start == this) { break; } if (start == null) { start = this; break; } } } getterThis = start; args = ScriptRuntime.emptyArgs; } else { getterThis = slot.delegateTo; args = new Object[] { this }; } return slot.getter.invoke(getterThis, args); } private void setBySetter(GetterSlot slot, Scriptable start, Object value) { if (start != this) { if (slot.delegateTo != null || !slot.setter.getDeclaringClass().isInstance(start)) { start.put(slot.stringKey, start, value); return; } } Object setterThis; Object[] args; Object setterResult; Context cx = Context.getContext(); Class pTypes[] = slot.setter.argTypes; Class desired = pTypes[pTypes.length - 1]; // ALERT: cache tag since it is already calculated in defineProperty ? int tag = FunctionObject.getTypeTag(desired); Object actualArg = FunctionObject.convertArg(cx, start, value, tag); if (slot.delegateTo == null) { setterThis = start; args = new Object[] { actualArg }; } else { if (start != this) Kit.codeBug(); setterThis = slot.delegateTo; args = new Object[] { this, actualArg }; } // Check start is sealed: start is always instance of ScriptableObject // due to logic in if (start != this) above if (((ScriptableObject)start).isSealed()) { throw Context.reportRuntimeError1("msg.modify.sealed", slot.stringKey); } setterResult = slot.setter.invoke(setterThis, args); if (slot.setter.method().getReturnType() != Void.TYPE) { // Replace Getter slot by a simple one Slot replacement = new Slot(); replacement.intKey = slot.intKey; replacement.stringKey = slot.stringKey; replacement.attributes = slot.attributes; replacement.value = setterResult; synchronized (this) { int i = getSlotPosition(slots, slot.stringKey, slot.intKey); // Check slot was not deleted/replaced before synchronization if (i >= 0 && slots[i] == slot) { slots[i] = replacement; // It is important to make sure that lastAccess != slot // to prevent accessing the old slot via lastAccess and // then invoking setter one more time lastAccess = replacement; } } } } private Slot getNamedSlot(String name) { // Query last access cache and check that it was not deleted Slot slot = lastAccess; if (name == slot.stringKey && slot.wasDeleted == 0) { return slot; } int hash = name.hashCode(); Slot[] slots = this.slots; // Get stable local reference int i = getSlotPosition(slots, name, hash); if (i < 0) { return null; } slot = slots[i]; // Update cache - here stringKey.equals(name) holds, but it can be // that slot.stringKey != name. To make last name cache work, need // to change the key slot.stringKey = name; lastAccess = slot; return slot; } private Slot getSlot(String id, int index) { Slot[] slots = this.slots; // Get local copy int i = getSlotPosition(slots, id, index); return (i < 0) ? null : slots[i]; } private static int getSlotPosition(Slot[] slots, String id, int index) { if (slots != null) { int start = (index & 0x7fffffff) % slots.length; int i = start; do { Slot slot = slots[i]; if (slot == null) break; if (slot != REMOVED && slot.intKey == index && (slot.stringKey == id || (id != null && id.equals(slot.stringKey)))) { return i; } if (++i == slots.length) i = 0; } while (i != start); } return -1; } /** * Add a new slot to the hash table. * * This method must be synchronized since it is altering the hash * table itself. Note that we search again for the slot to set * since another thread could have added the given property or * caused the table to grow while this thread was searching. */ private synchronized Slot addSlot(String id, int index, Slot newSlot) { if (isSealed()) { String str = (id != null) ? id : Integer.toString(index); throw Context.reportRuntimeError1("msg.add.sealed", str); } if (slots == null) { slots = new Slot[5]; } return addSlotImpl(id, index, newSlot); } // Must be inside synchronized (this) private Slot addSlotImpl(String id, int index, Slot newSlot) { int start = (index & 0x7fffffff) % slots.length; int i = start; for (;;) { Slot slot = slots[i]; if (slot == null || slot == REMOVED) { if ((4 * (count + 1)) > (3 * slots.length)) { grow(); return addSlotImpl(id, index, newSlot); } slot = (newSlot == null) ? new Slot() : newSlot; slot.stringKey = id; slot.intKey = index; slots[i] = slot; count++; return slot; } if (slot.intKey == index && (slot.stringKey == id || (id != null && id.equals(slot.stringKey)))) { return slot; } if (++i == slots.length) i = 0; if (i == start) { // slots should never be full or bug in grow code throw new IllegalStateException(); } } } /** * Remove a slot from the hash table. * * This method must be synchronized since it is altering the hash * table itself. We might be able to optimize this more, but * deletes are not common. */ private synchronized void removeSlot(String name, int index) { if (isSealed()) { String str = (name != null) ? name : Integer.toString(index); throw Context.reportRuntimeError1("msg.remove.sealed", str); } int i = getSlotPosition(slots, name, index); if (i >= 0) { Slot slot = slots[i]; if ((slot.attributes & PERMANENT) == 0) { // 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; } count--; if (count != 0) { slots[i] = REMOVED; } else { // With no slots it is OK to mark with null. slots[i] = null; } } } } // Grow the hash table to accommodate new entries. // // Note that by assigning the new array back at the end we // can continue reading the array from other threads. // Must be inside synchronized (this) private void grow() { Slot[] newSlots = new Slot[slots.length*2 + 1]; for (int j=slots.length-1; j >= 0 ; j--) { Slot slot = slots[j]; if (slot == null || slot == REMOVED) continue; int k = (slot.intKey & 0x7fffffff) % newSlots.length; while (newSlots[k] != null) if (++k == newSlots.length) k = 0; // The end of the "synchronized" statement will cause the memory // writes to be propagated on a multiprocessor machine. We want // to make sure that the new table is prepared to be read. // XXX causes the 'this' pointer to be null in calling stack frames // on the MS JVM //synchronized (slot) { } newSlots[k] = slot; } slots = newSlots; } 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]; if (slot == null || slot == REMOVED) continue; if (getAll || (slot.attributes & DONTENUM) == 0) { if (c == 0) a = new Object[s.length - i]; a[c++] = slot.stringKey != null ? (Object) slot.stringKey : new Integer(slot.intKey); } } 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 N = count; if (N < 0) { N = -1 - count; } Slot[] s = slots; if (s == null) { if (N != 0) Kit.codeBug(); out.writeInt(0); } else { out.writeInt(s.length); for (int i = 0; N != 0; ++i) { Slot slot = s[i]; if (slot != null && slot != REMOVED) { --N; out.writeObject(slot); } } } } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); lastAccess = REMOVED; int capacity = in.readInt(); if (capacity != 0) { slots = new Slot[capacity]; int N = count; boolean wasSealed = false; if (N < 0) { N = -1 - N; wasSealed = true; } count = 0; for (int i = 0; i != N; ++i) { Slot s = (Slot)in.readObject(); addSlotImpl(s.stringKey, s.intKey, s); } if (wasSealed) { count = - 1 - count; } } } /** * The prototype of this object. */ private Scriptable prototypeObject; /** * The parent scope of this object. */ private Scriptable parentScopeObject; private static final Object HAS_STATIC_ACCESSORS = Void.TYPE; private static final Slot REMOVED = new Slot(); private transient Slot[] slots; // If count >= 0, it gives number of keys or if count < 0, // it indicates sealed object where -1 - 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 class Slot implements Serializable { static final long serialVersionUID = -3539051633409902634L; private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); if (stringKey != null) { intKey = stringKey.hashCode(); } } int intKey; String stringKey; Object value; short attributes; transient byte wasDeleted; } private static final class GetterSlot extends Slot { static final long serialVersionUID = -4900574849788797588L; Object delegateTo; MemberBox getter; MemberBox setter; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy