package com.oracle.truffle.js.runtime.builtins;
import java.util.List;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.Shape;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSArguments;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSException;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.Symbol;
import com.oracle.truffle.js.runtime.objects.JSObject;
import com.oracle.truffle.js.runtime.objects.JSObjectUtil;
import com.oracle.truffle.js.runtime.objects.PropertyDescriptor;
public final class JSAdapter extends AbstractJSClass implements JSConstructorFactory.Default, PrototypeSupplier {
public static final String CLASS_NAME = "JSAdapter";
public static final JSAdapter INSTANCE = new JSAdapter();
private static final String GET = "__get__";
private static final String PUT = "__put__";
private static final String HAS = "__has__";
private static final String CALL = "__call__";
private static final String DELETE = "__delete__";
public static final String NEW = "__new__";
public static final String GET_IDS = "__getIds__";
public static final String GET_VALUES = "__getValues__";
private JSAdapter() {
}
@Override
public String getClassName() {
return CLASS_NAME;
}
@Override
public String getClassName(DynamicObject object) {
return getClassName();
}
@Override
public String toString() {
return getClassName();
}
public static DynamicObject create(JSContext context, DynamicObject adaptee, DynamicObject overrides, DynamicObject proto) {
JSRealm realm = context.getRealm();
JSObjectFactory factory = context.getJSAdapterFactory();
DynamicObject obj = new JSAdapterObject(factory.getShape(realm), adaptee, overrides);
factory.initProto(obj, realm);
if (proto != null) {
JSObject.setPrototype(obj, proto);
}
return context.trackAllocation(obj);
}
public static DynamicObject getAdaptee(DynamicObject obj) {
assert isJSAdapter(obj);
return ((JSAdapterObject) obj).getAdaptee();
}
public static DynamicObject getOverrides(DynamicObject obj) {
assert isJSAdapter(obj);
return ((JSAdapterObject) obj).getOverrides();
}
public static boolean isJSAdapter(Object obj) {
return obj instanceof JSAdapterObject;
}
private static JSException typeError() {
return Errors.createTypeError("operation not supported");
}
@TruffleBoundary
@Override
public Object getOwnHelper(DynamicObject store, Object thisObj, Object key, Node encapsulatingNode) {
assert JSRuntime.isPropertyKey(key);
DynamicObject overrides = getOverrides(store);
if (overrides != null && JSObject.hasOwnProperty(overrides, key)) {
return JSObject.get(overrides, key);
}
return getIntl(store, key);
}
@TruffleBoundary
@Override
public Object getOwnHelper(DynamicObject store, Object thisObj, long index, Node encapsulatingNode) {
DynamicObject overrides = getOverrides(store);
if (overrides != null && JSObject.hasOwnProperty(overrides, index)) {
return JSObject.get(overrides, index);
}
assert JSRuntime.longIsRepresentableAsInt(index);
return getIntl(store, (int) index);
}
private static Object getIntl(DynamicObject thisObj, Object key) {
if (key instanceof Symbol) {
return null;
}
DynamicObject adaptee = getAdaptee(thisObj);
Object get = JSObject.get(adaptee, GET);
if (JSFunction.isJSFunction(get)) {
return JSFunction.call((DynamicObject) get, thisObj, new Object[]{key});
}
return null;
}
@TruffleBoundary
@Override
public boolean hasOwnProperty(DynamicObject thisObj, long index) {
DynamicObject overrides = getOverrides(thisObj);
if (overrides != null && JSObject.hasOwnProperty(overrides, index)) {
return true;
}
return hasOwnPropertyIntl(thisObj, index);
}
@TruffleBoundary
@Override
public boolean hasOwnProperty(DynamicObject thisObj, Object key) {
assert JSRuntime.isPropertyKey(key);
DynamicObject overrides = getOverrides(thisObj);
if (overrides != null && JSObject.hasOwnProperty(overrides, key)) {
return true;
}
return hasOwnPropertyIntl(thisObj, key);
}
private static boolean hasOwnPropertyIntl(DynamicObject thisObj, Object key) {
DynamicObject adaptee = getAdaptee(thisObj);
Object has = JSObject.get(adaptee, HAS);
if (JSFunction.isJSFunction(has)) {
return JSRuntime.toBoolean(JSFunction.call((DynamicObject) has, thisObj, new Object[]{key}));
}
return false;
}
@TruffleBoundary
@Override
public boolean set(DynamicObject thisObj, long index, Object value, Object receiver, boolean isStrict, Node encapsulatingNode) {
DynamicObject overrides = getOverrides(thisObj);
if (overrides != null && JSObject.hasOwnProperty(overrides, index)) {
JSObject.set(overrides, index, value, isStrict, encapsulatingNode);
return true;
}
DynamicObject adaptee = getAdaptee(thisObj);
Object set = JSObject.get(adaptee, PUT);
if (JSFunction.isJSFunction(set)) {
assert JSRuntime.longIsRepresentableAsInt(index);
JSFunction.call((DynamicObject) set, thisObj, new Object[]{(int) index, value});
}
return true;
}
@TruffleBoundary
@Override
public boolean set(DynamicObject thisObj, Object key, Object value, Object receiver, boolean isStrict, Node encapsulatingNode) {
assert JSRuntime.isPropertyKey(key);
DynamicObject overrides = getOverrides(thisObj);
if (overrides != null && JSObject.hasOwnProperty(overrides, key)) {
return JSObject.set(overrides, key, value, isStrict, encapsulatingNode);
}
DynamicObject adaptee = getAdaptee(thisObj);
Object set = JSObject.get(adaptee, PUT);
if (JSFunction.isJSFunction(set)) {
JSFunction.call((DynamicObject) set, thisObj, new Object[]{key, value});
}
return true;
}
@TruffleBoundary
@Override
public boolean delete(DynamicObject thisObj, long index, boolean isStrict) {
DynamicObject overrides = getOverrides(thisObj);
if (overrides != null && JSObject.delete(overrides, index, isStrict)) {
return true;
}
DynamicObject adaptee = getAdaptee(thisObj);
Object delete = JSObject.get(adaptee, DELETE);
if (JSFunction.isJSFunction(delete)) {
JSFunction.call((DynamicObject) delete, thisObj, new Object[]{index});
}
return true;
}
@TruffleBoundary
@Override
public boolean delete(DynamicObject thisObj, Object key, boolean isStrict) {
DynamicObject overrides = getOverrides(thisObj);
if (overrides != null && JSObject.delete(overrides, key, isStrict)) {
return true;
}
DynamicObject adaptee = getAdaptee(thisObj);
Object delete = JSObject.get(adaptee, DELETE);
if (JSFunction.isJSFunction(delete)) {
JSFunction.call((DynamicObject) delete, thisObj, new Object[]{key});
}
return true;
}
@Override
public boolean defineOwnProperty(DynamicObject thisObj, Object key, PropertyDescriptor desc, boolean doThrow) {
return set(thisObj, key, desc.getValue(), thisObj, doThrow, null);
}
@Override
public boolean preventExtensions(DynamicObject thisObj, boolean doThrow) {
throw typeError();
}
@Override
public boolean isExtensible(DynamicObject thisObj) {
throw typeError();
}
@TruffleBoundary
@Override
public List<Object> getOwnPropertyKeys(DynamicObject thisObj, boolean strings, boolean symbols) {
DynamicObject adaptee = getAdaptee(thisObj);
Object getIds = JSObject.get(adaptee, GET_IDS);
if (JSFunction.isJSFunction(getIds)) {
Object returnValue = JSFunction.call((DynamicObject) getIds, thisObj, JSArguments.EMPTY_ARGUMENTS_ARRAY);
if (JSRuntime.isObject(returnValue)) {
return filterOwnPropertyKeys(JSRuntime.createListFromArrayLikeAllowSymbolString(returnValue), strings, symbols);
}
}
return super.getOwnPropertyKeys(thisObj, strings, symbols);
}
@Override
public String toDisplayStringImpl(DynamicObject object, int depth, boolean allowSideEffects, JSContext context) {
return defaultToString(object);
}
@Override
public DynamicObject createPrototype(final JSRealm realm, DynamicObject ctor) {
DynamicObject prototype = JSObjectUtil.createOrdinaryPrototypeObject(realm);
JSObjectUtil.putConstructorProperty(realm.getContext(), prototype, ctor);
JSObjectUtil.putToStringTag(prototype, CLASS_NAME);
return prototype;
}
@Override
public Shape makeInitialShape(JSContext context, DynamicObject prototype) {
return JSObjectUtil.getProtoChildShape(prototype, INSTANCE, context);
}
public static JSConstructor createConstructor(JSRealm realm) {
return INSTANCE.createConstructorAndPrototype(realm);
}
@TruffleBoundary
@Override
public Object getMethodHelper(DynamicObject store, Object thisObj, Object key, Node encapsulatingNode) {
if (key instanceof Symbol) {
return null;
}
DynamicObject adaptee = getAdaptee(store);
Object call = JSObject.get(adaptee, CALL);
if (JSFunction.isJSFunction(call)) {
return JSFunction.bind(JSFunction.getRealm((DynamicObject) call), (DynamicObject) call, store, new Object[]{key});
} else {
throw createTypeErrorNoSuchFunction(store, key);
}
}
@TruffleBoundary
private JSException createTypeErrorNoSuchFunction(DynamicObject thisObj, Object key) {
return Errors.createTypeErrorFormat("%s has no such function \"%s\"", defaultToString(thisObj), key);
}
@TruffleBoundary
@Override
public DynamicObject getPrototypeOf(DynamicObject thisObj) {
return JSObjectUtil.getPrototype(thisObj);
}
@Override
public boolean setPrototypeOf(DynamicObject thisObj, DynamicObject newPrototype) {
return JSNonProxy.setPrototypeStatic(thisObj, newPrototype);
}
@Override
public PropertyDescriptor getOwnProperty(DynamicObject thisObj, Object key) {
throw typeError();
}
@Override
public DynamicObject getIntrinsicDefaultProto(JSRealm realm) {
return realm.getJSAdapterPrototype();
}
}