/**
* 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.runtime.objects.observable;
import static com.github.anba.es6draft.runtime.AbstractOperations.CreateDataProperty;
import static com.github.anba.es6draft.runtime.AbstractOperations.GetMethod;
import static com.github.anba.es6draft.runtime.AbstractOperations.Invoke;
import static com.github.anba.es6draft.runtime.AbstractOperations.IsCallable;
import static com.github.anba.es6draft.runtime.internal.Errors.newTypeError;
import static com.github.anba.es6draft.runtime.internal.Properties.createProperties;
import static com.github.anba.es6draft.runtime.objects.observable.SubscriptionAbstractOperations.CleanupSubscription;
import static com.github.anba.es6draft.runtime.objects.observable.SubscriptionAbstractOperations.CreateSubscription;
import static com.github.anba.es6draft.runtime.objects.observable.SubscriptionAbstractOperations.SubscriptionClosed;
import static com.github.anba.es6draft.runtime.objects.observable.SubscriptionObserverAbstractOperations.CreateSubscriptionObserver;
import static com.github.anba.es6draft.runtime.objects.promise.PromiseAbstractOperations.PromiseBuiltinCapability;
import static com.github.anba.es6draft.runtime.objects.promise.PromiseCapability.IfAbruptRejectPromise;
import static com.github.anba.es6draft.runtime.types.Undefined.UNDEFINED;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.Realm;
import com.github.anba.es6draft.runtime.internal.Initializable;
import com.github.anba.es6draft.runtime.internal.Messages;
import com.github.anba.es6draft.runtime.internal.Properties.Function;
import com.github.anba.es6draft.runtime.internal.Properties.Prototype;
import com.github.anba.es6draft.runtime.internal.Properties.Value;
import com.github.anba.es6draft.runtime.internal.ScriptException;
import com.github.anba.es6draft.runtime.objects.promise.PromiseCapability;
import com.github.anba.es6draft.runtime.objects.promise.PromiseObject;
import com.github.anba.es6draft.runtime.types.BuiltinSymbol;
import com.github.anba.es6draft.runtime.types.Callable;
import com.github.anba.es6draft.runtime.types.Intrinsics;
import com.github.anba.es6draft.runtime.types.ScriptObject;
import com.github.anba.es6draft.runtime.types.Type;
import com.github.anba.es6draft.runtime.types.builtins.BuiltinFunction;
import com.github.anba.es6draft.runtime.types.builtins.OrdinaryObject;
/**
* <h1>Observable</h1>
* <ul>
* <li>Properties of the Observable Prototype Object
* </ul>
*/
public final class ObservablePrototype extends OrdinaryObject implements Initializable {
/**
* Constructs a new Observable prototype object.
*
* @param realm
* the realm object
*/
public ObservablePrototype(Realm realm) {
super(realm);
}
@Override
public void initialize(Realm realm) {
createProperties(realm, this, Properties.class);
}
/**
* Properties of the Observable Prototype Object
*/
public enum Properties {
;
@Prototype
public static final Intrinsics __proto__ = Intrinsics.ObjectPrototype;
/**
* Observable.prototype.constructor
*/
@Value(name = "constructor")
public static final Intrinsics constructor = Intrinsics.Observable;
/**
* Observable.prototype.subscribe ( observer )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param observer
* the observer object
* @return the new subscription object
*/
@Function(name = "subscribe", arity = 1)
public static Object subscribe(ExecutionContext cx, Object thisValue, Object observer) {
/* steps 2-3 */
if (!(thisValue instanceof ObservableObject)) {
throw newTypeError(cx, Messages.Key.IncompatibleObject);
}
/* step 1 */
ObservableObject observable = (ObservableObject) thisValue;
/* step 4 */
if (!Type.isObject(observer)) {
throw newTypeError(cx, Messages.Key.NotObjectType);
}
ScriptObject observerObj = Type.objectValue(observer);
/* steps 5-6 */
// FIXME: spec bug - unnecessary ReturnIfAbrupt
SubscriptionObject subscription = CreateSubscription(cx, observerObj);
/* steps 7-8 */
// FIXME: spec bug - unnecessary ReturnIfAbrupt
SubscriptionObserverObject subscriptionObserver = CreateSubscriptionObserver(cx, subscription);
/* step 9 */
Callable subscriber = observable.getSubscriber();
/* step 10 (implicit) */
/* steps 11-13 */
try {
/* step 11 */
Callable subscriberResult = ExecuteSubscriber(cx, subscriber, subscriptionObserver);
/* step 13 */
// FIXME: spec bug - typo 'observer'
subscription.setCleanup(subscriberResult);
} catch (ScriptException e) {
/* step 12 */
Invoke(cx, subscriptionObserver, "error", e.getValue());
}
/* step 14 */
if (SubscriptionClosed(subscription)) {
CleanupSubscription(cx, subscription);
}
/* step 15 */
return subscription;
}
/**
* Observable.prototype.forEach ( callbackFn [, thisArg] )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @param callbackFn
* the callback function
* @param thisArg
* the optional this-argument for the callback function
* @return the new promise object
*/
@Function(name = "forEach", arity = 1)
public static Object forEach(ExecutionContext cx, Object thisValue, Object callbackFn, Object thisArg) {
/* step 2 */
if (!Type.isObject(thisValue)) {
throw newTypeError(cx, Messages.Key.NotObjectType);
}
/* step 1 */
ScriptObject o = Type.objectValue(thisValue);
/* steps 3-4 */
PromiseCapability<PromiseObject> promiseCapability = PromiseBuiltinCapability(cx);
/* step 5 */
if (!IsCallable(callbackFn)) {
/* step 5.a */
ScriptException r = newTypeError(cx, Messages.Key.NotCallable);
/* step 5.b */
// FIXME: spec bug - missing ThisValue in Call()
promiseCapability.getReject().call(cx, UNDEFINED, r.getValue());
/* step 5.c */
return promiseCapability.getPromise();
}
/* step 6 */
OrdinaryObject observer = ObjectCreate(cx, Intrinsics.ObjectPrototype);
/* steps 7-9 */
ObserverablePrototypeForEachNextFunction next = new ObserverablePrototypeForEachNextFunction(cx.getRealm(),
(Callable) callbackFn, thisArg, promiseCapability.getReject());
/* step 12 */
CreateDataProperty(cx, observer, "next", next);
/* step 13 */
CreateDataProperty(cx, observer, "error", promiseCapability.getReject());
/* step 14 */
CreateDataProperty(cx, observer, "complete", promiseCapability.getResolve());
/* steps 15-16 */
try {
/* step 15 */
Invoke(cx, o, "subscribe", observer);
} catch (ScriptException e) {
/* step 16 */
return IfAbruptRejectPromise(cx, e, promiseCapability);
}
/* step 17 */
return promiseCapability.getPromise();
}
/**
* Observable.prototype [ @@observable ] ( )
*
* @param cx
* the execution context
* @param thisValue
* the function this-value
* @return the this value
*/
@Function(name = "[Symbol.observable]", symbol = BuiltinSymbol.observable, arity = 0)
public static Object observable(ExecutionContext cx, Object thisValue) {
/* step 1 */
return thisValue;
}
}
/**
* Runtime Semantics: ExecuteSubscriber ( subscriber, observer )
*
* @param cx
* the execution context
* @param subscriber
* the subscriber function
* @param observer
* the observer object
* @return the subscriber result value
*/
public static Callable ExecuteSubscriber(ExecutionContext cx, Callable subscriber,
SubscriptionObserverObject observer) {
/* steps 1-2 (implicit) */
/* steps 3-4 */
Object subscriberResult = subscriber.call(cx, UNDEFINED, observer);
/* step 5 */
if (Type.isUndefinedOrNull(subscriberResult)) {
return null;
}
/* step 6 */
if (IsCallable(subscriberResult)) {
return (Callable) subscriberResult;
}
/* steps 7-8 */
Callable result = GetMethod(cx, subscriberResult, "unsubscribe");
/* step 9 */
if (result == null) {
throw newTypeError(cx, Messages.Key.PropertyNotCallable, "unsubscribe");
}
/* steps 10-11 */
SubscriptionCleanupFunction cleanupFunction = new SubscriptionCleanupFunction(cx.getRealm(), subscriberResult);
/* step 12 */
// FIXME: spec bug - typo 'cancelFunction'
return cleanupFunction;
}
/**
* Subscription Cleanup Functions
*/
public static final class SubscriptionCleanupFunction extends BuiltinFunction {
/** [[Subscription]] */
private final Object subscription;
public SubscriptionCleanupFunction(Realm realm, Object subscription) {
this(realm, subscription, null);
createDefaultFunctionProperties();
}
private SubscriptionCleanupFunction(Realm realm, Object subscription, Void ignore) {
super(realm, ANONYMOUS, 0);
this.subscription = subscription;
}
@Override
protected SubscriptionCleanupFunction clone() {
return new SubscriptionCleanupFunction(getRealm(), subscription, null);
}
@Override
public Object call(ExecutionContext callerContext, Object thisValue, Object... args) {
ExecutionContext calleeContext = calleeContext();
/* step 1 */
// FIXME: spec bug - invalid assertion
/* step 2 */
Object subscription = this.subscription;
/* step 3 */
return Invoke(calleeContext, subscription, "unsubscribe");
}
}
/**
* Observable.prototype.forEach Next Functions
*/
public static final class ObserverablePrototypeForEachNextFunction extends BuiltinFunction {
/** [[Subscription]] */
private final Callable callbackFn;
/** [[ThisArg]] */
private Object thisArg;
/** [[Subscription]] */
private final Callable reject;
public ObserverablePrototypeForEachNextFunction(Realm realm, Callable callbackFn, Object thisArg,
Callable reject) {
this(realm, callbackFn, thisArg, reject, null);
createDefaultFunctionProperties();
}
private ObserverablePrototypeForEachNextFunction(Realm realm, Callable callbackFn, Object thisArg,
Callable reject, Void ignore) {
// FIXME: spec bug - missing length definition
super(realm, ANONYMOUS, 1);
this.callbackFn = callbackFn;
this.thisArg = thisArg;
this.reject = reject;
}
@Override
protected ObserverablePrototypeForEachNextFunction clone() {
return new ObserverablePrototypeForEachNextFunction(getRealm(), callbackFn, thisArg, reject, null);
}
@Override
public Object call(ExecutionContext callerContext, Object thisValue, Object... args) {
ExecutionContext calleeContext = calleeContext();
Object x = argument(args, 0);
/* step 1 */
Callable callbackFn = this.callbackFn;
/* step 2 */
Object thisArg = this.thisArg;
/* step 3 */
Callable promiseReject = this.reject;
/* steps 4-5 */
Object result;
try {
/* step 4 */
result = callbackFn.call(calleeContext, thisArg, x);
} catch (ScriptException e) {
/* steps 5.a-b */
promiseReject.call(calleeContext, UNDEFINED, e.getValue());
/* step 5.c */
return UNDEFINED;
}
/* step 6 */
return result;
}
}
}