diff --git a/src/main/java/rx/Completable.java b/src/main/java/rx/Completable.java new file mode 100644 index 0000000000..5fd7216a3b --- /dev/null +++ b/src/main/java/rx/Completable.java @@ -0,0 +1,2181 @@ +/** + * 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; + +import java.util.Iterator; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; + +import rx.Observable.OnSubscribe; +import rx.annotations.Experimental; +import rx.exceptions.Exceptions; +import rx.functions.*; +import rx.internal.operators.*; +import rx.internal.util.*; +import rx.plugins.*; +import rx.schedulers.Schedulers; +import rx.subscriptions.*; + +/** + * Represents a deferred computation without any value but only indication for completion or exception. + * + * The class follows a similar event pattern as Reactive-Streams: onSubscribe (onError|onComplete)? + */ +@Experimental +public class Completable { + /** + * Callback used for building deferred computations that takes a CompletableSubscriber. + */ + public interface CompletableOnSubscribe extends Action1 { + + } + + /** + * Convenience interface and callback used by the lift operator that given a child CompletableSubscriber, + * return a parent CompletableSubscriber that does any kind of lifecycle-related transformations. + */ + public interface CompletableOperator extends Func1 { + + } + + /** + * Represents the subscription API callbacks when subscribing to a Completable instance. + */ + public interface CompletableSubscriber { + /** + * Called once the deferred computation completes normally. + */ + void onCompleted(); + + /** + * Called once if the deferred computation 'throws' an exception. + * @param e the exception, not null. + */ + void onError(Throwable e); + + /** + * Called once by the Completable to set a Subscription on this instance which + * then can be used to cancel the subscription at any time. + * @param d the Subscription instance to call dispose on for cancellation, not null + */ + void onSubscribe(Subscription d); + } + + /** + * Convenience interface and callback used by the compose operator to turn a Completable into another + * Completable fluently. + */ + public interface CompletableTransformer extends Func1 { + + } + + /** Single instance of a complete Completable. */ + static final Completable COMPLETE = create(new CompletableOnSubscribe() { + @Override + public void call(CompletableSubscriber s) { + s.onSubscribe(Subscriptions.unsubscribed()); + s.onCompleted(); + } + }); + + /** Single instance of a never Completable. */ + static final Completable NEVER = create(new CompletableOnSubscribe() { + @Override + public void call(CompletableSubscriber s) { + s.onSubscribe(Subscriptions.unsubscribed()); + } + }); + + /** The error handler instance. */ + static final RxJavaErrorHandler ERROR_HANDLER = RxJavaPlugins.getInstance().getErrorHandler(); + + /** + * Returns a Completable which terminates as soon as one of the source Completables + * terminates (normally or with an error) and cancels all other Completables. + * @param sources the array of source Completables + * @return the new Completable instance + * @throws NullPointerException if sources is null + */ + public static Completable amb(final Completable... sources) { + requireNonNull(sources); + if (sources.length == 0) { + return complete(); + } + if (sources.length == 1) { + return sources[0]; + } + + return create(new CompletableOnSubscribe() { + @Override + public void call(final CompletableSubscriber s) { + final CompositeSubscription set = new CompositeSubscription(); + s.onSubscribe(set); + + final AtomicBoolean once = new AtomicBoolean(); + + CompletableSubscriber inner = new CompletableSubscriber() { + @Override + public void onCompleted() { + if (once.compareAndSet(false, true)) { + set.unsubscribe(); + s.onCompleted(); + } + } + + @Override + public void onError(Throwable e) { + if (once.compareAndSet(false, true)) { + set.unsubscribe(); + s.onError(e); + } else { + ERROR_HANDLER.handleError(e); + } + } + + @Override + public void onSubscribe(Subscription d) { + set.add(d); + } + + }; + + for (Completable c : sources) { + if (set.isUnsubscribed()) { + return; + } + if (c == null) { + NullPointerException npe = new NullPointerException("One of the sources is null"); + if (once.compareAndSet(false, true)) { + set.unsubscribe(); + s.onError(npe); + } else { + ERROR_HANDLER.handleError(npe); + } + return; + } + if (once.get() || set.isUnsubscribed()) { + return; + } + + // no need to have separate subscribers because inner is stateless + c.subscribe(inner); + } + } + }); + } + + /** + * Returns a Completable which terminates as soon as one of the source Completables + * terminates (normally or with an error) and cancels all other Completables. + * @param sources the array of source Completables + * @return the new Completable instance + * @throws NullPointerException if sources is null + */ + public static Completable amb(final Iterable sources) { + requireNonNull(sources); + + return create(new CompletableOnSubscribe() { + @Override + public void call(final CompletableSubscriber s) { + final CompositeSubscription set = new CompositeSubscription(); + s.onSubscribe(set); + + final AtomicBoolean once = new AtomicBoolean(); + + CompletableSubscriber inner = new CompletableSubscriber() { + @Override + public void onCompleted() { + if (once.compareAndSet(false, true)) { + set.unsubscribe(); + s.onCompleted(); + } + } + + @Override + public void onError(Throwable e) { + if (once.compareAndSet(false, true)) { + set.unsubscribe(); + s.onError(e); + } else { + ERROR_HANDLER.handleError(e); + } + } + + @Override + public void onSubscribe(Subscription d) { + set.add(d); + } + + }; + + Iterator it; + + try { + it = sources.iterator(); + } catch (Throwable e) { + s.onError(e); + return; + } + + if (it == null) { + s.onError(new NullPointerException("The iterator returned is null")); + return; + } + + boolean empty = true; + + for (;;) { + if (once.get() || set.isUnsubscribed()) { + return; + } + + boolean b; + + try { + b = it.hasNext(); + } catch (Throwable e) { + if (once.compareAndSet(false, true)) { + set.unsubscribe(); + s.onError(e); + } else { + ERROR_HANDLER.handleError(e); + } + return; + } + + if (!b) { + if (empty) { + s.onCompleted(); + } + break; + } + + empty = false; + + if (once.get() || set.isUnsubscribed()) { + return; + } + + Completable c; + + try { + c = it.next(); + } catch (Throwable e) { + if (once.compareAndSet(false, true)) { + set.unsubscribe(); + s.onError(e); + } else { + ERROR_HANDLER.handleError(e); + } + return; + } + + if (c == null) { + NullPointerException npe = new NullPointerException("One of the sources is null"); + if (once.compareAndSet(false, true)) { + set.unsubscribe(); + s.onError(npe); + } else { + ERROR_HANDLER.handleError(npe); + } + return; + } + + if (once.get() || set.isUnsubscribed()) { + return; + } + + // no need to have separate subscribers because inner is stateless + c.subscribe(inner); + } + } + }); + } + + /** + * Returns a Completable instance that completes immediately when subscribed to. + * @return a Completable instance that completes immediately + */ + public static Completable complete() { + return COMPLETE; + } + + /** + * Returns a Completable which completes only when all sources complete, one after another. + * @param sources the sources to concatenate + * @return the Completable instance which completes only when all sources complete + * @throws NullPointerException if sources is null + */ + public static Completable concat(Completable... sources) { + requireNonNull(sources); + if (sources.length == 0) { + return complete(); + } else + if (sources.length == 1) { + return sources[0]; + } + return create(new CompletableOnSubscribeConcatArray(sources)); + } + + /** + * Returns a Completable which completes only when all sources complete, one after another. + * @param sources the sources to concatenate + * @return the Completable instance which completes only when all sources complete + * @throws NullPointerException if sources is null + */ + public static Completable concat(Iterable sources) { + requireNonNull(sources); + + return create(new CompletableOnSubscribeConcatIterable(sources)); + } + + /** + * Returns a Completable which completes only when all sources complete, one after another. + * @param sources the sources to concatenate + * @return the Completable instance which completes only when all sources complete + * @throws NullPointerException if sources is null + */ + public static Completable concat(Observable sources) { + return concat(sources, 2); + } + + /** + * Returns a Completable which completes only when all sources complete, one after another. + * @param sources the sources to concatenate + * @param prefetch the number of sources to prefetch from the sources + * @return the Completable instance which completes only when all sources complete + * @throws NullPointerException if sources is null + */ + public static Completable concat(Observable sources, int prefetch) { + requireNonNull(sources); + if (prefetch < 1) { + throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch); + } + return create(new CompletableOnSubscribeConcat(sources, prefetch)); + } + + /** + * Constructs a Completable instance by wrapping the given onSubscribe callback. + * @param onSubscribe the callback which will receive the CompletableSubscriber instances + * when the Completable is subscribed to. + * @return the created Completable instance + * @throws NullPointerException if onSubscribe is null + */ + public static Completable create(CompletableOnSubscribe onSubscribe) { + requireNonNull(onSubscribe); + try { + // TODO plugin wrapping onSubscribe + + return new Completable(onSubscribe); + } catch (NullPointerException ex) { + throw ex; + } catch (Throwable ex) { + ERROR_HANDLER.handleError(ex); + throw toNpe(ex); + } + } + + /** + * Defers the subscription to a Completable instance returned by a supplier. + * @param completableFunc0 the supplier that returns the Completable that will be subscribed to. + * @return the Completable instance + */ + public static Completable defer(final Func0 completableFunc0) { + requireNonNull(completableFunc0); + return create(new CompletableOnSubscribe() { + @Override + public void call(CompletableSubscriber s) { + Completable c; + + try { + c = completableFunc0.call(); + } catch (Throwable e) { + s.onSubscribe(Subscriptions.unsubscribed()); + s.onError(e); + return; + } + + if (c == null) { + s.onSubscribe(Subscriptions.unsubscribed()); + s.onError(new NullPointerException("The completable returned is null")); + return; + } + + c.subscribe(s); + } + }); + } + + /** + * Creates a Completable which calls the given error supplier for each subscriber + * and emits its returned Throwable. + *

+ * If the errorFunc0 returns null, the child CompletableSubscribers will receive a + * NullPointerException. + * @param errorFunc0 the error supplier, not null + * @return the new Completable instance + * @throws NullPointerException if errorFunc0 is null + */ + public static Completable error(final Func0 errorFunc0) { + requireNonNull(errorFunc0); + return create(new CompletableOnSubscribe() { + @Override + public void call(CompletableSubscriber s) { + s.onSubscribe(Subscriptions.unsubscribed()); + Throwable error; + + try { + error = errorFunc0.call(); + } catch (Throwable e) { + error = e; + } + + if (error == null) { + error = new NullPointerException("The error supplied is null"); + } + s.onError(error); + } + }); + } + + /** + * Creates a Completable instance that emits the given Throwable exception to subscribers. + * @param error the Throwable instance to emit, not null + * @return the new Completable instance + * @throws NullPointerException if error is null + */ + public static Completable error(final Throwable error) { + requireNonNull(error); + return create(new CompletableOnSubscribe() { + @Override + public void call(CompletableSubscriber s) { + s.onSubscribe(Subscriptions.unsubscribed()); + s.onError(error); + } + }); + } + + /** + * Returns a Completable instance that runs the given Action0 for each subscriber and + * emits either an unchecked exception or simply completes. + * @param run the runnable to run for each subscriber + * @return the new Completable instance + * @throws NullPointerException if run is null + */ + public static Completable fromAction(final Action0 action) { + requireNonNull(action); + return create(new CompletableOnSubscribe() { + @Override + public void call(CompletableSubscriber s) { + BooleanSubscription bs = new BooleanSubscription(); + s.onSubscribe(bs); + try { + action.call(); + } catch (Throwable e) { + if (!bs.isUnsubscribed()) { + s.onError(e); + } + return; + } + if (!bs.isUnsubscribed()) { + s.onCompleted(); + } + } + }); + } + + /** + * Returns a Completable which when subscribed, executes the callable function, ignores its + * normal result and emits onError or onCompleted only. + * @param callable the callable instance to execute for each subscriber + * @return the new Completable instance + */ + public static Completable fromCallable(final Callable callable) { + requireNonNull(callable); + return create(new CompletableOnSubscribe() { + @Override + public void call(CompletableSubscriber s) { + BooleanSubscription bs = new BooleanSubscription(); + s.onSubscribe(bs); + try { + callable.call(); + } catch (Throwable e) { + if (!bs.isUnsubscribed()) { + s.onError(e); + } + return; + } + if (!bs.isUnsubscribed()) { + s.onCompleted(); + } + } + }); + } + + /** + * Returns a Completable instance that reacts to the termination of the given Future in a blocking fashion. + *

+ * Note that cancellation from any of the subscribers to this Completable will cancel the future. + * @param future the future to react to + * @return the new Completable instance + */ + public static Completable fromFuture(Future future) { + requireNonNull(future); + return fromObservable(Observable.from(future)); + } + + /** + * Returns a Completable instance that subscribes to the given flowable, ignores all values and + * emits only the terminal event. + * @param flowable the Flowable instance to subscribe to, not null + * @return the new Completable instance + * @throws NullPointerException if flowable is null + */ + public static Completable fromObservable(final Observable flowable) { + requireNonNull(flowable); + return create(new CompletableOnSubscribe() { + @Override + public void call(final CompletableSubscriber cs) { + Subscriber subscriber = new Subscriber() { + + @Override + public void onCompleted() { + cs.onCompleted(); + } + + @Override + public void onError(Throwable t) { + cs.onError(t); + } + + @Override + public void onNext(Object t) { + // ignored + } + }; + cs.onSubscribe(subscriber); + flowable.subscribe(subscriber); + } + }); + } + + /** + * Returns a Completable instance that when subscribed to, subscribes to the Single instance and + * emits a completion event if the single emits onSuccess or forwards any onError events. + * @param single the Single instance to subscribe to, not null + * @return the new Completable instance + * @throws NullPointerException if single is null + */ + public static Completable fromSingle(final Single single) { + requireNonNull(single); + return create(new CompletableOnSubscribe() { + @Override + public void call(final CompletableSubscriber s) { + SingleSubscriber te = new SingleSubscriber() { + + @Override + public void onError(Throwable e) { + s.onError(e); + } + + @Override + public void onSuccess(Object value) { + s.onCompleted(); + } + + }; + s.onSubscribe(te); + single.subscribe(te); + } + }); + } + + /** + * Returns a Completable instance that subscribes to all sources at once and + * completes only when all source Completables complete or one of them emits an error. + * @param sources the iterable sequence of sources. + * @return the new Completable instance + * @throws NullPointerException if sources is null + */ + public static Completable merge(Completable... sources) { + requireNonNull(sources); + if (sources.length == 0) { + return complete(); + } else + if (sources.length == 1) { + return sources[0]; + } + return create(new CompletableOnSubscribeMergeArray(sources)); + } + + /** + * Returns a Completable instance that subscribes to all sources at once and + * completes only when all source Completables complete or one of them emits an error. + * @param sources the iterable sequence of sources. + * @return the new Completable instance + * @throws NullPointerException if sources is null + */ + public static Completable merge(Iterable sources) { + requireNonNull(sources); + return create(new CompletableOnSubscribeMergeIterable(sources)); + } + + /** + * Returns a Completable instance that subscribes to all sources at once and + * completes only when all source Completables complete or one of them emits an error. + * @param sources the iterable sequence of sources. + * @return the new Completable instance + * @throws NullPointerException if sources is null + */ + public static Completable merge(Observable sources) { + return merge0(sources, Integer.MAX_VALUE, false); + } + + /** + * Returns a Completable instance that keeps subscriptions to a limited number of sources at once and + * completes only when all source Completables complete or one of them emits an error. + * @param sources the iterable sequence of sources. + * @param maxConcurrency the maximum number of concurrent subscriptions + * @return the new Completable instance + * @throws NullPointerException if sources is null + * @throws IllegalArgumentException if maxConcurrency is less than 1 + */ + public static Completable merge(Observable sources, int maxConcurrency) { + return merge0(sources, maxConcurrency, false); + + } + + /** + * Returns a Completable instance that keeps subscriptions to a limited number of sources at once and + * completes only when all source Completables terminate in one way or another, combining any exceptions + * thrown by either the sources Observable or the inner Completable instances. + * @param sources the iterable sequence of sources. + * @param maxConcurrency the maximum number of concurrent subscriptions + * @param delayErrors delay all errors from the main source and from the inner Completables? + * @return the new Completable instance + * @throws NullPointerException if sources is null + * @throws IllegalArgumentException if maxConcurrency is less than 1 + */ + protected static Completable merge0(Observable sources, int maxConcurrency, boolean delayErrors) { + requireNonNull(sources); + if (maxConcurrency < 1) { + throw new IllegalArgumentException("maxConcurrency > 0 required but it was " + maxConcurrency); + } + return create(new CompletableOnSubscribeMerge(sources, maxConcurrency, delayErrors)); + } + + /** + * Returns a Completable that subscribes to all Completables in the source array and delays + * any error emitted by either the sources observable or any of the inner Completables until all of + * them terminate in a way or another. + * @param sources the array of Completables + * @return the new Completable instance + * @throws NullPointerException if sources is null + */ + public static Completable mergeDelayError(Completable... sources) { + requireNonNull(sources); + return create(new CompletableOnSubscribeMergeDelayErrorArray(sources)); + } + + /** + * Returns a Completable that subscribes to all Completables in the source sequence and delays + * any error emitted by either the sources observable or any of the inner Completables until all of + * them terminate in a way or another. + * @param sources the sequence of Completables + * @return the new Completable instance + * @throws NullPointerException if sources is null + */ + public static Completable mergeDelayError(Iterable sources) { + requireNonNull(sources); + return create(new CompletableOnSubscribeMergeDelayErrorIterable(sources)); + } + + /** + * Returns a Completable that subscribes to all Completables in the source sequence and delays + * any error emitted by either the sources observable or any of the inner Completables until all of + * them terminate in a way or another. + * @param sources the sequence of Completables + * @return the new Completable instance + * @throws NullPointerException if sources is null + */ + public static Completable mergeDelayError(Observable sources) { + return merge0(sources, Integer.MAX_VALUE, true); + } + + + /** + * Returns a Completable that subscribes to a limited number of inner Completables at once in + * the source sequence and delays any error emitted by either the sources + * observable or any of the inner Completables until all of + * them terminate in a way or another. + * @param sources the sequence of Completables + * @return the new Completable instance + * @throws NullPointerException if sources is null + */ + public static Completable mergeDelayError(Observable sources, int maxConcurrency) { + return merge0(sources, maxConcurrency, true); + } + + /** + * Returns a Completable that never calls onError or onComplete. + * @return the singleton instance that never calls onError or onComplete + */ + public static Completable never() { + return NEVER; + } + + /** + * Java 7 backport: throws a NullPointerException if o is null. + * @param o the object to check + * @return the o value + * @throws NullPointerException if o is null + */ + static T requireNonNull(T o) { + if (o == null) { + throw new NullPointerException(); + } + return o; + } + + /** + * Returns a Completable instance that fires its onComplete event after the given delay ellapsed. + * @param delay the delay time + * @param unit the delay unit + * @return the new Completable instance + */ + public static Completable timer(long delay, TimeUnit unit) { + return timer(delay, unit, Schedulers.computation()); + } + + /** + * Returns a Completable instance that fires its onComplete event after the given delay ellapsed + * by using the supplied scheduler. + * @param delay the delay time + * @param unit the delay unit + * @return the new Completable instance + */ + public static Completable timer(final long delay, final TimeUnit unit, final Scheduler scheduler) { + requireNonNull(unit); + requireNonNull(scheduler); + return create(new CompletableOnSubscribe() { + @Override + public void call(final CompletableSubscriber s) { + MultipleAssignmentSubscription mad = new MultipleAssignmentSubscription(); + s.onSubscribe(mad); + if (!mad.isUnsubscribed()) { + final Scheduler.Worker w = scheduler.createWorker(); + mad.set(w); + w.schedule(new Action0() { + @Override + public void call() { + try { + s.onCompleted(); + } finally { + w.unsubscribe(); + } + } + }, delay, unit); + } + } + }); + } + + /** + * Creates a NullPointerException instance and sets the given Throwable as its initial cause. + * @param ex the Throwable instance to use as cause, not null (not verified) + * @return the created NullPointerException + */ + static NullPointerException toNpe(Throwable ex) { + NullPointerException npe = new NullPointerException("Actually not, but can't pass out an exception otherwise..."); + npe.initCause(ex); + return npe; + } + + /** + * Returns a Completable instance which manages a resource along + * with a custom Completable instance while the subscription is active. + *

+ * This overload performs an eager unsubscription before the terminal event is emitted. + * + * @param resourceFunc0 the supplier that returns a resource to be managed. + * @param completableFunc1 the function that given a resource returns a Completable instance that will be subscribed to + * @param disposer the consumer that disposes the resource created by the resource supplier + * @return the new Completable instance + */ + public static Completable using(Func0 resourceFunc0, + Func1 completableFunc1, + Action1 disposer) { + return using(resourceFunc0, completableFunc1, disposer, true); + } + + /** + * Returns a Completable instance which manages a resource along + * with a custom Completable instance while the subscription is active and performs eager or lazy + * resource disposition. + *

+ * If this overload performs a lazy unsubscription after the terminal event is emitted. + * Exceptions thrown at this time will be delivered to RxJavaPlugins only. + * + * @param resourceFunc0 the supplier that returns a resource to be managed + * @param completableFunc1 the function that given a resource returns a non-null + * Completable instance that will be subscribed to + * @param disposer the consumer that disposes the resource created by the resource supplier + * @param eager if true, the resource is disposed before the terminal event is emitted, if false, the + * resource is disposed after the terminal event has been emitted + * @return the new Completable instance + */ + public static Completable using(final Func0 resourceFunc0, + final Func1 completableFunc1, + final Action1 disposer, + final boolean eager) { + requireNonNull(resourceFunc0); + requireNonNull(completableFunc1); + requireNonNull(disposer); + + return create(new CompletableOnSubscribe() { + @Override + public void call(final CompletableSubscriber s) { + final R resource; + + try { + resource = resourceFunc0.call(); + } catch (Throwable e) { + s.onSubscribe(Subscriptions.unsubscribed()); + s.onError(e); + return; + } + + Completable cs; + + try { + cs = completableFunc1.call(resource); + } catch (Throwable e) { + s.onSubscribe(Subscriptions.unsubscribed()); + s.onError(e); + return; + } + + if (cs == null) { + s.onSubscribe(Subscriptions.unsubscribed()); + s.onError(new NullPointerException("The completable supplied is null")); + return; + } + + final AtomicBoolean once = new AtomicBoolean(); + + cs.subscribe(new CompletableSubscriber() { + Subscription d; + void dispose() { + d.unsubscribe(); + if (once.compareAndSet(false, true)) { + try { + disposer.call(resource); + } catch (Throwable ex) { + ERROR_HANDLER.handleError(ex); + } + } + } + + @Override + public void onCompleted() { + if (eager) { + if (once.compareAndSet(false, true)) { + try { + disposer.call(resource); + } catch (Throwable ex) { + s.onError(ex); + return; + } + } + } + + s.onCompleted(); + + if (!eager) { + dispose(); + } + } + + @Override + public void onError(Throwable e) { + if (eager) { + if (once.compareAndSet(false, true)) { + try { + disposer.call(resource); + } catch (Throwable ex) { + ex.addSuppressed(e); + e = ex; + } + } + } + + s.onError(e); + + if (!eager) { + dispose(); + } + } + + @Override + public void onSubscribe(Subscription d) { + this.d = d; + s.onSubscribe(Subscriptions.create(new Action0() { + @Override + public void call() { + dispose(); + } + })); + } + }); + } + }); + } + + /** The actual subscription action. */ + private final CompletableOnSubscribe onSubscribe; + + /** + * Constructs a Completable instance with the given onSubscribe callback. + * @param onSubscribe the callback that will receive CompletableSubscribers when they subscribe, + * not null (not verified) + */ + protected Completable(CompletableOnSubscribe onSubscribe) { + this.onSubscribe = onSubscribe; + } + + /** + * Returns a Completable that emits the a terminated event of either this Completable + * or the other Completable whichever fires first. + * @param other the other Completable, not null + * @return the new Completable instance + * @throws NullPointerException if other is null + */ + public final Completable ambWith(Completable other) { + requireNonNull(other); + return amb(this, other); + } + + /** + * Subscribes to and awaits the termination of this Completable instance in a blocking manner and + * rethrows any exception emitted. + * @throws RuntimeException wrapping an InterruptedException if the current thread is interrupted + */ + public final void await() { + final CountDownLatch cdl = new CountDownLatch(1); + final Throwable[] err = new Throwable[1]; + + subscribe(new CompletableSubscriber() { + + @Override + public void onCompleted() { + cdl.countDown(); + } + + @Override + public void onError(Throwable e) { + err[0] = e; + cdl.countDown(); + } + + @Override + public void onSubscribe(Subscription d) { + // ignored + } + + }); + + if (cdl.getCount() == 0) { + if (err[0] != null) { + Exceptions.propagate(err[0]); + } + return; + } + try { + cdl.await(); + } catch (InterruptedException ex) { + throw Exceptions.propagate(ex); + } + if (err[0] != null) { + Exceptions.propagate(err[0]); + } + } + + /** + * Subscribes to and awaits the termination of this Completable instance in a blocking manner + * with a specific timeout and rethrows any exception emitted within the timeout window. + * @param timeout the timeout value + * @param unit the timeout unit + * @return true if the this Completable instance completed normally within the time limit, + * false if the timeout ellapsed before this Completable terminated. + * @throws RuntimeException wrapping an InterruptedException if the current thread is interrupted + */ + public final boolean await(long timeout, TimeUnit unit) { + requireNonNull(unit); + + final CountDownLatch cdl = new CountDownLatch(1); + final Throwable[] err = new Throwable[1]; + + subscribe(new CompletableSubscriber() { + + @Override + public void onCompleted() { + cdl.countDown(); + } + + @Override + public void onError(Throwable e) { + err[0] = e; + cdl.countDown(); + } + + @Override + public void onSubscribe(Subscription d) { + // ignored + } + + }); + + if (cdl.getCount() == 0) { + if (err[0] != null) { + Exceptions.propagate(err[0]); + } + return true; + } + boolean b; + try { + b = cdl.await(timeout, unit); + } catch (InterruptedException ex) { + throw Exceptions.propagate(ex); + } + if (b) { + if (err[0] != null) { + Exceptions.propagate(err[0]); + } + } + return b; + } + + /** + * Calls the given transformer function with this instance and returns the function's resulting + * Completable. + * @param transformer the transformer function, not null + * @return the Completable returned by the function + * @throws NullPointerException if transformer is null + */ + public final Completable compose(CompletableTransformer transformer) { + return to(transformer); + } + + /** + * Concatenates this Completable with another Completable. + * @param other the other Completable, not null + * @return the new Completable which subscribes to this and then the other Completable + * @throws NullPointerException if other is null + */ + public final Completable concatWith(Completable other) { + requireNonNull(other); + return concat(this, other); + } + + /** + * Returns a Completable which delays the emission of the completion event by the given time. + * @param delay the delay time + * @param unit the delay unit + * @return the new Completable instance + * @throws NullPointerException if unit is null + */ + public final Completable delay(long delay, TimeUnit unit) { + return delay(delay, unit, Schedulers.computation(), false); + } + + /** + * Returns a Completable which delays the emission of the completion event by the given time while + * running on the specified scheduler. + * @param delay the delay time + * @param unit the delay unit + * @param scheduler the scheduler to run the delayed completion on + * @return the new Completable instance + * @throws NullPointerException if unit or scheduler is null + */ + public final Completable delay(long delay, TimeUnit unit, Scheduler scheduler) { + return delay(delay, unit, scheduler, false); + } + + /** + * Returns a Completable which delays the emission of the completion event, and optionally the error as well, by the given time while + * running on the specified scheduler. + * @param delay the delay time + * @param unit the delay unit + * @param scheduler the scheduler to run the delayed completion on + * @param delayError delay the error emission as well? + * @return the new Completable instance + * @throws NullPointerException if unit or scheduler is null + */ + public final Completable delay(final long delay, final TimeUnit unit, final Scheduler scheduler, final boolean delayError) { + requireNonNull(unit); + requireNonNull(scheduler); + return create(new CompletableOnSubscribe() { + @Override + public void call(final CompletableSubscriber s) { + final CompositeSubscription set = new CompositeSubscription(); + + final Scheduler.Worker w = scheduler.createWorker(); + set.add(w); + + subscribe(new CompletableSubscriber() { + + + @Override + public void onCompleted() { + set.add(w.schedule(new Action0() { + @Override + public void call() { + try { + s.onCompleted(); + } finally { + w.unsubscribe(); + } + } + }, delay, unit)); + } + + @Override + public void onError(final Throwable e) { + if (delayError) { + set.add(w.schedule(new Action0() { + @Override + public void call() { + try { + s.onError(e); + } finally { + w.unsubscribe(); + } + } + }, delay, unit)); + } else { + s.onError(e); + } + } + + @Override + public void onSubscribe(Subscription d) { + set.add(d); + s.onSubscribe(set); + } + + }); + } + }); + } + + /** + * Returns a Completable which calls the given onComplete callback if this Completable completes. + * @param onComplete the callback to call when this emits an onComplete event + * @return the new Completable instance + * @throws NullPointerException if onComplete is null + */ + public final Completable doOnComplete(Action0 onComplete) { + return doOnLifecycle(Actions.empty(), Actions.empty(), onComplete, Actions.empty(), Actions.empty()); + } + + /** + * Returns a Completable which calls the giveon onUnsubscribe callback if the child subscriber cancels + * the subscription. + * @param onUnsubscribe the callback to call when the child subscriber cancels the subscription + * @return the new Completable instance + * @throws NullPointerException if onDispose is null + */ + public final Completable doOnUnsubscribe(Action0 onUnsubscribe) { + return doOnLifecycle(Actions.empty(), Actions.empty(), Actions.empty(), Actions.empty(), onUnsubscribe); + } + + /** + * Returns a Completable which calls the given onError callback if this Completable emits an error. + * @param onError the error callback + * @return the new Completable instance + * @throws NullPointerException if onError is null + */ + public final Completable doOnError(Action1 onError) { + return doOnLifecycle(Actions.empty(), onError, Actions.empty(), Actions.empty(), Actions.empty()); + } + + /** + * Returns a Completable instance that calls the various callbacks on the specific + * lifecycle events. + * @param onSubscribe the consumer called when a CompletableSubscriber subscribes. + * @param onError the consumer called when this emits an onError event + * @param onComplete the runnable called just before when this Completable completes normally + * @param onAfterComplete the runnable called after this Completable completes normally + * @param onUnsubscribe the runnable called when the child cancels the subscription + * @return the new Completable instance + */ + protected final Completable doOnLifecycle( + final Action1 onSubscribe, + final Action1 onError, + final Action0 onComplete, + final Action0 onAfterComplete, + final Action0 onUnsubscribe) { + requireNonNull(onSubscribe); + requireNonNull(onError); + requireNonNull(onComplete); + requireNonNull(onAfterComplete); + requireNonNull(onUnsubscribe); + return create(new CompletableOnSubscribe() { + @Override + public void call(final CompletableSubscriber s) { + subscribe(new CompletableSubscriber() { + + @Override + public void onCompleted() { + try { + onComplete.call(); + } catch (Throwable e) { + s.onError(e); + return; + } + + s.onCompleted(); + + try { + onAfterComplete.call(); + } catch (Throwable e) { + ERROR_HANDLER.handleError(e); + } + } + + @Override + public void onError(Throwable e) { + try { + onError.call(e); + } catch (Throwable ex) { + ex.addSuppressed(e); + e = ex; + } + + s.onError(e); + } + + @Override + public void onSubscribe(final Subscription d) { + + try { + onSubscribe.call(d); + } catch (Throwable ex) { + d.unsubscribe(); + s.onSubscribe(Subscriptions.unsubscribed()); + s.onError(ex); + return; + } + + s.onSubscribe(Subscriptions.create(new Action0() { + @Override + public void call() { + try { + onUnsubscribe.call(); + } catch (Throwable e) { + ERROR_HANDLER.handleError(e); + } + d.unsubscribe(); + } + })); + } + + }); + } + }); + } + + /** + * Returns a Completable instance that calls the given onSubscribe callback with the disposable + * that child subscribers receive on subscription. + * @param onSubscribe the callback called when a child subscriber subscribes + * @return the new Completable instance + * @throws NullPointerException if onSubscribe is null + */ + public final Completable doOnSubscribe(Action1 onSubscribe) { + return doOnLifecycle(onSubscribe, Actions.empty(), Actions.empty(), Actions.empty(), Actions.empty()); + } + + /** + * Returns a Completable instance that calls the given onTerminate callback just before this Completable + * completes normally or with an exception + * @param onTerminate the callback to call just before this Completable terminates + * @return the new Completable instance + */ + public final Completable doOnTerminate(final Action0 onTerminate) { + return doOnLifecycle(Actions.empty(), new Action1() { + @Override + public void call(Throwable e) { + onTerminate.call(); + } + }, onTerminate, Actions.empty(), Actions.empty()); + } + + /** + * Returns a completable that first runs this Completable + * and then the other completable. + *

+ * This is an alias for {@link #concatWith(Completable)}. + * @param other the other Completable, not null + * @return the new Completable instance + * @throws NullPointerException if other is null + */ + public final Completable endWith(Completable other) { + return concatWith(other); + } + + /** + * Returns an Observable that first runs this Completable instance and + * resumes with the given next Observable. + * @param next the next Observable to continue + * @return the new Observable instance + * @throws NullPointerException if next is null + */ + public final Observable endWith(Observable next) { + return next.startWith(this.toObservable()); + } + + /** + * Returns a Completable instace that calls the given onAfterComplete callback after this + * Completable completes normally. + * @param onAfterComplete the callback to call after this Completable emits an onComplete event. + * @return the new Completable instance + * @throws NullPointerException if onAfterComplete is null + */ + public final Completable finallyDo(Action0 onAfterComplete) { + return doOnLifecycle(Actions.empty(), Actions.empty(), Actions.empty(), onAfterComplete, Actions.empty()); + } + + /** + * Subscribes to this Completable instance and blocks until it terminates, then returns null or + * the emitted exception if any. + * @return the throwable if this terminated with an error, null otherwise + * @throws RuntimeException that wraps an InterruptedException if the wait is interrupted + */ + public final Throwable get() { + final CountDownLatch cdl = new CountDownLatch(1); + final Throwable[] err = new Throwable[1]; + + subscribe(new CompletableSubscriber() { + + @Override + public void onCompleted() { + cdl.countDown(); + } + + @Override + public void onError(Throwable e) { + err[0] = e; + cdl.countDown(); + } + + @Override + public void onSubscribe(Subscription d) { + // ignored + } + + }); + + if (cdl.getCount() == 0) { + return err[0]; + } + try { + cdl.await(); + } catch (InterruptedException ex) { + throw Exceptions.propagate(ex); + } + return err[0]; + } + + /** + * Subscribes to this Completable instance and blocks until it terminates or the specified timeout + * ellapses, then returns null for normal termination or the emitted exception if any. + * @return the throwable if this terminated with an error, null otherwise + * @throws RuntimeException that wraps an InterruptedException if the wait is interrupted or + * TimeoutException if the specified timeout ellapsed before it + */ + public final Throwable get(long timeout, TimeUnit unit) { + requireNonNull(unit); + + final CountDownLatch cdl = new CountDownLatch(1); + final Throwable[] err = new Throwable[1]; + + subscribe(new CompletableSubscriber() { + + @Override + public void onCompleted() { + cdl.countDown(); + } + + @Override + public void onError(Throwable e) { + err[0] = e; + cdl.countDown(); + } + + @Override + public void onSubscribe(Subscription d) { + // ignored + } + + }); + + if (cdl.getCount() == 0) { + return err[0]; + } + boolean b; + try { + b = cdl.await(timeout, unit); + } catch (InterruptedException ex) { + throw Exceptions.propagate(ex); + } + if (b) { + return err[0]; + } + Exceptions.propagate(new TimeoutException()); + return null; + } + + /** + * Lifts a CompletableSubscriber transformation into the chain of Completables. + * @param onLift the lifting function that transforms the child subscriber with a parent subscriber. + * @return the new Completable instance + * @throws NullPointerException if onLift is null + */ + public final Completable lift(final CompletableOperator onLift) { + requireNonNull(onLift); + return create(new CompletableOnSubscribe() { + @Override + public void call(CompletableSubscriber s) { + try { + // TODO plugin wrapping + + CompletableSubscriber sw = onLift.call(s); + + subscribe(sw); + } catch (NullPointerException ex) { + throw ex; + } catch (Throwable ex) { + throw toNpe(ex); + } + } + }); + } + + /** + * Returns a Completable which subscribes to this and the other Completable and completes + * when both of them complete or one emits an error. + * @param other the other Completable instance + * @return the new Completable instance + * @throws NullPointerException if other is null + */ + public final Completable mergeWith(Completable other) { + requireNonNull(other); + return merge(this, other); + } + + /** + * Returns a Completable which emits the terminal events from the thread of the specified scheduler. + * @param scheduler the scheduler to emit terminal events on + * @return the new Completable instance + * @throws NullPointerException if scheduler is null + */ + public final Completable observeOn(final Scheduler scheduler) { + requireNonNull(scheduler); + return create(new CompletableOnSubscribe() { + @Override + public void call(final CompletableSubscriber s) { + + final SubscriptionList ad = new SubscriptionList(); + + final Scheduler.Worker w = scheduler.createWorker(); + ad.add(w); + + s.onSubscribe(ad); + + subscribe(new CompletableSubscriber() { + + @Override + public void onCompleted() { + w.schedule(new Action0() { + @Override + public void call() { + try { + s.onCompleted(); + } finally { + ad.unsubscribe(); + } + } + }); + } + + @Override + public void onError(final Throwable e) { + w.schedule(new Action0() { + @Override + public void call() { + try { + s.onError(e); + } finally { + ad.unsubscribe(); + } + } + }); + } + + @Override + public void onSubscribe(Subscription d) { + ad.add(d); + } + + }); + } + }); + } + + /** + * Returns a Completable instance that if this Completable emits an error, it will emit an onComplete + * and swallow the throwable. + * @return the new Completable instance + */ + public final Completable onErrorComplete() { + return onErrorComplete(UtilityFunctions.alwaysTrue()); + } + + /** + * Returns a Completable instance that if this Completable emits an error and the predicate returns + * true, it will emit an onComplete and swallow the throwable. + * @param predicate the predicate to call when an Throwable is emitted which should return true + * if the Throwable should be swallowed and replaced with an onComplete. + * @return the new Completable instance + */ + public final Completable onErrorComplete(final Func1 predicate) { + requireNonNull(predicate); + + return create(new CompletableOnSubscribe() { + @Override + public void call(final CompletableSubscriber s) { + subscribe(new CompletableSubscriber() { + + @Override + public void onCompleted() { + s.onCompleted(); + } + + @Override + public void onError(Throwable e) { + boolean b; + + try { + b = predicate.call(e); + } catch (Throwable ex) { + e.addSuppressed(ex); + s.onError(e); + return; + } + + if (b) { + s.onCompleted(); + } else { + s.onError(e); + } + } + + @Override + public void onSubscribe(Subscription d) { + s.onSubscribe(d); + } + + }); + } + }); + } + + /** + * Returns a Completable instance that when encounters an error from this Completable, calls the + * specified mapper function that returns another Completable instance for it and resumes the + * execution with it. + * @param errorMapper the mapper function that takes the error and should return a Completable as + * continuation. + * @return the new Completable instance + */ + public final Completable onErrorResumeNext(final Func1 errorMapper) { + requireNonNull(errorMapper); + return create(new CompletableOnSubscribe() { + @Override + public void call(final CompletableSubscriber s) { + final SerialSubscription sd = new SerialSubscription(); + subscribe(new CompletableSubscriber() { + + @Override + public void onCompleted() { + s.onCompleted(); + } + + @Override + public void onError(Throwable e) { + Completable c; + + try { + c = errorMapper.call(e); + } catch (Throwable ex) { + ex.addSuppressed(e); + s.onError(ex); + return; + } + + if (c == null) { + NullPointerException npe = new NullPointerException("The completable returned is null"); + npe.addSuppressed(e); + s.onError(npe); + return; + } + + c.subscribe(new CompletableSubscriber() { + + @Override + public void onCompleted() { + s.onCompleted(); + } + + @Override + public void onError(Throwable e) { + s.onError(e); + } + + @Override + public void onSubscribe(Subscription d) { + sd.set(d); + } + + }); + } + + @Override + public void onSubscribe(Subscription d) { + sd.set(d); + } + + }); + } + }); + } + + /** + * Returns a Completable that repeatedly subscribes to this Completable until cancelled. + * @return the new Completable instance + */ + public final Completable repeat() { + return fromObservable(toObservable().repeat()); + } + + /** + * Returns a Completable that subscribes repeatedly at most the given times to this Completable. + * @param times the number of times the resubscription should happen + * @return the new Completable instance + * @throws IllegalArgumentException if times is less than zero + */ + public final Completable repeat(long times) { + return fromObservable(toObservable().repeat(times)); + } + + /** + * Returns a Completable instance that repeats when the Publisher returned by the handler + * emits an item or completes when this Publisher emits a completed event. + * @param handler the function that transforms the stream of values indicating the completion of + * this Completable and returns a Publisher that emits items for repeating or completes to indicate the + * repetition should stop + * @return the new Completable instance + * @throws NullPointerException if stop is null + */ + public final Completable repeatWhen(Func1, ? extends Observable> handler) { + requireNonNull(handler); // FIXME do a null check in Observable + return fromObservable(toObservable().repeatWhen(handler)); + } + + /** + * Returns a Completable that retries this Completable as long as it emits an onError event. + * @return the new Completable instance + */ + public final Completable retry() { + return fromObservable(toObservable().retry()); + } + + /** + * Returns a Completable that retries this Completable in case of an error as long as the predicate + * returns true. + * @param predicate the predicate called when this emits an error with the repeat count and the latest exception + * and should return true to retry. + * @return the new Completable instance + */ + public final Completable retry(Func2 predicate) { + return fromObservable(toObservable().retry(predicate)); + } + + /** + * Returns a Completable that when this Completable emits an error, retries at most the given + * number of times before giving up and emitting the last error. + * @param times the number of times the returned Completable should retry this Completable + * @return the new Completable instance + * @throws IllegalArgumentException if times is negative + */ + public final Completable retry(long times) { + return fromObservable(toObservable().retry(times)); + } + + /** + * Returns a Completable which given a Publisher and when this Completable emits an error, delivers + * that error through an Observable and the Publisher should return a value indicating a retry in response + * or a terminal event indicating a termination. + * @param handler the handler that receives an Observable delivering Throwables and should return a Publisher that + * emits items to indicate retries or emits terminal events to indicate termination. + * @return the new Completable instance + * @throws NullPointerException if handler is null + */ + public final Completable retryWhen(Func1, ? extends Observable> handler) { + return fromObservable(toObservable().retryWhen(handler)); + } + + /** + * Returns a Completable which first runs the other Completable + * then this completable if the other completed normally. + * @param other the other completable to run first + * @return the new Completable instance + * @throws NullPointerException if other is null + */ + public final Completable startWith(Completable other) { + requireNonNull(other); + return concat(other, this); + } + + /** + * Returns an Observable which first delivers the events + * of the other Observable then runs this Completable. + * @param other the other Observable to run first + * @return the new Observable instance + * @throws NullPointerException if other is null + */ + public final Observable startWith(Observable other) { + requireNonNull(other); + return this.toObservable().startWith(other); + } + + /** + * Subscribes to this Completable and returns a Subscription which can be used to cancel + * the subscription. + * @return the Subscription that allows cancelling the subscription + */ + public final Subscription subscribe() { + final MultipleAssignmentSubscription mad = new MultipleAssignmentSubscription(); + subscribe(new CompletableSubscriber() { + @Override + public void onCompleted() { + // nothing to do + } + + @Override + public void onError(Throwable e) { + ERROR_HANDLER.handleError(e); + } + + @Override + public void onSubscribe(Subscription d) { + mad.set(d); + } + }); + + return mad; + } + /** + * Subscribes to this Completable and calls the given Action0 when this Completable + * completes normally. + *

+ * If this Completable emits an error, it is sent to ERROR_HANDLER.handleError and gets swallowed. + * @param onComplete the runnable called when this Completable completes normally + * @return the Subscription that allows cancelling the subscription + */ + public final Subscription subscribe(final Action0 onComplete) { + requireNonNull(onComplete); + + final MultipleAssignmentSubscription mad = new MultipleAssignmentSubscription(); + subscribe(new CompletableSubscriber() { + @Override + public void onCompleted() { + try { + onComplete.call(); + } catch (Throwable e) { + ERROR_HANDLER.handleError(e); + } + } + + @Override + public void onError(Throwable e) { + ERROR_HANDLER.handleError(e); + } + + @Override + public void onSubscribe(Subscription d) { + mad.set(d); + } + }); + + return mad; + } + + /** + * Subscribes to this Completable and calls back either the onError or onComplete functions. + * + * @param onError the consumer that is called if this Completable emits an error + * @param onComplete the runnable that is called if the Completable completes normally + * @return the Subscription that can be used for cancelling the subscription asynchronously + * @throws NullPointerException if either callback is null + */ + public final Subscription subscribe(final Action1 onError, final Action0 onComplete) { + requireNonNull(onError); + requireNonNull(onComplete); + + final MultipleAssignmentSubscription mad = new MultipleAssignmentSubscription(); + subscribe(new CompletableSubscriber() { + @Override + public void onCompleted() { + try { + onComplete.call(); + } catch (Throwable e) { + onError(e); + } + } + + @Override + public void onError(Throwable e) { + try { + onError.call(e); + } catch (Throwable ex) { + e.addSuppressed(ex); + ERROR_HANDLER.handleError(e); + } + } + + @Override + public void onSubscribe(Subscription d) { + mad.set(d); + } + }); + + return mad; + } + + /** + * Subscribes the given CompletableSubscriber to this Completable instance. + * @param s the CompletableSubscriber, not null + * @throws NullPointerException if s is null + */ + public final void subscribe(CompletableSubscriber s) { + requireNonNull(s); + try { + // TODO plugin wrapping the subscriber + + onSubscribe.call(s); + } catch (NullPointerException ex) { + throw ex; + } catch (Throwable ex) { + ERROR_HANDLER.handleError(ex); + throw toNpe(ex); + } + } + + /** + * Subscribes a reactive-streams Subscriber to this Completable instance which + * will receive only an onError or onComplete event. + * @param s the reactive-streams Subscriber, not null + * @throws NullPointerException if s is null + */ + public final void subscribe(Subscriber s) { + requireNonNull(s); + try { + final Subscriber sw = s; // FIXME hooking in 1.x is kind of strange to me + + if (sw == null) { + throw new NullPointerException("The RxJavaPlugins.onSubscribe returned a null Subscriber"); + } + + subscribe(new CompletableSubscriber() { + @Override + public void onCompleted() { + sw.onCompleted(); + } + + @Override + public void onError(Throwable e) { + sw.onError(e); + } + + @Override + public void onSubscribe(Subscription d) { + sw.add(d); + } + }); + + } catch (NullPointerException ex) { + throw ex; + } catch (Throwable ex) { + ERROR_HANDLER.handleError(ex); + throw toNpe(ex); + } + } + + /** + * Returns a Completable which subscribes the child subscriber on the specified scheduler, making + * sure the subscription side-effects happen on that specific thread of the scheduler. + * @param scheduler the Scheduler to subscribe on + * @return the new Completable instance + * @throws NullPointerException if scheduler is null + */ + public final Completable subscribeOn(final Scheduler scheduler) { + requireNonNull(scheduler); + + return create(new CompletableOnSubscribe() { + @Override + public void call(final CompletableSubscriber s) { + // FIXME cancellation of this schedule + + final Scheduler.Worker w = scheduler.createWorker(); + + w.schedule(new Action0() { + @Override + public void call() { + try { + subscribe(s); + } finally { + w.unsubscribe(); + } + } + }); + } + }); + } + + /** + * Returns a Completable that runs this Completable and emits a TimeoutException in case + * this Completable doesn't complete within the given time. + * @param timeout the timeout value + * @param unit the timeout unit + * @return the new Completable instance + * @throws NullPointerException if unit is null + */ + public final Completable timeout(long timeout, TimeUnit unit) { + return timeout0(timeout, unit, Schedulers.computation(), null); + } + + /** + * Returns a Completable that runs this Completable and switches to the other Completable + * in case this Completable doesn't complete within the given time. + * @param timeout the timeout value + * @param unit the timeout unit + * @param other the other Completable instance to switch to in case of a timeout + * @return the new Completable instance + * @throws NullPointerException if unit or other is null + */ + public final Completable timeout(long timeout, TimeUnit unit, Completable other) { + requireNonNull(other); + return timeout0(timeout, unit, Schedulers.computation(), other); + } + + /** + * Returns a Completable that runs this Completable and emits a TimeoutException in case + * this Completable doesn't complete within the given time while "waiting" on the specified + * Scheduler. + * @param timeout the timeout value + * @param unit the timeout unit + * @param scheduler the scheduler to use to wait for completion + * @return the new Completable instance + * @throws NullPointerException if unit or scheduler is null + */ + public final Completable timeout(long timeout, TimeUnit unit, Scheduler scheduler) { + return timeout0(timeout, unit, scheduler, null); + } + + /** + * Returns a Completable that runs this Completable and switches to the other Completable + * in case this Completable doesn't complete within the given time while "waiting" on + * the specified scheduler. + * @param timeout the timeout value + * @param unit the timeout unit + * @param scheduler the scheduler to use to wait for completion + * @param other the other Completable instance to switch to in case of a timeout + * @return the new Completable instance + * @throws NullPointerException if unit, scheduler or other is null + */ + public final Completable timeout(long timeout, TimeUnit unit, Scheduler scheduler, Completable other) { + requireNonNull(other); + return timeout0(timeout, unit, scheduler, other); + } + + /** + * Returns a Completable that runs this Completable and optionally switches to the other Completable + * in case this Completable doesn't complete within the given time while "waiting" on + * the specified scheduler. + * @param timeout the timeout value + * @param unit the timeout unit + * @param scheduler the scheduler to use to wait for completion + * @param other the other Completable instance to switch to in case of a timeout, + * if null a TimeoutException is emitted instead + * @return the new Completable instance + * @throws NullPointerException if unit or scheduler + */ + public final Completable timeout0(long timeout, TimeUnit unit, Scheduler scheduler, Completable other) { + requireNonNull(unit); + requireNonNull(scheduler); + return create(new CompletableOnSubscribeTimeout(this, timeout, unit, scheduler, other)); + } + + /** + * Allows fluent conversion to another type via a function callback. + * @param converter the function called with this which should return some other value. + * @return the converted value + * @throws NullPointerException if converter is null + */ + public final U to(Func1 converter) { + return converter.call(this); + } + + /** + * Returns an Observable which when subscribed to subscribes to this Completable and + * relays the terminal events to the subscriber. + * @return the new Observable created + */ + public final Observable toObservable() { + return Observable.create(new OnSubscribe() { + @Override + public void call(Subscriber s) { + subscribe(s); + } + }); + } + + /** + * Convers this Completable into a Single which when this Completable completes normally, + * calls the given supplier and emits its returned value through onSuccess. + * @param completionValueFunc0 the value supplier called when this Completable completes normally + * @return the new Single instance + * @throws NullPointerException if completionValueFunc0 is null + */ + public final Single toSingle(final Func0 completionValueFunc0) { + requireNonNull(completionValueFunc0); + return Single.create(new rx.Single.OnSubscribe() { + @Override + public void call(final SingleSubscriber s) { + subscribe(new CompletableSubscriber() { + + @Override + public void onCompleted() { + T v; + + try { + v = completionValueFunc0.call(); + } catch (Throwable e) { + s.onError(e); + return; + } + + if (v == null) { + s.onError(new NullPointerException("The value supplied is null")); + } else { + s.onSuccess(v); + } + } + + @Override + public void onError(Throwable e) { + s.onError(e); + } + + @Override + public void onSubscribe(Subscription d) { + s.add(d); + } + + }); + } + }); + } + + /** + * Convers this Completable into a Single which when this Completable completes normally, + * emits the given value through onSuccess. + * @param completionValue the value to emit when this Completable completes normally + * @return the new Single instance + * @throws NullPointerException if completionValue is null + */ + public final Single toSingleDefault(final T completionValue) { + requireNonNull(completionValue); + return toSingle(new Func0() { + @Override + public T call() { + return completionValue; + } + }); + } + + /** + * Returns a Completable which makes sure when a subscriber cancels the subscription, the + * dispose is called on the specified scheduler + * @param scheduler the target scheduler where to execute the cancellation + * @return the new Completable instance + * @throws NullPointerException if scheduler is null + */ + public final Completable unsubscribeOn(final Scheduler scheduler) { + requireNonNull(scheduler); + return create(new CompletableOnSubscribe() { + @Override + public void call(final CompletableSubscriber s) { + subscribe(new CompletableSubscriber() { + + @Override + public void onCompleted() { + s.onCompleted(); + } + + @Override + public void onError(Throwable e) { + s.onError(e); + } + + @Override + public void onSubscribe(final Subscription d) { + s.onSubscribe(Subscriptions.create(new Action0() { + @Override + public void call() { + final Scheduler.Worker w = scheduler.createWorker(); + w.schedule(new Action0() { + @Override + public void call() { + try { + d.unsubscribe(); + } finally { + w.unsubscribe(); + } + } + }); + } + })); + } + + }); + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcat.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcat.java new file mode 100644 index 0000000000..c7da20df07 --- /dev/null +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcat.java @@ -0,0 +1,152 @@ +/** + * 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.internal.operators; + +import java.util.concurrent.atomic.*; + +import rx.*; +import rx.Completable.*; +import rx.exceptions.MissingBackpressureException; +import rx.internal.util.unsafe.SpscArrayQueue; +import rx.plugins.RxJavaPlugins; +import rx.subscriptions.SerialSubscription; + +public final class CompletableOnSubscribeConcat implements CompletableOnSubscribe { + final Observable sources; + final int prefetch; + + public CompletableOnSubscribeConcat(Observable sources, int prefetch) { + this.sources = sources; + this.prefetch = prefetch; + } + + @Override + public void call(CompletableSubscriber s) { + CompletableConcatSubscriber parent = new CompletableConcatSubscriber(s, prefetch); + s.onSubscribe(parent); + sources.subscribe(parent); + } + + static final class CompletableConcatSubscriber + extends Subscriber { + final CompletableSubscriber actual; + final int prefetch; + final SerialSubscription sr; + + final SpscArrayQueue queue; + + volatile boolean done; + + volatile int once; + static final AtomicIntegerFieldUpdater ONCE = + AtomicIntegerFieldUpdater.newUpdater(CompletableConcatSubscriber.class, "once"); + + final ConcatInnerSubscriber inner; + + final AtomicInteger wip; + + public CompletableConcatSubscriber(CompletableSubscriber actual, int prefetch) { + this.actual = actual; + this.prefetch = prefetch; + this.queue = new SpscArrayQueue(prefetch); + this.sr = new SerialSubscription(); + this.inner = new ConcatInnerSubscriber(); + this.wip = new AtomicInteger(); + add(sr); + request(prefetch); + } + + @Override + public void onNext(Completable t) { + if (!queue.offer(t)) { + onError(new MissingBackpressureException()); + return; + } + if (wip.getAndIncrement() == 0) { + next(); + } + } + + @Override + public void onError(Throwable t) { + if (ONCE.compareAndSet(this, 0, 1)) { + actual.onError(t); + return; + } + RxJavaPlugins.getInstance().getErrorHandler().handleError(t); + } + + @Override + public void onCompleted() { + if (done) { + return; + } + done = true; + if (wip.getAndIncrement() == 0) { + next(); + } + } + + void innerError(Throwable e) { + unsubscribe(); + onError(e); + } + + void innerComplete() { + if (wip.decrementAndGet() != 0) { + next(); + } + if (!done) { + request(1); + } + } + + void next() { + boolean d = done; + Completable c = queue.poll(); + if (c == null) { + if (d) { + if (ONCE.compareAndSet(this, 0, 1)) { + actual.onCompleted(); + } + return; + } + RxJavaPlugins.getInstance().getErrorHandler().handleError(new IllegalStateException("Queue is empty?!")); + return; + } + + c.subscribe(inner); + } + + final class ConcatInnerSubscriber implements CompletableSubscriber { + @Override + public void onSubscribe(Subscription d) { + sr.set(d); + } + + @Override + public void onError(Throwable e) { + innerError(e); + } + + @Override + public void onCompleted() { + innerComplete(); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatArray.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatArray.java new file mode 100644 index 0000000000..c1f48f61b7 --- /dev/null +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatArray.java @@ -0,0 +1,96 @@ +/** + * 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.internal.operators; + +import java.util.concurrent.atomic.AtomicInteger; + +import rx.*; +import rx.Completable.*; +import rx.subscriptions.SerialSubscription; + +public final class CompletableOnSubscribeConcatArray implements CompletableOnSubscribe { + final Completable[] sources; + + public CompletableOnSubscribeConcatArray(Completable[] sources) { + this.sources = sources; + } + + @Override + public void call(CompletableSubscriber s) { + ConcatInnerSubscriber inner = new ConcatInnerSubscriber(s, sources); + s.onSubscribe(inner.sd); + inner.next(); + } + + static final class ConcatInnerSubscriber extends AtomicInteger implements CompletableSubscriber { + /** */ + private static final long serialVersionUID = -7965400327305809232L; + + final CompletableSubscriber actual; + final Completable[] sources; + + int index; + + final SerialSubscription sd; + + public ConcatInnerSubscriber(CompletableSubscriber actual, Completable[] sources) { + this.actual = actual; + this.sources = sources; + this.sd = new SerialSubscription(); + } + + @Override + public void onSubscribe(Subscription d) { + sd.set(d); + } + + @Override + public void onError(Throwable e) { + actual.onError(e); + } + + @Override + public void onCompleted() { + next(); + } + + void next() { + if (sd.isUnsubscribed()) { + return; + } + + if (getAndIncrement() != 0) { + return; + } + + Completable[] a = sources; + do { + if (sd.isUnsubscribed()) { + return; + } + + int idx = index++; + if (idx == a.length) { + actual.onCompleted(); + return; + } + + a[idx].subscribe(this); + } while (decrementAndGet() != 0); + } + } +} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatIterable.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatIterable.java new file mode 100644 index 0000000000..fe6211153e --- /dev/null +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeConcatIterable.java @@ -0,0 +1,135 @@ +/** + * 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.internal.operators; + +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicInteger; + +import rx.*; +import rx.Completable.*; +import rx.subscriptions.*; + +public final class CompletableOnSubscribeConcatIterable implements CompletableOnSubscribe { + final Iterable sources; + + public CompletableOnSubscribeConcatIterable(Iterable sources) { + this.sources = sources; + } + + @Override + public void call(CompletableSubscriber s) { + + Iterator it; + + try { + it = sources.iterator(); + } catch (Throwable e) { + s.onSubscribe(Subscriptions.unsubscribed()); + s.onError(e); + return; + } + + if (it == null) { + s.onSubscribe(Subscriptions.unsubscribed()); + s.onError(new NullPointerException("The iterator returned is null")); + return; + } + + ConcatInnerSubscriber inner = new ConcatInnerSubscriber(s, it); + s.onSubscribe(inner.sd); + inner.next(); + } + + static final class ConcatInnerSubscriber extends AtomicInteger implements CompletableSubscriber { + /** */ + private static final long serialVersionUID = -7965400327305809232L; + + final CompletableSubscriber actual; + final Iterator sources; + + int index; + + final SerialSubscription sd; + + public ConcatInnerSubscriber(CompletableSubscriber actual, Iterator sources) { + this.actual = actual; + this.sources = sources; + this.sd = new SerialSubscription(); + } + + @Override + public void onSubscribe(Subscription d) { + sd.set(d); + } + + @Override + public void onError(Throwable e) { + actual.onError(e); + } + + @Override + public void onCompleted() { + next(); + } + + void next() { + if (sd.isUnsubscribed()) { + return; + } + + if (getAndIncrement() != 0) { + return; + } + + Iterator a = sources; + do { + if (sd.isUnsubscribed()) { + return; + } + + boolean b; + try { + b = a.hasNext(); + } catch (Throwable ex) { + actual.onError(ex); + return; + } + + if (!b) { + actual.onCompleted(); + return; + } + + Completable c; + + try { + c = a.next(); + } catch (Throwable ex) { + actual.onError(ex); + return; + } + + if (c == null) { + actual.onError(new NullPointerException("The completable returned is null")); + return; + } + + c.subscribe(this); + } while (decrementAndGet() != 0); + } + } +} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeMerge.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeMerge.java new file mode 100644 index 0000000000..2b1a3ad2f0 --- /dev/null +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeMerge.java @@ -0,0 +1,215 @@ +/** + * 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.internal.operators; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.*; + +import rx.*; +import rx.Completable.*; +import rx.plugins.RxJavaPlugins; +import rx.subscriptions.CompositeSubscription; + +public final class CompletableOnSubscribeMerge implements CompletableOnSubscribe { + final Observable source; + final int maxConcurrency; + final boolean delayErrors; + + public CompletableOnSubscribeMerge(Observable source, int maxConcurrency, boolean delayErrors) { + this.source = source; + this.maxConcurrency = maxConcurrency; + this.delayErrors = delayErrors; + } + + @Override + public void call(CompletableSubscriber s) { + CompletableMergeSubscriber parent = new CompletableMergeSubscriber(s, maxConcurrency, delayErrors); + s.onSubscribe(parent); + source.subscribe(parent); + } + + static final class CompletableMergeSubscriber + extends Subscriber { + final CompletableSubscriber actual; + final CompositeSubscription set; + final int maxConcurrency; + final boolean delayErrors; + + volatile boolean done; + + volatile Queue errors; + @SuppressWarnings("rawtypes") + static final AtomicReferenceFieldUpdater ERRORS = + AtomicReferenceFieldUpdater.newUpdater(CompletableMergeSubscriber.class, Queue.class, "errors"); + + volatile int once; + static final AtomicIntegerFieldUpdater ONCE = + AtomicIntegerFieldUpdater.newUpdater(CompletableMergeSubscriber.class, "once"); + + final AtomicInteger wip; + + public CompletableMergeSubscriber(CompletableSubscriber actual, int maxConcurrency, boolean delayErrors) { + this.actual = actual; + this.maxConcurrency = maxConcurrency; + this.delayErrors = delayErrors; + this.set = new CompositeSubscription(); + this.wip = new AtomicInteger(1); + if (maxConcurrency == Integer.MAX_VALUE) { + request(Long.MAX_VALUE); + } else { + request(maxConcurrency); + } + } + + Queue getOrCreateErrors() { + Queue q = errors; + + if (q != null) { + return q; + } + + q = new ConcurrentLinkedQueue(); + if (ERRORS.compareAndSet(this, null, q)) { + return q; + } + return errors; + } + + @Override + public void onNext(Completable t) { + if (done) { + return; + } + + wip.getAndIncrement(); + + t.subscribe(new CompletableSubscriber() { + Subscription d; + boolean innerDone; + @Override + public void onSubscribe(Subscription d) { + this.d = d; + set.add(d); + } + + @Override + public void onError(Throwable e) { + if (innerDone) { + RxJavaPlugins.getInstance().getErrorHandler().handleError(e); + return; + } + innerDone = true; + set.remove(d); + + getOrCreateErrors().offer(e); + + terminate(); + + if (delayErrors && !done) { + request(1); + } + } + + @Override + public void onCompleted() { + if (innerDone) { + return; + } + innerDone = true; + set.remove(d); + + terminate(); + + if (!done) { + request(1); + } + } + }); + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.getInstance().getErrorHandler().handleError(t); + return; + } + getOrCreateErrors().offer(t); + done = true; + terminate(); + } + + @Override + public void onCompleted() { + if (done) { + return; + } + done = true; + terminate(); + } + + void terminate() { + if (wip.decrementAndGet() == 0) { + Queue q = errors; + if (q == null || q.isEmpty()) { + actual.onCompleted(); + } else { + Throwable e = collectErrors(q); + if (ONCE.compareAndSet(this, 0, 1)) { + actual.onError(e); + } else { + RxJavaPlugins.getInstance().getErrorHandler().handleError(e); + } + } + } else + if (!delayErrors) { + Queue q = errors; + if (q != null && !q.isEmpty()) { + Throwable e = collectErrors(q); + if (ONCE.compareAndSet(this, 0, 1)) { + actual.onError(e); + } else { + RxJavaPlugins.getInstance().getErrorHandler().handleError(e); + } + } + } + } + } + + /** + * Collects the Throwables from the queue, adding subsequent Throwables as suppressed to + * the first Throwable and returns it. + * @param q the queue to drain + * @return the Throwable containing all other Throwables as suppressed + */ + public static Throwable collectErrors(Queue q) { + Throwable ex = null; + + Throwable t; + int count = 0; + while ((t = q.poll()) != null) { + if (count == 0) { + ex = t; + } else { + ex.addSuppressed(t); + } + + count++; + } + return ex; + } +} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeArray.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeArray.java new file mode 100644 index 0000000000..85d3d59b3a --- /dev/null +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeArray.java @@ -0,0 +1,91 @@ +/** + * 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.internal.operators; + +import java.util.concurrent.atomic.*; + +import rx.*; +import rx.Completable.*; +import rx.plugins.RxJavaPlugins; +import rx.subscriptions.CompositeSubscription; + +public final class CompletableOnSubscribeMergeArray implements CompletableOnSubscribe { + final Completable[] sources; + + public CompletableOnSubscribeMergeArray(Completable[] sources) { + this.sources = sources; + } + + @Override + public void call(final CompletableSubscriber s) { + final CompositeSubscription set = new CompositeSubscription(); + final AtomicInteger wip = new AtomicInteger(sources.length + 1); + final AtomicBoolean once = new AtomicBoolean(); + + s.onSubscribe(set); + + for (Completable c : sources) { + if (set.isUnsubscribed()) { + return; + } + + if (c == null) { + set.unsubscribe(); + NullPointerException npe = new NullPointerException("A completable source is null"); + if (once.compareAndSet(false, true)) { + s.onError(npe); + return; + } else { + RxJavaPlugins.getInstance().getErrorHandler().handleError(npe); + } + } + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + set.add(d); + } + + @Override + public void onError(Throwable e) { + set.unsubscribe(); + if (once.compareAndSet(false, true)) { + s.onError(e); + } else { + RxJavaPlugins.getInstance().getErrorHandler().handleError(e); + } + } + + @Override + public void onCompleted() { + if (wip.decrementAndGet() == 0) { + if (once.compareAndSet(false, true)) { + s.onCompleted(); + } + } + } + + }); + } + + if (wip.decrementAndGet() == 0) { + if (once.compareAndSet(false, true)) { + s.onCompleted(); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorArray.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorArray.java new file mode 100644 index 0000000000..2a89afbaa2 --- /dev/null +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorArray.java @@ -0,0 +1,93 @@ +/** + * 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.internal.operators; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; + +import rx.*; +import rx.Completable.*; +import rx.subscriptions.CompositeSubscription; + +public final class CompletableOnSubscribeMergeDelayErrorArray implements CompletableOnSubscribe { + final Completable[] sources; + + public CompletableOnSubscribeMergeDelayErrorArray(Completable[] sources) { + this.sources = sources; + } + + @Override + public void call(final CompletableSubscriber s) { + final CompositeSubscription set = new CompositeSubscription(); + final AtomicInteger wip = new AtomicInteger(sources.length + 1); + + final Queue q = new ConcurrentLinkedQueue(); + + s.onSubscribe(set); + + for (Completable c : sources) { + if (set.isUnsubscribed()) { + return; + } + + if (c == null) { + q.offer(new NullPointerException("A completable source is null")); + wip.decrementAndGet(); + continue; + } + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + set.add(d); + } + + @Override + public void onError(Throwable e) { + q.offer(e); + tryTerminate(); + } + + @Override + public void onCompleted() { + tryTerminate(); + } + + void tryTerminate() { + if (wip.decrementAndGet() == 0) { + if (q.isEmpty()) { + s.onCompleted(); + } else { + s.onError(CompletableOnSubscribeMerge.collectErrors(q)); + } + } + } + + }); + } + + if (wip.decrementAndGet() == 0) { + if (q.isEmpty()) { + s.onCompleted(); + } else { + s.onError(CompletableOnSubscribeMerge.collectErrors(q)); + } + } + + } +} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorIterable.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorIterable.java new file mode 100644 index 0000000000..be783e6d6d --- /dev/null +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeDelayErrorIterable.java @@ -0,0 +1,157 @@ +/** + * 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.internal.operators; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +import rx.*; +import rx.Completable.*; +import rx.internal.util.unsafe.MpscLinkedQueue; +import rx.subscriptions.CompositeSubscription; + +public final class CompletableOnSubscribeMergeDelayErrorIterable implements CompletableOnSubscribe { + final Iterable sources; + + public CompletableOnSubscribeMergeDelayErrorIterable(Iterable sources) { + this.sources = sources; + } + + @Override + public void call(final CompletableSubscriber s) { + final CompositeSubscription set = new CompositeSubscription(); + final AtomicInteger wip = new AtomicInteger(1); + + final Queue queue = new MpscLinkedQueue(); + + s.onSubscribe(set); + + Iterator iterator; + + try { + iterator = sources.iterator(); + } catch (Throwable e) { + s.onError(e); + return; + } + + if (iterator == null) { + s.onError(new NullPointerException("The source iterator returned is null")); + return; + } + + for (;;) { + if (set.isUnsubscribed()) { + return; + } + + boolean b; + try { + b = iterator.hasNext(); + } catch (Throwable e) { + queue.offer(e); + if (wip.decrementAndGet() == 0) { + if (queue.isEmpty()) { + s.onCompleted(); + } else { + s.onError(CompletableOnSubscribeMerge.collectErrors(queue)); + } + } + return; + } + + if (!b) { + break; + } + + if (set.isUnsubscribed()) { + return; + } + + Completable c; + + try { + c = iterator.next(); + } catch (Throwable e) { + queue.offer(e); + if (wip.decrementAndGet() == 0) { + if (queue.isEmpty()) { + s.onCompleted(); + } else { + s.onError(CompletableOnSubscribeMerge.collectErrors(queue)); + } + } + return; + } + + if (set.isUnsubscribed()) { + return; + } + + if (c == null) { + NullPointerException e = new NullPointerException("A completable source is null"); + queue.offer(e); + if (wip.decrementAndGet() == 0) { + if (queue.isEmpty()) { + s.onCompleted(); + } else { + s.onError(CompletableOnSubscribeMerge.collectErrors(queue)); + } + } + return; + } + + wip.getAndIncrement(); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + set.add(d); + } + + @Override + public void onError(Throwable e) { + queue.offer(e); + tryTerminate(); + } + + @Override + public void onCompleted() { + tryTerminate(); + } + + void tryTerminate() { + if (wip.decrementAndGet() == 0) { + if (queue.isEmpty()) { + s.onCompleted(); + } else { + s.onError(CompletableOnSubscribeMerge.collectErrors(queue)); + } + } + } + }); + } + + if (wip.decrementAndGet() == 0) { + if (queue.isEmpty()) { + s.onCompleted(); + } else { + s.onError(CompletableOnSubscribeMerge.collectErrors(queue)); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeIterable.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeIterable.java new file mode 100644 index 0000000000..7ad953e4de --- /dev/null +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeMergeIterable.java @@ -0,0 +1,147 @@ +/** + * 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.internal.operators; + +import java.util.Iterator; +import java.util.concurrent.atomic.*; + +import rx.*; +import rx.Completable.*; +import rx.plugins.RxJavaPlugins; +import rx.subscriptions.CompositeSubscription; + +public final class CompletableOnSubscribeMergeIterable implements CompletableOnSubscribe { + final Iterable sources; + + public CompletableOnSubscribeMergeIterable(Iterable sources) { + this.sources = sources; + } + + @Override + public void call(final CompletableSubscriber s) { + final CompositeSubscription set = new CompositeSubscription(); + final AtomicInteger wip = new AtomicInteger(1); + final AtomicBoolean once = new AtomicBoolean(); + + s.onSubscribe(set); + + Iterator iterator; + + try { + iterator = sources.iterator(); + } catch (Throwable e) { + s.onError(e); + return; + } + + if (iterator == null) { + s.onError(new NullPointerException("The source iterator returned is null")); + return; + } + + for (;;) { + if (set.isUnsubscribed()) { + return; + } + + boolean b; + try { + b = iterator.hasNext(); + } catch (Throwable e) { + set.unsubscribe(); + if (once.compareAndSet(false, true)) { + s.onError(e); + } else { + RxJavaPlugins.getInstance().getErrorHandler().handleError(e); + } + return; + } + + if (!b) { + break; + } + + if (set.isUnsubscribed()) { + return; + } + + Completable c; + + try { + c = iterator.next(); + } catch (Throwable e) { + set.unsubscribe(); + if (once.compareAndSet(false, true)) { + s.onError(e); + } else { + RxJavaPlugins.getInstance().getErrorHandler().handleError(e); + } + return; + } + + if (set.isUnsubscribed()) { + return; + } + + if (c == null) { + set.unsubscribe(); + NullPointerException npe = new NullPointerException("A completable source is null"); + if (once.compareAndSet(false, true)) { + s.onError(npe); + } else { + RxJavaPlugins.getInstance().getErrorHandler().handleError(npe); + } + return; + } + + wip.getAndIncrement(); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + set.add(d); + } + + @Override + public void onError(Throwable e) { + set.unsubscribe(); + if (once.compareAndSet(false, true)) { + s.onError(e); + } else { + RxJavaPlugins.getInstance().getErrorHandler().handleError(e); + } + } + + @Override + public void onCompleted() { + if (wip.decrementAndGet() == 0) { + if (once.compareAndSet(false, true)) { + s.onCompleted(); + } + } + } + + }); + } + + if (wip.decrementAndGet() == 0) { + if (once.compareAndSet(false, true)) { + s.onCompleted(); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/rx/internal/operators/CompletableOnSubscribeTimeout.java b/src/main/java/rx/internal/operators/CompletableOnSubscribeTimeout.java new file mode 100644 index 0000000000..2a9c8e31e2 --- /dev/null +++ b/src/main/java/rx/internal/operators/CompletableOnSubscribeTimeout.java @@ -0,0 +1,115 @@ +/** + * 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.internal.operators; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; + +import rx.*; +import rx.Completable.*; +import rx.functions.Action0; +import rx.plugins.RxJavaPlugins; +import rx.subscriptions.CompositeSubscription; + +public final class CompletableOnSubscribeTimeout implements CompletableOnSubscribe { + + final Completable source; + final long timeout; + final TimeUnit unit; + final Scheduler scheduler; + final Completable other; + + public CompletableOnSubscribeTimeout(Completable source, long timeout, + TimeUnit unit, Scheduler scheduler, Completable other) { + this.source = source; + this.timeout = timeout; + this.unit = unit; + this.scheduler = scheduler; + this.other = other; + } + + @Override + public void call(final CompletableSubscriber s) { + final CompositeSubscription set = new CompositeSubscription(); + s.onSubscribe(set); + + final AtomicBoolean once = new AtomicBoolean(); + + Scheduler.Worker w = scheduler.createWorker(); + + set.add(w); + w.schedule(new Action0() { + @Override + public void call() { + if (once.compareAndSet(false, true)) { + set.clear(); + if (other == null) { + s.onError(new TimeoutException()); + } else { + other.subscribe(new CompletableSubscriber() { + + @Override + public void onSubscribe(Subscription d) { + set.add(d); + } + + @Override + public void onError(Throwable e) { + set.unsubscribe(); + s.onError(e); + } + + @Override + public void onCompleted() { + set.unsubscribe(); + s.onCompleted(); + } + + }); + } + } + } + }, timeout, unit); + + source.subscribe(new CompletableSubscriber() { + + @Override + public void onSubscribe(Subscription d) { + set.add(d); + } + + @Override + public void onError(Throwable e) { + if (once.compareAndSet(false, true)) { + set.unsubscribe(); + s.onError(e); + } else { + RxJavaPlugins.getInstance().getErrorHandler().handleError(e); + } + } + + @Override + public void onCompleted() { + if (once.compareAndSet(false, true)) { + set.unsubscribe(); + s.onCompleted(); + } + } + + }); + } +} \ No newline at end of file diff --git a/src/test/java/rx/CompletableTest.java b/src/test/java/rx/CompletableTest.java new file mode 100644 index 0000000000..ec73ad0141 --- /dev/null +++ b/src/test/java/rx/CompletableTest.java @@ -0,0 +1,3413 @@ +/** + * 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; + +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.*; + +import org.junit.*; + +import rx.Completable.*; +import rx.exceptions.*; +import rx.functions.*; +import rx.observers.TestSubscriber; +import rx.plugins.RxJavaPlugins; +import rx.schedulers.*; +import rx.subjects.PublishSubject; +import rx.subscriptions.*; + +/** + * Test Completable methods and operators. + */ +public class CompletableTest { + /** + * Iterable that returns an Iterator that throws in its hasNext method. + */ + static final class IterableIteratorNextThrows implements Iterable { + @Override + public Iterator iterator() { + return new Iterator() { + @Override + public boolean hasNext() { + return true; + } + + @Override + public Completable next() { + throw new TestException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + } + + /** + * Iterable that returns an Iterator that throws in its next method. + */ + static final class IterableIteratorHasNextThrows implements Iterable { + @Override + public Iterator iterator() { + return new Iterator() { + @Override + public boolean hasNext() { + throw new TestException(); + } + + @Override + public Completable next() { + return null; + } + + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + } + + /** + * A class containing a completable instance and counts the number of subscribers. + */ + static final class NormalCompletable extends AtomicInteger { + /** */ + private static final long serialVersionUID = 7192337844700923752L; + + public final Completable completable = Completable.create(new CompletableOnSubscribe() { + @Override + public void call(CompletableSubscriber s) { + getAndIncrement(); + s.onSubscribe(Subscriptions.unsubscribed()); + s.onCompleted(); + } + }); + + /** + * Asserts the given number of subscriptions happened. + * @param n the expected number of subscriptions + */ + public void assertSubscriptions(int n) { + Assert.assertEquals(n, get()); + } + } + + /** + * A class containing a completable instance that emits a TestException and counts + * the number of subscribers. + */ + static final class ErrorCompletable extends AtomicInteger { + /** */ + private static final long serialVersionUID = 7192337844700923752L; + + public final Completable completable = Completable.create(new CompletableOnSubscribe() { + @Override + public void call(CompletableSubscriber s) { + getAndIncrement(); + s.onSubscribe(Subscriptions.unsubscribed()); + s.onError(new TestException()); + } + }); + + /** + * Asserts the given number of subscriptions happened. + * @param n the expected number of subscriptions + */ + public void assertSubscriptions(int n) { + Assert.assertEquals(n, get()); + } + } + + /** A normal Completable object. */ + final NormalCompletable normal = new NormalCompletable(); + + /** An error Completable object. */ + final ErrorCompletable error = new ErrorCompletable(); + + @Test(timeout = 1000) + public void complete() { + Completable c = Completable.complete(); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void concatNull() { + Completable.concat((Completable[])null); + } + + @Test(timeout = 1000) + public void concatEmpty() { + Completable c = Completable.concat(); + + c.await(); + } + + @Test(timeout = 1000) + public void concatSingleSource() { + Completable c = Completable.concat(normal.completable); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 1000, expected = TestException.class) + public void concatSingleSourceThrows() { + Completable c = Completable.concat(error.completable); + + c.await(); + } + + @Test(timeout = 1000) + public void concatMultipleSources() { + Completable c = Completable.concat(normal.completable, normal.completable, normal.completable); + + c.await(); + + normal.assertSubscriptions(3); + } + + @Test(timeout = 1000, expected = TestException.class) + public void concatMultipleOneThrows() { + Completable c = Completable.concat(normal.completable, error.completable, normal.completable); + + c.await(); + } + + @Test(timeout = 1000, expected = NullPointerException.class) + public void concatMultipleOneIsNull() { + Completable c = Completable.concat(normal.completable, null); + + c.await(); + } + + @Test(timeout = 1000) + public void concatIterableEmpty() { + Completable c = Completable.concat(Collections.emptyList()); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void concatIterableNull() { + Completable.concat((Iterable)null); + } + + @Test(timeout = 1000, expected = NullPointerException.class) + public void concatIterableIteratorNull() { + Completable c = Completable.concat(new Iterable() { + @Override + public Iterator iterator() { + return null; + } + }); + + c.await(); + } + + @Test(timeout = 1000, expected = NullPointerException.class) + public void concatIterableWithNull() { + Completable c = Completable.concat(Arrays.asList(normal.completable, (Completable)null)); + + c.await(); + } + + @Test(timeout = 1000) + public void concatIterableSingle() { + Completable c = Completable.concat(Collections.singleton(normal.completable)); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 1000) + public void concatIterableMany() { + Completable c = Completable.concat(Arrays.asList(normal.completable, normal.completable, normal.completable)); + + c.await(); + + normal.assertSubscriptions(3); + } + + @Test(timeout = 1000, expected = TestException.class) + public void concatIterableOneThrows() { + Completable c = Completable.concat(Collections.singleton(error.completable)); + + c.await(); + } + + @Test(timeout = 1000, expected = TestException.class) + public void concatIterableManyOneThrows() { + Completable c = Completable.concat(Arrays.asList(normal.completable, error.completable)); + + c.await(); + } + + @Test(expected = TestException.class) + public void concatIterableIterableThrows() { + Completable c = Completable.concat(new Iterable() { + @Override + public Iterator iterator() { + throw new TestException(); + } + }); + + c.await(); + } + + @Test(expected = TestException.class) + public void concatIterableIteratorHasNextThrows() { + Completable c = Completable.concat(new IterableIteratorHasNextThrows()); + + c.await(); + } + + @Test(expected = TestException.class) + public void concatIterableIteratorNextThrows() { + Completable c = Completable.concat(new IterableIteratorNextThrows()); + + c.await(); + } + + @Test(timeout = 1000) + public void concatObservableEmpty() { + Completable c = Completable.concat(Observable.empty()); + + c.await(); + } + + @Test(timeout = 1000, expected = TestException.class) + public void concatObservableError() { + Completable c = Completable.concat(Observable.error(new TestException())); + + c.await(); + } + + @Test(timeout = 1000) + public void concatObservableSingle() { + Completable c = Completable.concat(Observable.just(normal.completable)); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 1000, expected = TestException.class) + public void concatObservableSingleThrows() { + Completable c = Completable.concat(Observable.just(error.completable)); + + c.await(); + } + + @Test(timeout = 1000) + public void concatObservableMany() { + Completable c = Completable.concat(Observable.just(normal.completable).repeat(3)); + + c.await(); + + normal.assertSubscriptions(3); + } + + @Test(timeout = 1000, expected = TestException.class) + public void concatObservableManyOneThrows() { + Completable c = Completable.concat(Observable.just(normal.completable, error.completable)); + + c.await(); + } + + @Test(timeout = 1000) + public void concatObservablePrefetch() { + final List requested = new ArrayList(); + Observable cs = Observable + .just(normal.completable) + .repeat(10) + .doOnRequest(new Action1() { + @Override + public void call(Long v) { + requested.add(v); + } + }); + + Completable c = Completable.concat(cs, 5); + + c.await(); + + // FIXME this request pattern looks odd because all 10 completions trigger 1 requests + Assert.assertEquals(Arrays.asList(5L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), requested); + } + + @Test(expected = NullPointerException.class) + public void createNull() { + Completable.create(null); + } + + @Test(timeout = 1000, expected = NullPointerException.class) + public void createOnSubscribeThrowsNPE() { + Completable c = Completable.create(new CompletableOnSubscribe() { + @Override + public void call(CompletableSubscriber s) { throw new NullPointerException(); } + }); + + c.await(); + } + + @Test(timeout = 1000) + public void createOnSubscribeThrowsRuntimeException() { + try { + Completable c = Completable.create(new CompletableOnSubscribe() { + @Override + public void call(CompletableSubscriber s) { + throw new TestException(); + } + }); + + c.await(); + + Assert.fail("Did not throw exception"); + } catch (NullPointerException ex) { + if (!(ex.getCause() instanceof TestException)) { + ex.printStackTrace(); + Assert.fail("Did not wrap the TestException but it returned: " + ex); + } + } + } + + @Test(timeout = 1000) + public void defer() { + Completable c = Completable.defer(new Func0() { + @Override + public Completable call() { + return normal.completable; + } + }); + + normal.assertSubscriptions(0); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(expected = NullPointerException.class) + public void deferNull() { + Completable.defer(null); + } + + @Test(timeout = 1000, expected = NullPointerException.class) + public void deferReturnsNull() { + Completable c = Completable.defer(new Func0() { + @Override + public Completable call() { + return null; + } + }); + + c.await(); + } + + @Test(timeout = 1000, expected = TestException.class) + public void deferFunctionThrows() { + Completable c = Completable.defer(new Func0() { + @Override + public Completable call() { throw new TestException(); } + }); + + c.await(); + } + + @Test(timeout = 1000, expected = TestException.class) + public void deferErrorSource() { + Completable c = Completable.defer(new Func0() { + @Override + public Completable call() { + return error.completable; + } + }); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void errorNull() { + Completable.error((Throwable)null); + } + + @Test(timeout = 1000, expected = TestException.class) + public void errorNormal() { + Completable c = Completable.error(new TestException()); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void fromCallableNull() { + Completable.fromCallable(null); + } + + @Test(timeout = 1000) + public void fromCallableNormal() { + final AtomicInteger calls = new AtomicInteger(); + + Completable c = Completable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + return calls.getAndIncrement(); + } + }); + + c.await(); + + Assert.assertEquals(1, calls.get()); + } + + @Test(timeout = 1000, expected = TestException.class) + public void fromCallableThrows() { + Completable c = Completable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { throw new TestException(); } + }); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void fromObservableNull() { + Completable.fromObservable(null); + } + + @Test(timeout = 1000) + public void fromObservableEmpty() { + Completable c = Completable.fromObservable(Observable.empty()); + + c.await(); + } + + @Test(timeout = 5000) + public void fromObservableSome() { + for (int n = 1; n < 10000; n *= 10) { + Completable c = Completable.fromObservable(Observable.range(1, n)); + + c.await(); + } + } + + @Test(timeout = 1000, expected = TestException.class) + public void fromObservableError() { + Completable c = Completable.fromObservable(Observable.error(new TestException())); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void fromFutureNull() { + Completable.fromFuture(null); + } + + @Test(timeout = 1000) + public void fromFutureNormal() { + ExecutorService exec = Executors.newSingleThreadExecutor(); + + try { + Completable c = Completable.fromFuture(exec.submit(new Runnable() { + @Override + public void run() { + // no action + } + })); + + c.await(); + } finally { + exec.shutdown(); + } + } + + @Test(timeout = 1000) + public void fromFutureThrows() { + ExecutorService exec = Executors.newSingleThreadExecutor(); + + Completable c = Completable.fromFuture(exec.submit(new Runnable() { + @Override + public void run() { + throw new TestException(); + } + })); + + try { + c.await(); + Assert.fail("Failed to throw Exception"); + } catch (RuntimeException ex) { + if (!((ex.getCause() instanceof ExecutionException) && (ex.getCause().getCause() instanceof TestException))) { + ex.printStackTrace(); + Assert.fail("Wrong exception received"); + } + } finally { + exec.shutdown(); + } + } + + @Test(expected = NullPointerException.class) + public void fromActionNull() { + Completable.fromAction(null); + } + + @Test(timeout = 1000) + public void fromActionNormal() { + final AtomicInteger calls = new AtomicInteger(); + + Completable c = Completable.fromAction(new Action0() { + @Override + public void call() { + calls.getAndIncrement(); + } + }); + + c.await(); + + Assert.assertEquals(1, calls.get()); + } + + @Test(timeout = 1000, expected = TestException.class) + public void fromActionThrows() { + Completable c = Completable.fromAction(new Action0() { + @Override + public void call() { throw new TestException(); } + }); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void fromSingleNull() { + Completable.fromSingle(null); + } + + @Test(timeout = 1000) + public void fromSingleNormal() { + Completable c = Completable.fromSingle(Single.just(1)); + + c.await(); + } + + @Test(timeout = 1000, expected = TestException.class) + public void fromSingleThrows() { + Completable c = Completable.fromSingle(Single.error(new TestException())); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void mergeNull() { + Completable.merge((Completable[])null); + } + + @Test(timeout = 1000) + public void mergeEmpty() { + Completable c = Completable.merge(); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeSingleSource() { + Completable c = Completable.merge(normal.completable); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 1000, expected = TestException.class) + public void mergeSingleSourceThrows() { + Completable c = Completable.merge(error.completable); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeMultipleSources() { + Completable c = Completable.merge(normal.completable, normal.completable, normal.completable); + + c.await(); + + normal.assertSubscriptions(3); + } + + @Test(timeout = 1000, expected = TestException.class) + public void mergeMultipleOneThrows() { + Completable c = Completable.merge(normal.completable, error.completable, normal.completable); + + c.await(); + } + + @Test(timeout = 1000, expected = NullPointerException.class) + public void mergeMultipleOneIsNull() { + Completable c = Completable.merge(normal.completable, null); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeIterableEmpty() { + Completable c = Completable.merge(Collections.emptyList()); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void mergeIterableNull() { + Completable.merge((Iterable)null); + } + + @Test(timeout = 1000, expected = NullPointerException.class) + public void mergeIterableIteratorNull() { + Completable c = Completable.merge(new Iterable() { + @Override + public Iterator iterator() { + return null; + } + }); + + c.await(); + } + + @Test(timeout = 1000, expected = NullPointerException.class) + public void mergeIterableWithNull() { + Completable c = Completable.merge(Arrays.asList(normal.completable, (Completable)null)); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeIterableSingle() { + Completable c = Completable.merge(Collections.singleton(normal.completable)); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 1000) + public void mergeIterableMany() { + Completable c = Completable.merge(Arrays.asList(normal.completable, normal.completable, normal.completable)); + + c.await(); + + normal.assertSubscriptions(3); + } + + @Test(timeout = 1000, expected = TestException.class) + public void mergeIterableOneThrows() { + Completable c = Completable.merge(Collections.singleton(error.completable)); + + c.await(); + } + + @Test(timeout = 1000, expected = TestException.class) + public void mergeIterableManyOneThrows() { + Completable c = Completable.merge(Arrays.asList(normal.completable, error.completable)); + + c.await(); + } + + @Test(expected = TestException.class) + public void mergeIterableIterableThrows() { + Completable c = Completable.merge(new Iterable() { + @Override + public Iterator iterator() { + throw new TestException(); + } + }); + + c.await(); + } + + @Test(expected = TestException.class) + public void mergeIterableIteratorHasNextThrows() { + Completable c = Completable.merge(new IterableIteratorHasNextThrows()); + + c.await(); + } + + @Test(expected = TestException.class) + public void mergeIterableIteratorNextThrows() { + Completable c = Completable.merge(new IterableIteratorNextThrows()); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeObservableEmpty() { + Completable c = Completable.merge(Observable.empty()); + + c.await(); + } + + @Test(timeout = 1000, expected = TestException.class) + public void mergeObservableError() { + Completable c = Completable.merge(Observable.error(new TestException())); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeObservableSingle() { + Completable c = Completable.merge(Observable.just(normal.completable)); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 1000, expected = TestException.class) + public void mergeObservableSingleThrows() { + Completable c = Completable.merge(Observable.just(error.completable)); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeObservableMany() { + Completable c = Completable.merge(Observable.just(normal.completable).repeat(3)); + + c.await(); + + normal.assertSubscriptions(3); + } + + @Test(timeout = 1000, expected = TestException.class) + public void mergeObservableManyOneThrows() { + Completable c = Completable.merge(Observable.just(normal.completable, error.completable)); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeObservableMaxConcurrent() { + final List requested = new ArrayList(); + Observable cs = Observable + .just(normal.completable) + .repeat(10) + .doOnRequest(new Action1() { + @Override + public void call(Long v) { + requested.add(v); + } + }); + + Completable c = Completable.merge(cs, 5); + + c.await(); + + // FIXME this request pattern looks odd because all 10 completions trigger 1 requests + Assert.assertEquals(Arrays.asList(5L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), requested); + } + + @Test(expected = NullPointerException.class) + public void mergeDelayErrorNull() { + Completable.mergeDelayError((Completable[])null); + } + + @Test(timeout = 1000) + public void mergeDelayErrorEmpty() { + Completable c = Completable.mergeDelayError(); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeDelayErrorSingleSource() { + Completable c = Completable.mergeDelayError(normal.completable); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 1000, expected = TestException.class) + public void mergeDelayErrorSingleSourceThrows() { + Completable c = Completable.mergeDelayError(error.completable); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeDelayErrorMultipleSources() { + Completable c = Completable.mergeDelayError(normal.completable, normal.completable, normal.completable); + + c.await(); + + normal.assertSubscriptions(3); + } + + @Test(timeout = 1000) + public void mergeDelayErrorMultipleOneThrows() { + Completable c = Completable.mergeDelayError(normal.completable, error.completable, normal.completable); + + try { + c.await(); + } catch (TestException ex) { + normal.assertSubscriptions(2); + } + } + + @Test(timeout = 1000, expected = NullPointerException.class) + public void mergeDelayErrorMultipleOneIsNull() { + Completable c = Completable.mergeDelayError(normal.completable, null); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeDelayErrorIterableEmpty() { + Completable c = Completable.mergeDelayError(Collections.emptyList()); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void mergeDelayErrorIterableNull() { + Completable.mergeDelayError((Iterable)null); + } + + @Test(timeout = 1000, expected = NullPointerException.class) + public void mergeDelayErrorIterableIteratorNull() { + Completable c = Completable.mergeDelayError(new Iterable() { + @Override + public Iterator iterator() { + return null; + } + }); + + c.await(); + } + + @Test(timeout = 1000, expected = NullPointerException.class) + public void mergeDelayErrorIterableWithNull() { + Completable c = Completable.mergeDelayError(Arrays.asList(normal.completable, (Completable)null)); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeDelayErrorIterableSingle() { + Completable c = Completable.mergeDelayError(Collections.singleton(normal.completable)); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 1000) + public void mergeDelayErrorIterableMany() { + Completable c = Completable.mergeDelayError(Arrays.asList(normal.completable, normal.completable, normal.completable)); + + c.await(); + + normal.assertSubscriptions(3); + } + + @Test(timeout = 1000, expected = TestException.class) + public void mergeDelayErrorIterableOneThrows() { + Completable c = Completable.mergeDelayError(Collections.singleton(error.completable)); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeDelayErrorIterableManyOneThrows() { + Completable c = Completable.mergeDelayError(Arrays.asList(normal.completable, error.completable, normal.completable)); + + try { + c.await(); + } catch (TestException ex) { + normal.assertSubscriptions(2); + } + } + + @Test(expected = TestException.class) + public void mergeDelayErrorIterableIterableThrows() { + Completable c = Completable.mergeDelayError(new Iterable() { + @Override + public Iterator iterator() { + throw new TestException(); + } + }); + + c.await(); + } + + @Test(expected = TestException.class) + public void mergeDelayErrorIterableIteratorHasNextThrows() { + Completable c = Completable.mergeDelayError(new IterableIteratorHasNextThrows()); + + c.await(); + } + + @Test(expected = TestException.class) + public void mergeDelayErrorIterableIteratorNextThrows() { + Completable c = Completable.mergeDelayError(new IterableIteratorNextThrows()); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeDelayErrorObservableEmpty() { + Completable c = Completable.mergeDelayError(Observable.empty()); + + c.await(); + } + + @Test(timeout = 1000, expected = TestException.class) + public void mergeDelayErrorObservableError() { + Completable c = Completable.mergeDelayError(Observable.error(new TestException())); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeDelayErrorObservableSingle() { + Completable c = Completable.mergeDelayError(Observable.just(normal.completable)); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 1000, expected = TestException.class) + public void mergeDelayErrorObservableSingleThrows() { + Completable c = Completable.mergeDelayError(Observable.just(error.completable)); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeDelayErrorObservableMany() { + Completable c = Completable.mergeDelayError(Observable.just(normal.completable).repeat(3)); + + c.await(); + + normal.assertSubscriptions(3); + } + + @Test(timeout = 1000, expected = TestException.class) + public void mergeDelayErrorObservableManyOneThrows() { + Completable c = Completable.mergeDelayError(Observable.just(normal.completable, error.completable)); + + c.await(); + } + + @Test(timeout = 1000) + public void mergeDelayErrorObservableMaxConcurrent() { + final List requested = new ArrayList(); + Observable cs = Observable + .just(normal.completable) + .repeat(10) + .doOnRequest(new Action1() { + @Override + public void call(Long v) { + requested.add(v); + } + }); + + Completable c = Completable.mergeDelayError(cs, 5); + + c.await(); + + // FIXME this request pattern looks odd because all 10 completions trigger 1 requests + Assert.assertEquals(Arrays.asList(5L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L), requested); + } + + @Test(timeout = 1000) + public void never() { + final AtomicBoolean onSubscribeCalled = new AtomicBoolean(); + final AtomicInteger calls = new AtomicInteger(); + Completable.never().subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + onSubscribeCalled.set(true); + } + + @Override + public void onError(Throwable e) { + calls.getAndIncrement(); + } + + @Override + public void onCompleted() { + calls.getAndIncrement(); + } + }); + + Assert.assertTrue("onSubscribe not called", onSubscribeCalled.get()); + Assert.assertEquals("There were calls to onXXX methods", 0, calls.get()); + } + + @Test(timeout = 1500) + public void timer() { + Completable c = Completable.timer(500, TimeUnit.MILLISECONDS); + + c.await(); + } + + @Test(timeout = 1500) + public void timerNewThread() { + Completable c = Completable.timer(500, TimeUnit.MILLISECONDS, Schedulers.newThread()); + + c.await(); + } + + @Test(timeout = 1000) + public void timerTestScheduler() { + TestScheduler scheduler = Schedulers.test(); + + Completable c = Completable.timer(250, TimeUnit.MILLISECONDS, scheduler); + + final AtomicInteger calls = new AtomicInteger(); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + + } + + @Override + public void onCompleted() { + calls.getAndIncrement(); + } + + @Override + public void onError(Throwable e) { + RxJavaPlugins.getInstance().getErrorHandler().handleError(e); + } + }); + + scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); + + Assert.assertEquals(0, calls.get()); + + scheduler.advanceTimeBy(200, TimeUnit.MILLISECONDS); + + Assert.assertEquals(1, calls.get()); + } + + @Test(timeout = 2000) + public void timerCancel() throws InterruptedException { + Completable c = Completable.timer(250, TimeUnit.MILLISECONDS); + + final MultipleAssignmentSubscription mad = new MultipleAssignmentSubscription(); + final AtomicInteger calls = new AtomicInteger(); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + mad.set(d); + } + + @Override + public void onError(Throwable e) { + calls.getAndIncrement(); + } + + @Override + public void onCompleted() { + calls.getAndIncrement(); + } + }); + + Thread.sleep(100); + + mad.unsubscribe(); + + Thread.sleep(200); + + Assert.assertEquals(0, calls.get()); + } + + @Test(expected = NullPointerException.class) + public void timerUnitNull() { + Completable.timer(1, null); + } + + @Test(expected = NullPointerException.class) + public void timerSchedulerNull() { + Completable.timer(1, TimeUnit.SECONDS, null); + } + + @Test(timeout = 1000) + public void usingNormalEager() { + final AtomicInteger unsubscribe = new AtomicInteger(); + + Completable c = Completable.using(new Func0() { + @Override + public Integer call() { + return 1; + } + }, new Func1() { + @Override + public Completable call(Object v) { + return normal.completable; + } + }, new Action1() { + @Override + public void call(Integer d) { + unsubscribe.set(d); + } + }); + + final AtomicBoolean unsubscribedFirst = new AtomicBoolean(); + final AtomicReference error = new AtomicReference(); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + + } + + @Override + public void onError(Throwable e) { + error.lazySet(e); + } + + @Override + public void onCompleted() { + unsubscribedFirst.set(unsubscribe.get() != 0); + } + }); + + Assert.assertEquals(1, unsubscribe.get()); + Assert.assertTrue("Not unsubscribed first", unsubscribedFirst.get()); + Assert.assertNull(error.get()); + } + + @Test(timeout = 1000) + public void usingNormalLazy() { + final AtomicInteger unsubscribe = new AtomicInteger(); + + Completable c = Completable.using(new Func0() { + @Override + public Integer call() { + return 1; + } + }, new Func1() { + @Override + public Completable call(Integer v) { + return normal.completable; + } + }, new Action1() { + @Override + public void call(Integer d) { + unsubscribe.set(d); + } + }, false); + + final AtomicBoolean unsubscribedFirst = new AtomicBoolean(); + final AtomicReference error = new AtomicReference(); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + + } + + @Override + public void onError(Throwable e) { + error.lazySet(e); + } + + @Override + public void onCompleted() { + unsubscribedFirst.set(unsubscribe.get() != 0); + } + }); + + Assert.assertEquals(1, unsubscribe.get()); + Assert.assertFalse("Disposed first", unsubscribedFirst.get()); + Assert.assertNull(error.get()); + } + + @Test(timeout = 1000) + public void usingErrorEager() { + final AtomicInteger unsubscribe = new AtomicInteger(); + + Completable c = Completable.using(new Func0() { + @Override + public Integer call() { + return 1; + } + }, new Func1() { + @Override + public Completable call(Integer v) { + return error.completable; + } + }, new Action1() { + @Override + public void call(Integer d) { + unsubscribe.set(d); + } + }); + + final AtomicBoolean unsubscribedFirst = new AtomicBoolean(); + final AtomicBoolean complete = new AtomicBoolean(); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + + } + + @Override + public void onError(Throwable e) { + unsubscribedFirst.set(unsubscribe.get() != 0); + } + + @Override + public void onCompleted() { + complete.set(true); + } + }); + + Assert.assertEquals(1, unsubscribe.get()); + Assert.assertTrue("Not unsubscribed first", unsubscribedFirst.get()); + Assert.assertFalse(complete.get()); + } + + @Test(timeout = 1000) + public void usingErrorLazy() { + final AtomicInteger unsubscribe = new AtomicInteger(); + + Completable c = Completable.using(new Func0() { + @Override + public Integer call() { + return 1; + } + }, new Func1() { + @Override + public Completable call(Integer v) { + return error.completable; + } + }, new Action1() { + @Override + public void call(Integer d) { + unsubscribe.set(d); + } + }, false); + + final AtomicBoolean unsubscribedFirst = new AtomicBoolean(); + final AtomicBoolean complete = new AtomicBoolean(); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + + } + + @Override + public void onError(Throwable e) { + unsubscribedFirst.set(unsubscribe.get() != 0); + } + + @Override + public void onCompleted() { + complete.set(true); + } + }); + + Assert.assertEquals(1, unsubscribe.get()); + Assert.assertFalse("Disposed first", unsubscribedFirst.get()); + Assert.assertFalse(complete.get()); + } + + @Test(expected = NullPointerException.class) + public void usingResourceSupplierNull() { + Completable.using(null, new Func1() { + @Override + public Completable call(Object v) { + return normal.completable; + } + }, new Action1() { + @Override + public void call(Object v) { } + }); + } + + @Test(expected = NullPointerException.class) + public void usingMapperNull() { + Completable.using(new Func0() { + @Override + public Object call() { + return 1; + } + }, null, new Action1() { + @Override + public void call(Object v) { } + }); + } + + @Test(expected = NullPointerException.class) + public void usingMapperReturnsNull() { + Completable c = Completable.using(new Func0() { + @Override + public Object call() { + return 1; + } + }, new Func1() { + @Override + public Completable call(Object v) { + return null; + } + }, new Action1() { + @Override + public void call(Object v) { } + }); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void usingDisposeNull() { + Completable.using(new Func0() { + @Override + public Object call() { + return 1; + } + }, new Func1() { + @Override + public Completable call(Object v) { + return normal.completable; + } + }, null); + } + + @Test(expected = TestException.class) + public void usingResourceThrows() { + Completable c = Completable.using(new Func0() { + @Override + public Object call() { throw new TestException(); } + }, + new Func1() { + @Override + public Completable call(Object v) { + return normal.completable; + } + }, new Action1() { + @Override + public void call(Object v) { } + }); + + c.await(); + } + + @Test(expected = TestException.class) + public void usingMapperThrows() { + Completable c = Completable.using(new Func0() { + @Override + public Object call() { + return 1; + } + }, + new Func1() { + @Override + public Completable call(Object v) { throw new TestException(); } + }, new Action1() { + @Override + public void call(Object v) { } + }); + + c.await(); + } + + @Test(expected = TestException.class) + public void usingDisposerThrows() { + Completable c = Completable.using(new Func0() { + @Override + public Object call() { + return 1; + } + }, + new Func1() { + @Override + public Completable call(Object v) { + return normal.completable; + } + }, new Action1() { + @Override + public void call(Object v) { throw new TestException(); } + }); + + c.await(); + } + + @Test(timeout = 1000) + public void composeNormal() { + Completable c = error.completable.compose(new CompletableTransformer() { + @Override + public Completable call(Completable n) { + return n.onErrorComplete(); + } + }); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void composeNull() { + error.completable.compose(null); + } + + @Test(timeout = 1000) + public void concatWithNormal() { + Completable c = normal.completable.concatWith(normal.completable); + + c.await(); + + normal.assertSubscriptions(2); + } + + @Test(timeout = 1000, expected = TestException.class) + public void concatWithError() { + Completable c = normal.completable.concatWith(error.completable); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void concatWithNull() { + normal.completable.concatWith(null); + } + + @Test(expected = NullPointerException.class) + public void delayUnitNull() { + normal.completable.delay(1, null); + } + + @Test(expected = NullPointerException.class) + public void delaySchedulerNull() { + normal.completable.delay(1, TimeUnit.SECONDS, null); + } + + @Test(timeout = 1000) + public void delayNormal() throws InterruptedException { + Completable c = normal.completable.delay(250, TimeUnit.MILLISECONDS); + + final AtomicBoolean done = new AtomicBoolean(); + final AtomicReference error = new AtomicReference(); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + + } + + @Override + public void onError(Throwable e) { + error.set(e); + } + + @Override + public void onCompleted() { + done.set(true); + } + }); + + Thread.sleep(100); + + Assert.assertFalse("Already done", done.get()); + + Thread.sleep(200); + + Assert.assertTrue("Not done", done.get()); + + Assert.assertNull(error.get()); + } + + @Test(timeout = 1000) + public void delayErrorImmediately() throws InterruptedException { + Completable c = error.completable.delay(250, TimeUnit.MILLISECONDS); + + final AtomicBoolean done = new AtomicBoolean(); + final AtomicReference error = new AtomicReference(); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + + } + + @Override + public void onError(Throwable e) { + error.set(e); + } + + @Override + public void onCompleted() { + done.set(true); + } + }); + + Assert.assertTrue(error.get().toString(), error.get() instanceof TestException); + Assert.assertFalse("Already done", done.get()); + + Thread.sleep(100); + + Assert.assertFalse("Already done", done.get()); + + Thread.sleep(200); + } + + @Test(timeout = 1000) + public void delayErrorToo() throws InterruptedException { + Completable c = error.completable.delay(250, TimeUnit.MILLISECONDS, Schedulers.computation(), true); + + final AtomicBoolean done = new AtomicBoolean(); + final AtomicReference error = new AtomicReference(); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + + } + + @Override + public void onError(Throwable e) { + error.set(e); + } + + @Override + public void onCompleted() { + done.set(true); + } + }); + + Thread.sleep(100); + + Assert.assertFalse("Already done", done.get()); + Assert.assertNull(error.get()); + + Thread.sleep(200); + + Assert.assertFalse("Already done", done.get()); + Assert.assertTrue(error.get() instanceof TestException); + } + + @Test(timeout = 1000) + public void doOnCompleteNormal() { + final AtomicInteger calls = new AtomicInteger(); + + Completable c = normal.completable.doOnComplete(new Action0() { + @Override + public void call() { + calls.getAndIncrement(); + } + }); + + c.await(); + + Assert.assertEquals(1, calls.get()); + } + + @Test(timeout = 1000) + public void doOnCompleteError() { + final AtomicInteger calls = new AtomicInteger(); + + Completable c = error.completable.doOnComplete(new Action0() { + @Override + public void call() { + calls.getAndIncrement(); + } + }); + + try { + c.await(); + Assert.fail("Failed to throw TestException"); + } catch (TestException ex) { + // expected + } + + Assert.assertEquals(0, calls.get()); + } + + @Test(expected = NullPointerException.class) + public void doOnCompleteNull() { + normal.completable.doOnComplete(null); + } + + @Test(timeout = 1000, expected = TestException.class) + public void doOnCompleteThrows() { + Completable c = normal.completable.doOnComplete(new Action0() { + @Override + public void call() { throw new TestException(); } + }); + + c.await(); + } + + @Test(timeout = 1000) + public void doOnDisposeNormalDoesntCall() { + final AtomicInteger calls = new AtomicInteger(); + + Completable c = normal.completable.doOnUnsubscribe(new Action0() { + @Override + public void call() { + calls.getAndIncrement(); + } + }); + + c.await(); + + Assert.assertEquals(0, calls.get()); + } + + @Test(timeout = 1000) + public void doOnDisposeErrorDoesntCall() { + final AtomicInteger calls = new AtomicInteger(); + + Completable c = error.completable.doOnUnsubscribe(new Action0() { + @Override + public void call() { + calls.getAndIncrement(); + } + }); + + try { + c.await(); + Assert.fail("No exception thrown"); + } catch (TestException ex) { + // expected + } + Assert.assertEquals(0, calls.get()); + } + + @Test(timeout = 1000) + public void doOnDisposeChildCancels() { + final AtomicInteger calls = new AtomicInteger(); + + Completable c = normal.completable.doOnUnsubscribe(new Action0() { + @Override + public void call() { + calls.getAndIncrement(); + } + }); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + d.unsubscribe(); + } + + @Override + public void onError(Throwable e) { + // ignored + } + + @Override + public void onCompleted() { + // ignored + } + }); + + Assert.assertEquals(1, calls.get()); + } + + @Test(expected = NullPointerException.class) + public void doOnDisposeNull() { + normal.completable.doOnUnsubscribe(null); + } + + @Test(timeout = 1000) + public void doOnDisposeThrows() { + Completable c = normal.completable.doOnUnsubscribe(new Action0() { + @Override + public void call() { throw new TestException(); } + }); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + d.unsubscribe(); + } + + @Override + public void onError(Throwable e) { + // ignored + } + + @Override + public void onCompleted() { + // ignored + } + }); + } + + @Test(timeout = 1000) + public void doOnErrorNoError() { + final AtomicReference error = new AtomicReference(); + + Completable c = normal.completable.doOnError(new Action1() { + @Override + public void call(Throwable e) { + error.set(e); + } + }); + + c.await(); + + Assert.assertNull(error.get()); + } + + @Test(timeout = 1000) + public void doOnErrorHasError() { + final AtomicReference err = new AtomicReference(); + + Completable c = error.completable.doOnError(new Action1() { + @Override + public void call(Throwable e) { + err.set(e); + } + }); + + try { + c.await(); + Assert.fail("Did not throw exception"); + } catch (Throwable e) { + // expected + } + + Assert.assertTrue(err.get() instanceof TestException); + } + + @Test(expected = NullPointerException.class) + public void doOnErrorNull() { + normal.completable.doOnError(null); + } + + @Test(timeout = 1000) + public void doOnErrorThrows() { + Completable c = error.completable.doOnError(new Action1() { + @Override + public void call(Throwable e) { throw new IllegalStateException(); } + }); + + try { + c.await(); + } catch (IllegalStateException ex) { + Throwable[] a = ex.getSuppressed(); + Assert.assertEquals(1, a.length); + Assert.assertTrue(a[0] instanceof TestException); + } + } + + @Test(timeout = 1000) + public void doOnSubscribeNormal() { + final AtomicInteger calls = new AtomicInteger(); + + Completable c = normal.completable.doOnSubscribe(new Action1() { + @Override + public void call(Subscription s) { + calls.getAndIncrement(); + } + }); + + for (int i = 0; i < 10; i++) { + c.await(); + } + + Assert.assertEquals(10, calls.get()); + } + + @Test(expected = NullPointerException.class) + public void doOnSubscribeNull() { + normal.completable.doOnSubscribe(null); + } + + @Test(expected = TestException.class) + public void doOnSubscribeThrows() { + Completable c = normal.completable.doOnSubscribe(new Action1() { + @Override + public void call(Subscription d) { throw new TestException(); } + }); + + c.await(); + } + + @Test(timeout = 1000) + public void doOnTerminateNormal() { + final AtomicInteger calls = new AtomicInteger(); + + Completable c = normal.completable.doOnTerminate(new Action0() { + @Override + public void call() { + calls.getAndIncrement(); + } + }); + + c.await(); + + Assert.assertEquals(1, calls.get()); + } + + @Test(timeout = 1000) + public void doOnTerminateError() { + final AtomicInteger calls = new AtomicInteger(); + + Completable c = error.completable.doOnTerminate(new Action0() { + @Override + public void call() { + calls.getAndIncrement(); + } + }); + + try { + c.await(); + Assert.fail("Did dot throw exception"); + } catch (TestException ex) { + // expected + } + + Assert.assertEquals(1, calls.get()); + } + + @Test(timeout = 1000) + public void finallyDoNormal() { + final AtomicBoolean doneAfter = new AtomicBoolean(); + final AtomicBoolean complete = new AtomicBoolean(); + + Completable c = normal.completable.finallyDo(new Action0() { + @Override + public void call() { + doneAfter.set(complete.get()); + } + }); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onCompleted() { + complete.set(true); + } + }); + + c.await(); + + Assert.assertTrue("Not completed", complete.get()); + Assert.assertTrue("Finally called before onComplete", doneAfter.get()); + } + + @Test(timeout = 1000) + public void finallyDoWithError() { + final AtomicBoolean doneAfter = new AtomicBoolean(); + + Completable c = error.completable.finallyDo(new Action0() { + @Override + public void call() { + doneAfter.set(true); + } + }); + + try { + c.await(); + Assert.fail("Did not throw TestException"); + } catch (TestException ex) { + // expected + } + + Assert.assertFalse("FinallyDo called", doneAfter.get()); + } + + @Test(expected = NullPointerException.class) + public void finallyDoNull() { + normal.completable.finallyDo(null); + } + + @Test(timeout = 1000) + public void getNormal() { + Assert.assertNull(normal.completable.get()); + } + + @Test(timeout = 1000) + public void getError() { + Assert.assertTrue(error.completable.get() instanceof TestException); + } + + @Test(timeout = 1000) + public void getTimeout() { + try { + Completable.never().get(100, TimeUnit.MILLISECONDS); + } catch (RuntimeException ex) { + if (!(ex.getCause() instanceof TimeoutException)) { + Assert.fail("Wrong exception cause: " + ex.getCause()); + } + } + } + + @Test(expected = NullPointerException.class) + public void getNullUnit() { + normal.completable.get(1, null); + } + + @Test(expected = NullPointerException.class) + public void liftNull() { + normal.completable.lift(null); + } + + @Test(timeout = 1000, expected = NullPointerException.class) + public void liftReturnsNull() { + Completable c = normal.completable.lift(new CompletableOperator() { + @Override + public CompletableSubscriber call(CompletableSubscriber v) { + return null; + } + }); + + c.await(); + } + + final static class CompletableOperatorSwap implements CompletableOperator { + @Override + public CompletableSubscriber call(final CompletableSubscriber v) { + return new CompletableSubscriber() { + + @Override + public void onCompleted() { + v.onError(new TestException()); + } + + @Override + public void onError(Throwable e) { + v.onCompleted(); + } + + @Override + public void onSubscribe(Subscription d) { + v.onSubscribe(d); + } + + }; + } + } + @Test(timeout = 1000, expected = TestException.class) + public void liftOnCompleteError() { + Completable c = normal.completable.lift(new CompletableOperatorSwap()); + + c.await(); + } + + @Test(timeout = 1000) + public void liftOnErrorComplete() { + Completable c = error.completable.lift(new CompletableOperatorSwap()); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void mergeWithNull() { + normal.completable.mergeWith(null); + } + + @Test(timeout = 1000) + public void mergeWithNormal() { + Completable c = normal.completable.mergeWith(normal.completable); + + c.await(); + + normal.assertSubscriptions(2); + } + + @Test(expected = NullPointerException.class) + public void observeOnNull() { + normal.completable.observeOn(null); + } + + @Test(timeout = 1000) + public void observeOnNormal() throws InterruptedException { + final AtomicReference name = new AtomicReference(); + final AtomicReference err = new AtomicReference(); + final CountDownLatch cdl = new CountDownLatch(1); + + Completable c = normal.completable.observeOn(Schedulers.computation()); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + + } + + @Override + public void onCompleted() { + name.set(Thread.currentThread().getName()); + cdl.countDown(); + } + + @Override + public void onError(Throwable e) { + err.set(e); + cdl.countDown(); + } + }); + + cdl.await(); + + Assert.assertNull(err.get()); + Assert.assertTrue(name.get().startsWith("RxComputation")); + } + + @Test(timeout = 1000) + public void observeOnError() throws InterruptedException { + final AtomicReference name = new AtomicReference(); + final AtomicReference err = new AtomicReference(); + final CountDownLatch cdl = new CountDownLatch(1); + + Completable c = error.completable.observeOn(Schedulers.computation()); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(Subscription d) { + + } + + @Override + public void onCompleted() { + name.set(Thread.currentThread().getName()); + cdl.countDown(); + } + + @Override + public void onError(Throwable e) { + name.set(Thread.currentThread().getName()); + err.set(e); + cdl.countDown(); + } + }); + + cdl.await(); + + Assert.assertTrue(err.get() instanceof TestException); + Assert.assertTrue(name.get().startsWith("RxComputation")); + } + + @Test(timeout = 1000) + public void onErrorComplete() { + Completable c = error.completable.onErrorComplete(); + + c.await(); + } + + @Test(timeout = 1000, expected = TestException.class) + public void onErrorCompleteFalse() { + Completable c = error.completable.onErrorComplete(new Func1() { + @Override + public Boolean call(Throwable e) { + return e instanceof IllegalStateException; + } + }); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void onErrorCompleteNull() { + error.completable.onErrorComplete(null); + } + + @Test(expected = NullPointerException.class) + public void onErrorResumeNextNull() { + error.completable.onErrorResumeNext(null); + } + + @Test(timeout = 1000) + public void onErrorResumeNextFunctionReturnsNull() { + Completable c = error.completable.onErrorResumeNext(new Func1() { + @Override + public Completable call(Throwable e) { + return null; + } + }); + + try { + c.await(); + Assert.fail("Did not throw an exception"); + } catch (NullPointerException ex) { + Throwable[] a = ex.getSuppressed(); + + Assert.assertEquals(1, a.length); + Assert.assertTrue(a[0] instanceof TestException); + } + } + + @Test(timeout = 1000) + public void onErrorResumeNextFunctionThrows() { + Completable c = error.completable.onErrorResumeNext(new Func1() { + @Override + public Completable call(Throwable e) { throw new TestException(); } + }); + + try { + c.await(); + Assert.fail("Did not throw an exception"); + } catch (TestException ex) { + Throwable[] a = ex.getSuppressed(); + + Assert.assertEquals(1, a.length); + Assert.assertTrue(a[0] instanceof TestException); + } + } + + @Test(timeout = 1000) + public void onErrorResumeNextNormal() { + Completable c = error.completable.onErrorResumeNext(new Func1() { + @Override + public Completable call(Throwable v) { + return normal.completable; + } + }); + + c.await(); + } + + @Test(timeout = 1000, expected = TestException.class) + public void onErrorResumeNextError() { + Completable c = error.completable.onErrorResumeNext(new Func1() { + @Override + public Completable call(Throwable v) { + return error.completable; + } + }); + + c.await(); + } + + @Test(timeout = 2000) + public void repeatNormal() { + final AtomicReference err = new AtomicReference(); + final AtomicInteger calls = new AtomicInteger(); + + Completable c = Completable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + calls.getAndIncrement(); + Thread.sleep(100); + return null; + } + }).repeat(); + + c.subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(final Subscription d) { + final Scheduler.Worker w = Schedulers.io().createWorker(); + w.schedule(new Action0() { + @Override + public void call() { + try { + d.unsubscribe(); + } finally { + w.unsubscribe(); + } + } + }, 550, TimeUnit.MILLISECONDS); + } + + @Override + public void onError(Throwable e) { + err.set(e); + } + + @Override + public void onCompleted() { + + } + }); + + Assert.assertEquals(6, calls.get()); + Assert.assertNull(err.get()); + } + + @Test(timeout = 1000, expected = TestException.class) + public void repeatError() { + Completable c = error.completable.repeat(); + + c.await(); + } + + @Test(timeout = 1000) + public void repeat5Times() { + final AtomicInteger calls = new AtomicInteger(); + + Completable c = Completable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + calls.getAndIncrement(); + return null; + } + }).repeat(5); + + c.await(); + + Assert.assertEquals(5, calls.get()); + } + + @Test(timeout = 1000) + public void repeat1Time() { + final AtomicInteger calls = new AtomicInteger(); + + Completable c = Completable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + calls.getAndIncrement(); + return null; + } + }).repeat(1); + + c.await(); + + Assert.assertEquals(1, calls.get()); + } + + @Test(timeout = 1000) + public void repeat0Time() { + final AtomicInteger calls = new AtomicInteger(); + + Completable c = Completable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + calls.getAndIncrement(); + return null; + } + }).repeat(0); + + c.await(); + + Assert.assertEquals(0, calls.get()); + } + + @Test(expected = NullPointerException.class) + public void repeatWhenNull() { + normal.completable.repeatWhen(null); + } + + @Test(timeout = 1000) + public void retryNormal() { + Completable c = normal.completable.retry(); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 1000) + public void retry5Times() { + final AtomicInteger calls = new AtomicInteger(5); + + Completable c = Completable.fromAction(new Action0() { + @Override + public void call() { + if (calls.decrementAndGet() != 0) { + throw new TestException(); + } + } + }).retry(); + + c.await(); + } + + @Test(timeout = 1000, expected = TestException.class) + public void retryBiPredicate5Times() { + Completable c = error.completable.retry(new Func2() { + @Override + public Boolean call(Integer n, Throwable e) { + return n < 5; + } + }); + + c.await(); + } + + @Test(timeout = 1000, expected = TestException.class) + public void retryTimes5Error() { + Completable c = error.completable.retry(5); + + c.await(); + } + + @Test(timeout = 1000) + public void retryTimes5Normal() { + final AtomicInteger calls = new AtomicInteger(5); + + Completable c = Completable.fromAction(new Action0() { + @Override + public void call() { + if (calls.decrementAndGet() != 0) { + throw new TestException(); + } + } + }).retry(5); + + c.await(); + } + + @Test(expected = IllegalArgumentException.class) + public void retryNegativeTimes() { + normal.completable.retry(-1); + } + + @Test(timeout = 1000) + public void retryWhen5Times() { + final AtomicInteger calls = new AtomicInteger(5); + + Completable c = Completable.fromAction(new Action0() { + @Override + public void call() { + if (calls.decrementAndGet() != 0) { + throw new TestException(); + } + } + }).retryWhen(new Func1, Observable>() { + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Observable call(Observable o) { + return (Observable)o; + } + }); + + c.await(); + } + + @Test(timeout = 1000) + public void subscribe() throws InterruptedException { + final AtomicBoolean complete = new AtomicBoolean(); + + Completable c = normal.completable + .delay(100, TimeUnit.MILLISECONDS) + .doOnComplete(new Action0() { + @Override + public void call() { + complete.set(true); + } + }); + + c.subscribe(); + + Thread.sleep(150); + + Assert.assertTrue("Not completed", complete.get()); + } + + @Test(timeout = 1000) + public void subscribeDispose() throws InterruptedException { + final AtomicBoolean complete = new AtomicBoolean(); + + Completable c = normal.completable + .delay(200, TimeUnit.MILLISECONDS) + .doOnComplete(new Action0() { + @Override + public void call() { + complete.set(true); + } + }); + + Subscription d = c.subscribe(); + + Thread.sleep(100); + + d.unsubscribe(); + + Thread.sleep(150); + + Assert.assertFalse("Completed", complete.get()); + } + + @Test(timeout = 1000) + public void subscribeTwoCallbacksNormal() { + final AtomicReference err = new AtomicReference(); + final AtomicBoolean complete = new AtomicBoolean(); + normal.completable.subscribe(new Action1() { + @Override + public void call(Throwable e) { + err.set(e); + } + }, new Action0() { + @Override + public void call() { + complete.set(true); + } + }); + + Assert.assertNull(err.get()); + Assert.assertTrue("Not completed", complete.get()); + } + + @Test(timeout = 1000) + public void subscribeTwoCallbacksError() { + final AtomicReference err = new AtomicReference(); + final AtomicBoolean complete = new AtomicBoolean(); + error.completable.subscribe(new Action1() { + @Override + public void call(Throwable e) { + err.set(e); + } + }, new Action0() { + @Override + public void call() { + complete.set(true); + } + }); + + Assert.assertTrue(err.get() instanceof TestException); + Assert.assertFalse("Not completed", complete.get()); + } + + @Test(expected = NullPointerException.class) + public void subscribeTwoCallbacksFirstNull() { + normal.completable.subscribe(null, new Action0() { + @Override + public void call() { } + }); + } + + @Test(expected = NullPointerException.class) + public void subscribeTwoCallbacksSecondNull() { + normal.completable.subscribe(null, new Action0() { + @Override + public void call() { } + }); + } + + @Test(timeout = 1000) + public void subscribeTwoCallbacksCompleteThrows() { + final AtomicReference err = new AtomicReference(); + normal.completable.subscribe(new Action1() { + @Override + public void call(Throwable e) { + err.set(e); + } + }, new Action0() { + @Override + public void call() { throw new TestException(); } + }); + + Assert.assertTrue(String.valueOf(err.get()), err.get() instanceof TestException); + } + + @Test(timeout = 1000) + public void subscribeTwoCallbacksOnErrorThrows() { + error.completable.subscribe(new Action1() { + @Override + public void call(Throwable e) { throw new TestException(); } + }, new Action0() { + @Override + public void call() { } + }); + } + + @Test(timeout = 1000) + public void subscribeActionNormal() { + final AtomicBoolean run = new AtomicBoolean(); + + normal.completable.subscribe(new Action0() { + @Override + public void call() { + run.set(true); + } + }); + + Assert.assertTrue("Not completed", run.get()); + } + + @Test(timeout = 1000) + public void subscribeActionError() { + final AtomicBoolean run = new AtomicBoolean(); + + error.completable.subscribe(new Action0() { + @Override + public void call() { + run.set(true); + } + }); + + Assert.assertFalse("Completed", run.get()); + } + + @Test(expected = NullPointerException.class) + public void subscribeActionNull() { + normal.completable.subscribe((Action0)null); + } + + @Test(expected = NullPointerException.class) + public void subscribeSubscriberNull() { + normal.completable.subscribe((Subscriber)null); + } + + @Test(expected = NullPointerException.class) + public void subscribeCompletableSubscriberNull() { + normal.completable.subscribe((CompletableSubscriber)null); + } + + @Test(timeout = 1000) + public void subscribeSubscriberNormal() { + TestSubscriber ts = new TestSubscriber(); + + normal.completable.subscribe(ts); + + ts.assertCompleted(); + ts.assertNoValues(); + ts.assertNoErrors(); + } + + @Test(timeout = 1000) + public void subscribeSubscriberError() { + TestSubscriber ts = new TestSubscriber(); + + error.completable.subscribe(ts); + + ts.assertNotCompleted(); + ts.assertNoValues(); + ts.assertError(TestException.class); + } + + @Test(expected = NullPointerException.class) + public void subscribeOnNull() { + normal.completable.subscribeOn(null); + } + + @Test(timeout = 1000) + public void subscribeOnNormal() { + final AtomicReference name = new AtomicReference(); + + Completable c = Completable.create(new CompletableOnSubscribe() { + @Override + public void call(CompletableSubscriber s) { + name.set(Thread.currentThread().getName()); + s.onSubscribe(Subscriptions.unsubscribed()); + s.onCompleted(); + } + }).subscribeOn(Schedulers.computation()); + + c.await(); + + Assert.assertTrue(name.get().startsWith("RxComputation")); + } + + @Test(timeout = 1000) + public void subscribeOnError() { + final AtomicReference name = new AtomicReference(); + + Completable c = Completable.create(new CompletableOnSubscribe() { + @Override + public void call(CompletableSubscriber s) { + name.set(Thread.currentThread().getName()); + s.onSubscribe(Subscriptions.unsubscribed()); + s.onError(new TestException()); + } + }).subscribeOn(Schedulers.computation()); + + try { + c.await(); + Assert.fail("No exception thrown"); + } catch (TestException ex) { + // expected + } + + Assert.assertTrue(name.get().startsWith("RxComputation")); + } + + @Test(timeout = 1000) + public void timeoutEmitError() { + Throwable e = Completable.never().timeout(100, TimeUnit.MILLISECONDS).get(); + + Assert.assertTrue(e instanceof TimeoutException); + } + + @Test(timeout = 1000) + public void timeoutSwitchNormal() { + Completable c = Completable.never().timeout(100, TimeUnit.MILLISECONDS, normal.completable); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 1000) + public void timeoutTimerCancelled() throws InterruptedException { + Completable c = Completable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + Thread.sleep(50); + return null; + } + }).timeout(100, TimeUnit.MILLISECONDS, normal.completable); + + c.await(); + + Thread.sleep(100); + + normal.assertSubscriptions(0); + } + + @Test(expected = NullPointerException.class) + public void timeoutUnitNull() { + normal.completable.timeout(1, null); + } + + @Test(expected = NullPointerException.class) + public void timeoutSchedulerNull() { + normal.completable.timeout(1, TimeUnit.SECONDS, (Scheduler)null); + } + + @Test(expected = NullPointerException.class) + public void timeoutOtherNull() { + normal.completable.timeout(1, TimeUnit.SECONDS, (Completable)null); + } + + @Test(timeout = 1000) + public void toNormal() { + Observable flow = normal.completable.to(new Func1>() { + @Override + public Observable call(Completable c) { + return c.toObservable(); + } + }); + + flow.toBlocking().forEach(new Action1(){ + @Override + public void call(Object e){ } + }); + } + + @Test(expected = NullPointerException.class) + public void toNull() { + normal.completable.to(null); + } + + @Test(timeout = 1000) + public void toObservableNormal() { + normal.completable.toObservable().toBlocking().forEach(new Action1() { + @Override + public void call(Object e) { } + }); + } + + @Test(timeout = 1000, expected = TestException.class) + public void toObservableError() { + error.completable.toObservable().toBlocking().forEach(new Action1() { + @Override + public void call(Object e) { } + }); + } + + static T get(Single single) { + final CountDownLatch cdl = new CountDownLatch(1); + + final AtomicReference v = new AtomicReference(); + final AtomicReference e = new AtomicReference(); + + single.subscribe(new SingleSubscriber() { + + @Override + public void onSuccess(T value) { + v.set(value); + cdl.countDown(); + } + + @Override + public void onError(Throwable error) { + e.set(error); + cdl.countDown(); + } + }); + + try { + cdl.await(); + } catch (InterruptedException ex) { + Exceptions.propagate(ex); + } + + if (e.get() != null) { + Exceptions.propagate(e.get()); + } + return v.get(); + } + + @Test(timeout = 1000) + public void toSingleSupplierNormal() { + int v = get(normal.completable.toSingle(new Func0() { + @Override + public Integer call() { + return 1; + } + })); + + Assert.assertEquals(1, v); + } + + @Test(timeout = 1000, expected = TestException.class) + public void toSingleSupplierError() { + get(error.completable.toSingle(new Func0() { + @Override + public Object call() { + return 1; + } + })); + } + + @Test(expected = NullPointerException.class) + public void toSingleSupplierNull() { + normal.completable.toSingle(null); + } + + @Test(expected = NullPointerException.class) + public void toSingleSupplierReturnsNull() { + get(normal.completable.toSingle(new Func0() { + @Override + public Object call() { + return null; + } + })); + } + + @Test(expected = TestException.class) + public void toSingleSupplierThrows() { + get(normal.completable.toSingle(new Func0() { + @Override + public Object call() { throw new TestException(); } + })); + } + + @Test(timeout = 1000, expected = TestException.class) + public void toSingleDefaultError() { + get(error.completable.toSingleDefault(1)); + } + + @Test(timeout = 1000) + public void toSingleDefaultNormal() { + Assert.assertEquals((Integer)1, get(normal.completable.toSingleDefault(1))); + } + + @Test(expected = NullPointerException.class) + public void toSingleDefaultNull() { + normal.completable.toSingleDefault(null); + } + + @Test(timeout = 1000) + public void unsubscribeOnNormal() throws InterruptedException { + final AtomicReference name = new AtomicReference(); + final CountDownLatch cdl = new CountDownLatch(1); + + normal.completable.delay(1, TimeUnit.SECONDS) + .doOnUnsubscribe(new Action0() { + @Override + public void call() { + name.set(Thread.currentThread().getName()); + cdl.countDown(); + } + }) + .unsubscribeOn(Schedulers.computation()) + .subscribe(new CompletableSubscriber() { + @Override + public void onSubscribe(final Subscription d) { + final Scheduler.Worker w = Schedulers.io().createWorker(); + + w.schedule(new Action0() { + @Override + public void call() { + try { + d.unsubscribe(); + } finally { + w.unsubscribe(); + } + } + }, 100, TimeUnit.MILLISECONDS); + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onCompleted() { + + } + }); + + cdl.await(); + + Assert.assertTrue(name.get().startsWith("RxComputation")); + } + + @Test(expected = NullPointerException.class) + public void ambArrayNull() { + Completable.amb((Completable[])null); + } + + @Test(timeout = 1000) + public void ambArrayEmpty() { + Completable c = Completable.amb(); + + c.await(); + } + + @Test(timeout = 1000) + public void ambArraySingleNormal() { + Completable c = Completable.amb(normal.completable); + + c.await(); + } + + @Test(timeout = 1000, expected = TestException.class) + public void ambArraySingleError() { + Completable c = Completable.amb(error.completable); + + c.await(); + } + + @Test(timeout = 1000) + public void ambArrayOneFires() { + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + Completable c1 = Completable.fromObservable(ps1); + + Completable c2 = Completable.fromObservable(ps2); + + Completable c = Completable.amb(c1, c2); + + final AtomicBoolean complete = new AtomicBoolean(); + + c.subscribe(new Action0() { + @Override + public void call() { + complete.set(true); + } + }); + + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); + Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); + + ps1.onCompleted(); + + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); + Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); + + Assert.assertTrue("Not completed", complete.get()); + } + + @Test(timeout = 1000) + public void ambArrayOneFiresError() { + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + Completable c1 = Completable.fromObservable(ps1); + + Completable c2 = Completable.fromObservable(ps2); + + Completable c = Completable.amb(c1, c2); + + final AtomicReference complete = new AtomicReference(); + + c.subscribe(new Action1() { + @Override + public void call(Throwable e) { + complete.set(e); + } + }, new Action0() { + @Override + public void call() { } + }); + + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); + Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); + + ps1.onError(new TestException()); + + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); + Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); + + Assert.assertTrue("Not completed", complete.get() instanceof TestException); + } + + @Test(timeout = 1000) + public void ambArraySecondFires() { + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + Completable c1 = Completable.fromObservable(ps1); + + Completable c2 = Completable.fromObservable(ps2); + + Completable c = Completable.amb(c1, c2); + + final AtomicBoolean complete = new AtomicBoolean(); + + c.subscribe(new Action0() { + @Override + public void call() { + complete.set(true); + } + }); + + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); + Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); + + ps2.onCompleted(); + + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); + Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); + + Assert.assertTrue("Not completed", complete.get()); + } + + @Test(timeout = 1000) + public void ambArraySecondFiresError() { + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + Completable c1 = Completable.fromObservable(ps1); + + Completable c2 = Completable.fromObservable(ps2); + + Completable c = Completable.amb(c1, c2); + + final AtomicReference complete = new AtomicReference(); + + c.subscribe(new Action1() { + @Override + public void call(Throwable e) { + complete.set(e); + } + }, new Action0() { + @Override + public void call() { } + }); + + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); + Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); + + ps2.onError(new TestException()); + + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); + Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); + + Assert.assertTrue("Not completed", complete.get() instanceof TestException); + } + + @Test(timeout = 1000, expected = NullPointerException.class) + public void ambMultipleOneIsNull() { + Completable c = Completable.amb(null, normal.completable); + + c.await(); + } + + @Test(timeout = 1000) + public void ambIterableEmpty() { + Completable c = Completable.amb(Collections.emptyList()); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void ambIterableNull() { + Completable.amb((Iterable)null); + } + + @Test(timeout = 1000, expected = NullPointerException.class) + public void ambIterableIteratorNull() { + Completable c = Completable.amb(new Iterable() { + @Override + public Iterator iterator() { + return null; + } + }); + + c.await(); + } + + @Test(timeout = 1000, expected = NullPointerException.class) + public void ambIterableWithNull() { + Completable c = Completable.amb(Arrays.asList(null, normal.completable)); + + c.await(); + } + + @Test(timeout = 1000) + public void ambIterableSingle() { + Completable c = Completable.amb(Collections.singleton(normal.completable)); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 1000) + public void ambIterableMany() { + Completable c = Completable.amb(Arrays.asList(normal.completable, normal.completable, normal.completable)); + + c.await(); + + normal.assertSubscriptions(1); + } + + @Test(timeout = 1000, expected = TestException.class) + public void ambIterableOneThrows() { + Completable c = Completable.amb(Collections.singleton(error.completable)); + + c.await(); + } + + @Test(timeout = 1000, expected = TestException.class) + public void ambIterableManyOneThrows() { + Completable c = Completable.amb(Arrays.asList(error.completable, normal.completable)); + + c.await(); + } + + @Test(expected = TestException.class) + public void ambIterableIterableThrows() { + Completable c = Completable.amb(new Iterable() { + @Override + public Iterator iterator() { + throw new TestException(); + } + }); + + c.await(); + } + + @Test(expected = TestException.class) + public void ambIterableIteratorHasNextThrows() { + Completable c = Completable.amb(new IterableIteratorHasNextThrows()); + + c.await(); + } + + @Test(expected = TestException.class) + public void ambIterableIteratorNextThrows() { + Completable c = Completable.amb(new IterableIteratorNextThrows()); + + c.await(); + } + + @Test(expected = NullPointerException.class) + public void ambWithNull() { + normal.completable.ambWith(null); + } + + @Test(timeout = 1000) + public void ambWithArrayOneFires() { + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + Completable c1 = Completable.fromObservable(ps1); + + Completable c2 = Completable.fromObservable(ps2); + + Completable c = c1.ambWith(c2); + + final AtomicBoolean complete = new AtomicBoolean(); + + c.subscribe(new Action0() { + @Override + public void call() { + complete.set(true); + } + }); + + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); + Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); + + ps1.onCompleted(); + + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); + Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); + + Assert.assertTrue("Not completed", complete.get()); + } + + @Test(timeout = 1000) + public void ambWithArrayOneFiresError() { + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + Completable c1 = Completable.fromObservable(ps1); + + Completable c2 = Completable.fromObservable(ps2); + + Completable c = c1.ambWith(c2); + + final AtomicReference complete = new AtomicReference(); + + c.subscribe(new Action1() { + @Override + public void call(Throwable e) { + complete.set(e); + } + }, new Action0() { + @Override + public void call() { } + }); + + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); + Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); + + ps1.onError(new TestException()); + + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); + Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); + + Assert.assertTrue("Not completed", complete.get() instanceof TestException); + } + + @Test(timeout = 1000) + public void ambWithArraySecondFires() { + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + Completable c1 = Completable.fromObservable(ps1); + + Completable c2 = Completable.fromObservable(ps2); + + Completable c = c1.ambWith(c2); + + final AtomicBoolean complete = new AtomicBoolean(); + + c.subscribe(new Action0() { + @Override + public void call() { + complete.set(true); + } + }); + + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); + Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); + + ps2.onCompleted(); + + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); + Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); + + Assert.assertTrue("Not completed", complete.get()); + } + + @Test(timeout = 1000) + public void ambWithArraySecondFiresError() { + PublishSubject ps1 = PublishSubject.create(); + PublishSubject ps2 = PublishSubject.create(); + + Completable c1 = Completable.fromObservable(ps1); + + Completable c2 = Completable.fromObservable(ps2); + + Completable c = c1.ambWith(c2); + + final AtomicReference complete = new AtomicReference(); + + c.subscribe(new Action1() { + @Override + public void call(Throwable e) { + complete.set(e); + } + }, new Action0() { + @Override + public void call() { } + }); + + Assert.assertTrue("First subject no subscribers", ps1.hasObservers()); + Assert.assertTrue("Second subject no subscribers", ps2.hasObservers()); + + ps2.onError(new TestException()); + + Assert.assertFalse("First subject has subscribers", ps1.hasObservers()); + Assert.assertFalse("Second subject has subscribers", ps2.hasObservers()); + + Assert.assertTrue("Not completed", complete.get() instanceof TestException); + } + + @Test(timeout = 1000) + public void startWithCompletableNormal() { + final AtomicBoolean run = new AtomicBoolean(); + Completable c = normal.completable + .startWith(Completable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + run.set(normal.get() == 0); + return null; + } + })); + + c.await(); + + Assert.assertTrue("Did not start with other", run.get()); + normal.assertSubscriptions(1); + } + + @Test(timeout = 1000) + public void startWithCompletableError() { + Completable c = normal.completable.startWith(error.completable); + + try { + c.await(); + Assert.fail("Did not throw TestException"); + } catch (TestException ex) { + normal.assertSubscriptions(0); + error.assertSubscriptions(1); + } + } + + @Test(timeout = 1000) + public void startWithFlowableNormal() { + final AtomicBoolean run = new AtomicBoolean(); + Observable c = normal.completable + .startWith(Observable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + run.set(normal.get() == 0); + return 1; + } + })); + + TestSubscriber ts = new TestSubscriber(); + + c.subscribe(ts); + + Assert.assertTrue("Did not start with other", run.get()); + normal.assertSubscriptions(1); + + ts.assertValue(1); + ts.assertCompleted(); + ts.assertNoErrors(); + } + + @Test(timeout = 1000) + public void startWithFlowableError() { + Observable c = normal.completable + .startWith(Observable.error(new TestException())); + + TestSubscriber ts = new TestSubscriber(); + + c.subscribe(ts); + + normal.assertSubscriptions(0); + + ts.assertNoValues(); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } + + @Test(expected = NullPointerException.class) + public void startWithCompletableNull() { + normal.completable.startWith((Completable)null); + } + + @Test(expected = NullPointerException.class) + public void startWithFlowableNull() { + normal.completable.startWith((Observable)null); + } + + @Test(expected = NullPointerException.class) + public void endWithCompletableNull() { + normal.completable.endWith((Completable)null); + } + + @Test(expected = NullPointerException.class) + public void endWithFlowableNull() { + normal.completable.endWith((Observable)null); + } + + @Test(timeout = 1000) + public void endWithCompletableNormal() { + final AtomicBoolean run = new AtomicBoolean(); + Completable c = normal.completable + .endWith(Completable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + run.set(normal.get() == 0); + return null; + } + })); + + c.await(); + + Assert.assertFalse("Start with other", run.get()); + normal.assertSubscriptions(1); + } + + @Test(timeout = 1000) + public void endWithCompletableError() { + Completable c = normal.completable.endWith(error.completable); + + try { + c.await(); + Assert.fail("Did not throw TestException"); + } catch (TestException ex) { + normal.assertSubscriptions(1); + error.assertSubscriptions(1); + } + } + + @Test(timeout = 1000) + public void endWithFlowableNormal() { + final AtomicBoolean run = new AtomicBoolean(); + Observable c = normal.completable + .endWith(Observable.fromCallable(new Callable() { + @Override + public Object call() throws Exception { + run.set(normal.get() == 0); + return 1; + } + })); + + TestSubscriber ts = new TestSubscriber(); + + c.subscribe(ts); + + Assert.assertFalse("Start with other", run.get()); + normal.assertSubscriptions(1); + + ts.assertValue(1); + ts.assertCompleted(); + ts.assertNoErrors(); + } + + @Test(timeout = 1000) + public void endWithFlowableError() { + Observable c = normal.completable + .endWith(Observable.error(new TestException())); + + TestSubscriber ts = new TestSubscriber(); + + c.subscribe(ts); + + normal.assertSubscriptions(1); + + ts.assertNoValues(); + ts.assertError(TestException.class); + ts.assertNotCompleted(); + } +} \ No newline at end of file