This example Java source code file (ScriptObject.java) is included in the alvinalexander.com
"Java Source Code
Warehouse" project. The intent of this project is to help you "Learn
Java by Example" TM.
/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.runtime;
import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCall;
import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup;
import static jdk.nashorn.internal.lookup.Lookup.MH;
import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static jdk.nashorn.internal.runtime.PropertyDescriptor.CONFIGURABLE;
import static jdk.nashorn.internal.runtime.PropertyDescriptor.ENUMERABLE;
import static jdk.nashorn.internal.runtime.PropertyDescriptor.GET;
import static jdk.nashorn.internal.runtime.PropertyDescriptor.SET;
import static jdk.nashorn.internal.runtime.PropertyDescriptor.VALUE;
import static jdk.nashorn.internal.runtime.PropertyDescriptor.WRITABLE;
import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex;
import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import jdk.internal.dynalink.CallSiteDescriptor;
import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.internal.dynalink.linker.LinkRequest;
import jdk.internal.dynalink.support.CallSiteDescriptorFactory;
import jdk.nashorn.internal.codegen.CompilerConstants.Call;
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
import jdk.nashorn.internal.lookup.Lookup;
import jdk.nashorn.internal.lookup.MethodHandleFactory;
import jdk.nashorn.internal.objects.AccessorPropertyDescriptor;
import jdk.nashorn.internal.objects.DataPropertyDescriptor;
import jdk.nashorn.internal.runtime.arrays.ArrayData;
import jdk.nashorn.internal.runtime.arrays.ArrayIndex;
import jdk.nashorn.internal.runtime.linker.Bootstrap;
import jdk.nashorn.internal.runtime.linker.LinkerCallSite;
import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
import jdk.nashorn.internal.runtime.linker.NashornGuards;
/**
* Base class for generic JavaScript objects.
* <p>
* Notes:
* <ul>
* <li>The map is used to identify properties in the object.
* <li>If the map is modified then it must be cloned and replaced. This notifies
* any code that made assumptions about the object that things have changed.
* Ex. CallSites that have been validated must check to see if the map has
* changed (or a map from a different object type) and hence relink the method
* to call.</li>
* <li>Modifications of the map include adding/deleting attributes or changing a
* function field value.</li>
* </ul>
*/
public abstract class ScriptObject extends PropertyListenerManager implements PropertyAccess {
/** __proto__ special property name */
public static final String PROTO_PROPERTY_NAME = "__proto__";
/** Search fall back routine name for "no such method" */
static final String NO_SUCH_METHOD_NAME = "__noSuchMethod__";
/** Search fall back routine name for "no such property" */
static final String NO_SUCH_PROPERTY_NAME = "__noSuchProperty__";
/** Per ScriptObject flag - is this a scope object? */
public static final int IS_SCOPE = 0b0000_0001;
/** Per ScriptObject flag - is this an array object? */
public static final int IS_ARRAY = 0b0000_0010;
/** Per ScriptObject flag - is this an arguments object? */
public static final int IS_ARGUMENTS = 0b0000_0100;
/** Is this a prototype PropertyMap? */
public static final int IS_PROTOTYPE = 0b0000_1000;
/** Is length property not-writable? */
public static final int IS_LENGTH_NOT_WRITABLE = 0b0001_0000;
/** Spill growth rate - by how many elements does {@link ScriptObject#spill} when full */
public static final int SPILL_RATE = 8;
/** Map to property information and accessor functions. Ordered by insertion. */
private PropertyMap map;
/** objects proto. */
private ScriptObject proto;
/** Object flags. */
private int flags;
/** Area for properties added to object after instantiation, see {@link AccessorProperty} */
public Object[] spill;
/** Indexed array data. */
private ArrayData arrayData;
static final MethodHandle GETPROTO = findOwnMH("getProto", ScriptObject.class);
static final MethodHandle SETPROTOCHECK = findOwnMH("setProtoCheck", void.class, Object.class);
static final MethodHandle MEGAMORPHIC_GET = findOwnMH("megamorphicGet", Object.class, String.class, boolean.class);
static final MethodHandle SETFIELD = findOwnMH("setField", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, MethodHandle.class, Object.class, Object.class);
static final MethodHandle SETSPILL = findOwnMH("setSpill", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, int.class, Object.class, Object.class);
static final MethodHandle SETSPILLWITHNEW = findOwnMH("setSpillWithNew", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, int.class, Object.class, Object.class);
static final MethodHandle SETSPILLWITHGROW = findOwnMH("setSpillWithGrow", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, int.class, int.class, Object.class, Object.class);
private static final MethodHandle TRUNCATINGFILTER = findOwnMH("truncatingFilter", Object[].class, int.class, Object[].class);
private static final MethodHandle KNOWNFUNCPROPGUARD = findOwnMH("knownFunctionPropertyGuard", boolean.class, Object.class, PropertyMap.class, MethodHandle.class, Object.class, ScriptFunction.class);
/** Method handle for getting a function argument at a given index. Used from MapCreator */
public static final Call GET_ARGUMENT = virtualCall(MethodHandles.lookup(), ScriptObject.class, "getArgument", Object.class, int.class);
/** Method handle for setting a function argument at a given index. Used from MapCreator */
public static final Call SET_ARGUMENT = virtualCall(MethodHandles.lookup(), ScriptObject.class, "setArgument", void.class, int.class, Object.class);
/** Method handle for getting the proto of a ScriptObject */
public static final Call GET_PROTO = virtualCallNoLookup(ScriptObject.class, "getProto", ScriptObject.class);
/** Method handle for setting the proto of a ScriptObject */
public static final Call SET_PROTO = virtualCallNoLookup(ScriptObject.class, "setProto", void.class, ScriptObject.class);
/** Method handle for setting the proto of a ScriptObject after checking argument */
public static final Call SET_PROTO_CHECK = virtualCallNoLookup(ScriptObject.class, "setProtoCheck", void.class, Object.class);
/** Method handle for setting the user accessors of a ScriptObject */
public static final Call SET_USER_ACCESSORS = virtualCall(MethodHandles.lookup(), ScriptObject.class, "setUserAccessors", void.class, String.class, ScriptFunction.class, ScriptFunction.class);
/**
* Constructor
*/
public ScriptObject() {
this(null);
}
/**
* Constructor
*
* @param map {@link PropertyMap} used to create the initial object
*/
public ScriptObject(final PropertyMap map) {
if (Context.DEBUG) {
ScriptObject.count++;
}
this.arrayData = ArrayData.EMPTY_ARRAY;
this.setMap(map == null ? PropertyMap.newMap() : map);
}
/**
* Constructor that directly sets the prototype to {@code proto} and property map to
* {@code map} without invalidating the map as calling {@link #setProto(ScriptObject)}
* would do. This should only be used for objects that are always constructed with the
* same combination of prototype and property map.
*
* @param proto the prototype object
* @param map intial {@link PropertyMap}
*/
protected ScriptObject(final ScriptObject proto, final PropertyMap map) {
if (Context.DEBUG) {
ScriptObject.count++;
}
this.arrayData = ArrayData.EMPTY_ARRAY;
this.setMap(map == null ? PropertyMap.newMap() : map);
this.proto = proto;
if (proto != null) {
proto.setIsPrototype();
}
}
/**
* Copy all properties from the source object with their receiver bound to the source.
* This function was known as mergeMap
*
* @param source The source object to copy from.
*/
public void addBoundProperties(final ScriptObject source) {
addBoundProperties(source, source.getMap().getProperties());
}
/**
* Copy all properties from the array with their receiver bound to the source.
*
* @param source The source object to copy from.
* @param properties The array of properties to copy.
*/
public void addBoundProperties(final ScriptObject source, final Property[] properties) {
PropertyMap newMap = this.getMap();
for (final Property property : properties) {
final String key = property.getKey();
final Property oldProp = newMap.findProperty(key);
if (oldProp == null) {
if (property instanceof UserAccessorProperty) {
final UserAccessorProperty prop = this.newUserAccessors(key, property.getFlags(), property.getGetterFunction(source), property.getSetterFunction(source));
newMap = newMap.addProperty(prop);
} else {
newMap = newMap.addPropertyBind((AccessorProperty)property, source);
}
} else {
// See ECMA section 10.5 Declaration Binding Instantiation
// step 5 processing each function declaration.
if (property.isFunctionDeclaration() && !oldProp.isConfigurable()) {
if (oldProp instanceof UserAccessorProperty ||
!(oldProp.isWritable() && oldProp.isEnumerable())) {
throw typeError("cant.redefine.property", key, ScriptRuntime.safeToString(this));
}
}
}
}
this.setMap(newMap);
}
/**
* Copy all properties from the array with their receiver bound to the source.
*
* @param source The source object to copy from.
* @param properties The collection of accessor properties to copy.
*/
public void addBoundProperties(final Object source, final AccessorProperty[] properties) {
PropertyMap newMap = this.getMap();
for (final AccessorProperty property : properties) {
final String key = property.getKey();
if (newMap.findProperty(key) == null) {
newMap = newMap.addPropertyBind(property, source);
}
}
this.setMap(newMap);
}
/**
* Bind the method handle to the specified receiver, while preserving its original type (it will just ignore the
* first argument in lieu of the bound argument).
* @param methodHandle Method handle to bind to.
* @param receiver Object to bind.
* @return Bound method handle.
*/
static MethodHandle bindTo(final MethodHandle methodHandle, final Object receiver) {
return MH.dropArguments(MH.bindTo(methodHandle, receiver), 0, methodHandle.type().parameterType(0));
}
/**
* Return a property iterator.
* @return Property iterator.
*/
public Iterator<String> propertyIterator() {
return new KeyIterator(this);
}
/**
* Return a property value iterator.
* @return Property value iterator.
*/
public Iterator<Object> valueIterator() {
return new ValueIterator(this);
}
/**
* ECMA 8.10.1 IsAccessorDescriptor ( Desc )
* @return true if this has a {@link AccessorPropertyDescriptor} with a getter or a setter
*/
public final boolean isAccessorDescriptor() {
return has(GET) || has(SET);
}
/**
* ECMA 8.10.2 IsDataDescriptor ( Desc )
* @return true if this has a {@link DataPropertyDescriptor}, i.e. the object has a property value and is writable
*/
public final boolean isDataDescriptor() {
return has(VALUE) || has(WRITABLE);
}
/**
* ECMA 8.10.3 IsGenericDescriptor ( Desc )
* @return true if this has a descriptor describing an {@link AccessorPropertyDescriptor} or {@link DataPropertyDescriptor}
*/
public final boolean isGenericDescriptor() {
return isAccessorDescriptor() || isDataDescriptor();
}
/**
* ECMA 8.10.5 ToPropertyDescriptor ( Obj )
*
* @return property descriptor
*/
public final PropertyDescriptor toPropertyDescriptor() {
final GlobalObject global = (GlobalObject) Context.getGlobalTrusted();
final PropertyDescriptor desc;
if (isDataDescriptor()) {
if (has(SET) || has(GET)) {
throw typeError((ScriptObject)global, "inconsistent.property.descriptor");
}
desc = global.newDataDescriptor(UNDEFINED, false, false, false);
} else if (isAccessorDescriptor()) {
if (has(VALUE) || has(WRITABLE)) {
throw typeError((ScriptObject)global, "inconsistent.property.descriptor");
}
desc = global.newAccessorDescriptor(UNDEFINED, UNDEFINED, false, false);
} else {
desc = global.newGenericDescriptor(false, false);
}
return desc.fillFrom(this);
}
/**
* ECMA 8.10.5 ToPropertyDescriptor ( Obj )
*
* @param global global scope object
* @param obj object to create property descriptor from
*
* @return property descriptor
*/
public static PropertyDescriptor toPropertyDescriptor(final ScriptObject global, final Object obj) {
if (obj instanceof ScriptObject) {
return ((ScriptObject)obj).toPropertyDescriptor();
}
throw typeError(global, "not.an.object", ScriptRuntime.safeToString(obj));
}
/**
* ECMA 8.12.1 [[GetOwnProperty]] (P)
*
* @param key property key
*
* @return Returns the Property Descriptor of the named own property of this
* object, or undefined if absent.
*/
public Object getOwnPropertyDescriptor(final String key) {
final Property property = getMap().findProperty(key);
final GlobalObject global = (GlobalObject)Context.getGlobalTrusted();
if (property != null) {
final ScriptFunction get = property.getGetterFunction(this);
final ScriptFunction set = property.getSetterFunction(this);
final boolean configurable = property.isConfigurable();
final boolean enumerable = property.isEnumerable();
final boolean writable = property.isWritable();
if (property instanceof UserAccessorProperty) {
return global.newAccessorDescriptor(
(get != null) ?
get :
UNDEFINED,
(set != null) ?
set :
UNDEFINED,
configurable,
enumerable);
}
return global.newDataDescriptor(getWithProperty(property), configurable, enumerable, writable);
}
final int index = getArrayIndex(key);
final ArrayData array = getArray();
if (array.has(index)) {
return array.getDescriptor(global, index);
}
return UNDEFINED;
}
/**
* ECMA 8.12.2 [[GetProperty]] (P)
*
* @param key property key
*
* @return Returns the fully populated Property Descriptor of the named property
* of this object, or undefined if absent.
*/
public Object getPropertyDescriptor(final String key) {
final Object res = getOwnPropertyDescriptor(key);
if (res != UNDEFINED) {
return res;
} else if (getProto() != null) {
return getProto().getOwnPropertyDescriptor(key);
} else {
return UNDEFINED;
}
}
/**
* ECMA 8.12.9 [[DefineOwnProperty]] (P, Desc, Throw)
*
* @param key the property key
* @param propertyDesc the property descriptor
* @param reject is the property extensible - true means new definitions are rejected
*
* @return true if property was successfully defined
*/
public boolean defineOwnProperty(final String key, final Object propertyDesc, final boolean reject) {
final ScriptObject global = Context.getGlobalTrusted();
final PropertyDescriptor desc = toPropertyDescriptor(global, propertyDesc);
final Object current = getOwnPropertyDescriptor(key);
final String name = JSType.toString(key);
if (current == UNDEFINED) {
if (isExtensible()) {
// add a new own property
addOwnProperty(key, desc);
return true;
}
// new property added to non-extensible object
if (reject) {
throw typeError(global, "object.non.extensible", name, ScriptRuntime.safeToString(this));
}
return false;
}
// modifying an existing property
final PropertyDescriptor currentDesc = (PropertyDescriptor) current;
final PropertyDescriptor newDesc = desc;
if (newDesc.type() == PropertyDescriptor.GENERIC &&
! newDesc.has(CONFIGURABLE) && ! newDesc.has(ENUMERABLE)) {
// every descriptor field is absent
return true;
}
if (currentDesc.equals(newDesc)) {
// every descriptor field of the new is same as the current
return true;
}
if (! currentDesc.isConfigurable()) {
if (newDesc.has(CONFIGURABLE) && newDesc.isConfigurable()) {
// not configurable can not be made configurable
if (reject) {
throw typeError(global, "cant.redefine.property", name, ScriptRuntime.safeToString(this));
}
return false;
}
if (newDesc.has(ENUMERABLE) &&
currentDesc.isEnumerable() != newDesc.isEnumerable()) {
// cannot make non-enumerable as enumerable or vice-versa
if (reject) {
throw typeError(global, "cant.redefine.property", name, ScriptRuntime.safeToString(this));
}
return false;
}
}
int propFlags = Property.mergeFlags(currentDesc, newDesc);
Property property = getMap().findProperty(key);
if (currentDesc.type() == PropertyDescriptor.DATA &&
(newDesc.type() == PropertyDescriptor.DATA || newDesc.type() == PropertyDescriptor.GENERIC)) {
if (! currentDesc.isConfigurable() && ! currentDesc.isWritable()) {
if (newDesc.has(WRITABLE) && newDesc.isWritable() ||
newDesc.has(VALUE) && ! ScriptRuntime.sameValue(currentDesc.getValue(), newDesc.getValue())) {
if (reject) {
throw typeError(global, "cant.redefine.property", name, ScriptRuntime.safeToString(this));
}
return false;
}
}
final boolean newValue = newDesc.has(VALUE);
final Object value = newValue? newDesc.getValue() : currentDesc.getValue();
if (newValue && property != null) {
// Temporarily clear flags.
property = modifyOwnProperty(property, 0);
set(key, value, false);
}
if (property == null) {
// promoting an arrayData value to actual property
addOwnProperty(key, propFlags, value);
checkIntegerKey(key);
} else {
// Now set the new flags
modifyOwnProperty(property, propFlags);
}
} else if (currentDesc.type() == PropertyDescriptor.ACCESSOR &&
(newDesc.type() == PropertyDescriptor.ACCESSOR ||
newDesc.type() == PropertyDescriptor.GENERIC)) {
if (! currentDesc.isConfigurable()) {
if (newDesc.has(PropertyDescriptor.GET) && ! ScriptRuntime.sameValue(currentDesc.getGetter(), newDesc.getGetter()) ||
newDesc.has(PropertyDescriptor.SET) && ! ScriptRuntime.sameValue(currentDesc.getSetter(), newDesc.getSetter())) {
if (reject) {
throw typeError(global, "cant.redefine.property", name, ScriptRuntime.safeToString(this));
}
return false;
}
}
// New set the new features.
modifyOwnProperty(property, propFlags,
newDesc.has(GET) ? newDesc.getGetter() : currentDesc.getGetter(),
newDesc.has(SET) ? newDesc.getSetter() : currentDesc.getSetter());
} else {
// changing descriptor type
if (! currentDesc.isConfigurable()) {
// not configurable can not be made configurable
if (reject) {
throw typeError(global, "cant.redefine.property", name, ScriptRuntime.safeToString(this));
}
return false;
}
propFlags = 0;
// Preserve only configurable and enumerable from current desc
// if those are not overridden in the new property descriptor.
boolean value = newDesc.has(CONFIGURABLE)? newDesc.isConfigurable() : currentDesc.isConfigurable();
if (!value) {
propFlags |= Property.NOT_CONFIGURABLE;
}
value = newDesc.has(ENUMERABLE)? newDesc.isEnumerable() : currentDesc.isEnumerable();
if (!value) {
propFlags |= Property.NOT_ENUMERABLE;
}
final int type = newDesc.type();
if (type == PropertyDescriptor.DATA) {
// get writable from the new descriptor
value = newDesc.has(WRITABLE) && newDesc.isWritable();
if (! value) {
propFlags |= Property.NOT_WRITABLE;
}
// delete the old property
deleteOwnProperty(property);
// add new data property
addOwnProperty(key, propFlags, newDesc.getValue());
} else if (type == PropertyDescriptor.ACCESSOR) {
if (property == null) {
addOwnProperty(key, propFlags,
newDesc.has(GET) ? newDesc.getGetter() : null,
newDesc.has(SET) ? newDesc.getSetter() : null);
} else {
// Modify old property with the new features.
modifyOwnProperty(property, propFlags,
newDesc.has(GET) ? newDesc.getGetter() : null,
newDesc.has(SET) ? newDesc.getSetter() : null);
}
}
}
checkIntegerKey(key);
return true;
}
/**
* Spec. mentions use of [[DefineOwnProperty]] for indexed properties in
* certain places (eg. Array.prototype.map, filter). We can not use ScriptObject.set
* method in such cases. This is because set method uses inherited setters (if any)
* from any object in proto chain such as Array.prototype, Object.prototype.
* This method directly sets a particular element value in the current object.
*
* @param index key for property
* @param value value to define
*/
public final void defineOwnProperty(final int index, final Object value) {
assert isValidArrayIndex(index) : "invalid array index";
final long longIndex = ArrayIndex.toLongIndex(index);
if (longIndex >= getArray().length()) {
// make array big enough to hold..
setArray(getArray().ensure(longIndex));
}
setArray(getArray().set(index, value, false));
}
private void checkIntegerKey(final String key) {
final int index = getArrayIndex(key);
if (isValidArrayIndex(index)) {
final ArrayData data = getArray();
if (data.has(index)) {
setArray(data.delete(index));
}
}
}
/**
* Add a new property to the object.
*
* @param key property key
* @param propertyDesc property descriptor for property
*/
public final void addOwnProperty(final String key, final PropertyDescriptor propertyDesc) {
// Already checked that there is no own property with that key.
PropertyDescriptor pdesc = propertyDesc;
final int propFlags = Property.toFlags(pdesc);
if (pdesc.type() == PropertyDescriptor.GENERIC) {
final GlobalObject global = (GlobalObject) Context.getGlobalTrusted();
final PropertyDescriptor dDesc = global.newDataDescriptor(UNDEFINED, false, false, false);
dDesc.fillFrom((ScriptObject)pdesc);
pdesc = dDesc;
}
final int type = pdesc.type();
if (type == PropertyDescriptor.DATA) {
addOwnProperty(key, propFlags, pdesc.getValue());
} else if (type == PropertyDescriptor.ACCESSOR) {
addOwnProperty(key, propFlags,
pdesc.has(GET) ? pdesc.getGetter() : null,
pdesc.has(SET) ? pdesc.getSetter() : null);
}
checkIntegerKey(key);
}
/**
* Low level property API (not using property descriptors)
* <p>
* Find a property in the prototype hierarchy. Note: this is final and not
* a good idea to override. If you have to, use
* {jdk.nashorn.internal.objects.NativeArray{@link #getProperty(String)} or
* {jdk.nashorn.internal.objects.NativeArray{@link #getPropertyDescriptor(String)} as the
* overriding way to find array properties
*
* @see jdk.nashorn.internal.objects.NativeArray
*
* @param key Property key.
* @param deep Whether the search should look up proto chain.
*
* @return FindPropertyData or null if not found.
*/
public final FindProperty findProperty(final String key, final boolean deep) {
return findProperty(key, deep, false, this);
}
/**
* Low level property API (not using property descriptors)
* <p>
* Find a property in the prototype hierarchy. Note: this is not a good idea
* to override except as it was done in {@link WithObject}.
* If you have to, use
* {jdk.nashorn.internal.objects.NativeArray{@link #getProperty(String)} or
* {jdk.nashorn.internal.objects.NativeArray{@link #getPropertyDescriptor(String)} as the
* overriding way to find array properties
*
* @see jdk.nashorn.internal.objects.NativeArray
*
* @param key Property key.
* @param deep Whether the search should look up proto chain.
* @param stopOnNonScope should a deep search stop on the first non-scope object?
* @param start the object on which the lookup was originally initiated
*
* @return FindPropertyData or null if not found.
*/
FindProperty findProperty(final String key, final boolean deep, final boolean stopOnNonScope, final ScriptObject start) {
// if doing deep search, stop search on the first non-scope object if asked to do so
if (stopOnNonScope && start != this && !isScope()) {
return null;
}
final PropertyMap selfMap = getMap();
final Property property = selfMap.findProperty(key);
if (property != null) {
return new FindProperty(start, this, property);
}
if (deep) {
final ScriptObject myProto = getProto();
if (myProto != null) {
return myProto.findProperty(key, deep, stopOnNonScope, start);
}
}
return null;
}
/**
* Low level property API. This is similar to {@link #findProperty(String, boolean)} but returns a
* {@code boolean} value instead of a {@link FindProperty} object.
* @param key Property key.
* @param deep Whether the search should look up proto chain.
* @return true if the property was found.
*/
boolean hasProperty(final String key, final boolean deep) {
if (getMap().findProperty(key) != null) {
return true;
}
if (deep) {
final ScriptObject myProto = getProto();
if (myProto != null) {
return myProto.hasProperty(key, deep);
}
}
return false;
}
/**
* Add a new property to the object.
* <p>
* This a more "low level" way that doesn't involve {@link PropertyDescriptor}s
*
* @param key Property key.
* @param propertyFlags Property flags.
* @param getter Property getter, or null if not defined
* @param setter Property setter, or null if not defined
*
* @return New property.
*/
public final Property addOwnProperty(final String key, final int propertyFlags, final ScriptFunction getter, final ScriptFunction setter) {
return addOwnProperty(newUserAccessors(key, propertyFlags, getter, setter));
}
/**
* Add a new property to the object.
* <p>
* This a more "low level" way that doesn't involve {@link PropertyDescriptor}s
*
* @param key Property key.
* @param propertyFlags Property flags.
* @param value Value of property
*
* @return New property.
*/
public final Property addOwnProperty(final String key, final int propertyFlags, final Object value) {
final Property property = addSpillProperty(key, propertyFlags);
property.setObjectValue(this, this, value, false);
return property;
}
/**
* Add a new property to the object.
* <p>
* This a more "low level" way that doesn't involve {@link PropertyDescriptor}s
*
* @param newProperty property to add
*
* @return New property.
*/
public final Property addOwnProperty(final Property newProperty) {
PropertyMap oldMap = getMap();
while (true) {
final PropertyMap newMap = oldMap.addProperty(newProperty);
if (!compareAndSetMap(oldMap, newMap)) {
oldMap = getMap();
final Property oldProperty = oldMap.findProperty(newProperty.getKey());
if (oldProperty != null) {
return oldProperty;
}
} else {
return newProperty;
}
}
}
private void erasePropertyValue(final Property property) {
// Erase the property field value with undefined. If the property is defined
// by user-defined accessors, we don't want to call the setter!!
if (!(property instanceof UserAccessorProperty)) {
property.setObjectValue(this, this, UNDEFINED, false);
}
}
/**
* Delete a property from the object.
*
* @param property Property to delete.
*
* @return true if deleted.
*/
public final boolean deleteOwnProperty(final Property property) {
erasePropertyValue(property);
PropertyMap oldMap = getMap();
while (true) {
final PropertyMap newMap = oldMap.deleteProperty(property);
if (newMap == null) {
return false;
}
if (!compareAndSetMap(oldMap, newMap)) {
oldMap = getMap();
} else {
// delete getter and setter function references so that we don't leak
if (property instanceof UserAccessorProperty) {
final UserAccessorProperty uc = (UserAccessorProperty) property;
setSpill(uc.getGetterSlot(), null);
setSpill(uc.getSetterSlot(), null);
}
return true;
}
}
}
/**
* Modify a property in the object
*
* @param oldProperty property to modify
* @param propertyFlags new property flags
* @param getter getter for {@link UserAccessorProperty}, null if not present or N/A
* @param setter setter for {@link UserAccessorProperty}, null if not present or N/A
*
* @return new property
*/
public final Property modifyOwnProperty(final Property oldProperty, final int propertyFlags, final ScriptFunction getter, final ScriptFunction setter) {
Property newProperty;
if (oldProperty instanceof UserAccessorProperty) {
final UserAccessorProperty uc = (UserAccessorProperty) oldProperty;
final int getterSlot = uc.getGetterSlot();
final int setterSlot = uc.getSetterSlot();
setSpill(getterSlot, getter);
setSpill(setterSlot, setter);
// if just flipping getter and setter with new functions, no need to change property or map
if (uc.flags == propertyFlags) {
return oldProperty;
}
newProperty = new UserAccessorProperty(oldProperty.getKey(), propertyFlags, getterSlot, setterSlot);
} else {
// erase old property value and create new user accessor property
erasePropertyValue(oldProperty);
newProperty = newUserAccessors(oldProperty.getKey(), propertyFlags, getter, setter);
}
notifyPropertyModified(this, oldProperty, newProperty);
return modifyOwnProperty(oldProperty, newProperty);
}
/**
* Modify a property in the object
*
* @param oldProperty property to modify
* @param propertyFlags new property flags
*
* @return new property
*/
public final Property modifyOwnProperty(final Property oldProperty, final int propertyFlags) {
return modifyOwnProperty(oldProperty, oldProperty.setFlags(propertyFlags));
}
/**
* Modify a property in the object, replacing a property with a new one
*
* @param oldProperty property to replace
* @param newProperty property to replace it with
*
* @return new property
*/
private Property modifyOwnProperty(final Property oldProperty, final Property newProperty) {
assert newProperty.getKey().equals(oldProperty.getKey()) : "replacing property with different key";
PropertyMap oldMap = getMap();
while (true) {
final PropertyMap newMap = oldMap.replaceProperty(oldProperty, newProperty);
if (!compareAndSetMap(oldMap, newMap)) {
oldMap = getMap();
final Property oldPropertyLookup = oldMap.findProperty(oldProperty.getKey());
if (oldPropertyLookup != null && oldPropertyLookup.equals(newProperty)) {
return oldPropertyLookup;
}
} else {
return newProperty;
}
}
}
/**
* Update getter and setter in an object literal.
*
* @param key Property key.
* @param getter {@link UserAccessorProperty} defined getter, or null if none
* @param setter {@link UserAccessorProperty} defined setter, or null if none
*/
public final void setUserAccessors(final String key, final ScriptFunction getter, final ScriptFunction setter) {
final Property oldProperty = getMap().findProperty(key);
if (oldProperty instanceof UserAccessorProperty) {
modifyOwnProperty(oldProperty, oldProperty.getFlags(), getter, setter);
} else {
addOwnProperty(newUserAccessors(key, oldProperty != null ? oldProperty.getFlags() : 0, getter, setter));
}
}
private static int getIntValue(final FindProperty find) {
final MethodHandle getter = find.getGetter(int.class);
if (getter != null) {
try {
return (int)getter.invokeExact((Object)find.getGetterReceiver());
} catch (final Error|RuntimeException e) {
throw e;
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
return ObjectClassGenerator.UNDEFINED_INT;
}
private static long getLongValue(final FindProperty find) {
final MethodHandle getter = find.getGetter(long.class);
if (getter != null) {
try {
return (long)getter.invokeExact((Object)find.getGetterReceiver());
} catch (final Error|RuntimeException e) {
throw e;
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
return ObjectClassGenerator.UNDEFINED_LONG;
}
private static double getDoubleValue(final FindProperty find) {
final MethodHandle getter = find.getGetter(double.class);
if (getter != null) {
try {
return (double)getter.invokeExact((Object)find.getGetterReceiver());
} catch (final Error|RuntimeException e) {
throw e;
} catch (final Throwable e) {
throw new RuntimeException(e);
}
}
return ObjectClassGenerator.UNDEFINED_DOUBLE;
}
/**
* Get the object value of a property
*
* @param find {@link FindProperty} lookup result
*
* @return the value of the property
*/
protected static Object getObjectValue(final FindProperty find) {
return find.getObjectValue();
}
/**
* Return methodHandle of value function for call.
*
* @param find data from find property.
* @param type method type of function.
* @param bindName null or name to bind to second argument (property not found method.)
*
* @return value of property as a MethodHandle or null.
*/
protected MethodHandle getCallMethodHandle(final FindProperty find, final MethodType type, final String bindName) {
return getCallMethodHandle(getObjectValue(find), type, bindName);
}
/**
* Return methodHandle of value function for call.
*
* @param value value of receiver, it not a {@link ScriptFunction} this will return null.
* @param type method type of function.
* @param bindName null or name to bind to second argument (property not found method.)
*
* @return value of property as a MethodHandle or null.
*/
protected static MethodHandle getCallMethodHandle(final Object value, final MethodType type, final String bindName) {
return value instanceof ScriptFunction ? ((ScriptFunction)value).getCallMethodHandle(type, bindName) : null;
}
/**
* Get value using found property.
*
* @param property Found property.
*
* @return Value of property.
*/
public final Object getWithProperty(final Property property) {
return getObjectValue(new FindProperty(this, this, property));
}
/**
* Get a property given a key
*
* @param key property key
*
* @return property for key
*/
public final Property getProperty(final String key) {
return getMap().findProperty(key);
}
/**
* Overridden by {@link jdk.nashorn.internal.objects.NativeArguments} class (internal use.)
* Used for argument access in a vararg function using parameter name.
* Returns the argument at a given key (index)
*
* @param key argument index
*
* @return the argument at the given position, or undefined if not present
*/
public Object getArgument(final int key) {
return get(key);
}
/**
* Overridden by {@link jdk.nashorn.internal.objects.NativeArguments} class (internal use.)
* Used for argument access in a vararg function using parameter name.
* Returns the argument at a given key (index)
*
* @param key argument index
* @param value the value to write at the given index
*/
public void setArgument(final int key, final Object value) {
set(key, value, false);
}
/**
* Return the current context from the object's map.
* @return Current context.
*/
protected Context getContext() {
return Context.fromClass(getClass());
}
/**
* Return the map of an object.
* @return PropertyMap object.
*/
public final PropertyMap getMap() {
return map;
}
/**
* Set the initial map.
* @param map Initial map.
*/
public final void setMap(final PropertyMap map) {
this.map = map;
}
/**
* Conditionally set the new map if the old map is the same.
* @param oldMap Map prior to manipulation.
* @param newMap Replacement map.
* @return true if the operation succeeded.
*/
protected synchronized final boolean compareAndSetMap(final PropertyMap oldMap, final PropertyMap newMap) {
final boolean update = oldMap == this.map;
if (update) {
this.map = newMap;
}
return update;
}
/**
* Return the __proto__ of an object.
* @return __proto__ object.
*/
public final ScriptObject getProto() {
return proto;
}
/**
* Set the __proto__ of an object.
* @param newProto new __proto__ to set.
*/
public synchronized final void setProto(final ScriptObject newProto) {
final ScriptObject oldProto = proto;
map = map.changeProto(oldProto, newProto);
if (newProto != null) {
newProto.setIsPrototype();
}
proto = newProto;
if (isPrototype()) {
// tell listeners that my __proto__ has been changed
notifyProtoChanged(this, oldProto, newProto);
if (oldProto != null) {
oldProto.removePropertyListener(this);
}
if (newProto != null) {
newProto.addPropertyListener(this);
}
}
}
/**
* Set the __proto__ of an object with checks.
* @param newProto Prototype to set.
*/
public final void setProtoCheck(final Object newProto) {
if (!isExtensible()) {
throw typeError("__proto__.set.non.extensible", ScriptRuntime.safeToString(this));
}
if (newProto == null || newProto instanceof ScriptObject) {
// check for circularity
ScriptObject p = (ScriptObject)newProto;
while (p != null) {
if (p == this) {
throw typeError("circular.__proto__.set", ScriptRuntime.safeToString(this));
}
p = p.getProto();
}
setProto((ScriptObject)newProto);
} else {
final ScriptObject global = Context.getGlobalTrusted();
final Object newProtoObject = JSType.toScriptObject(global, newProto);
if (newProtoObject instanceof ScriptObject) {
setProto((ScriptObject)newProtoObject);
} else {
throw typeError(global, "cant.set.proto.to.non.object", ScriptRuntime.safeToString(this), ScriptRuntime.safeToString(newProto));
}
}
}
/**
* return an array of own property keys associated with the object.
*
* @param all True if to include non-enumerable keys.
* @return Array of keys.
*/
public String[] getOwnKeys(final boolean all) {
final List<Object> keys = new ArrayList<>();
final PropertyMap selfMap = this.getMap();
final ArrayData array = getArray();
final long length = array.length();
for (long i = 0; i < length; i = array.nextIndex(i)) {
if (array.has((int)i)) {
keys.add(JSType.toString(i));
}
}
for (final Property property : selfMap.getProperties()) {
if (all || property.isEnumerable()) {
keys.add(property.getKey());
}
}
return keys.toArray(new String[keys.size()]);
}
/**
* Check if this ScriptObject has array entries. This means that someone has
* set values with numeric keys in the object.
*
* @return true if array entries exists.
*/
public boolean hasArrayEntries() {
return getArray().length() > 0 || getMap().containsArrayKeys();
}
/**
* Return the valid JavaScript type name descriptor
*
* @return "Object"
*/
public String getClassName() {
return "Object";
}
/**
* {@code length} is a well known property. This is its getter.
* Note that this *may* be optimized by other classes
*
* @return length property value for this ScriptObject
*/
public Object getLength() {
return get("length");
}
/**
* Stateless toString for ScriptObjects.
*
* @return string description of this object, e.g. {@code [object Object]}
*/
public String safeToString() {
return "[object " + getClassName() + "]";
}
/**
* Return the default value of the object with a given preferred type hint.
* The preferred type hints are String.class for type String, Number.class
* for type Number. <p>
*
* A <code>hint of null means "no hint".
*
* ECMA 8.12.8 [[DefaultValue]](hint)
*
* @param typeHint the preferred type hint
* @return the default value
*/
public Object getDefaultValue(final Class<?> typeHint) {
// We delegate to GlobalObject, as the implementation uses dynamic call sites to invoke object's "toString" and
// "valueOf" methods, and in order to avoid those call sites from becoming megamorphic when multiple contexts
// are being executed in a long-running program, we move the code and their associated dynamic call sites
// (Global.TO_STRING and Global.VALUE_OF) into per-context code.
return ((GlobalObject)Context.getGlobalTrusted()).getDefaultValue(this, typeHint);
}
/**
* Checking whether a script object is an instance of another. Used
* in {@link ScriptFunction} for hasInstance implementation, walks
* the proto chain
*
* @param instance instace to check
* @return true if 'instance' is an instance of this object
*/
public boolean isInstance(final ScriptObject instance) {
return false;
}
/**
* Flag this ScriptObject as non extensible
*
* @return the object after being made non extensible
*/
public ScriptObject preventExtensions() {
PropertyMap oldMap = getMap();
while (true) {
final PropertyMap newMap = getMap().preventExtensions();
if (!compareAndSetMap(oldMap, newMap)) {
oldMap = getMap();
} else {
return this;
}
}
}
/**
* Check whether if an Object (not just a ScriptObject) represents JavaScript array
*
* @param obj object to check
*
* @return true if array
*/
public static boolean isArray(final Object obj) {
return (obj instanceof ScriptObject) && ((ScriptObject)obj).isArray();
}
/**
* Check if this ScriptObject is an array
* @return true if array
*/
public final boolean isArray() {
return (flags & IS_ARRAY) != 0;
}
/**
* Flag this ScriptObject as being an array
*/
public final void setIsArray() {
flags |= IS_ARRAY;
}
/**
* Check if this ScriptObject is an {@code arguments} vector
* @return true if arguments vector
*/
public final boolean isArguments() {
return (flags & IS_ARGUMENTS) != 0;
}
/**
* Flag this ScriptObject as being an {@code arguments} vector
*/
public final void setIsArguments() {
flags |= IS_ARGUMENTS;
}
/**
* Check if this object is a prototype
*
* @return {@code true} if is prototype
*/
public final boolean isPrototype() {
return (flags & IS_PROTOTYPE) != 0;
}
/**
* Flag this object as having a prototype.
*/
public final void setIsPrototype() {
if (proto != null && !isPrototype()) {
proto.addPropertyListener(this);
}
flags |= IS_PROTOTYPE;
}
/**
* Check if this object has non-writable length property
*
* @return {@code true} if 'length' property is non-writable
*/
public final boolean isLengthNotWritable() {
return (flags & IS_LENGTH_NOT_WRITABLE) != 0;
}
/**
* Flag this object as having non-writable length property
*/
public void setIsLengthNotWritable() {
flags |= IS_LENGTH_NOT_WRITABLE;
}
/**
* Get the {@link ArrayData} for this ScriptObject if it is an array
* @return array data
*/
public final ArrayData getArray() {
return arrayData;
}
/**
* Set the {@link ArrayData} for this ScriptObject if it is to be an array
* @param arrayData the array data
*/
public final void setArray(final ArrayData arrayData) {
this.arrayData = arrayData;
}
/**
* Check if this ScriptObject is extensible
* @return true if extensible
*/
public boolean isExtensible() {
return getMap().isExtensible();
}
/**
* ECMAScript 15.2.3.8 - seal implementation
* @return the sealed ScriptObject
*/
public ScriptObject seal() {
PropertyMap oldMap = getMap();
while (true) {
final PropertyMap newMap = getMap().seal();
if (!compareAndSetMap(oldMap, newMap)) {
oldMap = getMap();
} else {
setArray(ArrayData.seal(getArray()));
return this;
}
}
}
/**
* Check whether this ScriptObject is sealed
* @return true if sealed
*/
public boolean isSealed() {
return getMap().isSealed();
}
/**
* ECMA 15.2.39 - freeze implementation. Freeze this ScriptObject
* @return the frozen ScriptObject
*/
public ScriptObject freeze() {
PropertyMap oldMap = getMap();
while (true) {
final PropertyMap newMap = getMap().freeze();
if (!compareAndSetMap(oldMap, newMap)) {
oldMap = getMap();
} else {
setArray(ArrayData.freeze(getArray()));
return this;
}
}
}
/**
* Check whether this ScriptObject is frozen
* @return true if frozen
*/
public boolean isFrozen() {
return getMap().isFrozen();
}
/**
* Flag this ScriptObject as scope
*/
public final void setIsScope() {
if (Context.DEBUG) {
scopeCount++;
}
flags |= IS_SCOPE;
}
/**
* Check whether this ScriptObject is scope
* @return true if scope
*/
public final boolean isScope() {
return (flags & IS_SCOPE) != 0;
}
/**
* Clears the properties from a ScriptObject
* (java.util.Map-like method to help ScriptObjectMirror implementation)
*
* @param strict strict mode or not
*/
public void clear(final boolean strict) {
final Iterator<String> iter = propertyIterator();
while (iter.hasNext()) {
delete(iter.next(), strict);
}
}
/**
* Checks if a property with a given key is present in a ScriptObject
* (java.util.Map-like method to help ScriptObjectMirror implementation)
*
* @param key the key to check for
* @return true if a property with the given key exists, false otherwise
*/
public boolean containsKey(final Object key) {
return has(key);
}
/**
* Checks if a property with a given value is present in a ScriptObject
* (java.util.Map-like method to help ScriptObjectMirror implementation)
*
* @param value value to check for
* @return true if a property with the given value exists, false otherwise
*/
public boolean containsValue(final Object value) {
final Iterator<Object> iter = valueIterator();
while (iter.hasNext()) {
if (iter.next().equals(value)) {
return true;
}
}
return false;
}
/**
* Returns the set of {@literal <property, value>} entries that make up this
* ScriptObject's properties
* (java.util.Map-like method to help ScriptObjectMirror implementation)
*
* @return an entry set of all the properties in this object
*/
public Set<Map.Entry
Other Java examples (source code examples)
Here is a short list of links related to this Java ScriptObject.java source code file: