/** * 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.*; 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.types.Undefined.UNDEFINED; import java.util.concurrent.atomic.AtomicBoolean; import com.github.anba.es6draft.runtime.ExecutionContext; import com.github.anba.es6draft.runtime.Realm; import com.github.anba.es6draft.runtime.Task; import com.github.anba.es6draft.runtime.internal.Initializable; import com.github.anba.es6draft.runtime.internal.Messages; import com.github.anba.es6draft.runtime.internal.Properties.Attributes; 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.internal.ScriptIterator; import com.github.anba.es6draft.runtime.types.BuiltinSymbol; import com.github.anba.es6draft.runtime.types.Callable; import com.github.anba.es6draft.runtime.types.Constructor; 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.Undefined; import com.github.anba.es6draft.runtime.types.builtins.BuiltinConstructor; import com.github.anba.es6draft.runtime.types.builtins.BuiltinFunction; /** * <h1>Observable</h1> * <ul> * <li>The Observable Constructor * <li>Properties of the Observable Constructor * </ul> */ public final class ObservableConstructor extends BuiltinConstructor implements Initializable { /** * Constructs a new Observable constructor function. * * @param realm * the realm object */ public ObservableConstructor(Realm realm) { super(realm, "Observable", 1); } @Override public void initialize(Realm realm) { createProperties(realm, this, Properties.class); } @Override protected ObservableConstructor clone() { return new ObservableConstructor(getRealm()); } @Override public Object call(ExecutionContext callerContext, Object thisValue, Object... args) { /* step 1 */ throw newTypeError(calleeContext(), Messages.Key.InvalidCall, "Observable"); } @Override public ObservableObject construct(ExecutionContext callerContext, Constructor newTarget, Object... args) { ExecutionContext calleeContext = calleeContext(); Object subscriber = argument(args, 0); /* step 1 (not applicable) */ /* step 2 */ if (!IsCallable(subscriber)) { throw newTypeError(calleeContext, Messages.Key.NotCallable); } /* steps 3-5 */ ObservableObject observable = new ObservableObject(calleeContext.getRealm(), (Callable) subscriber, GetPrototypeFromConstructor(calleeContext, newTarget, Intrinsics.ObservablePrototype)); /* step 6 */ return observable; } /** * Properties of the Observable Constructor */ public enum Properties { ; @Prototype public static final Intrinsics __proto__ = Intrinsics.FunctionPrototype; @Value(name = "length", attributes = @Attributes(writable = false, enumerable = false, configurable = true) ) public static final int length = 1; @Value(name = "name", attributes = @Attributes(writable = false, enumerable = false, configurable = true) ) public static final String name = "Observable"; /** * Observable.prototype */ @Value(name = "prototype", attributes = @Attributes(writable = false, enumerable = false, configurable = false) ) public static final Intrinsics prototype = Intrinsics.ObservablePrototype; /** * Observable.from ( x ) * * @param cx * the execution context * @param thisValue * the function this-value * @param x * the observable object * @return the new observable object */ @Function(name = "from", arity = 1) public static Object from(ExecutionContext cx, Object thisValue, Object x) { /* steps 1-2 */ Constructor c = (Constructor) (IsConstructor(thisValue) ? thisValue : cx.getIntrinsic(Intrinsics.Observable)); /* steps 3-4 */ Callable observableMethod = GetMethod(cx, x, BuiltinSymbol.observable.get()); /* steps 5-6 */ Callable subscriber; if (observableMethod != null) { /* step 5 */ /* steps 5.a-b */ Object observable = observableMethod.call(cx, x); /* step 5.c */ if (!Type.isObject(observable)) { throw newTypeError(cx, Messages.Key.NotObjectType); } ScriptObject observableObj = Type.objectValue(observable); /* steps 5.d-e */ Object constructor = Get(cx, observableObj, "constructor"); /* step 5.f */ if (constructor == c) { return observable; } /* steps 5.g-h */ subscriber = new ObservableFromDelegatingFunction(cx.getRealm(), observableObj); } else { /* step 6 */ /* steps 6.a-b */ subscriber = new ObservableFromIterationFunction(cx.getRealm(), x); } /* step 7 */ return c.construct(cx, c, subscriber); } /** * Observable.of ( ...items ) * * @param cx * the execution context * @param thisValue * the function this-value * @param items * the element values * @return the new observable object */ @Function(name = "of", arity = 0) public static Object of(ExecutionContext cx, Object thisValue, Object... items) { /* steps 1-2 */ Constructor c = (Constructor) (IsConstructor(thisValue) ? thisValue : cx.getIntrinsic(Intrinsics.Observable)); /* steps 3-4 */ Callable subscriber = new ObservableOfSubscriberFunction(cx.getRealm(), items); /* step 5 */ return c.construct(cx, c, subscriber); } } /** * Observable.from Delegating Functions */ public static final class ObservableFromDelegatingFunction extends BuiltinFunction { /** [[Observable]] */ private final ScriptObject observable; public ObservableFromDelegatingFunction(Realm realm, ScriptObject observable) { this(realm, observable, null); createDefaultFunctionProperties(); } private ObservableFromDelegatingFunction(Realm realm, ScriptObject observable, Void ignore) { super(realm, ANONYMOUS, 1); this.observable = observable; } @Override protected ObservableFromDelegatingFunction clone() { return new ObservableFromDelegatingFunction(getRealm(), observable, null); } @Override public Object call(ExecutionContext callerContext, Object thisValue, Object... args) { ExecutionContext calleeContext = calleeContext(); Object observer = argument(args, 0); /* step 1 */ ScriptObject observable = this.observable; /* step 2 */ return Invoke(calleeContext, observable, "subscribe", observer); } } /** * Observable.from Iteration Functions */ public static final class ObservableFromIterationFunction extends BuiltinFunction { /** [[Items]] */ private final Object items; public ObservableFromIterationFunction(Realm realm, Object items) { this(realm, items, null); createDefaultFunctionProperties(); } private ObservableFromIterationFunction(Realm realm, Object items, Void ignore) { super(realm, ANONYMOUS, 1); this.items = items; } @Override protected ObservableFromIterationFunction clone() { return new ObservableFromIterationFunction(getRealm(), items, null); } @Override public Callable call(ExecutionContext callerContext, Object thisValue, Object... args) { ExecutionContext calleeContext = calleeContext(); Realm realm = calleeContext.getRealm(); Object observer = argument(args, 0); // FIXME: spec bug - missing type check if (!Type.isObject(observer)) { throw newTypeError(calleeContext, Messages.Key.NotObjectType); } /* step 1 */ Object items = this.items; /* steps 2-3 */ SubscriberClosedFunction cleanup = new SubscriberClosedFunction(realm); /* step 4 */ realm.enqueuePromiseTask(new ObservableFromJob(realm, Type.objectValue(observer), items, cleanup)); /* step 5 */ return cleanup; } } public static final class SubscriberClosedFunction extends BuiltinFunction { /** [[SubscriptionClosed]] */ private final AtomicBoolean subscriptionClosed; public SubscriberClosedFunction(Realm realm) { this(realm, new AtomicBoolean(false)); createDefaultFunctionProperties(); } private SubscriberClosedFunction(Realm realm, AtomicBoolean subscriptionClosed) { super(realm, ANONYMOUS, 0); this.subscriptionClosed = subscriptionClosed; } public boolean isSubscriptionClosed() { return subscriptionClosed.get(); } @Override protected SubscriberClosedFunction clone() { return new SubscriberClosedFunction(getRealm(), subscriptionClosed); } @Override public Undefined call(ExecutionContext callerContext, Object thisValue, Object... args) { /* step 1 */ subscriptionClosed.set(true); /* step 2 */ return UNDEFINED; } } /** * ObservableFromJob ( observer, items ) */ public static final class ObservableFromJob implements Task { private final Realm realm; private final ScriptObject observer; private final Object items; private final SubscriberClosedFunction cleanup; ObservableFromJob(Realm realm, ScriptObject observer, Object items, SubscriberClosedFunction cleanup) { this.realm = realm; this.observer = observer; this.items = items; this.cleanup = cleanup; } @Override public void execute() { ExecutionContext cx = realm.defaultContext(); /* step 1 */ if (cleanup.isSubscriptionClosed()) { return; } /* steps 2-4 */ ScriptIterator<?> iterator; try { /* steps 2, 4 */ // FIXME: spec bug - typo 'observer' instead of 'items' iterator = GetScriptIterator(cx, items); } catch (ScriptException e) { /* step 3 */ Invoke(cx, observer, "error", e.getValue()); return; } /* step 5 */ boolean rethrow = false; try { while (iterator.hasNext()) { /* steps 5.a-g */ Object nextValue = iterator.next(); /* step 5.h */ { rethrow = true; Invoke(cx, observer, "next", nextValue); rethrow = false; } /* step 5.j */ if (cleanup.isSubscriptionClosed()) { return; } } } catch (ScriptException e) { if (rethrow) { /* step 5.i */ iterator.close(e); throw e; } /* steps 5.b.i, 5.f.i */ Invoke(cx, observer, "error", e.getValue()); return; } /* step 5.d.i */ Invoke(cx, observer, "complete"); } } /** * Observable.of Subscriber Functions */ public static final class ObservableOfSubscriberFunction extends BuiltinFunction { /** [[Items]] */ private final Object[] items; public ObservableOfSubscriberFunction(Realm realm, Object[] items) { this(realm, items, null); createDefaultFunctionProperties(); } private ObservableOfSubscriberFunction(Realm realm, Object[] items, Void ignore) { super(realm, ANONYMOUS, 1); this.items = items; } @Override protected ObservableOfSubscriberFunction clone() { return new ObservableOfSubscriberFunction(getRealm(), items, null); } @Override public Callable call(ExecutionContext callerContext, Object thisValue, Object... args) { ExecutionContext calleeContext = calleeContext(); Realm realm = calleeContext.getRealm(); Object observer = argument(args, 0); // FIXME: spec bug - missing type check if (!Type.isObject(observer)) { throw newTypeError(calleeContext, Messages.Key.NotObjectType); } /* step 1 */ Object[] items = this.items; /* steps 2-3 */ SubscriberClosedFunction cleanup = new SubscriberClosedFunction(realm); /* step 4 */ realm.enqueuePromiseTask(new ObservableOfJob(realm, Type.objectValue(observer), items, cleanup)); /* step 5 */ return cleanup; } } /** * ObservableOfJob ( observer, items ) */ public static final class ObservableOfJob implements Task { private final Realm realm; private final ScriptObject observer; private final Object[] items; private final SubscriberClosedFunction cleanup; ObservableOfJob(Realm realm, ScriptObject observer, Object[] items, SubscriberClosedFunction cleanup) { this.realm = realm; this.observer = observer; this.items = items; this.cleanup = cleanup; } @Override public void execute() { ExecutionContext cx = realm.defaultContext(); /* step 1 */ if (cleanup.isSubscriptionClosed()) { return; } /* steps 2-4, 4.a, 4.e */ for (Object kValue : items) { /* steps 4.b-c */ Invoke(cx, observer, "next", kValue); /* step 4.d */ if (cleanup.isSubscriptionClosed()) { return; } } /* step 5 */ Invoke(cx, observer, "complete"); } } }