package com.oracle.truffle.js.runtime.objects;
import java.util.List;
import java.util.function.IntUnaryOperator;
import java.util.function.Predicate;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Fallback;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.DynamicObjectLibrary;
import com.oracle.truffle.api.object.Property;
import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.api.utilities.TriState;
import com.oracle.truffle.js.runtime.Boundaries;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.Symbol;
import com.oracle.truffle.js.runtime.builtins.JSClass;
@ExportLibrary(InteropLibrary.class)
public abstract class JSDynamicObject extends DynamicObject implements TruffleObject {
protected JSDynamicObject(Shape shape) {
super(shape);
}
@ExportMessage
public static final class IsIdenticalOrUndefined {
@Specialization
public static TriState doHostObject(JSDynamicObject receiver, JSDynamicObject other) {
return TriState.valueOf(receiver == other);
}
@SuppressWarnings("unused")
@Fallback
public static TriState doOther(JSDynamicObject receiver, Object other) {
return TriState.UNDEFINED;
}
}
@ExportMessage
@TruffleBoundary
public final int identityHashCode() {
return super.hashCode();
}
public final JSContext getJSContext() {
return getJSSharedData(this).getContext();
}
public JSClass getJSClass() {
return (JSClass) getDynamicType(this);
}
@TruffleBoundary
public abstract JSDynamicObject getPrototypeOf();
@TruffleBoundary
public abstract boolean setPrototypeOf(JSDynamicObject newPrototype);
@TruffleBoundary
public abstract boolean isExtensible();
@TruffleBoundary
public abstract boolean preventExtensions(boolean doThrow);
@TruffleBoundary
public abstract PropertyDescriptor getOwnProperty(Object propertyKey);
@TruffleBoundary
public abstract boolean defineOwnProperty(Object key, PropertyDescriptor value, boolean doThrow);
@TruffleBoundary
public abstract boolean hasProperty(Object key);
@TruffleBoundary
public abstract boolean hasProperty(long index);
@TruffleBoundary
public abstract boolean hasOwnProperty(Object propName);
@TruffleBoundary
public abstract boolean hasOwnProperty(long index);
@SuppressWarnings("javadoc")
public Object getValue(Object key) {
return JSRuntime.nullToUndefined(getHelper(this, key, null));
}
public Object getValue(long index) {
return JSRuntime.nullToUndefined(getHelper(this, index, null));
}
@TruffleBoundary
public abstract Object getHelper(Object receiver, Object key, Node encapsulatingNode);
@TruffleBoundary
public abstract Object getHelper(Object receiver, long index, Node encapsulatingNode);
@TruffleBoundary
public abstract Object getOwnHelper(Object receiver, Object key, Node encapsulatingNode);
@TruffleBoundary
public abstract Object getOwnHelper(Object receiver, long index, Node encapsulatingNode);
@TruffleBoundary
public abstract Object getMethodHelper(Object receiver, Object key, Node encapsulatingNode);
@TruffleBoundary
public abstract boolean set(Object key, Object value, Object receiver, boolean isStrict, Node encapsulatingNode);
@TruffleBoundary
public abstract boolean set(long index, Object value, Object receiver, boolean isStrict, Node encapsulatingNode);
@TruffleBoundary
public abstract boolean delete(Object key, boolean isStrict);
@TruffleBoundary
public abstract boolean delete(long propIdx, boolean isStrict);
@TruffleBoundary
public List<Object> ownPropertyKeys() {
return getOwnPropertyKeys(true, true);
}
@TruffleBoundary
public abstract List<Object> getOwnPropertyKeys(boolean strings, boolean symbols);
@TruffleBoundary
public abstract boolean hasOnlyShapeProperties();
@TruffleBoundary
public abstract String getClassName();
@Override
@TruffleBoundary
public abstract String toString();
boolean isObject() {
return true;
}
@TruffleBoundary
public String defaultToString() {
JSContext context = getJSContext();
if (context.getEcmaScriptVersion() <= 5) {
return JSObjectUtil.formatToString(getClassName());
}
String result = null;
if (isObject()) {
Object toStringTag = getValue(Symbol.SYMBOL_TO_STRING_TAG);
if (JSRuntime.isString(toStringTag)) {
result = Boundaries.javaToString(toStringTag);
}
}
if (result == null) {
result = getBuiltinToStringTag();
}
return JSObjectUtil.formatToString(result);
}
@TruffleBoundary
public String getBuiltinToStringTag() {
return getClassName();
}
@TruffleBoundary
public abstract String toDisplayStringImpl(int depth, boolean allowSideEffects);
@TruffleBoundary
public boolean testIntegrityLevel(boolean frozen) {
assert isObject();
boolean status = isExtensible();
if (status) {
return false;
}
for (Object key : ownPropertyKeys()) {
PropertyDescriptor desc = getOwnProperty(key);
if (desc != null) {
if (desc.getConfigurable()) {
return false;
}
if (frozen && desc.isDataDescriptor() && desc.getWritable()) {
return false;
}
}
}
return true;
}
@TruffleBoundary
public boolean setIntegrityLevel(boolean freeze, boolean doThrow) {
assert isObject();
if (!preventExtensions(doThrow)) {
return false;
}
Iterable<Object> keys = ownPropertyKeys();
if (freeze) {
PropertyDescriptor accDesc = PropertyDescriptor.createEmpty();
accDesc.setConfigurable(false);
PropertyDescriptor dataDesc = PropertyDescriptor.createEmpty();
dataDesc.setConfigurable(false);
dataDesc.setWritable(false);
for (Object key : keys) {
PropertyDescriptor currentDesc = getOwnProperty(key);
if (currentDesc != null) {
PropertyDescriptor newDesc = null;
if (currentDesc.isAccessorDescriptor()) {
newDesc = accDesc;
} else {
newDesc = dataDesc;
}
defineOwnProperty(key, newDesc, true);
}
}
} else {
PropertyDescriptor desc = PropertyDescriptor.createEmpty();
desc.setConfigurable(false);
for (Object key : keys) {
defineOwnProperty(key, desc, true);
}
}
return true;
}
public static boolean isJSDynamicObject(Object object) {
return object instanceof JSDynamicObject;
}
public static JSContext getJSContext(DynamicObject obj) {
return getJSSharedData(obj).getContext();
}
public static JSClass getJSClass(DynamicObject obj) {
return (JSClass) getDynamicType(obj);
}
public static void setJSClass(DynamicObject obj, JSClass jsclass) {
DynamicObjectLibrary.getUncached().setDynamicType(obj, jsclass);
}
public static Object getDynamicType(DynamicObject obj) {
return obj.getShape().getDynamicType();
}
public static boolean hasProperty(DynamicObject obj, Object key) {
return DynamicObjectLibrary.getUncached().containsKey(obj, key);
}
public static Property getProperty(DynamicObject obj, Object key) {
return DynamicObjectLibrary.getUncached().getProperty(obj, key);
}
public static Object[] getKeyArray(DynamicObject obj) {
return obj.getShape().getKeyList().toArray();
}
public static Property[] getPropertyArray(DynamicObject obj) {
return obj.getShape().getPropertyList().toArray(new Property[0]);
}
public static Object getOrNull(DynamicObject obj, Object key) {
return DynamicObjectLibrary.getUncached().getOrDefault(obj, key, null);
}
public static Object getOrDefault(DynamicObject obj, Object key, Object defaultValue) {
return DynamicObjectLibrary.getUncached().getOrDefault(obj, key, defaultValue);
}
public static int getIntOrDefault(DynamicObject obj, Object key, int defaultValue) {
try {
return DynamicObjectLibrary.getUncached().getIntOrDefault(obj, key, defaultValue);
} catch (UnexpectedResultException e) {
throw Errors.shouldNotReachHere();
}
}
public static int getObjectFlags(DynamicObject obj) {
return obj.getShape().getFlags();
}
public static void setObjectFlags(DynamicObject obj, int flags) {
DynamicObjectLibrary.getUncached().setShapeFlags(obj, flags);
}
public static void setPropertyFlags(DynamicObject obj, Object key, int flags) {
DynamicObjectLibrary.getUncached().setPropertyFlags(obj, key, flags);
}
public static int getPropertyFlags(DynamicObject obj, Object key) {
return DynamicObjectLibrary.getUncached().getProperty(obj, key).getFlags();
}
public static boolean updatePropertyFlags(DynamicObject obj, Object key, IntUnaryOperator updateFunction) {
DynamicObjectLibrary uncached = DynamicObjectLibrary.getUncached();
Property property = uncached.getProperty(obj, key);
if (property == null) {
return false;
}
int oldFlags = property.getFlags();
int newFlags = updateFunction.applyAsInt(oldFlags);
if (oldFlags == newFlags) {
return false;
}
return uncached.setPropertyFlags(obj, key, newFlags);
}
public static boolean testProperties(DynamicObject obj, Predicate<Property> predicate) {
return obj.getShape().allPropertiesMatch(predicate);
}
public static boolean removeKey(DynamicObject obj, Object key) {
return DynamicObjectLibrary.getUncached().removeKey(obj, key);
}
public static JSSharedData getJSSharedData(DynamicObject obj) {
return JSShape.getSharedData(obj.getShape());
}
}