/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.repl.global;
import static com.github.anba.es6draft.runtime.AbstractOperations.*;
import static com.github.anba.es6draft.runtime.internal.Errors.newTypeError;
import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED;
import static java.util.Collections.emptyIterator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.Realm;
import com.github.anba.es6draft.runtime.internal.Messages;
import com.github.anba.es6draft.runtime.internal.ScriptIterator;
import com.github.anba.es6draft.runtime.internal.SimpleIterator;
import com.github.anba.es6draft.runtime.types.Callable;
import com.github.anba.es6draft.runtime.types.Property;
import com.github.anba.es6draft.runtime.types.PropertyDescriptor;
import com.github.anba.es6draft.runtime.types.ScriptObject;
import com.github.anba.es6draft.runtime.types.Symbol;
import com.github.anba.es6draft.runtime.types.Type;
/**
* Wrapper-Proxy for shell tests
*
* @see MozShellFunctions#wrapWithProto(ExecutionContext, Object, Object)
*/
class WrapperProxy implements ScriptObject {
/** [[ProxyTarget]] */
protected final ScriptObject proxyTarget;
/** [[Prototype]] */
protected final ScriptObject prototype;
public WrapperProxy(ScriptObject target, ScriptObject prototype) {
this.proxyTarget = target;
this.prototype = prototype;
}
@Override
public String toString() {
return String.format("%s@%x {{%n\tTarget=%s%n}}", getClass().getSimpleName(), System.identityHashCode(this),
proxyTarget);
}
static final class CallableWrapperProxy extends WrapperProxy implements Callable {
public CallableWrapperProxy(Callable target, ScriptObject prototype) {
super(target, prototype);
}
@Override
public Object call(ExecutionContext callerContext, Object thisValue, Object... args) {
return ((Callable) proxyTarget).call(callerContext, thisValue, args);
}
@Override
public Object tailCall(ExecutionContext callerContext, Object thisValue, Object... args) {
return call(callerContext, thisValue, args);
}
@Override
public String toSource(ExecutionContext cx) {
return ((Callable) proxyTarget).toSource(cx);
}
@Override
public CallableWrapperProxy clone(ExecutionContext cx) {
throw newTypeError(cx, Messages.Key.FunctionNotCloneable);
}
@Override
public Realm getRealm(ExecutionContext cx) {
return ((Callable) proxyTarget).getRealm(cx);
}
}
/**
* (extension): CreateWrapProxy
*
* @param cx
* the execution context
* @param target
* the proxy target object
* @param proto
* the proxy protoype object
* @return the new wrapper proxy
*/
public static WrapperProxy CreateWrapProxy(ExecutionContext cx, Object target, Object proto) {
if (!Type.isObject(target)) {
throw newTypeError(cx, Messages.Key.NotObjectType);
}
if (!Type.isObjectOrNull(proto)) {
throw newTypeError(cx, Messages.Key.NotObjectOrNull);
}
ScriptObject proxyTarget = Type.objectValue(target);
ScriptObject prototype = Type.objectValueOrNull(proto);
WrapperProxy proxy;
if (IsCallable(proxyTarget)) {
proxy = new CallableWrapperProxy((Callable) proxyTarget, prototype);
} else {
proxy = new WrapperProxy(proxyTarget, prototype);
}
return proxy;
}
protected final ScriptObject getPrototype() {
return prototype;
}
@Override
public ScriptObject getPrototypeOf(ExecutionContext cx) {
// Always return the original prototype.
return prototype;
}
@Override
public boolean setPrototypeOf(ExecutionContext cx, ScriptObject prototype) {
return proxyTarget.setPrototypeOf(cx, prototype);
}
@Override
public boolean isExtensible(ExecutionContext cx) {
return proxyTarget.isExtensible(cx);
}
@Override
public boolean preventExtensions(ExecutionContext cx) {
return proxyTarget.preventExtensions(cx);
}
@Override
public Property getOwnProperty(ExecutionContext cx, long propertyKey) {
return proxyTarget.getOwnProperty(cx, propertyKey);
}
@Override
public Property getOwnProperty(ExecutionContext cx, String propertyKey) {
return proxyTarget.getOwnProperty(cx, propertyKey);
}
@Override
public Property getOwnProperty(ExecutionContext cx, Symbol propertyKey) {
return proxyTarget.getOwnProperty(cx, propertyKey);
}
@Override
public boolean defineOwnProperty(ExecutionContext cx, long propertyKey, PropertyDescriptor desc) {
return proxyTarget.defineOwnProperty(cx, propertyKey, desc);
}
@Override
public boolean defineOwnProperty(ExecutionContext cx, String propertyKey, PropertyDescriptor desc) {
return proxyTarget.defineOwnProperty(cx, propertyKey, desc);
}
@Override
public boolean defineOwnProperty(ExecutionContext cx, Symbol propertyKey, PropertyDescriptor desc) {
return proxyTarget.defineOwnProperty(cx, propertyKey, desc);
}
@Override
public boolean hasProperty(ExecutionContext cx, long propertyKey) {
/* modified 9.1.7 [[HasProperty]](P) */
boolean hasOwn = HasOwnProperty(cx, proxyTarget, propertyKey);
if (hasOwn) {
return true;
}
ScriptObject parent = getPrototype(); // modified
if (parent != null) {
return parent.hasProperty(cx, propertyKey);
}
return false;
}
@Override
public boolean hasProperty(ExecutionContext cx, String propertyKey) {
/* modified 9.1.7 [[HasProperty]](P) */
boolean hasOwn = HasOwnProperty(cx, proxyTarget, propertyKey);
if (hasOwn) {
return true;
}
ScriptObject parent = getPrototype(); // modified
if (parent != null) {
return parent.hasProperty(cx, propertyKey);
}
return false;
}
@Override
public boolean hasProperty(ExecutionContext cx, Symbol propertyKey) {
/* modified 9.1.7 [[HasProperty]](P) */
boolean hasOwn = HasOwnProperty(cx, proxyTarget, propertyKey);
if (hasOwn) {
return true;
}
ScriptObject parent = getPrototype(); // modified
if (parent != null) {
return parent.hasProperty(cx, propertyKey);
}
return false;
}
@Override
public Object get(ExecutionContext cx, long propertyKey, Object receiver) {
/* modified 9.1.8 [[Get]] (P, Receiver) */
Property desc = proxyTarget.getOwnProperty(cx, propertyKey);
if (desc == null) {
ScriptObject parent = getPrototype();// modified
if (parent == null) {
return UNDEFINED;
}
return parent.get(cx, propertyKey, receiver);
}
if (desc.isDataDescriptor()) {
return desc.getValue();
}
assert desc.isAccessorDescriptor();
Callable getter = desc.getGetter();
if (getter == null) {
return UNDEFINED;
}
return getter.call(cx, receiver);
}
@Override
public Object get(ExecutionContext cx, String propertyKey, Object receiver) {
/* modified 9.1.8 [[Get]] (P, Receiver) */
Property desc = proxyTarget.getOwnProperty(cx, propertyKey);
if (desc == null) {
ScriptObject parent = getPrototype();// modified
if (parent == null) {
return UNDEFINED;
}
return parent.get(cx, propertyKey, receiver);
}
if (desc.isDataDescriptor()) {
return desc.getValue();
}
assert desc.isAccessorDescriptor();
Callable getter = desc.getGetter();
if (getter == null) {
return UNDEFINED;
}
return getter.call(cx, receiver);
}
@Override
public Object get(ExecutionContext cx, Symbol propertyKey, Object receiver) {
/* modified 9.1.8 [[Get]] (P, Receiver) */
Property desc = proxyTarget.getOwnProperty(cx, propertyKey);
if (desc == null) {
ScriptObject parent = getPrototype();// modified
if (parent == null) {
return UNDEFINED;
}
return parent.get(cx, propertyKey, receiver);
}
if (desc.isDataDescriptor()) {
return desc.getValue();
}
assert desc.isAccessorDescriptor();
Callable getter = desc.getGetter();
if (getter == null) {
return UNDEFINED;
}
return getter.call(cx, receiver);
}
@Override
public boolean set(ExecutionContext cx, long propertyKey, Object value, Object receiver) {
/* modified 9.1.9 [[Set] (P, V, Receiver) */
Property ownDesc = proxyTarget.getOwnProperty(cx, propertyKey);
if (ownDesc == null) {
ScriptObject parent = getPrototype();// modified
if (parent != null) {
return parent.set(cx, propertyKey, value, receiver);
} else {
ownDesc = new Property(UNDEFINED, true, true, true);
}
}
if (ownDesc.isDataDescriptor()) {
if (!ownDesc.isWritable()) {
return false;
}
if (!Type.isObject(receiver)) {
return false;
}
ScriptObject _receiver = Type.objectValue(receiver);
Property existingDescriptor = _receiver.getOwnProperty(cx, propertyKey);
if (existingDescriptor != null) {
PropertyDescriptor valueDesc = new PropertyDescriptor(value);
return _receiver.defineOwnProperty(cx, propertyKey, valueDesc);
} else {
return CreateDataProperty(cx, _receiver, propertyKey, value);
}
}
assert ownDesc.isAccessorDescriptor();
Callable setter = ownDesc.getSetter();
if (setter == null) {
return false;
}
setter.call(cx, receiver, value);
return true;
}
@Override
public boolean set(ExecutionContext cx, String propertyKey, Object value, Object receiver) {
/* modified 9.1.9 [[Set] (P, V, Receiver) */
Property ownDesc = proxyTarget.getOwnProperty(cx, propertyKey);
if (ownDesc == null) {
ScriptObject parent = getPrototype();// modified
if (parent != null) {
return parent.set(cx, propertyKey, value, receiver);
} else {
ownDesc = new Property(UNDEFINED, true, true, true);
}
}
if (ownDesc.isDataDescriptor()) {
if (!ownDesc.isWritable()) {
return false;
}
if (!Type.isObject(receiver)) {
return false;
}
ScriptObject _receiver = Type.objectValue(receiver);
Property existingDescriptor = _receiver.getOwnProperty(cx, propertyKey);
if (existingDescriptor != null) {
PropertyDescriptor valueDesc = new PropertyDescriptor(value);
return _receiver.defineOwnProperty(cx, propertyKey, valueDesc);
} else {
return CreateDataProperty(cx, _receiver, propertyKey, value);
}
}
assert ownDesc.isAccessorDescriptor();
Callable setter = ownDesc.getSetter();
if (setter == null) {
return false;
}
setter.call(cx, receiver, value);
return true;
}
@Override
public boolean set(ExecutionContext cx, Symbol propertyKey, Object value, Object receiver) {
/* modified 9.1.9 [[Set] (P, V, Receiver) */
Property ownDesc = proxyTarget.getOwnProperty(cx, propertyKey);
if (ownDesc == null) {
ScriptObject parent = getPrototype();// modified
if (parent != null) {
return parent.set(cx, propertyKey, value, receiver);
} else {
ownDesc = new Property(UNDEFINED, true, true, true);
}
}
if (ownDesc.isDataDescriptor()) {
if (!ownDesc.isWritable()) {
return false;
}
if (!Type.isObject(receiver)) {
return false;
}
ScriptObject _receiver = Type.objectValue(receiver);
Property existingDescriptor = _receiver.getOwnProperty(cx, propertyKey);
if (existingDescriptor != null) {
PropertyDescriptor valueDesc = new PropertyDescriptor(value);
return _receiver.defineOwnProperty(cx, propertyKey, valueDesc);
} else {
return CreateDataProperty(cx, _receiver, propertyKey, value);
}
}
assert ownDesc.isAccessorDescriptor();
Callable setter = ownDesc.getSetter();
if (setter == null) {
return false;
}
setter.call(cx, receiver, value);
return true;
}
@Override
public boolean delete(ExecutionContext cx, long propertyKey) {
return proxyTarget.delete(cx, propertyKey);
}
@Override
public boolean delete(ExecutionContext cx, String propertyKey) {
return proxyTarget.delete(cx, propertyKey);
}
@Override
public boolean delete(ExecutionContext cx, Symbol propertyKey) {
return proxyTarget.delete(cx, propertyKey);
}
@Override
public ScriptObject enumerate(ExecutionContext cx) {
return CreateListIterator(cx, new AppendIterator(cx, proxyTarget, getPrototype()));
}
@Override
public ScriptIterator<?> enumerateKeys(ExecutionContext cx) {
return ToScriptIterator(cx, enumerate(cx));
}
private static final class AppendIterator extends SimpleIterator<Object> {
private final ExecutionContext cx;
private final ScriptObject proxyTarget;
private final Iterator<?> targetKeys;
private final Iterator<?> protoKeys;
private final HashSet<Object> visitedKeys = new HashSet<>();
AppendIterator(ExecutionContext cx, ScriptObject proxyTarget, ScriptObject proto) {
this.cx = cx;
this.proxyTarget = proxyTarget;
this.targetKeys = proxyTarget.ownKeys(cx);
this.protoKeys = proto != null ? proto.enumerateKeys(cx) : emptyIterator();
}
@Override
protected Object findNext() {
while (targetKeys.hasNext()) {
Object k = targetKeys.next();
if (k instanceof String) {
String propertyKey = (String) k;
Property property = proxyTarget.getOwnProperty(cx, propertyKey);
if (property != null && visitedKeys.add(propertyKey) && property.isEnumerable()) {
return propertyKey;
}
}
}
while (protoKeys.hasNext()) {
Object propertyKey = protoKeys.next();
if (visitedKeys.add(propertyKey)) {
return propertyKey;
}
}
return null;
}
}
@Override
public List<?> ownPropertyKeys(ExecutionContext cx) {
return proxyTarget.ownPropertyKeys(cx);
}
@Override
public Iterator<?> ownKeys(ExecutionContext cx) {
return proxyTarget.ownKeys(cx);
}
}