All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rx.observables.AbstractOnSubscribe Maven / Gradle / Ivy

There is a newer version: 1.3.8
Show newest version
/**
 * 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.*;
import rx.internal.operators.BackpressureUtils;

/**
 * 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.
 * 

*

Usage rules

* When you implement the {@code next()} method, you *
    *
  • should either *
      *
    • create the next value and signal it via {@link SubscriptionState#onNext state.onNext()},
    • *
    • signal a terminal condition via {@link SubscriptionState#onError state.onError()}, or * {@link SubscriptionState#onCompleted state.onCompleted()}, or
    • *
    • signal a stop condition via {@link SubscriptionState#stop state.stop()} indicating no further values * will be sent.
    • *
    *
  • *
  • may *
      *
    • call {@link SubscriptionState#onNext state.onNext()} and either * {@link SubscriptionState#onError state.onError()} or * {@link SubscriptionState#onCompleted state.onCompleted()} together, and *
    • block or sleep. *
    *
  • *
  • should not *
      *
    • 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;
    • *
    • call the {@code state.on}foo() methods more than once (yields * {@link IllegalStateException}).
    • *
    *
  • *
* * The {@link SubscriptionState} object features counters that may help implement a state machine: *
    *
  • A call counter, accessible via {@link SubscriptionState#calls state.calls()} tells how many times the * {@code next()} was run (zero based).
  • *
  • 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.)
  • *
  • 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)}.
  • *
*

* 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. *

* 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. * *

Examples

* Note: these examples use the lambda-helper factories to avoid boilerplate. * *

Implement: just

*

 * AbstractOnSubscribe.create(s -> {
 *   s.onNext(1);
 *   s.onCompleted();
 * }).toObservable().subscribe(System.out::println);
 * 
*

Implement: from Iterable

*

 * Iterable iterable = ...;
 * AbstractOnSubscribe.create(s -> {
 *   Iterator it = s.state();
 *   if (it.hasNext()) {
 *     s.onNext(it.next());
 *   }
 *   if (!it.hasNext()) {
 *     s.onCompleted();
 *   }
 * }, u -> iterable.iterator()).subscribe(System.out::println);
 * 
* *

Implement source that fails a number of times before succeeding

*

 * 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);
 * 
*

Implement: never

*

 * AbstractOnSubscribe.create(s -> {
 *   s.stop();
 * }).toObservable()
 * .timeout(1, TimeUnit.SECONDS)
 * .subscribe(System.out::println, Throwable::printStacktrace, () -> System.out.println("Done"));
 * 
* * @param the value type * @param 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 implements OnSubscribe { /** * Called when a Subscriber subscribes and lets the implementor create a per-subscriber custom state. *

* 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 subscriber) { return null; } /** * Called after the terminal emission or when the downstream unsubscribes. *

* 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 state); @Override public final void call(final Subscriber subscriber) { final S custom = onSubscribe(subscriber); final SubscriptionState state = new SubscriptionState(this, subscriber, custom); subscriber.add(new SubscriptionCompleter(state)); subscriber.setProducer(new SubscriptionProducer(state)); } /** * Convenience method to create an Observable from this implemented instance. * * @return the created observable */ public final Observable toObservable() { return Observable.create(this); } /** Function that returns null. */ private static final Func1 NULL_FUNC1 = new Func1() { @Override public Object call(Object t1) { return null; } }; /** * Creates an {@code AbstractOnSubscribe} instance which calls the provided {@code next} action. *

* This is a convenience method to help create {@code AbstractOnSubscribe} instances with the help of * lambdas. * * @param the value type * @param the per-subscriber user-defined state type * @param next the next action to call * @return an {@code AbstractOnSubscribe} instance */ public static AbstractOnSubscribe create(Action1> next) { @SuppressWarnings("unchecked") Func1, ? extends S> nullFunc = (Func1, ? 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. *

* This is a convenience method to help create {@code AbstractOnSubscribe} instances with the help of * lambdas. * * @param the value type * @param 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 AbstractOnSubscribe create(Action1> next, Func1, ? 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. *

* This is a convenience method to help create {@code AbstractOnSubscribe} instances with the help of * lambdas. * * @param the value type * @param 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 AbstractOnSubscribe create(Action1> next, Func1, ? extends S> onSubscribe, Action1 onTerminated) { return new LambdaOnSubscribe(next, onSubscribe, onTerminated); } /** * An implementation that forwards the three main methods ({@code next}, {@code onSubscribe}, and * {@code onTermianted}) to functional callbacks. * * @param the value type * @param the per-subscriber user-defined state type */ private static final class LambdaOnSubscribe extends AbstractOnSubscribe { final Action1> next; final Func1, ? extends S> onSubscribe; final Action1 onTerminated; private LambdaOnSubscribe(Action1> next, Func1, ? extends S> onSubscribe, Action1 onTerminated) { this.next = next; this.onSubscribe = onSubscribe; this.onTerminated = onTerminated; } @Override protected S onSubscribe(Subscriber subscriber) { return onSubscribe.call(subscriber); } @Override protected void onTerminated(S state) { onTerminated.call(state); } @Override protected void next(SubscriptionState state) { next.call(state); } } /** * Manages unsubscription of the state. * * @param the value type * @param the per-subscriber user-defined state type */ private static final class SubscriptionCompleter extends AtomicBoolean implements Subscription { private static final long serialVersionUID = 7993888274897325004L; private final SubscriptionState state; private SubscriptionCompleter(SubscriptionState 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 the value type * @param the per-subscriber user-defined state type */ private static final class SubscriptionProducer implements Producer { final SubscriptionState state; private SubscriptionProducer(SubscriptionState state) { this.state = state; } @Override public void request(long n) { if (n > 0 && BackpressureUtils.getAndAddRequest(state.requestCount, n) == 0) { if (n == Long.MAX_VALUE) { // fast-path for (; !state.subscriber.isUnsubscribed(); ) { if (!doNext()) { break; } } } else 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 the value type * @param 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 { private final AbstractOnSubscribe parent; private final Subscriber 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 parent, Subscriber 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; } } } } }