package com.oracle.truffle.js.nodes.access;
import java.util.List;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.js.builtins.helper.ListGetNode;
import com.oracle.truffle.js.builtins.helper.ListSizeNode;
import com.oracle.truffle.js.nodes.JavaScriptBaseNode;
import com.oracle.truffle.js.nodes.interop.ImportValueNode;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSConfig;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.interop.JSInteropUtil;
import com.oracle.truffle.js.runtime.objects.JSObject;
import com.oracle.truffle.js.runtime.objects.PropertyDescriptor;
import com.oracle.truffle.js.runtime.util.JSClassProfile;
@ImportStatic({JSConfig.class})
public abstract class CopyDataPropertiesNode extends JavaScriptBaseNode {
protected final JSContext context;
protected CopyDataPropertiesNode(JSContext context) {
this.context = context;
}
public static CopyDataPropertiesNode create(JSContext context) {
return CopyDataPropertiesNodeGen.create(context);
}
public final Object execute(Object target, Object source) {
return executeImpl(target, source, null, false);
}
public final Object execute(Object target, Object source, Object[] excludedItems) {
return executeImpl(target, source, excludedItems, true);
}
protected abstract Object executeImpl(Object target, Object source, Object[] excludedItems, boolean withExcluded);
@SuppressWarnings("unused")
@Specialization(guards = "isNullOrUndefined(value)")
protected static DynamicObject doNullOrUndefined(DynamicObject target, Object value, Object[] excludedItems, boolean withExcluded) {
return target;
}
@Specialization(guards = {"isJSObject(source)"})
protected static DynamicObject copyDataProperties(DynamicObject target, DynamicObject source, Object[] excludedItems, boolean withExcluded,
@Cached("create(context)") ReadElementNode getNode,
@Cached("create(false)") JSGetOwnPropertyNode getOwnProperty,
@Cached ListSizeNode listSize,
@Cached ListGetNode listGet,
@Cached JSClassProfile classProfile) {
List<Object> ownPropertyKeys = JSObject.ownPropertyKeys(source, classProfile);
int size = listSize.execute(ownPropertyKeys);
for (int i = 0; i < size; i++) {
Object nextKey = listGet.execute(ownPropertyKeys, i);
assert JSRuntime.isPropertyKey(nextKey);
if (!isExcluded(withExcluded, excludedItems, nextKey)) {
PropertyDescriptor desc = getOwnProperty.execute(source, nextKey);
if (desc != null && desc.getEnumerable()) {
Object propValue = getNode.executeWithTargetAndIndex(source, nextKey);
JSRuntime.createDataPropertyOrThrow(target, nextKey, propValue);
}
}
}
return target;
}
private static boolean isExcluded(boolean withExcluded, Object[] excludedKeys, Object key) {
CompilerAsserts.partialEvaluationConstant(withExcluded);
if (withExcluded) {
for (Object e : excludedKeys) {
assert JSRuntime.isPropertyKey(e);
if (JSRuntime.propertyKeyEquals(e, key)) {
return true;
}
}
}
return false;
}
@Specialization(guards = {"!isJSDynamicObject(from)"}, limit = "InteropLibraryLimit")
protected final DynamicObject copyDataPropertiesForeign(DynamicObject target, Object from, Object[] excludedItems, boolean withExcluded,
@CachedLibrary("from") InteropLibrary objInterop,
@CachedLibrary(limit = "InteropLibraryLimit") InteropLibrary keysInterop,
@CachedLibrary(limit = "InteropLibraryLimit") InteropLibrary stringInterop,
@Cached ImportValueNode importValue) {
if (objInterop.isNull(from)) {
return target;
}
try {
Object members = objInterop.getMembers(from);
long length = JSInteropUtil.getArraySize(members, keysInterop, this);
for (long i = 0; i < length; i++) {
Object key = keysInterop.readArrayElement(members, i);
assert InteropLibrary.getFactory().getUncached().isString(key);
String stringKey = key instanceof String ? (String) key : stringInterop.asString(key);
if (!isExcluded(withExcluded, excludedItems, stringKey)) {
Object value = objInterop.readMember(from, stringKey);
JSRuntime.createDataPropertyOrThrow(target, stringKey, importValue.executeWithTarget(value));
}
}
} catch (UnsupportedMessageException | InvalidArrayIndexException | UnknownIdentifierException e) {
throw Errors.createTypeErrorInteropException(from, e, "CopyDataProperties", this);
}
return target;
}
}