/** * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rx.observables; import java.util.Arrays; import java.util.concurrent.atomic.*; import rx.*; import rx.Observable.OnSubscribe; import rx.annotations.Experimental; import rx.exceptions.CompositeException; import rx.functions.*; /** * Abstract base class for the {@link OnSubscribe} interface that helps you build Observable sources one * {@code onNext} at a time, and automatically supports unsubscription and backpressure. * <p> * <h1>Usage rules</h1> * When you implement the {@code next()} method, you * <ul> * <li>should either * <ul> * <li>create the next value and signal it via {@link SubscriptionState#onNext state.onNext()},</li> * <li>signal a terminal condition via {@link SubscriptionState#onError state.onError()}, or * {@link SubscriptionState#onCompleted state.onCompleted()}, or</li> * <li>signal a stop condition via {@link SubscriptionState#stop state.stop()} indicating no further values * will be sent.</li> * </ul> * </li> * <li>may * <ul> * <li>call {@link SubscriptionState#onNext state.onNext()} and either * {@link SubscriptionState#onError state.onError()} or * {@link SubscriptionState#onCompleted state.onCompleted()} together, and * <li>block or sleep. * </ul> * </li> * <li>should not * <ul> * <li>do nothing or do async work and not produce any event or request stopping. If neither of * the methods are called, an {@link IllegalStateException} is forwarded to the {@code Subscriber} and * the Observable is terminated;</li> * <li>call the {@code state.on}<i>foo</i>() methods more than once (yields * {@link IllegalStateException}).</li> * </ul> * </li> * </ul> * * The {@link SubscriptionState} object features counters that may help implement a state machine: * <ul> * <li>A call counter, accessible via {@link SubscriptionState#calls state.calls()} tells how many times the * {@code next()} was run (zero based).</li> * <li>You can use a phase counter, accessible via {@link SubscriptionState#phase state.phase}, that helps track * the current emission phase, in a {@code switch()} statement to implement the state machine. (It is named * {@code phase} to avoid confusion with the per-subscriber state.)</li> * <li>You can arbitrarily change the current phase with * {@link SubscriptionState#advancePhase state.advancePhase()}, * {@link SubscriptionState#advancePhaseBy(int) state.advancedPhaseBy(int)} and * {@link SubscriptionState#phase(int) state.phase(int)}.</li> * </ul> * <p> * When you implement {@code AbstractOnSubscribe}, you may override {@link AbstractOnSubscribe#onSubscribe} to * perform special actions (such as registering {@code Subscription}s with {@code Subscriber.add()}) and return * additional state for each subscriber subscribing. You can access this custom state with the * {@link SubscriptionState#state state.state()} method. If you need to do some cleanup, you can override the * {@link #onTerminated} method. * <p> * For convenience, a lambda-accepting static factory method, {@link #create}, is available. * Another convenience is {@link #toObservable} which turns an {@code AbstractOnSubscribe} * instance into an {@code Observable} fluently. * * <h1>Examples</h1> * Note: these examples use the lambda-helper factories to avoid boilerplate. * * <h3>Implement: just</h3> * <pre><code> * AbstractOnSubscribe.create(s -> { * s.onNext(1); * s.onCompleted(); * }).toObservable().subscribe(System.out::println); * </code></pre> * <h3>Implement: from Iterable</h3> * <pre><code> * Iterable<T> iterable = ...; * AbstractOnSubscribe.create(s -> { * Iterator<T> it = s.state(); * if (it.hasNext()) { * s.onNext(it.next()); * } * if (!it.hasNext()) { * s.onCompleted(); * } * }, u -> iterable.iterator()).subscribe(System.out::println); * </code></pre> * * <h3>Implement source that fails a number of times before succeeding</h3> * <pre><code> * AtomicInteger fails = new AtomicInteger(); * int numFails = 50; * AbstractOnSubscribe.create(s -> { * long c = s.calls(); * switch (s.phase()) { * case 0: * s.onNext("Beginning"); * s.onError(new RuntimeException("Oh, failure."); * if (c == numFails.getAndIncrement()) { * s.advancePhase(); * } * break; * case 1: * s.onNext("Beginning"); * s.advancePhase(); * case 2: * s.onNext("Finally working"); * s.onCompleted(); * s.advancePhase(); * default: * throw new IllegalStateException("How did we get here?"); * } * }).subscribe(System.out::println); * </code></pre> * <h3>Implement: never</h3> * <pre><code> * AbstractOnSubscribe.create(s -> { * s.stop(); * }).toObservable() * .timeout(1, TimeUnit.SECONDS) * .subscribe(System.out::println, Throwable::printStacktrace, () -> System.out.println("Done")); * </code></pre> * * @param <T> the value type * @param <S> the per-subscriber user-defined state type * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) * @Experimental */ @Experimental public abstract class AbstractOnSubscribe<T, S> implements OnSubscribe<T> { /** * Called when a Subscriber subscribes and lets the implementor create a per-subscriber custom state. * <p> * Override this method to have custom state per-subscriber. The default implementation returns * {@code null}. * * @param subscriber the subscriber who is subscribing * @return the custom state */ protected S onSubscribe(Subscriber<? super T> subscriber) { return null; } /** * Called after the terminal emission or when the downstream unsubscribes. * <p> * This is called only once and no {@code onNext} call will run concurrently with it. The default * implementation does nothing. * * @param state the user-provided state */ protected void onTerminated(S state) { } /** * Override this method to create an emission state-machine. * * @param state the per-subscriber subscription state */ protected abstract void next(SubscriptionState<T, S> state); @Override public final void call(final Subscriber<? super T> subscriber) { final S custom = onSubscribe(subscriber); final SubscriptionState<T, S> state = new SubscriptionState<T, S>(this, subscriber, custom); subscriber.add(new SubscriptionCompleter<T, S>(state)); subscriber.setProducer(new SubscriptionProducer<T, S>(state)); } /** * Convenience method to create an Observable from this implemented instance. * * @return the created observable */ public final Observable<T> toObservable() { return Observable.create(this); } /** Function that returns null. */ private static final Func1<Object, Object> NULL_FUNC1 = new Func1<Object, Object>() { @Override public Object call(Object t1) { return null; } }; /** * Creates an {@code AbstractOnSubscribe} instance which calls the provided {@code next} action. * <p> * This is a convenience method to help create {@code AbstractOnSubscribe} instances with the help of * lambdas. * * @param <T> the value type * @param <S> the per-subscriber user-defined state type * @param next the next action to call * @return an {@code AbstractOnSubscribe} instance */ public static <T, S> AbstractOnSubscribe<T, S> create(Action1<SubscriptionState<T, S>> next) { @SuppressWarnings("unchecked") Func1<? super Subscriber<? super T>, ? extends S> nullFunc = (Func1<? super Subscriber<? super T>, ? extends S>)NULL_FUNC1; return create(next, nullFunc, Actions.empty()); } /** * Creates an {@code AbstractOnSubscribe} instance which creates a custom state with the {@code onSubscribe} * function and calls the provided {@code next} action. * <p> * This is a convenience method to help create {@code AbstractOnSubscribe} instances with the help of * lambdas. * * @param <T> the value type * @param <S> the per-subscriber user-defined state type * @param next the next action to call * @param onSubscribe the function that returns a per-subscriber state to be used by {@code next} * @return an {@code AbstractOnSubscribe} instance */ public static <T, S> AbstractOnSubscribe<T, S> create(Action1<SubscriptionState<T, S>> next, Func1<? super Subscriber<? super T>, ? extends S> onSubscribe) { return create(next, onSubscribe, Actions.empty()); } /** * Creates an {@code AbstractOnSubscribe} instance which creates a custom state with the {@code onSubscribe} * function, calls the provided {@code next} action and calls the {@code onTerminated} action to release the * state when its no longer needed. * <p> * This is a convenience method to help create {@code AbstractOnSubscribe} instances with the help of * lambdas. * * @param <T> the value type * @param <S> the per-subscriber user-defined state type * @param next the next action to call * @param onSubscribe the function that returns a per-subscriber state to be used by {@code next} * @param onTerminated the action to call to release the state created by the {@code onSubscribe} function * @return an {@code AbstractOnSubscribe} instance */ public static <T, S> AbstractOnSubscribe<T, S> create(Action1<SubscriptionState<T, S>> next, Func1<? super Subscriber<? super T>, ? extends S> onSubscribe, Action1<? super S> onTerminated) { return new LambdaOnSubscribe<T, S>(next, onSubscribe, onTerminated); } /** * An implementation that forwards the three main methods ({@code next}, {@code onSubscribe}, and * {@code onTermianted}) to functional callbacks. * * @param <T> the value type * @param <S> the per-subscriber user-defined state type */ private static final class LambdaOnSubscribe<T, S> extends AbstractOnSubscribe<T, S> { final Action1<SubscriptionState<T, S>> next; final Func1<? super Subscriber<? super T>, ? extends S> onSubscribe; final Action1<? super S> onTerminated; private LambdaOnSubscribe(Action1<SubscriptionState<T, S>> next, Func1<? super Subscriber<? super T>, ? extends S> onSubscribe, Action1<? super S> onTerminated) { this.next = next; this.onSubscribe = onSubscribe; this.onTerminated = onTerminated; } @Override protected S onSubscribe(Subscriber<? super T> subscriber) { return onSubscribe.call(subscriber); } @Override protected void onTerminated(S state) { onTerminated.call(state); } @Override protected void next(SubscriptionState<T, S> state) { next.call(state); } } /** * Manages unsubscription of the state. * * @param <T> the value type * @param <S> the per-subscriber user-defined state type */ private static final class SubscriptionCompleter<T, S> extends AtomicBoolean implements Subscription { private static final long serialVersionUID = 7993888274897325004L; private final SubscriptionState<T, S> state; private SubscriptionCompleter(SubscriptionState<T, S> state) { this.state = state; } @Override public boolean isUnsubscribed() { return get(); } @Override public void unsubscribe() { if (compareAndSet(false, true)) { state.free(); } } } /** * Contains the producer loop that reacts to downstream requests of work. * * @param <T> the value type * @param <S> the per-subscriber user-defined state type */ private static final class SubscriptionProducer<T, S> implements Producer { final SubscriptionState<T, S> state; private SubscriptionProducer(SubscriptionState<T, S> state) { this.state = state; } @Override public void request(long n) { if (n == Long.MAX_VALUE) { for (; !state.subscriber.isUnsubscribed(); ) { if (!doNext()) { break; } } } else if (n > 0 && state.requestCount.getAndAdd(n) == 0) { if (!state.subscriber.isUnsubscribed()) { do { if (!doNext()) { break; } } while (state.requestCount.decrementAndGet() > 0 && !state.subscriber.isUnsubscribed()); } } } /** * Executes the user-overridden next() method and performs state bookkeeping and * verification. * * @return true if the outer loop may continue */ protected boolean doNext() { if (state.use()) { try { int p = state.phase(); state.parent.next(state); if (!state.verify()) { throw new IllegalStateException("No event produced or stop called @ Phase: " + p + " -> " + state.phase() + ", Calls: " + state.calls()); } if (state.accept() || state.stopRequested()) { state.terminate(); return false; } state.calls++; } catch (Throwable t) { state.terminate(); state.subscriber.onError(t); return false; } finally { state.free(); } return true; } return false; } } /** * Represents a per-subscription state for the {@code AbstractOnSubscribe} operation. It supports phasing * and counts the number of times a value was requested by the downstream. * * @param <T> the value type * @param <S> the per-subscriber user-defined state type * @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number) * @Experimental */ public static final class SubscriptionState<T, S> { private final AbstractOnSubscribe<T, S> parent; private final Subscriber<? super T> subscriber; private final S state; private final AtomicLong requestCount; private final AtomicInteger inUse; private int phase; private long calls; private T theValue; private boolean hasOnNext; private boolean hasCompleted; private boolean stopRequested; private Throwable theException; private SubscriptionState(AbstractOnSubscribe<T, S> parent, Subscriber<? super T> subscriber, S state) { this.parent = parent; this.subscriber = subscriber; this.state = state; this.requestCount = new AtomicLong(); this.inUse = new AtomicInteger(1); } /** * @return the per-subscriber specific user-defined state created via * {@link AbstractOnSubscribe#onSubscribe} */ public S state() { return state; } /** * @return the current phase value */ public int phase() { return phase; } /** * Sets a new phase value. * * @param newPhase */ public void phase(int newPhase) { phase = newPhase; } /** * Advance the current phase by 1. */ public void advancePhase() { advancePhaseBy(1); } /** * Advance the current phase by the given amount (can be negative). * * @param amount the amount to advance the phase */ public void advancePhaseBy(int amount) { phase += amount; } /** * @return the number of times {@link AbstractOnSubscribe#next} was called so far, starting at 0 for the * very first call */ public long calls() { return calls; } /** * Call this method to offer the next {@code onNext} value for the subscriber. * * @param value the value to {@code onNext} * @throws IllegalStateException if there is a value already offered but not taken or a terminal state * is reached */ public void onNext(T value) { if (hasOnNext) { throw new IllegalStateException("onNext not consumed yet!"); } else if (hasCompleted) { throw new IllegalStateException("Already terminated", theException); } theValue = value; hasOnNext = true; } /** * Call this method to send an {@code onError} to the subscriber and terminate all further activities. * If there is a pending {@code onNext}, that value is emitted to the subscriber followed by this * exception. * * @param e the exception to deliver to the client * @throws IllegalStateException if the terminal state has been reached already */ public void onError(Throwable e) { if (e == null) { throw new NullPointerException("e != null required"); } if (hasCompleted) { throw new IllegalStateException("Already terminated", theException); } theException = e; hasCompleted = true; } /** * Call this method to send an {@code onCompleted} to the subscriber and terminate all further * activities. If there is a pending {@code onNext}, that value is emitted to the subscriber followed by * this exception. * * @throws IllegalStateException if the terminal state has been reached already */ public void onCompleted() { if (hasCompleted) { throw new IllegalStateException("Already terminated", theException); } hasCompleted = true; } /** * Signals that there won't be any further events. */ public void stop() { stopRequested = true; } /** * Emits the {@code onNext} and/or the terminal value to the actual subscriber. * * @return {@code true} if the event was a terminal event */ protected boolean accept() { if (hasOnNext) { T value = theValue; theValue = null; hasOnNext = false; try { subscriber.onNext(value); } catch (Throwable t) { hasCompleted = true; Throwable e = theException; theException = null; if (e == null) { subscriber.onError(t); } else { subscriber.onError(new CompositeException(Arrays.asList(t, e))); } return true; } } if (hasCompleted) { Throwable e = theException; theException = null; if (e != null) { subscriber.onError(e); } else { subscriber.onCompleted(); } return true; } return false; } /** * Verify if the {@code next()} generated an event or requested a stop. * * @return true if either event was generated or stop was requested */ protected boolean verify() { return hasOnNext || hasCompleted || stopRequested; } /** @return true if the {@code next()} requested a stop */ protected boolean stopRequested() { return stopRequested; } /** * Request the state to be used by {@code onNext} or returns {@code false} if the downstream has * unsubscribed. * * @return {@code true} if the state can be used exclusively * @throws IllegalStateEception * @warn "throws" section incomplete */ protected boolean use() { int i = inUse.get(); if (i == 0) { return false; } else if (i == 1 && inUse.compareAndSet(1, 2)) { return true; } throw new IllegalStateException("This is not reentrant nor threadsafe!"); } /** * Release the state if there are no more interest in it and it is not in use. */ protected void free() { int i = inUse.get(); if (i <= 0) { return; } else if (inUse.decrementAndGet() == 0) { parent.onTerminated(state); } } /** * Terminates the state immediately and calls {@link AbstractOnSubscribe#onTerminated} with the custom * state. */ protected void terminate() { for (;;) { int i = inUse.get(); if (i <= 0) { return; } if (inUse.compareAndSet(i, 0)) { parent.onTerminated(state); break; } } } } }