BuiltInSlot.java
package org.mozilla.javascript;
import java.io.Serializable;
/**
* This is a specialization of property access using some lambda functions designed for properties
* on built in objects that may be created extremely frequently. It is designed to expose a field on
* a native Java object as a property via static methods that can get and set this value. Custom
* operations are also supported for the setting of property attributes (as the class may need to
* check these during internal operations) and redefinition of the property via a descriptor (as
* array length has unusual behaviour in this respect).
*
* <p>It will generate a plain data descriptor when a property descriptor is produced from it, as
* the properties we might want to internalise do not necessarily have get and set functions.
*
* <p>Holding the `owner` on this object and passing it to the various accessor functions was a
* design choice to reduce object creation under the current slot implementation, and to facilitate
* the separation of slots and descriptors in future.
*
* <p>The owner must be held specifically as the current slot APIs do not pass in the owner of the
* map from which a slot was fetched. We store it in the slot's value field as this is not used for
* any real value storage on a built in slot.
*/
public class BuiltInSlot<T extends ScriptableObject> extends Slot {
public interface Getter<U extends ScriptableObject> extends Serializable {
Object apply(U builtIn, Scriptable start);
}
public interface Setter<U extends ScriptableObject> extends Serializable {
boolean apply(U builtIn, Object value, Scriptable owner, Scriptable start, boolean isThrow);
}
public interface AttributeSetter<U extends ScriptableObject> extends Serializable {
void apply(U builtIn, int attributes);
}
public interface PropDescriptionSetter<U extends ScriptableObject> extends Serializable {
boolean apply(
U builtIn,
BuiltInSlot<U> current,
Object id,
ScriptableObject desc,
boolean checkValid,
Object key,
int index);
}
private final Getter<T> getter;
private final Setter<T> setter;
private final AttributeSetter<T> attrUpdater;
private final PropDescriptionSetter<T> propDescSetter;
BuiltInSlot(Object name, int index, int attr, T builtIn, Getter<T> getter) {
this(
name,
index,
attr,
builtIn,
getter,
BuiltInSlot::defaultSetter,
BuiltInSlot::defaultAttrSetter,
BuiltInSlot::defaultPropDescSetter);
}
BuiltInSlot(Object name, int index, int attr, T builtIn, Getter<T> getter, Setter<T> setter) {
this(
name,
index,
attr,
builtIn,
getter,
setter,
BuiltInSlot::defaultAttrSetter,
BuiltInSlot::defaultPropDescSetter);
}
BuiltInSlot(
Object name,
int index,
int attr,
T builtIn,
Getter<T> getter,
Setter<T> setter,
AttributeSetter<T> attrUpdater) {
this(
name,
index,
attr,
builtIn,
getter,
setter,
attrUpdater,
BuiltInSlot::defaultPropDescSetter);
}
BuiltInSlot(
Object name,
int index,
int attr,
T builtIn,
Getter<T> getter,
Setter<T> setter,
AttributeSetter<T> attrUpdater,
PropDescriptionSetter<T> propDescSetter) {
super(name, index, attr);
this.value = builtIn;
this.getter = getter;
this.setter = setter;
this.attrUpdater = attrUpdater;
this.propDescSetter = propDescSetter;
}
BuiltInSlot(BuiltInSlot<T> slot) {
super(slot);
this.getter = slot.getter;
this.setter = slot.setter;
this.attrUpdater = slot.attrUpdater;
this.propDescSetter = slot.propDescSetter;
}
@Override
Slot copySlot() {
var res = new BuiltInSlot<T>(this);
res.next = null;
res.orderedNext = null;
return res;
}
@Override
@SuppressWarnings("unchecked")
public Object getValue(Scriptable start) {
return getter.apply(((T) this.value), start);
}
@Override
@SuppressWarnings("unchecked")
public boolean setValue(Object value, Scriptable owner, Scriptable start, boolean isThrow) {
if ((getAttributes() & ScriptableObject.READONLY) != 0) {
if (isThrow) {
throw ScriptRuntime.typeErrorById("msg.modify.readonly", name);
}
return true;
}
if (owner == start) {
return setter.apply(((T) this.value), value, owner, start, isThrow);
}
return false;
}
/* When setting a property descriptor we need to set the property
_without_ the normal checks on readonly and similar. */
@SuppressWarnings("unchecked")
public void setValueFromDescriptor(
Object value, Scriptable owner, Scriptable start, boolean isThrow) {
setter.apply(((T) this.value), value, owner, start, isThrow);
}
@Override
@SuppressWarnings("unchecked")
void setAttributes(int value) {
attrUpdater.apply(((T) this.value), value);
super.setAttributes(value);
}
@Override
@SuppressWarnings("unchecked")
ScriptableObject getPropertyDescriptor(Context cx, Scriptable scope) {
return ScriptableObject.buildDataDescriptor(
scope, getValue((T) this.value), getAttributes());
}
@SuppressWarnings("unchecked")
boolean applyNewDescriptor(
Object id, ScriptableObject desc, boolean checkValid, Object key, int index) {
return propDescSetter.apply(((T) this.value), this, id, desc, checkValid, key, index);
}
private static <T extends ScriptableObject> boolean defaultSetter(
T builtIn, Object value, Scriptable owner, Scriptable start, boolean isThrow) {
return true;
}
private static <T extends ScriptableObject> void defaultAttrSetter(T builtIn, int attributes) {
// Do nothing.
}
private static <T extends ScriptableObject> boolean defaultPropDescSetter(
T builtIn,
BuiltInSlot<T> current,
Object id,
ScriptableObject desc,
boolean checkValid,
Object key,
int index) {
return ScriptableObject.defineOrdinaryProperty(builtIn, id, desc, checkValid, key, index);
}
}