gecko-dev/js/rhino/org/mozilla/javascript/ScriptableObject.java
2000-07-07 14:41:35 +00:00

1698 lines
65 KiB
Java

/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Netscape 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/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
* 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 Netscape are
* Copyright (C) 1997-1999 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
* Norris Boyd
* Roger Lawrence
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU Public License (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 NPL, indicate your decision by
* deleting the provisions above and replace 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 NPL or the GPL.
*/
// API class
package org.mozilla.javascript;
import java.lang.reflect.*;
import java.util.Hashtable;
/**
* This is the default implementation of the Scriptable interface. This
* class provides convenient default behavior that makes it easier to
* define host objects.
* <p>
* Various properties and methods of JavaScript objects can be conveniently
* defined using methods of ScriptableObject.
* <p>
* Classes extending ScriptableObject must define the getClassName method.
*
* @see org.mozilla.javascript.Scriptable
* @author Norris Boyd
*/
public abstract class ScriptableObject implements Scriptable {
/**
* 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;
/**
* 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 getSlot(name, name.hashCode()) != SLOT_NOT_FOUND;
}
/**
* 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 getSlot(null, index) != SLOT_NOT_FOUND;
}
/**
* 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) {
int hashCode;
if (name == lastName) {
if (lastValue != REMOVED)
return lastValue;
hashCode = lastHash;
} else {
hashCode = name.hashCode();
}
int slotIndex = getSlot(name, hashCode);
if (slotIndex == SLOT_NOT_FOUND)
return Scriptable.NOT_FOUND;
Slot slot = slots[slotIndex];
if ((slot.flags & Slot.HAS_GETTER) != 0) {
GetterSlot getterSlot = (GetterSlot) slot;
try {
if (getterSlot.delegateTo == null) {
// Walk the prototype chain to find an appropriate
// object to invoke the getter on.
Class clazz = getterSlot.getter.getDeclaringClass();
while (!clazz.isInstance(start)) {
start = start.getPrototype();
if (start == null) {
start = this;
break;
}
}
return getterSlot.getter.invoke(start, ScriptRuntime.emptyArgs);
}
Object[] args = { this };
return getterSlot.getter.invoke(getterSlot.delegateTo, args);
}
catch (InvocationTargetException e) {
throw WrappedException.wrapException(e);
}
catch (IllegalAccessException e) {
throw WrappedException.wrapException(e);
}
}
// Update cache. Assign REMOVED to invalidate the cache while
// we update it in case there are accesses from another thread.
lastValue = REMOVED;
lastName = name;
lastHash = hashCode;
lastValue = slot.value;
return lastValue;
}
/**
* 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) {
int slotIndex = getSlot(null, index);
if (slotIndex == SLOT_NOT_FOUND)
return Scriptable.NOT_FOUND;
return slots[slotIndex].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. <p>
*
* 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) {
int hash = name.hashCode();
int slotIndex = getSlot(name, hash);
if (slotIndex == SLOT_NOT_FOUND) {
if (start != this) {
start.put(name, start, value);
return;
}
slotIndex = getSlotToSet(name, hash, false);
}
Slot slot = slots[slotIndex];
if ((slot.attributes & ScriptableObject.READONLY) != 0)
return;
if ((slot.flags & Slot.HAS_SETTER) != 0) {
GetterSlot getterSlot = (GetterSlot) slot;
try {
Class pTypes[] = getterSlot.setter.getParameterTypes();
Class desired = pTypes[pTypes.length - 1];
Object actualArg
= FunctionObject.convertArg(start, value, desired);
if (getterSlot.delegateTo == null) {
// Walk the prototype chain to find an appropriate
// object to invoke the setter on.
Object[] arg = { actualArg };
Class clazz = getterSlot.setter.getDeclaringClass();
while (!clazz.isInstance(start)) {
start = start.getPrototype();
if (start == null) {
start = this;
break;
}
}
Object v = getterSlot.setter.invoke(start, arg);
if (getterSlot.setterReturnsValue) {
slots[slotIndex].value = v;
if (!(v instanceof Method))
slots[slotIndex].flags = 0;
}
return;
}
Object[] args = { this, actualArg };
Object v = getterSlot.setter.invoke(getterSlot.delegateTo, args);
if (getterSlot.setterReturnsValue) {
slots[slotIndex].value = v;
if (!(v instanceof Method))
slots[slotIndex].flags = 0;
}
return;
}
catch (InvocationTargetException e) {
throw WrappedException.wrapException(e);
}
catch (IllegalAccessException e) {
throw WrappedException.wrapException(e);
}
}
if (this == start) {
slot.value = value;
// It could be the case that name.equals(lastName)
// even if name != lastName. However, it is more
// expensive to call String.equals than it is to
// just invalidate the cache.
lastValue = name == lastName ? value : REMOVED;
} 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) {
int slotIndex = getSlot(null, index);
if (slotIndex == SLOT_NOT_FOUND) {
if (start != this) {
start.put(index, start, value);
return;
}
slotIndex = getSlotToSet(null, index, false);
}
Slot slot = slots[slotIndex];
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) {
if (name.equals(lastName))
lastValue = REMOVED; // invalidate cache
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);
}
/**
* Get the attributes of a named property.
*
* The property is specified by <code>name</code>
* as defined for <code>has</code>.<p>
*
* @param name the identifier for the property
* @param start the object in which the lookup began
* @return the bitset of attributes
* @exception PropertyException 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, Scriptable start)
throws PropertyException
{
int slotIndex = getSlot(name, name.hashCode());
if (slotIndex == SLOT_NOT_FOUND) {
throw new PropertyException(
Context.getMessage("msg.prop.not.found", null));
}
return slots[slotIndex].attributes;
}
/**
* Get the attributes of an indexed property.
*
* @param index the numeric index for the property
* @param start the object in which the lookup began
* @exception PropertyException if the indexed property
* 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, Scriptable start)
throws PropertyException
{
int slotIndex = getSlot(null, index);
if (slotIndex == SLOT_NOT_FOUND) {
throw new PropertyException(
Context.getMessage("msg.prop.not.found", null));
}
return slots[slotIndex].attributes;
}
/**
* Set the attributes of a named property.
*
* The property is specified by <code>name</code>
* as defined for <code>has</code>.<p>
*
* 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 start the object in which the lookup began
* @param attributes the bitset of attributes
* @exception PropertyException 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, Scriptable start,
int attributes)
throws PropertyException
{
final int mask = READONLY | DONTENUM | PERMANENT;
attributes &= mask; // mask out unused bits
int slotIndex = getSlot(name, name.hashCode());
if (slotIndex == SLOT_NOT_FOUND) {
throw new PropertyException(
Context.getMessage("msg.prop.not.found", null));
}
slots[slotIndex].attributes = (short) attributes;
}
/**
* Set the attributes of an indexed property.
*
* @param index the numeric index for the property
* @param start the object in which the lookup began
* @param attributes the bitset of attributes
* @exception PropertyException if the indexed 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, Scriptable start,
int attributes)
throws PropertyException
{
int slotIndex = getSlot(null, index);
if (slotIndex == SLOT_NOT_FOUND) {
throw new PropertyException(
Context.getMessage("msg.prop.not.found", null));
}
slots[slotIndex].attributes = (short) attributes;
}
/**
* Returns the prototype of the object.
*/
public Scriptable getPrototype() {
return prototype;
}
/**
* Sets the prototype of the object.
*/
public void setPrototype(Scriptable m) {
prototype = m;
}
/**
* Returns the parent (enclosing) scope of the object.
*/
public Scriptable getParentScope() {
return parent;
}
/**
* Sets the parent (enclosing) scope of the object.
*/
public void setParentScope(Scriptable m) {
parent = m;
}
/**
* Returns an array of ids for the properties of the object.
*
* <p>Any properties with the attribute DONTENUM are not listed. <p>
*
* @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.
*
* <p>All properties, even those with attribute DONTENUM, are listed. <p>
*
* @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.
*
* <p>Note that the toPrimitive conversion is a no-op for
* every type other than Object, for which [[DefaultValue]]
* is called. See ECMA 9.1.<p>
*
* A <code>hint</code> 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) {
Object val;
Context cx = null;
try {
for (int i=0; i < 2; i++) {
if (typeHint == ScriptRuntime.StringClass ? i == 0 : i == 1) {
Object v = getProperty(this, "toString");
if (!(v instanceof Function))
continue;
Function fun = (Function) v;
if (cx == null)
cx = Context.getContext();
val = fun.call(cx, fun.getParentScope(), this,
ScriptRuntime.emptyArgs);
} else {
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 {
Object[] args = { typeHint.toString() };
throw Context.reportRuntimeError(
Context.getMessage("msg.invalid.type", args));
}
Object v = getProperty(this, "valueOf");
if (!(v instanceof Function))
continue;
Function fun = (Function) v;
Object[] args = { hint };
if (cx == null)
cx = Context.getContext();
val = fun.call(cx, fun.getParentScope(), this, args);
}
if (val != null && (val == Undefined.instance ||
!(val instanceof Scriptable) ||
typeHint == Scriptable.class ||
typeHint == Function.class))
{
return val;
}
if (val instanceof NativeJavaObject) {
// Let a wrapped java.lang.String pass for a primitive
// string.
Object u = ((Wrapper) val).unwrap();
if (u instanceof String)
return u;
}
}
// fall through to error
}
catch (JavaScriptException jse) {
// fall through to error
}
Object arg = typeHint == null ? "undefined" : typeHint.toString();
Object[] args = { arg };
throw NativeGlobal.constructError(
Context.getContext(), "TypeError",
ScriptRuntime.getMessage("msg.default.value", args),
this);
}
/**
* Implements the instanceof operator.
*
* <p>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);
}
/**
* Defines JavaScript objects from a Java class.
*
* If the given class has a method
* <pre>
* static void init(Scriptable scope);</pre>
*
* then it is invoked and no further initialization is done and the
* result of the invocation will be returned.<p>
*
* 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.<p>
*
* First, the zero-parameter constructor of the class is called to
* create the prototype. If no such constructor exists,
* a ClassDefinitionException is thrown. <p>
*
* Next, all methods are scanned for special prefixes that indicate that they
* have special meaning for defining JavaScript objects.
* These special prefixes are
* <ul>
* <li><code>jsFunction_</code> for a JavaScript function
* <li><code>jsStaticFunction_</code> for a JavaScript function that
* is a property of the constructor
* <li><code>jsGet_</code> for a getter of a JavaScript property
* <li><code>jsSet_</code> for a setter of a JavaScript property
* <li><code>jsConstructor</code> for a JavaScript function that
* is the constructor
* </ul><p>
*
* 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. <p>
*
* 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.<p>
*
* 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
* ClassDefinitionException 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 ClassDefinitionException
* will be thrown.<p>
*
* Finally, if there is a method
* <pre>
* static void finishInit(Scriptable scope, FunctionObject constructor,
* Scriptable prototype)</pre>
*
* it will be called to finish any initialization. The <code>scope</code>
* argument will be passed, along with the newly created constructor and
* the newly created prototype.<p>
*
* @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
* @exception ClassDefinitionException if an appropriate
* constructor cannot be found to create the prototype
* @exception PropertyException if getter and setter
* methods do not conform to the requirements of the
* defineProperty method
* @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, ClassDefinitionException,
PropertyException
{
defineClass(scope, clazz, false);
}
/**
* Defines JavaScript objects from a Java class, optionally
* allowing sealing.
*
* Similar to <code>defineClass(Scriptable scope, Class clazz)</code>
* 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
* @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
* @exception ClassDefinitionException if an appropriate
* constructor cannot be found to create the prototype
* @exception PropertyException if getter and setter
* methods do not conform to the requirements of the
* defineProperty method
* @since 1.4R3
*/
public static void defineClass(Scriptable scope, Class clazz,
boolean sealed)
throws IllegalAccessException, InstantiationException,
InvocationTargetException, ClassDefinitionException,
PropertyException
{
Method[] methods = FunctionObject.getMethodList(clazz);
for (int i=0; i < methods.length; i++) {
if (!methods[i].getName().equals("init"))
continue;
Class[] parmTypes = methods[i].getParameterTypes();
if (parmTypes.length == 1 &&
parmTypes[0] == ScriptRuntime.ScriptableClass &&
Modifier.isStatic(methods[i].getModifiers()))
{
Object args[] = { scope };
methods[i].invoke(null, args);
return;
}
}
// If we got here, there isn't an "init" method with the right
// parameter types.
Hashtable exclusionList = getExclusionList();
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) {
Object[] args = { clazz.getName() };
throw new ClassDefinitionException(
Context.getMessage("msg.zero.arg.ctor", args));
}
Scriptable proto = (Scriptable)
protoCtor.newInstance(ScriptRuntime.emptyArgs);
proto.setPrototype(getObjectPrototype(scope));
String className = proto.getClassName();
// 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.
// The prefixes "js_" and "jsProperty_" are deprecated.
final String genericPrefix = "js_";
final String functionPrefix = "jsFunction_";
final String staticFunctionPrefix = "jsStaticFunction_";
final String propertyPrefix = "jsProperty_";
final String getterPrefix = "jsGet_";
final String setterPrefix = "jsSet_";
final String ctorName = "jsConstructor";
Method[] ctorMeths = FunctionObject.findMethods(clazz, ctorName);
Member ctorMember = null;
if (ctorMeths != null) {
if (ctorMeths.length > 1) {
Object[] args = { ctorMeths[0], ctorMeths[1] };
throw new ClassDefinitionException(
Context.getMessage("msg.multiple.ctors", args));
}
ctorMember = ctorMeths[0];
}
// Deprecated: look for functions with the same name as the class
// and consider them constructors.
boolean hasPrefix = false;
for (int i=0; i < methods.length; i++) {
String name = methods[i].getName();
String prefix = null;
if (!name.startsWith("js")) // common start to all prefixes
prefix = null;
else if (name.startsWith(genericPrefix))
prefix = genericPrefix;
else if (name.startsWith(functionPrefix))
prefix = functionPrefix;
else if (name.startsWith(staticFunctionPrefix))
prefix = staticFunctionPrefix;
else if (name.startsWith(propertyPrefix))
prefix = propertyPrefix;
else if (name.startsWith(getterPrefix))
prefix = getterPrefix;
else if (name.startsWith(setterPrefix))
prefix = setterPrefix;
if (prefix != null) {
hasPrefix = true;
name = name.substring(prefix.length());
}
if (name.equals(className)) {
if (ctorMember != null) {
Object[] args = { ctorMember, methods[i] };
throw new ClassDefinitionException(
Context.getMessage("msg.multiple.ctors", args));
}
ctorMember = methods[i];
}
}
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) {
Object[] args = { clazz.getName() };
throw new ClassDefinitionException(
Context.getMessage("msg.ctor.multiple.parms", args));
}
}
FunctionObject ctor = new FunctionObject(className, ctorMember, scope);
if (ctor.isVarArgsMethod()) {
Object[] args = { ctorMember.getName() };
String message = Context.getMessage("msg.varargs.ctor", args);
throw Context.reportRuntimeError(message);
}
ctor.addAsConstructor(scope, proto);
if (!hasPrefix && exclusionList == null)
exclusionList = getExclusionList();
Method finishInit = null;
for (int i=0; i < methods.length; i++) {
if (!hasPrefix && methods[i].getDeclaringClass() != clazz)
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;
}
}
if (name.equals(ctorName))
continue;
String prefix = null;
if (hasPrefix) {
if (name.startsWith(genericPrefix)) {
prefix = genericPrefix;
} else if (name.startsWith(functionPrefix)) {
prefix = functionPrefix;
} else if (name.startsWith(staticFunctionPrefix)) {
prefix = staticFunctionPrefix;
if (!Modifier.isStatic(methods[i].getModifiers())) {
throw new ClassDefinitionException(
"jsStaticFunction must be used with static method.");
}
} else if (name.startsWith(propertyPrefix)) {
prefix = propertyPrefix;
} else if (name.startsWith(getterPrefix)) {
prefix = getterPrefix;
} else if (name.startsWith(setterPrefix)) {
prefix = setterPrefix;
} else {
continue;
}
name = name.substring(prefix.length());
} else if (exclusionList.get(name) != null)
continue;
if (methods[i] == ctorMember) {
continue;
}
if (prefix != null && prefix.equals(setterPrefix))
continue; // deal with set when we see get
if (prefix != null && prefix.equals(getterPrefix)) {
if (!(proto instanceof ScriptableObject)) {
Object[] args = { proto.getClass().toString(), name };
throw new PropertyException(
Context.getMessage("msg.extend.scriptable", args));
}
Method[] setter = FunctionObject.findMethods(
clazz,
setterPrefix + name);
if (setter != null && setter.length != 1) {
Object[] errArgs = { name, clazz.getName() };
throw new PropertyException(
Context.getMessage("msg.no.overload", errArgs));
}
int attr = ScriptableObject.PERMANENT |
ScriptableObject.DONTENUM |
(setter != null ? 0
: ScriptableObject.READONLY);
Method m = setter == null ? null : setter[0];
((ScriptableObject) proto).defineProperty(name, null,
methods[i], m,
attr);
continue;
}
if ((name.startsWith("get") || name.startsWith("set")) &&
name.length() > 3 &&
!(hasPrefix && (prefix.equals(functionPrefix) ||
prefix.equals(staticFunctionPrefix))))
{
if (!(proto instanceof ScriptableObject)) {
Object[] args = { proto.getClass().toString(), name };
throw new PropertyException(
Context.getMessage("msg.extend.scriptable", args));
}
if (name.startsWith("set"))
continue; // deal with set when we see get
StringBuffer buf = new StringBuffer();
char c = name.charAt(3);
buf.append(Character.toLowerCase(c));
if (name.length() > 4)
buf.append(name.substring(4));
String propertyName = buf.toString();
buf.setCharAt(0, c);
buf.insert(0, "set");
String setterName = buf.toString();
Method[] setter = FunctionObject.findMethods(
clazz,
hasPrefix ? genericPrefix + setterName
: setterName);
if (setter != null && setter.length != 1) {
Object[] errArgs = { name, clazz.getName() };
throw new PropertyException(
Context.getMessage("msg.no.overload", errArgs));
}
if (setter == null && hasPrefix)
setter = FunctionObject.findMethods(
clazz,
propertyPrefix + setterName);
int attr = ScriptableObject.PERMANENT |
ScriptableObject.DONTENUM |
(setter != null ? 0
: ScriptableObject.READONLY);
Method m = setter == null ? null : setter[0];
((ScriptableObject) proto).defineProperty(propertyName, null,
methods[i], m,
attr);
continue;
}
FunctionObject f = new FunctionObject(name, methods[i], proto);
if (f.isVarArgsConstructor()) {
Object[] args = { ctorMember.getName() };
String message = Context.getMessage("msg.varargs.fun", args);
throw Context.reportRuntimeError(message);
}
Scriptable dest = prefix == staticFunctionPrefix
? ctor
: proto;
if (dest instanceof ScriptableObject) {
((ScriptableObject) dest).defineProperty(name, f, DONTENUM);
} else {
dest.put(name, dest, f);
}
if (sealed) {
f.sealObject();
f.addPropertyAttribute(READONLY);
}
}
if (finishInit != null) {
// call user code to complete the initialization
Object[] finishArgs = { scope, ctor, proto };
finishInit.invoke(null, finishArgs);
}
if (sealed) {
ctor.sealObject();
ctor.addPropertyAttribute(READONLY);
if (proto instanceof ScriptableObject) {
((ScriptableObject) proto).sealObject();
((ScriptableObject) proto).addPropertyAttribute(READONLY);
}
}
}
/**
* 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);
try {
setAttributes(propertyName, this, attributes);
}
catch (PropertyException e) {
throw new RuntimeException("Cannot create property");
}
}
/**
* 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. <p>
*
* The getter must be a method with zero parameters, and the setter, if
* found, must be a method with one parameter.<p>
*
* @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
* <code>clazz</code> 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
* @exception PropertyException if multiple methods
* are found for the getter or setter, or if the getter
* or setter do not conform to the forms described in
* <code>defineProperty(String, Object, Method, Method,
* int)</code>
* @see org.mozilla.javascript.Scriptable#put
*/
public void defineProperty(String propertyName, Class clazz,
int attributes)
throws PropertyException
{
StringBuffer buf = new StringBuffer(propertyName);
buf.setCharAt(0, Character.toUpperCase(propertyName.charAt(0)));
String s = buf.toString();
Method[] getter = FunctionObject.findMethods(clazz, "get" + s);
Method[] setter = FunctionObject.findMethods(clazz, "set" + s);
if (setter == null)
attributes |= ScriptableObject.READONLY;
if (getter.length != 1 || (setter != null && setter.length != 1)) {
Object[] errArgs = { propertyName, clazz.getName() };
throw new PropertyException(
Context.getMessage("msg.no.overload", errArgs));
}
defineProperty(propertyName, null, getter[0],
setter == null ? null : setter[0], 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
* <code>defineProperty(String,int)</code>.
*
* If <code>setter</code> is null, the attribute READONLY is added to
* the given attributes.<p>
*
* 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':
* <pre>
* Object getFoo();
* void setFoo(SomeType value);</pre>
* Next are static methods that may be of any class; the object whose
* property is being accessed is passed in as an extra argument:
* <pre>
* static Object getFoo(ScriptableObject obj);
* static void setFoo(ScriptableObject obj, SomeType value);</pre>
* Finally, it is possible to delegate to another object entirely using
* the <code>delegateTo</code> 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:
* <pre>
* Object getFoo(ScriptableObject obj);
* void setFoo(ScriptableObject obj, SomeType value);</pre>
*
* @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
* @exception PropertyException if the getter or setter
* do not conform to the forms specified above
*/
public void defineProperty(String propertyName, Object delegateTo,
Method getter, Method setter, int attributes)
throws PropertyException
{
short flags = Slot.HAS_GETTER;
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] != ScriptableObject.class)
{
Object[] args = { getter.toString() };
throw new PropertyException(
Context.getMessage("msg.bad.getter.parms", args));
}
} else if (delegateTo != null) {
Object[] args = { getter.toString() };
throw new PropertyException(
Context.getMessage("msg.obj.getter.parms", args));
}
if (setter != null) {
flags |= Slot.HAS_SETTER;
if ((delegateTo == HAS_STATIC_ACCESSORS) !=
(Modifier.isStatic(setter.getModifiers())))
{
throw new PropertyException(
Context.getMessage("msg.getter.static", null));
}
parmTypes = setter.getParameterTypes();
if (parmTypes.length == 2) {
if (parmTypes[0] != ScriptableObject.class) {
throw new PropertyException(
Context.getMessage("msg.setter2.parms", null));
}
if (delegateTo == null) {
Object[] args = { setter.toString() };
throw new PropertyException(
Context.getMessage("msg.setter1.parms", args));
}
} else if (parmTypes.length == 1) {
if (delegateTo != null) {
Object[] args = { setter.toString() };
throw new PropertyException(
Context.getMessage("msg.setter2.expected", args));
}
} else {
throw new PropertyException(
Context.getMessage("msg.setter.parms", null));
}
}
int slotIndex = getSlotToSet(propertyName,
propertyName.hashCode(),
true);
GetterSlot slot = (GetterSlot) slots[slotIndex];
slot.delegateTo = delegateTo;
slot.getter = getter;
slot.setter = setter;
slot.setterReturnsValue = setter != null && setter.getReturnType() != Void.TYPE;
slot.value = null;
slot.attributes = (short) attributes;
slot.flags = flags;
}
/**
* Search for names in a class, adding the resulting methods
* as properties.
*
* <p> 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
* @exception PropertyException if any of the names
* has no corresponding method or more than one corresponding
* method in the class
* @see org.mozilla.javascript.FunctionObject
*/
public void defineFunctionProperties(String[] names, Class clazz,
int attributes)
throws PropertyException
{
for (int i=0; i < names.length; i++) {
String name = names[i];
Method[] m = FunctionObject.findMethods(clazz, name);
if (m == null) {
Object[] errArgs = { name, clazz.getName() };
throw new PropertyException(
Context.getMessage("msg.method.not.found", errArgs));
}
if (m.length > 1) {
Object[] errArgs = { name, clazz.getName() };
throw new PropertyException(
Context.getMessage("msg.no.overload", errArgs));
}
FunctionObject f = new FunctionObject(name, m[0], 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, <code>getClassPrototype(s, "Date")</code> 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 = ScriptRuntime.getTopLevelProp(scope, className);
if (ctor == NOT_FOUND || !(ctor instanceof Scriptable))
return null;
Scriptable ctorObj = (Scriptable) ctor;
if (!ctorObj.has("prototype", ctorObj))
return null;
Object proto = ctorObj.get("prototype", ctorObj);
if (!(proto instanceof Scriptable))
return null;
return (Scriptable) proto;
}
/**
* Get the global scope.
*
* <p>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) {
Scriptable next = obj;
do {
obj = next;
next = obj.getParentScope();
} while (next != null);
return 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 void sealObject() {
count = -1;
}
/**
* 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 boolean isSealed() {
return count == -1;
}
/**
* Gets a named property from an object or any object in its prototype chain.
* <p>
* Searches the prototype chain for a property named <code>name</code>.
* <p>
* @param obj a JavaScript object
* @param name a property name
* @return the value of a property with name <code>name</code> found in
* <code>obj</code> or any object in its prototype chain, or
* <code>Scriptable.NOT_FOUND</code> if not found
*/
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.
* <p>
* Searches the prototype chain for a property with integral index
* <code>index</code>. 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.
* <p>
* @param obj a JavaScript object
* @param index an integral index
* @return the value of a property with index <code>index</code> found in
* <code>obj</code> or any object in its prototype chain, or
* <code>Scriptable.NOT_FOUND</code> if not found
*/
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;
}
/**
* Puts a named property in an object or in an object in its prototype chain.
* <p>
* Seaches for the named property in the prototype chain. If it is found,
* the value of the property is changed. If it is not found, a new
* property is added in <code>obj</code>.
* @param obj a JavaScript object
* @param name a property name
* @param value any JavaScript value accepted by Scriptable.put
*/
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.
* <p>
* Seaches for the indexed property in the prototype chain. If it is found,
* the value of the property is changed. If it is not found, a new
* property is added in <code>obj</code>.
* @param obj a JavaScript object
* @param index a property index
* @param value any JavaScript value accepted by Scriptable.put
*/
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.
* <p>
* Searches for a property with <code>name</code> 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
*/
public static boolean deleteProperty(Scriptable obj, String name) {
Scriptable base = getBase(obj, name);
if (base == null)
return true;
base.delete(name);
return base.get(name, obj) == NOT_FOUND;
}
/**
* Removes the property from an object or its prototype chain.
* <p>
* Searches for a property with <code>index</code> 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
*/
public static boolean deleteProperty(Scriptable obj, int index) {
Scriptable base = getBase(obj, index);
if (base == null)
return true;
base.delete(index);
return base.get(index, obj) == NOT_FOUND;
}
private static Scriptable getBase(Scriptable obj, String s) {
Scriptable m = obj;
while (m != null) {
if (m.has(s, obj))
return m;
m = m.getPrototype();
}
return null;
}
private static Scriptable getBase(Scriptable obj, int index) {
Scriptable m = obj;
while (m != null) {
if (m.has(index, obj))
return m;
m = m.getPrototype();
}
return null;
}
/**
* Adds a property attribute to all properties.
*/
synchronized void addPropertyAttribute(int attribute) {
if (slots == null)
return;
for (int i=0; i < slots.length; i++) {
Slot slot = slots[i];
if (slot == null || slot == REMOVED)
continue;
if ((slot.flags & slot.HAS_SETTER) != 0 && attribute == READONLY)
continue;
slot.attributes |= attribute;
}
}
private static final int SLOT_NOT_FOUND = -1;
private int getSlot(String id, int index) {
if (slots == null)
return SLOT_NOT_FOUND;
int start = (index & 0x7fffffff) % slots.length;
int i = start;
do {
Slot slot = slots[i];
if (slot == null)
return SLOT_NOT_FOUND;
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 SLOT_NOT_FOUND;
}
private int getSlotToSet(String id, int index, boolean getterSlot) {
if (slots == null)
slots = new Slot[5];
int start = (index & 0x7fffffff) % slots.length;
int i = start;
do {
Slot slot = slots[i];
if (slot == null) {
return addSlot(id, index, getterSlot);
}
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);
throw new RuntimeException("Hashtable internal error");
}
/**
* 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 int addSlot(String id, int index, boolean getterSlot)
{
if (count == -1)
throw Context.reportRuntimeError(Context.getMessage
("msg.add.sealed", null));
int start = (index & 0x7fffffff) % slots.length;
int i = start;
do {
Slot slot = slots[i];
if (slot == null || slot == REMOVED) {
count++;
if ((4 * count) > (3 * slots.length)) {
grow();
return getSlotToSet(id, index, getterSlot);
}
slot = getterSlot ? new GetterSlot() : new Slot();
slot.stringKey = id;
slot.intKey = index;
slots[i] = slot;
return i;
}
if (slot.intKey == index &&
(slot.stringKey == id || (id != null &&
id.equals(slot.stringKey))))
{
return i;
}
if (++i == slots.length)
i = 0;
} while (i != start);
throw new RuntimeException("Hashtable internal error");
}
/**
* 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 (count == -1)
throw Context.reportRuntimeError(Context.getMessage
("msg.remove.sealed", null));
int slotIndex = getSlot(name, index);
if (slotIndex == SLOT_NOT_FOUND)
return;
if ((slots[slotIndex].attributes & ScriptableObject.PERMANENT) != 0)
return;
slots[slotIndex] = REMOVED;
count--;
}
/**
* 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.
*/
private synchronized 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;
}
private static Hashtable getExclusionList() {
if (exclusionList != null)
return exclusionList;
Hashtable result = new Hashtable(17);
Method[] methods = ScriptRuntime.FunctionClass.getMethods();
for (int i=0; i < methods.length; i++) {
result.put(methods[i].getName(), Boolean.TRUE);
}
exclusionList = result;
return result;
}
private Object[] getIds(boolean getAll) {
if (slots == null)
return ScriptRuntime.emptyArgs;
int count = 0;
for (int i=slots.length-1; i >= 0; i--) {
Slot slot = slots[i];
if (slot == null || slot == REMOVED)
continue;
if (getAll || (slot.attributes & DONTENUM) == 0)
count++;
}
Object[] result = new Object[count];
for (int i=slots.length-1; i >= 0; i--) {
Slot slot = slots[i];
if (slot == null || slot == REMOVED)
continue;
if (getAll || (slot.attributes & DONTENUM) == 0)
result[--count] = slot.stringKey != null
? (Object) slot.stringKey
: new Integer(slot.intKey);
}
return result;
}
/**
* The prototype of this object.
*/
protected Scriptable prototype;
/**
* The parent scope of this object.
*/
protected Scriptable parent;
private static final Object HAS_STATIC_ACCESSORS = Void.TYPE;
private static final Slot REMOVED = new Slot();
private static Hashtable exclusionList = null;
private Slot[] slots;
private int count;
// cache; may be removed for smaller memory footprint
private String lastName;
private int lastHash;
private Object lastValue = REMOVED;
}
class Slot {
static final int HAS_GETTER = 0x01;
static final int HAS_SETTER = 0x02;
int intKey;
String stringKey;
Object value;
short attributes;
short flags;
}
class GetterSlot extends Slot {
Object delegateTo; // OPT: merge with "value"
Method getter;
Method setter;
boolean setterReturnsValue;
}