diff --git a/.travis.yml b/.travis.yml index 762c980..2a1fc00 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ before_install: - chmod +x gradlew script: + - export GRADLE_OPTS=-Xmx1024m - ./gradlew assemble --stacktrace - ./gradlew check jacocoFullReport --stacktrace diff --git a/README.md b/README.md index 5e5df0b..f8c4c23 100644 --- a/README.md +++ b/README.md @@ -10,16 +10,16 @@ Preview for version 3 of RxJava, the modern ReactiveX style library for composin ```groovy // shared components -compile "com.github.akarnokd:rxjava3-common:0.1.0" +compile "com.github.akarnokd:rxjava3-common:0.2.0" // Flowable only -compile "com.github.akarnokd:rxjava3-flowable:0.1.0" +compile "com.github.akarnokd:rxjava3-flowable:0.2.0" // Observable, Single, Maybe, Completable -compile "com.github.akarnokd:rxjava3-observable:0.1.0" +compile "com.github.akarnokd:rxjava3-observable:0.2.0" // Interoperation between Flowable and the rest -compile "com.github.akarnokd:rxjava3-interop:0.1.0" +compile "com.github.akarnokd:rxjava3-interop:0.2.0" ``` ## Structure @@ -45,4 +45,11 @@ This is an unofficial preparation place for RxJava 3 where the major change is t - dependencies: **rxjava3-commons** - `rxjava3-interop` - transformers and converters between the backpressured `Flowable` and the non-backpressured `Observable` types - - dependencies: **rxjava3-flowable**, **rxjava3-observable**, (-> **rxjava3-commons**, **reactive-streams-extensions**, **reactive-streams**) + - dependencies: **rxjava3-flowable**, **rxjava3-observable**, (-> **rxjava3-commons**, **reactive-streams-extensions**, **reactive-streams**) + + +## TODOs + +### Work out how the snapshot release and final release works in RxJava 1/2's Nebula plugin + +Currently, this preview releases manually and not in response to merging or hitting the GitHub release button. I don't really know which and how the unsupported Nebula plugin works and if it supports Gradle subprojects. Also due to the encrypted credentials, such auto-release must happen from within ReactiveX/RxJava. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 8b32ca6..afcd230 100644 --- a/build.gradle +++ b/build.gradle @@ -205,6 +205,8 @@ subprojects { check.dependsOn testng task GCandMem(dependsOn: 'check') << { + print("Memory usage before GC: ") + println(java.lang.management.ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getUsed() / 1024.0 / 1024.0) System.gc() Thread.sleep(200) print("Memory usage: ") diff --git a/common/src/main/java/io/reactivex/common/RxJavaCommonPlugins.java b/common/src/main/java/io/reactivex/common/RxJavaCommonPlugins.java index bc98e07..35160bb 100644 --- a/common/src/main/java/io/reactivex/common/RxJavaCommonPlugins.java +++ b/common/src/main/java/io/reactivex/common/RxJavaCommonPlugins.java @@ -12,7 +12,6 @@ */ package io.reactivex.common; -import io.reactivex.common.annotations.Experimental; import io.reactivex.common.annotations.NonNull; import io.reactivex.common.annotations.Nullable; import io.reactivex.common.exceptions.*; @@ -98,10 +97,10 @@ public static boolean isLockdown() { * Enables or disables the blockingX operators to fail * with an IllegalStateException on a non-blocking * scheduler such as computation or single. + *

History: 2.0.5 - experimental * @param enable enable or disable the feature - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental public static void setFailOnNonBlockingScheduler(boolean enable) { if (lockdown) { throw new IllegalStateException("Plugins can't be changed anymore"); @@ -113,10 +112,10 @@ public static void setFailOnNonBlockingScheduler(boolean enable) { * Returns true if the blockingX operators fail * with an IllegalStateException on a non-blocking scheduler * such as computation or single. + *

History: 2.0.5 - experimental * @return true if the blockingX operators fail on a non-blocking scheduler - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental public static boolean isFailOnNonBlockingScheduler() { return failNonBlockingScheduler; } @@ -566,11 +565,11 @@ public static void setSingleSchedulerHandler(@Nullable FunctionHistory: 2.0. - experimental * @return true if the blocking should be prevented * @see #setFailOnNonBlockingScheduler(boolean) - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental public static boolean onBeforeBlocking() { BooleanSupplier f = onBeforeBlocking; if (f != null) { @@ -587,12 +586,12 @@ public static boolean onBeforeBlocking() { * Set the handler that is called when an operator attempts a blocking * await; the handler should return true to prevent the blocking * and to signal an IllegalStateException instead. + *

History: 2.0.5 - experimental * @param handler the handler to set, null resets to the default handler * that always returns false * @see #onBeforeBlocking() - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental public static void setOnBeforeBlocking(@Nullable BooleanSupplier handler) { if (lockdown) { throw new IllegalStateException("Plugins can't be changed anymore"); @@ -603,10 +602,10 @@ public static void setOnBeforeBlocking(@Nullable BooleanSupplier handler) { /** * Returns the current blocking handler or null if no custom handler * is set. + *

History: 2.0.5 - experimental * @return the current blocking handler or null if not specified - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental @Nullable public static BooleanSupplier getOnBeforeBlocking() { return onBeforeBlocking; @@ -615,12 +614,12 @@ public static BooleanSupplier getOnBeforeBlocking() { /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#computation()} * except using {@code threadFactory} for thread creation. + *

History: 2.0.5 - experimental * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. * @return the created Scheduler instance - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental @NonNull public static Scheduler createComputationScheduler(@NonNull ThreadFactory threadFactory) { return new ComputationScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); @@ -629,12 +628,12 @@ public static Scheduler createComputationScheduler(@NonNull ThreadFactory thread /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#io()} * except using {@code threadFactory} for thread creation. + *

History: 2.0.5 - experimental * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. * @return the created Scheduler instance - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental @NonNull public static Scheduler createIoScheduler(@NonNull ThreadFactory threadFactory) { return new IoScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); @@ -643,12 +642,12 @@ public static Scheduler createIoScheduler(@NonNull ThreadFactory threadFactory) /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#newThread()} * except using {@code threadFactory} for thread creation. + *

History: 2.0.5 - experimental * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. * @return the created Scheduler instance - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental @NonNull public static Scheduler createNewThreadScheduler(@NonNull ThreadFactory threadFactory) { return new NewThreadScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); @@ -657,12 +656,12 @@ public static Scheduler createNewThreadScheduler(@NonNull ThreadFactory threadFa /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#single()} * except using {@code threadFactory} for thread creation. + *

History: 2.0.5 - experimental * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. * @return the created Scheduler instance - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental @NonNull public static Scheduler createSingleScheduler(@NonNull ThreadFactory threadFactory) { return new SingleScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); diff --git a/common/src/main/java/io/reactivex/common/Schedulers.java b/common/src/main/java/io/reactivex/common/Schedulers.java index 1de06d4..3c5a3d4 100644 --- a/common/src/main/java/io/reactivex/common/Schedulers.java +++ b/common/src/main/java/io/reactivex/common/Schedulers.java @@ -312,7 +312,7 @@ public static Scheduler single() { *

* Starting, stopping and restarting this scheduler is not supported (no-op) and the provided * executor's lifecycle must be managed externally: - *

+     * 

      * ExecutorService exec = Executors.newSingleThreadedExecutor();
      * try {
      *     Scheduler scheduler = Schedulers.from(exec);
@@ -324,7 +324,7 @@ public static Scheduler single() {
      * } finally {
      *     exec.shutdown();
      * }
-     * 
+ *
*

* This type of scheduler is less sensitive to leaking {@link io.reactivex.common.Scheduler.Worker} instances, although * not disposing a worker that has timed/delayed tasks not cancelled by other means may leak resources and/or diff --git a/common/src/main/java/io/reactivex/common/TestConsumer.java b/common/src/main/java/io/reactivex/common/TestConsumer.java index 9d95462..85260ce 100644 --- a/common/src/main/java/io/reactivex/common/TestConsumer.java +++ b/common/src/main/java/io/reactivex/common/TestConsumer.java @@ -16,7 +16,6 @@ import java.util.*; import java.util.concurrent.*; -import io.reactivex.common.annotations.Experimental; import io.reactivex.common.exceptions.CompositeException; import io.reactivex.common.functions.Predicate; import io.reactivex.common.internal.functions.*; @@ -336,11 +335,11 @@ public final U assertValue(T value) { * Assert that this TestObserver/TestObserver did not receive an onNext value which is equal to * the given value with respect to Objects.equals. * - * @since 2.0.5 - experimental + *

History: 2.0.5 - experimental + * @since 2.1 * @param value the value to expect not being received * @return this; */ - @Experimental @SuppressWarnings("unchecked") public final U assertNever(T value) { int s = values.size(); @@ -377,12 +376,12 @@ public final U assertValue(Predicate valuePredicate) { * Asserts that this TestObserver/TestObserver did not receive any onNext value for which * the provided predicate returns true. * - * @since 2.0.5 - experimental + *

History: 2.0.5 - experimental + * @since 2.1 * @param valuePredicate the predicate that receives the onNext value * and should return true for the expected value. * @return this */ - @Experimental @SuppressWarnings("unchecked") public final U assertNever(Predicate valuePredicate) { int s = values.size(); @@ -548,7 +547,7 @@ public final U assertValueSequence(Iterable sequence) { throw fail("More values received than expected (" + i + ")"); } if (expectedNext) { - throw fail("Fever values received than expected (" + i + ")"); + throw fail("Fewer values received than expected (" + i + ")"); } return (U)this; } @@ -780,12 +779,12 @@ public final U assertEmpty() { /** * Set the tag displayed along with an assertion failure's * other state information. + *

History: 2.0.7 - experimental * @param tag the string to display (null won't print any tag) * @return this - * @since 2.0.7 - experimental + * @since 2.1 */ @SuppressWarnings("unchecked") - @Experimental public final U withTag(CharSequence tag) { this.tag = tag; return (U)this; @@ -794,9 +793,9 @@ public final U withTag(CharSequence tag) { /** * Enumeration of default wait strategies when waiting for a specific number of * items in {@link TestConsumer#awaitCount(int, Runnable)}. - * @since 2.0.7 - experimental + *

History: 2.0.7 - experimental + * @since 2.1 */ - @Experimental public enum TestWaitStrategy implements Runnable { /** The wait loop will spin as fast as possible. */ SPIN { @@ -859,12 +858,12 @@ static void sleep(int millis) { * Await until the TestObserver/TestObserver receives the given * number of items or terminates by sleeping 10 milliseconds at a time * up to 5000 milliseconds of timeout. + *

History: 2.0.7 - experimental * @param atLeast the number of items expected at least * @return this * @see #awaitCount(int, Runnable, long) - * @since 2.0.7 - experimental + * @since 2.1 */ - @Experimental public final U awaitCount(int atLeast) { return awaitCount(atLeast, TestWaitStrategy.SLEEP_10MS, 5000); } @@ -873,6 +872,7 @@ public final U awaitCount(int atLeast) { * Await until the TestObserver/TestObserver receives the given * number of items or terminates by waiting according to the wait * strategy and up to 5000 milliseconds of timeout. + *

History: 2.0.7 - experimental * @param atLeast the number of items expected at least * @param waitStrategy a Runnable called when the current received count * hasn't reached the expected value and there was @@ -880,9 +880,8 @@ public final U awaitCount(int atLeast) { * for examples * @return this * @see #awaitCount(int, Runnable, long) - * @since 2.0.7 - experimental + * @since 2.1 */ - @Experimental public final U awaitCount(int atLeast, Runnable waitStrategy) { return awaitCount(atLeast, waitStrategy, 5000); } @@ -890,6 +889,7 @@ public final U awaitCount(int atLeast, Runnable waitStrategy) { /** * Await until the TestObserver/TestObserver receives the given * number of items or terminates. + *

History: 2.0.7 - experimental * @param atLeast the number of items expected at least * @param waitStrategy a Runnable called when the current received count * hasn't reached the expected value and there was @@ -898,10 +898,9 @@ public final U awaitCount(int atLeast, Runnable waitStrategy) { * @param timeoutMillis if positive, the await ends if the specified amount of * time has passed no matter how many items were received * @return this - * @since 2.0.7 - experimental + * @since 2.1 */ @SuppressWarnings("unchecked") - @Experimental public final U awaitCount(int atLeast, Runnable waitStrategy, long timeoutMillis) { long start = System.currentTimeMillis(); for (;;) { @@ -922,25 +921,25 @@ public final U awaitCount(int atLeast, Runnable waitStrategy, long timeoutMillis } /** + *

History: 2.0.7 - experimental * @return true if one of the timeout-based await methods has timed out. * @see #clearTimeout() * @see #assertTimeout() * @see #assertNoTimeout() - * @since 2.0.7 - experimental + * @since 2.1 */ - @Experimental public final boolean isTimeout() { return timeout; } /** * Clears the timeout flag set by the await methods when they timed out. + *

History: 2.0.7 - experimental * @return this - * @since 2.0.7 - experimental + * @since 2.1 * @see #isTimeout() */ @SuppressWarnings("unchecked") - @Experimental public final U clearTimeout() { timeout = false; return (U)this; @@ -948,11 +947,11 @@ public final U clearTimeout() { /** * Asserts that some awaitX method has timed out. + *

History: 2.0.7 - experimental * @return this - * @since 2.0.7 - experimental + * @since 2.1 */ @SuppressWarnings("unchecked") - @Experimental public final U assertTimeout() { if (!timeout) { throw fail("No timeout?!"); @@ -963,11 +962,11 @@ public final U assertTimeout() { /** * Asserts that some awaitX method has not timed out. + *

History: 2.0.7 - experimental * @return this - * @since 2.0.7 - experimental + * @since 2.1 */ @SuppressWarnings("unchecked") - @Experimental public final U assertNoTimeout() { if (timeout) { throw fail("Timeout?!"); diff --git a/common/src/main/java/io/reactivex/common/annotations/CheckReturnValue.java b/common/src/main/java/io/reactivex/common/annotations/CheckReturnValue.java index 916e117..8c05fdd 100644 --- a/common/src/main/java/io/reactivex/common/annotations/CheckReturnValue.java +++ b/common/src/main/java/io/reactivex/common/annotations/CheckReturnValue.java @@ -22,12 +22,12 @@ /** * Marks methods whose return values should be checked. * - * @since 2.0.2 - experimental + *

History: 2.0.2 - experimental + * @since 2.1 */ @Retention(RetentionPolicy.RUNTIME) @Documented @Target(ElementType.METHOD) -@Experimental public @interface CheckReturnValue { } diff --git a/common/src/main/java/io/reactivex/common/annotations/SchedulerSupport.java b/common/src/main/java/io/reactivex/common/annotations/SchedulerSupport.java index 9ebba1c..38158fb 100644 --- a/common/src/main/java/io/reactivex/common/annotations/SchedulerSupport.java +++ b/common/src/main/java/io/reactivex/common/annotations/SchedulerSupport.java @@ -62,9 +62,9 @@ /** * The operator/class runs on RxJava's {@linkplain Schedulers#single() single scheduler} * or takes timing information from it. - * @since 2.0.8 - experimental + *

History: 2.0.8 - experimental + * @since 2.1 */ - @Experimental String SINGLE = "io.reactivex:single"; /** diff --git a/common/src/main/java/io/reactivex/common/exceptions/OnErrorNotImplementedException.java b/common/src/main/java/io/reactivex/common/exceptions/OnErrorNotImplementedException.java index 341cd31..c5068eb 100644 --- a/common/src/main/java/io/reactivex/common/exceptions/OnErrorNotImplementedException.java +++ b/common/src/main/java/io/reactivex/common/exceptions/OnErrorNotImplementedException.java @@ -19,9 +19,9 @@ * Represents an exception used to signal to the {@code RxJavaCommonPlugins.onError()} that a * callback-based subscribe() method on a base reactive type didn't specify * an onError handler. - * @since 2.0.6 - experimental + *

History: 2.0.7 - experimental + * @since 2.1 */ -@Experimental public final class OnErrorNotImplementedException extends RuntimeException { private static final long serialVersionUID = -6298857009889503852L; diff --git a/common/src/main/java/io/reactivex/common/exceptions/ProtocolViolationException.java b/common/src/main/java/io/reactivex/common/exceptions/ProtocolViolationException.java index e71a0ae..7c17090 100644 --- a/common/src/main/java/io/reactivex/common/exceptions/ProtocolViolationException.java +++ b/common/src/main/java/io/reactivex/common/exceptions/ProtocolViolationException.java @@ -13,14 +13,12 @@ package io.reactivex.common.exceptions; -import io.reactivex.common.annotations.Experimental; - /** * Explicitly named exception to indicate a Reactive-Streams * protocol violation. - * @since 2.0.6 - experimental + *

History: 2.0.6 - experimental + * @since 2.1 */ -@Experimental public final class ProtocolViolationException extends IllegalStateException { private static final long serialVersionUID = 1644750035281290266L; diff --git a/common/src/main/java/io/reactivex/common/exceptions/UndeliverableException.java b/common/src/main/java/io/reactivex/common/exceptions/UndeliverableException.java index 81c0f20..4b7a675 100644 --- a/common/src/main/java/io/reactivex/common/exceptions/UndeliverableException.java +++ b/common/src/main/java/io/reactivex/common/exceptions/UndeliverableException.java @@ -13,13 +13,11 @@ package io.reactivex.common.exceptions; -import io.reactivex.common.annotations.Experimental; - /** * Wrapper for Throwable errors that are sent to `RxJavaCommonPlugins.onError`. - * @since 2.0.6 - experimental + *

History: 2.0.6 - experimental + * @since 2.1 */ -@Experimental public final class UndeliverableException extends IllegalStateException { private static final long serialVersionUID = 1644750035281290266L; diff --git a/common/src/main/java/io/reactivex/common/functions/Consumer.java b/common/src/main/java/io/reactivex/common/functions/Consumer.java index 2ebef842..9e32af0 100644 --- a/common/src/main/java/io/reactivex/common/functions/Consumer.java +++ b/common/src/main/java/io/reactivex/common/functions/Consumer.java @@ -13,8 +13,6 @@ package io.reactivex.common.functions; -import io.reactivex.common.annotations.NonNull; - /** * A functional interface (callback) that accepts a single value. * @param the value type @@ -25,5 +23,5 @@ public interface Consumer { * @param t the value * @throws Exception on error */ - void accept(@NonNull T t) throws Exception; + void accept(T t) throws Exception; } diff --git a/common/src/main/java/io/reactivex/common/functions/Function.java b/common/src/main/java/io/reactivex/common/functions/Function.java index df1c687..d20713d 100644 --- a/common/src/main/java/io/reactivex/common/functions/Function.java +++ b/common/src/main/java/io/reactivex/common/functions/Function.java @@ -29,6 +29,5 @@ public interface Function { * @return the output value * @throws Exception on error */ - @NonNull R apply(@NonNull T t) throws Exception; } diff --git a/common/src/main/java/io/reactivex/common/functions/Function3.java b/common/src/main/java/io/reactivex/common/functions/Function3.java index 7283f76..0a0c649 100644 --- a/common/src/main/java/io/reactivex/common/functions/Function3.java +++ b/common/src/main/java/io/reactivex/common/functions/Function3.java @@ -19,7 +19,7 @@ * A functional interface (callback) that computes a value based on multiple input values. * @param the first value type * @param the second value type - * @param the second value type + * @param the third value type * @param the result type */ public interface Function3 { diff --git a/common/src/main/java/io/reactivex/common/functions/Function4.java b/common/src/main/java/io/reactivex/common/functions/Function4.java index 33428ad..9264850 100644 --- a/common/src/main/java/io/reactivex/common/functions/Function4.java +++ b/common/src/main/java/io/reactivex/common/functions/Function4.java @@ -19,8 +19,8 @@ * A functional interface (callback) that computes a value based on multiple input values. * @param the first value type * @param the second value type - * @param the second value type - * @param the second value type + * @param the third value type + * @param the fourth value type * @param the result type */ public interface Function4 { diff --git a/common/src/main/java/io/reactivex/common/functions/Function5.java b/common/src/main/java/io/reactivex/common/functions/Function5.java index 1670336..93bb17c 100644 --- a/common/src/main/java/io/reactivex/common/functions/Function5.java +++ b/common/src/main/java/io/reactivex/common/functions/Function5.java @@ -19,9 +19,9 @@ * A functional interface (callback) that computes a value based on multiple input values. * @param the first value type * @param the second value type - * @param the second value type - * @param the second value type - * @param the second value type + * @param the third value type + * @param the fourth value type + * @param the fifth value type * @param the result type */ public interface Function5 { diff --git a/common/src/main/java/io/reactivex/common/functions/Function6.java b/common/src/main/java/io/reactivex/common/functions/Function6.java index ecf18c7..dd4537d 100644 --- a/common/src/main/java/io/reactivex/common/functions/Function6.java +++ b/common/src/main/java/io/reactivex/common/functions/Function6.java @@ -19,10 +19,10 @@ * A functional interface (callback) that computes a value based on multiple input values. * @param the first value type * @param the second value type - * @param the second value type - * @param the second value type - * @param the second value type - * @param the second value type + * @param the third value type + * @param the fourth value type + * @param the fifth value type + * @param the sixth value type * @param the result type */ public interface Function6 { diff --git a/common/src/main/java/io/reactivex/common/functions/Function7.java b/common/src/main/java/io/reactivex/common/functions/Function7.java index 84fa1da..d1dacb7 100644 --- a/common/src/main/java/io/reactivex/common/functions/Function7.java +++ b/common/src/main/java/io/reactivex/common/functions/Function7.java @@ -19,11 +19,11 @@ * A functional interface (callback) that computes a value based on multiple input values. * @param the first value type * @param the second value type - * @param the second value type - * @param the second value type - * @param the second value type - * @param the second value type - * @param the second value type + * @param the third value type + * @param the fourth value type + * @param the fifth value type + * @param the sixth value type + * @param the seventh value type * @param the result type */ public interface Function7 { diff --git a/common/src/main/java/io/reactivex/common/functions/Function8.java b/common/src/main/java/io/reactivex/common/functions/Function8.java index aa56fc1..88c7ad5 100644 --- a/common/src/main/java/io/reactivex/common/functions/Function8.java +++ b/common/src/main/java/io/reactivex/common/functions/Function8.java @@ -19,12 +19,12 @@ * A functional interface (callback) that computes a value based on multiple input values. * @param the first value type * @param the second value type - * @param the second value type - * @param the second value type - * @param the second value type - * @param the second value type - * @param the second value type - * @param the second value type + * @param the third value type + * @param the fourth value type + * @param the fifth value type + * @param the sixth value type + * @param the seventh value type + * @param the eighth value type * @param the result type */ public interface Function8 { diff --git a/common/src/main/java/io/reactivex/common/functions/Function9.java b/common/src/main/java/io/reactivex/common/functions/Function9.java index 9ea7fe3..58a93f0 100644 --- a/common/src/main/java/io/reactivex/common/functions/Function9.java +++ b/common/src/main/java/io/reactivex/common/functions/Function9.java @@ -19,13 +19,13 @@ * A functional interface (callback) that computes a value based on multiple input values. * @param the first value type * @param the second value type - * @param the second value type - * @param the second value type - * @param the second value type - * @param the second value type - * @param the second value type - * @param the second value type - * @param the second value type + * @param the third value type + * @param the fourth value type + * @param the fifth value type + * @param the sixth value type + * @param the seventh value type + * @param the eighth value type + * @param the ninth value type * @param the result type */ public interface Function9 { diff --git a/common/src/main/java/io/reactivex/common/internal/queues/AbstractMpscLinkedQueue.java b/common/src/main/java/io/reactivex/common/internal/queues/AbstractMpscLinkedQueue.java index 6928067..97f41db 100644 --- a/common/src/main/java/io/reactivex/common/internal/queues/AbstractMpscLinkedQueue.java +++ b/common/src/main/java/io/reactivex/common/internal/queues/AbstractMpscLinkedQueue.java @@ -99,7 +99,7 @@ final void spConsumerNode(LinkedQueueNode node) { consumerNode.lazySet(node); } - final public boolean isEmpty() { + public final boolean isEmpty() { return lvConsumerNode() == lvProducerNode(); } diff --git a/common/src/main/java/io/reactivex/common/internal/schedulers/InstantPeriodicTask.java b/common/src/main/java/io/reactivex/common/internal/schedulers/InstantPeriodicTask.java new file mode 100644 index 0000000..0e5efcf --- /dev/null +++ b/common/src/main/java/io/reactivex/common/internal/schedulers/InstantPeriodicTask.java @@ -0,0 +1,103 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * 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 io.reactivex.common.internal.schedulers; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.common.*; +import io.reactivex.common.internal.functions.Functions; + +/** + * Wrapper for a regular task that gets immediately rescheduled when the task completed. + */ +final class InstantPeriodicTask implements Callable, Disposable { + + final Runnable task; + + final AtomicReference> rest; + + final AtomicReference> first; + + final ExecutorService executor; + + Thread runner; + + static final FutureTask CANCELLED = new FutureTask(Functions.EMPTY_RUNNABLE, null); + + InstantPeriodicTask(Runnable task, ExecutorService executor) { + super(); + this.task = task; + this.first = new AtomicReference>(); + this.rest = new AtomicReference>(); + this.executor = executor; + } + + @Override + public Void call() throws Exception { + try { + runner = Thread.currentThread(); + try { + task.run(); + setRest(executor.submit(this)); + } catch (Throwable ex) { + RxJavaCommonPlugins.onError(ex); + } + } finally { + runner = null; + } + return null; + } + + @Override + public void dispose() { + Future current = first.getAndSet(CANCELLED); + if (current != null && current != CANCELLED) { + current.cancel(runner != Thread.currentThread()); + } + current = rest.getAndSet(CANCELLED); + if (current != null && current != CANCELLED) { + current.cancel(runner != Thread.currentThread()); + } + } + + @Override + public boolean isDisposed() { + return first.get() == CANCELLED; + } + + void setFirst(Future f) { + for (;;) { + Future current = first.get(); + if (current == CANCELLED) { + f.cancel(runner != Thread.currentThread()); + } + if (first.compareAndSet(current, f)) { + return; + } + } + } + + void setRest(Future f) { + for (;;) { + Future current = rest.get(); + if (current == CANCELLED) { + f.cancel(runner != Thread.currentThread()); + } + if (rest.compareAndSet(current, f)) { + return; + } + } + } +} \ No newline at end of file diff --git a/common/src/main/java/io/reactivex/common/internal/schedulers/NewThreadWorker.java b/common/src/main/java/io/reactivex/common/internal/schedulers/NewThreadWorker.java index acff281..556d0fe 100644 --- a/common/src/main/java/io/reactivex/common/internal/schedulers/NewThreadWorker.java +++ b/common/src/main/java/io/reactivex/common/internal/schedulers/NewThreadWorker.java @@ -82,8 +82,27 @@ public Disposable scheduleDirect(final Runnable run, long delayTime, TimeUnit un * @param unit the time unit for both the initialDelay and period * @return the ScheduledRunnable instance */ - public Disposable schedulePeriodicallyDirect(final Runnable run, long initialDelay, long period, TimeUnit unit) { - ScheduledDirectPeriodicTask task = new ScheduledDirectPeriodicTask(RxJavaCommonPlugins.onSchedule(run)); + public Disposable schedulePeriodicallyDirect(Runnable run, long initialDelay, long period, TimeUnit unit) { + final Runnable decoratedRun = RxJavaCommonPlugins.onSchedule(run); + if (period <= 0L) { + + InstantPeriodicTask periodicWrapper = new InstantPeriodicTask(decoratedRun, executor); + try { + Future f; + if (initialDelay <= 0L) { + f = executor.submit(periodicWrapper); + } else { + f = executor.schedule(periodicWrapper, initialDelay, unit); + } + periodicWrapper.setFirst(f); + } catch (RejectedExecutionException ex) { + RxJavaCommonPlugins.onError(ex); + return Scheduler.REJECTED; + } + + return periodicWrapper; + } + ScheduledDirectPeriodicTask task = new ScheduledDirectPeriodicTask(decoratedRun); try { Future f = executor.scheduleAtFixedRate(task, initialDelay, period, unit); task.setFuture(f); diff --git a/common/src/main/java/io/reactivex/common/internal/schedulers/SchedulerPoolFactory.java b/common/src/main/java/io/reactivex/common/internal/schedulers/SchedulerPoolFactory.java index 4d73569..b1ee5ed 100644 --- a/common/src/main/java/io/reactivex/common/internal/schedulers/SchedulerPoolFactory.java +++ b/common/src/main/java/io/reactivex/common/internal/schedulers/SchedulerPoolFactory.java @@ -57,6 +57,9 @@ private SchedulerPoolFactory() { * Starts the purge thread if not already started. */ public static void start() { + if (!PURGE_ENABLED) { + return; + } for (;;) { ScheduledExecutorService curr = PURGE_THREAD.get(); if (curr != null && !curr.isShutdown()) { @@ -78,7 +81,10 @@ public static void start() { * Stops the purge thread. */ public static void shutdown() { - PURGE_THREAD.get().shutdownNow(); + ScheduledExecutorService exec = PURGE_THREAD.get(); + if (exec != null) { + exec.shutdownNow(); + } POOLS.clear(); } @@ -90,10 +96,10 @@ public static void shutdown() { if (properties.containsKey(PURGE_ENABLED_KEY)) { purgeEnable = Boolean.getBoolean(PURGE_ENABLED_KEY); + } - if (purgeEnable && properties.containsKey(PURGE_PERIOD_SECONDS_KEY)) { - purgePeriod = Integer.getInteger(PURGE_PERIOD_SECONDS_KEY, purgePeriod); - } + if (purgeEnable && properties.containsKey(PURGE_PERIOD_SECONDS_KEY)) { + purgePeriod = Integer.getInteger(PURGE_PERIOD_SECONDS_KEY, purgePeriod); } PURGE_ENABLED = purgeEnable; @@ -109,7 +115,7 @@ public static void shutdown() { */ public static ScheduledExecutorService create(ThreadFactory factory) { final ScheduledExecutorService exec = Executors.newScheduledThreadPool(1, factory); - if (exec instanceof ScheduledThreadPoolExecutor) { + if (PURGE_ENABLED && exec instanceof ScheduledThreadPoolExecutor) { ScheduledThreadPoolExecutor e = (ScheduledThreadPoolExecutor) exec; POOLS.put(e, exec); } diff --git a/common/src/main/java/io/reactivex/common/internal/schedulers/SingleScheduler.java b/common/src/main/java/io/reactivex/common/internal/schedulers/SingleScheduler.java index df7c8f7..2358c24 100644 --- a/common/src/main/java/io/reactivex/common/internal/schedulers/SingleScheduler.java +++ b/common/src/main/java/io/reactivex/common/internal/schedulers/SingleScheduler.java @@ -123,7 +123,28 @@ public Disposable scheduleDirect(@NonNull Runnable run, long delay, TimeUnit uni @NonNull @Override public Disposable schedulePeriodicallyDirect(@NonNull Runnable run, long initialDelay, long period, TimeUnit unit) { - ScheduledDirectPeriodicTask task = new ScheduledDirectPeriodicTask(RxJavaCommonPlugins.onSchedule(run)); + final Runnable decoratedRun = RxJavaCommonPlugins.onSchedule(run); + if (period <= 0L) { + + ScheduledExecutorService exec = executor.get(); + + InstantPeriodicTask periodicWrapper = new InstantPeriodicTask(decoratedRun, exec); + Future f; + try { + if (initialDelay <= 0L) { + f = exec.submit(periodicWrapper); + } else { + f = exec.schedule(periodicWrapper, initialDelay, unit); + } + periodicWrapper.setFirst(f); + } catch (RejectedExecutionException ex) { + RxJavaCommonPlugins.onError(ex); + return REJECTED; + } + + return periodicWrapper; + } + ScheduledDirectPeriodicTask task = new ScheduledDirectPeriodicTask(decoratedRun); try { Future f = executor.get().scheduleAtFixedRate(task, initialDelay, period, unit); task.setFuture(f); diff --git a/common/src/main/java/io/reactivex/common/internal/schedulers/TrampolineScheduler.java b/common/src/main/java/io/reactivex/common/internal/schedulers/TrampolineScheduler.java index 73fb1db..df25240 100644 --- a/common/src/main/java/io/reactivex/common/internal/schedulers/TrampolineScheduler.java +++ b/common/src/main/java/io/reactivex/common/internal/schedulers/TrampolineScheduler.java @@ -107,6 +107,10 @@ Disposable enqueue(Runnable action, long execTime) { int missed = 1; for (;;) { for (;;) { + if (disposed) { + queue.clear(); + return DONE; + } final TimedRunnable polled = queue.poll(); if (polled == null) { break; diff --git a/common/src/main/java/io/reactivex/common/internal/utils/ExceptionHelper.java b/common/src/main/java/io/reactivex/common/internal/utils/ExceptionHelper.java index 32fefe9..979a078 100644 --- a/common/src/main/java/io/reactivex/common/internal/utils/ExceptionHelper.java +++ b/common/src/main/java/io/reactivex/common/internal/utils/ExceptionHelper.java @@ -106,6 +106,21 @@ public static List flatten(Throwable t) { return list; } + /** + * Workaround for Java 6 not supporting throwing a final Throwable from a catch block. + * @param the generic exception type + * @param e the Throwable error to return or throw + * @return the Throwable e if it is a subclass of Exception + * @throws E the generic exception thrown + */ + @SuppressWarnings("unchecked") + public static Exception throwIfThrowable(Throwable e) throws E { + if (e instanceof Exception) { + return (Exception)e; + } + throw (E)e; + } + static final class Termination extends Throwable { private static final long serialVersionUID = -4649703670690200604L; diff --git a/common/src/main/java/io/reactivex/common/internal/utils/VolatileSizeArrayList.java b/common/src/main/java/io/reactivex/common/internal/utils/VolatileSizeArrayList.java index d2130c4..9c978dc 100644 --- a/common/src/main/java/io/reactivex/common/internal/utils/VolatileSizeArrayList.java +++ b/common/src/main/java/io/reactivex/common/internal/utils/VolatileSizeArrayList.java @@ -22,7 +22,7 @@ * @param the element type * @since 2.0.7 */ -public final class VolatileSizeArrayList extends AtomicInteger implements List { +public final class VolatileSizeArrayList extends AtomicInteger implements List, RandomAccess { private static final long serialVersionUID = 3972397474470203923L; diff --git a/common/src/test/java/io/reactivex/common/RxJavaCommonPluginsTest.java b/common/src/test/java/io/reactivex/common/RxJavaCommonPluginsTest.java index fa55df8..de923e6 100644 --- a/common/src/test/java/io/reactivex/common/RxJavaCommonPluginsTest.java +++ b/common/src/test/java/io/reactivex/common/RxJavaCommonPluginsTest.java @@ -846,7 +846,6 @@ public void uncaughtException(Thread t, Throwable e) { * @throws Exception on error */ @Test - @SuppressWarnings("rawtypes") public void onErrorWithSuper() throws Exception { try { Consumer errorHandler = new Consumer() { @@ -893,7 +892,6 @@ public Runnable apply(Runnable runnable) throws Exception { } } - @SuppressWarnings({"rawtypes", "unchecked" }) @Test public void clearIsPassthrough() { try { diff --git a/common/src/test/java/io/reactivex/common/SchedulerTest.java b/common/src/test/java/io/reactivex/common/SchedulerTest.java index a85e60d..4890c51 100644 --- a/common/src/test/java/io/reactivex/common/SchedulerTest.java +++ b/common/src/test/java/io/reactivex/common/SchedulerTest.java @@ -282,4 +282,26 @@ public void holders() { assertNotNull(new Schedulers.SingleHolder()); } + + static final class CustomScheduler extends Scheduler { + + @Override + public Worker createWorker() { + return Schedulers.single().createWorker(); + } + + } + + @Test + public void customScheduleDirectDisposed() { + CustomScheduler scheduler = new CustomScheduler(); + + Disposable d = scheduler.scheduleDirect(Functions.EMPTY_RUNNABLE, 1, TimeUnit.MINUTES); + + assertFalse(d.isDisposed()); + + d.dispose(); + + assertTrue(d.isDisposed()); + } } diff --git a/common/src/test/java/io/reactivex/common/exceptions/ExceptionsTest.java b/common/src/test/java/io/reactivex/common/exceptions/ExceptionsTest.java index 826ff8b..6f2ca85 100644 --- a/common/src/test/java/io/reactivex/common/exceptions/ExceptionsTest.java +++ b/common/src/test/java/io/reactivex/common/exceptions/ExceptionsTest.java @@ -15,7 +15,7 @@ */ package io.reactivex.common.exceptions; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import java.io.IOException; @@ -87,4 +87,27 @@ public void manualPropagate() { } } + @Test + public void errorNotImplementedNull1() { + OnErrorNotImplementedException ex = new OnErrorNotImplementedException(null); + + assertTrue("" + ex.getCause(), ex.getCause() instanceof NullPointerException); + } + + @Test + public void errorNotImplementedNull2() { + OnErrorNotImplementedException ex = new OnErrorNotImplementedException("Message", null); + + assertTrue("" + ex.getCause(), ex.getCause() instanceof NullPointerException); + } + + @Test + public void errorNotImplementedWithCause() { + OnErrorNotImplementedException ex = new OnErrorNotImplementedException("Message", new TestException("Forced failure")); + + assertTrue("" + ex.getCause(), ex.getCause() instanceof TestException); + + assertEquals("" + ex.getCause(), "Forced failure", ex.getCause().getMessage()); + } + } diff --git a/common/src/test/java/io/reactivex/common/internal/functions/FunctionsTest.java b/common/src/test/java/io/reactivex/common/internal/functions/FunctionsTest.java index 3b23322..36fbd96 100644 --- a/common/src/test/java/io/reactivex/common/internal/functions/FunctionsTest.java +++ b/common/src/test/java/io/reactivex/common/internal/functions/FunctionsTest.java @@ -16,10 +16,12 @@ import static org.junit.Assert.*; import java.lang.reflect.Method; +import java.util.List; import org.junit.Test; -import io.reactivex.common.TestCommonHelper; +import io.reactivex.common.*; +import io.reactivex.common.exceptions.TestException; import io.reactivex.common.functions.*; import io.reactivex.common.internal.functions.Functions.*; import io.reactivex.common.internal.utils.ExceptionHelper; @@ -245,4 +247,16 @@ public void emptyConsumerToString() { assertEquals("EmptyConsumer", Functions.EMPTY_CONSUMER.toString()); } + @Test + public void errorConsumerEmpty() throws Exception { + List errors = TestCommonHelper.trackPluginErrors(); + try { + Functions.ERROR_CONSUMER.accept(new TestException()); + + TestCommonHelper.assertUndeliverable(errors, 0, TestException.class); + assertEquals(errors.toString(), 1, errors.size()); + } finally { + RxJavaCommonPlugins.reset(); + } + } } diff --git a/common/src/test/java/io/reactivex/common/internal/schedulers/SingleSchedulerTest.java b/common/src/test/java/io/reactivex/common/internal/schedulers/SingleSchedulerTest.java index f213a17..49f575c 100644 --- a/common/src/test/java/io/reactivex/common/internal/schedulers/SingleSchedulerTest.java +++ b/common/src/test/java/io/reactivex/common/internal/schedulers/SingleSchedulerTest.java @@ -15,12 +15,13 @@ import static org.junit.Assert.*; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import org.junit.Test; import io.reactivex.common.*; import io.reactivex.common.Scheduler.Worker; +import io.reactivex.common.internal.disposables.SequentialDisposable; import io.reactivex.common.internal.functions.Functions; import io.reactivex.common.internal.schedulers.SingleScheduler.ScheduledWorker; @@ -115,4 +116,64 @@ public void runnableDisposedAsyncTimed() throws Exception { Thread.sleep(1); } } + + @Test(timeout = 10000) + public void schedulePeriodicallyDirectZeroPeriod() throws Exception { + Scheduler s = Schedulers.single(); + + for (int initial = 0; initial < 2; initial++) { + final CountDownLatch cdl = new CountDownLatch(1); + + final SequentialDisposable sd = new SequentialDisposable(); + + try { + sd.replace(s.schedulePeriodicallyDirect(new Runnable() { + int count; + @Override + public void run() { + if (++count == 10) { + sd.dispose(); + cdl.countDown(); + } + } + }, initial, 0, TimeUnit.MILLISECONDS)); + + assertTrue("" + initial, cdl.await(5, TimeUnit.SECONDS)); + } finally { + sd.dispose(); + } + } + } + + @Test(timeout = 10000) + public void schedulePeriodicallyZeroPeriod() throws Exception { + Scheduler s = Schedulers.single(); + + for (int initial = 0; initial < 2; initial++) { + + final CountDownLatch cdl = new CountDownLatch(1); + + final SequentialDisposable sd = new SequentialDisposable(); + + Scheduler.Worker w = s.createWorker(); + + try { + sd.replace(w.schedulePeriodically(new Runnable() { + int count; + @Override + public void run() { + if (++count == 10) { + sd.dispose(); + cdl.countDown(); + } + } + }, initial, 0, TimeUnit.MILLISECONDS)); + + assertTrue("" + initial, cdl.await(5, TimeUnit.SECONDS)); + } finally { + sd.dispose(); + w.dispose(); + } + } + } } diff --git a/common/src/test/java/io/reactivex/common/internal/utils/ExceptionHelperTest.java b/common/src/test/java/io/reactivex/common/internal/utils/ExceptionHelperTest.java index 032376b..c13ec66 100644 --- a/common/src/test/java/io/reactivex/common/internal/utils/ExceptionHelperTest.java +++ b/common/src/test/java/io/reactivex/common/internal/utils/ExceptionHelperTest.java @@ -46,4 +46,9 @@ public void run() { TestCommonHelper.race(r, r, Schedulers.single()); } } + + @Test(expected = InternalError.class) + public void throwIfThrowable() throws Exception { + ExceptionHelper.throwIfThrowable(new InternalError()); + } } diff --git a/common/src/test/java/io/reactivex/common/schedulers/AbstractSchedulerTests.java b/common/src/test/java/io/reactivex/common/schedulers/AbstractSchedulerTests.java index 9b6ed4a..84bc4d1 100644 --- a/common/src/test/java/io/reactivex/common/schedulers/AbstractSchedulerTests.java +++ b/common/src/test/java/io/reactivex/common/schedulers/AbstractSchedulerTests.java @@ -25,6 +25,7 @@ import org.mockito.stubbing.Answer; import io.reactivex.common.*; +import io.reactivex.common.internal.disposables.SequentialDisposable; import io.reactivex.common.internal.schedulers.TrampolineScheduler; /** @@ -340,4 +341,71 @@ public void run() { } assertTrue(d.isDisposed()); } + + @Test(timeout = 10000) + public void schedulePeriodicallyDirectZeroPeriod() throws Exception { + Scheduler s = getScheduler(); + if (s instanceof TrampolineScheduler) { + // can't properly stop a trampolined periodic task + return; + } + + for (int initial = 0; initial < 2; initial++) { + final CountDownLatch cdl = new CountDownLatch(1); + + final SequentialDisposable sd = new SequentialDisposable(); + + try { + sd.replace(s.schedulePeriodicallyDirect(new Runnable() { + int count; + @Override + public void run() { + if (++count == 10) { + sd.dispose(); + cdl.countDown(); + } + } + }, initial, 0, TimeUnit.MILLISECONDS)); + + assertTrue("" + initial, cdl.await(5, TimeUnit.SECONDS)); + } finally { + sd.dispose(); + } + } + } + + @Test(timeout = 10000) + public void schedulePeriodicallyZeroPeriod() throws Exception { + Scheduler s = getScheduler(); + if (s instanceof TrampolineScheduler) { + // can't properly stop a trampolined periodic task + return; + } + + for (int initial = 0; initial < 2; initial++) { + final CountDownLatch cdl = new CountDownLatch(1); + + final SequentialDisposable sd = new SequentialDisposable(); + + Scheduler.Worker w = s.createWorker(); + + try { + sd.replace(w.schedulePeriodically(new Runnable() { + int count; + @Override + public void run() { + if (++count == 10) { + sd.dispose(); + cdl.countDown(); + } + } + }, initial, 0, TimeUnit.MILLISECONDS)); + + assertTrue("" + initial, cdl.await(5, TimeUnit.SECONDS)); + } finally { + sd.dispose(); + w.dispose(); + } + } + } } diff --git a/flowable/src/main/java/io/reactivex/flowable/ConnectableFlowable.java b/flowable/src/main/java/io/reactivex/flowable/ConnectableFlowable.java index cd5af38..fb0badf 100644 --- a/flowable/src/main/java/io/reactivex/flowable/ConnectableFlowable.java +++ b/flowable/src/main/java/io/reactivex/flowable/ConnectableFlowable.java @@ -109,7 +109,7 @@ public Flowable autoConnect(int numberOfSubscribers) { * @param numberOfSubscribers the number of subscribers to await before calling connect * on the ConnectableObservable. A non-positive value indicates * an immediate connection. - * @param connection the callback Action1 that will receive the Subscription representing the + * @param connection the callback Consumer that will receive the Subscription representing the * established connection * @return an Observable that automatically connects to this ConnectableObservable * when the specified number of Subscribers subscribe to it and calls the diff --git a/flowable/src/main/java/io/reactivex/flowable/Flowable.java b/flowable/src/main/java/io/reactivex/flowable/Flowable.java index 59e80c5..b622b20 100644 --- a/flowable/src/main/java/io/reactivex/flowable/Flowable.java +++ b/flowable/src/main/java/io/reactivex/flowable/Flowable.java @@ -145,6 +145,9 @@ public static int bufferSize() { * If any of the sources never produces an item but only terminates (normally or with an error), the * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided array of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. * *

*
Backpressure:
@@ -186,6 +189,9 @@ public static Flowable combineLatest(Publisher[] sources, * If any of the sources never produces an item but only terminates (normally or with an error), the * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If there are no source Publishers provided, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. * *

*
Backpressure:
@@ -227,6 +233,9 @@ public static Flowable combineLatest(Function + * If the provided array of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. * *
*
Backpressure:
@@ -276,6 +285,9 @@ public static Flowable combineLatest(Publisher[] sources, * If any of the sources never produces an item but only terminates (normally or with an error), the * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided iterable of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. * *

*
Backpressure:
@@ -318,6 +330,9 @@ public static Flowable combineLatest(Iterable + * If the provided iterable of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. * *
*
Backpressure:
@@ -365,6 +380,9 @@ public static Flowable combineLatest(Iterable + * If the provided array of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. * *
*
Backpressure:
@@ -408,6 +426,9 @@ public static Flowable combineLatestDelayError(Publisher[ * If any of the sources never produces an item but only terminates (normally or with an error), the * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided array of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. * *

*
Backpressure:
@@ -451,6 +472,9 @@ public static Flowable combineLatestDelayError(Function + * If there are no source Publishers provided, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. * *
*
Backpressure:
@@ -496,6 +520,9 @@ public static Flowable combineLatestDelayError(Function + * If the provided array of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. * *
*
Backpressure:
@@ -547,6 +574,9 @@ public static Flowable combineLatestDelayError(Publisher[ * If any of the sources never produces an item but only terminates (normally or with an error), the * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). * If that input source is also synchronous, other sources after it will not be subscribed to. + *

+ * If the provided iterable of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. * *

*
Backpressure:
@@ -590,6 +620,9 @@ public static Flowable combineLatestDelayError(Iterable + * If the provided iterable of source Publishers is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. * *
*
Backpressure:
@@ -1313,7 +1346,7 @@ public static Flowable concat( /** * Concatenates a variable number of Publisher sources. *

- * Note: named this way because of overload conflict with concat(Publisher<Publisher>). + * Note: named this way because of overload conflict with concat(Publisher<Publisher>). *

* *

@@ -2083,7 +2116,6 @@ public static Flowable fromPublisher(final Publisher source) /** * Returns a cold, synchronous, stateless and backpressure-aware generator of values. - *

*

*
Backpressure:
*
The operator honors downstream backpressure.
@@ -2110,7 +2142,6 @@ public static Flowable generate(final Consumer> generator) { /** * Returns a cold, synchronous, stateful and backpressure-aware generator of values. - *

*

*
Backpressure:
*
The operator honors downstream backpressure.
@@ -2138,7 +2169,6 @@ public static Flowable generate(Callable initialState, final BiCons /** * Returns a cold, synchronous, stateful and backpressure-aware generator of values. - *

*

*
Backpressure:
*
The operator honors downstream backpressure.
@@ -2168,7 +2198,6 @@ public static Flowable generate(Callable initialState, final BiCons /** * Returns a cold, synchronous, stateful and backpressure-aware generator of values. - *

*

*
Backpressure:
*
The operator honors downstream backpressure.
@@ -2195,7 +2224,6 @@ public static Flowable generate(Callable initialState, BiFunction *
*
Backpressure:
*
The operator honors downstream backpressure.
@@ -4168,7 +4196,7 @@ public static Flowable using(Callable resourceSupplier, *

* *

- *
Backpressure:
+ *
Backpressure:
*
The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
@@ -4221,7 +4249,7 @@ public static Flowable zip(Iterable> *

* *

- *
Backpressure:
+ *
Backpressure:
*
The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
@@ -4276,7 +4304,7 @@ public static Flowable zip(Publisher> * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. *
- *
Backpressure:
+ *
Backpressure:
*
The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
@@ -4336,7 +4364,7 @@ public static Flowable zip( * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. *
- *
Backpressure:
+ *
Backpressure:
*
The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
@@ -4398,7 +4426,7 @@ public static Flowable zip( * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. *
- *
Backpressure:
+ *
Backpressure:
*
The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
@@ -4461,7 +4489,7 @@ public static Flowable zip( * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. *
- *
Backpressure:
+ *
Backpressure:
*
The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
@@ -4526,7 +4554,7 @@ public static Flowable zip( * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. *
- *
Backpressure:
+ *
Backpressure:
*
The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
@@ -4596,7 +4624,7 @@ public static Flowable zip( * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. *
- *
Backpressure:
+ *
Backpressure:
*
The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
@@ -4669,7 +4697,7 @@ public static Flowable zip( * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. *
- *
Backpressure:
+ *
Backpressure:
*
The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
@@ -4746,7 +4774,7 @@ public static Flowable zip( * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. *
- *
Backpressure:
+ *
Backpressure:
*
The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
@@ -4828,7 +4856,7 @@ public static Flowable zip( * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. *
- *
Backpressure:
+ *
Backpressure:
*
The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
@@ -4914,7 +4942,7 @@ public static Flowable zip( * use {@link #doOnCancel(Action)} as well or use {@code using()} to do cleanup in case of completion * or cancellation. *
- *
Backpressure:
+ *
Backpressure:
*
The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
@@ -5005,7 +5033,7 @@ public static Flowable zip( *

* *

- *
Backpressure:
+ *
Backpressure:
*
The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
@@ -5066,7 +5094,7 @@ public static Flowable zipArray(Function * *
- *
Backpressure:
+ *
Backpressure:
*
The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
@@ -5579,7 +5607,6 @@ public final void blockingSubscribe() { * If the Flowable emits an error, it is wrapped into an * {@link io.reactivex.common.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} * and routed to the RxJavaFlowablePlugins.onError handler. - *

*

*
Backpressure:
*
The operator consumes the source {@code Flowable} in an unbounded manner @@ -5638,7 +5665,6 @@ public final void blockingSubscribe(Consumer onNext, Consumeron the current thread. - *

*

*
Backpressure:
*
The supplied {@code Subscriber} determines how backpressure is applied.
@@ -6871,8 +6897,6 @@ public final Flowable concatMapEagerDelayError(Function - * *
*
Backpressure:
*
The operator honors backpressure from downstream. The source {@code Publisher}s is @@ -6901,8 +6925,6 @@ public final Flowable concatMapIterable(Function - * *
*
Backpressure:
*
The operator honors backpressure from downstream. The source {@code Publisher}s is @@ -7053,7 +7075,6 @@ public final Flowable debounce(Function * *

* Information on debounce vs throttle: - *

*

    *
  • Debounce and Throttle: visual explanation
  • *
  • Debouncing: javascript methods
  • @@ -7095,7 +7116,6 @@ public final Flowable debounce(long timeout, TimeUnit unit) { * *

    * Information on debounce vs throttle: - *

    *

      *
    • Debounce and Throttle: visual explanation
    • *
    • Debouncing: javascript methods
    • @@ -7355,7 +7375,6 @@ public final Flowable delay(Publisher subscriptionIndicator, /** * Returns a Flowable that delays the subscription to this Publisher * until the other Publisher emits an element or completes normally. - *

      *

      *
      Backpressure:
      *
      The operator forwards the backpressure requests to this Publisher once @@ -7639,7 +7658,7 @@ public final Flowable distinctUntilChanged(BiPredicate * behavior.
      *
      Scheduler:
      *
      {@code doFinally} does not operate by default on a particular {@link Scheduler}.
      - * Operator-fusion: + *
      Operator-fusion:
      *
      This operator supports normal and conditional Subscribers as well as boundary-limited * synchronous or asynchronous queue-fusion.
      *
      @@ -7666,18 +7685,18 @@ public final Flowable doFinally(Action onFinally) { * behavior.
*
Scheduler:
*
{@code doAfterNext} does not operate by default on a particular {@link Scheduler}.
- * Operator-fusion: + *
Operator-fusion:
*
This operator supports normal and conditional Subscribers as well as boundary-limited * synchronous or asynchronous queue-fusion.
*
+ *

History: 2.0.1 - experimental * @param onAfterNext the Consumer that will be called after emitting an item from upstream to the downstream * @return the new Flowable instance - * @since 2.0.1 - experimental + * @since 2.1 */ @CheckReturnValue @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.NONE) - @Experimental public final Flowable doAfterNext(Consumer onAfterNext) { ObjectHelper.requireNonNull(onAfterNext, "onAfterNext is null"); return RxJavaFlowablePlugins.onAssembly(new FlowableDoAfterNext(this, onAfterNext)); @@ -8299,7 +8318,7 @@ public final Flowable flatMap(Function - * + * *

*
Backpressure:
*
The operator honors backpressure from downstream. The upstream Flowable is consumed @@ -8335,7 +8354,7 @@ public final Flowable flatMap(Function - * + * *
*
Backpressure:
*
The operator honors backpressure from downstream. The upstream Flowable is consumed @@ -8374,7 +8393,7 @@ public final Flowable flatMap(Function - * + * *
*
Backpressure:
*
The operator honors backpressure from downstream. The upstream Flowable is consumed @@ -8468,7 +8487,7 @@ public final Flowable flatMap( * Publisher and then flattens the Publishers returned from these functions and emits the resulting items, * while limiting the maximum number of concurrent subscriptions to these Publishers. *

- * + * *

*
Backpressure:
*
The operator honors backpressure from downstream. The upstream Flowable is consumed @@ -8591,7 +8610,7 @@ public final Flowable flatMap(Function - * + * *
*
Backpressure:
*
The operator honors backpressure from downstream. The upstream Flowable is consumed @@ -8634,7 +8653,7 @@ public final Flowable flatMap(Function - * + * *
*
Backpressure:
*
The operator honors backpressure from downstream. The upstream Flowable is consumed @@ -8683,7 +8702,7 @@ public final Flowable flatMap(final Function - * + * *
*
Backpressure:
*
The operator honors backpressure from downstream. The upstream Flowable is consumed @@ -8866,7 +8885,7 @@ public final Flowable flatMapIterable(final Function * Alias to {@link #subscribe(Consumer)} *
- *
Backpressure:
+ *
Backpressure:
*
The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no * backpressure is applied to it).
*
Scheduler:
@@ -8896,7 +8915,7 @@ public final Disposable forEach(Consumer onNext) { * {@link io.reactivex.common.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} * and routed to the RxJavaFlowablePlugins.onError handler. *
- *
Backpressure:
+ *
Backpressure:
*
The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no * backpressure is applied to it).
*
Scheduler:
@@ -8922,7 +8941,7 @@ public final Disposable forEachWhile(Predicate onNext) { * Subscribes to the {@link Publisher} and receives notifications for each element and error events until the * onNext Predicate returns false. *
- *
Backpressure:
+ *
Backpressure:
*
The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no * backpressure is applied to it).
*
Scheduler:
@@ -8951,7 +8970,7 @@ public final Disposable forEachWhile(Predicate onNext, Consumer - *
Backpressure:
+ *
Backpressure:
*
The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no * backpressure is applied to it).
*
Scheduler:
@@ -9934,7 +9953,7 @@ public final Flowable onBackpressureBuffer(int capacity, Action onOverflow) { * cancelling the source, and notifying the producer with {@code onOverflow}. *
  • {@code BackpressureOverflow.Strategy.ON_OVERFLOW_DROP_LATEST} will drop any new items emitted by the producer while * the buffer is full, without generating any {@code onError}. Each drop will however invoke {@code onOverflow} - * to signal the overflow to the producer.
  • j + * to signal the overflow to the producer. *
  • {@code BackpressureOverflow.Strategy.ON_OVERFLOW_DROP_OLDEST} will drop the oldest items in the buffer in order to make * room for newly emitted ones. Overflow will not generate an{@code onError}, but each drop will invoke * {@code onOverflow} to signal the overflow to the producer.
  • @@ -10035,7 +10054,6 @@ public final Flowable onBackpressureDrop(Consumer onDrop) { *

    * Note that due to the nature of how backpressure requests are propagated through subscribeOn/observeOn, * requesting more than 1 from downstream doesn't guarantee a continuous delivery of onNext events. - *

    *

    *
    Backpressure:
    *
    The operator honors backpressure from downstream and consumes the source {@code Publisher} in an unbounded @@ -10332,14 +10350,14 @@ public final ParallelFlowable parallel() { *
    Scheduler:
    *
    {@code parallel} does not operate by default on a particular {@link Scheduler}.
    *
    + *

    History: 2.0.5 - experimental * @param parallelism the number of 'rails' to use * @return the new ParallelFlowable instance - * @since 2.0.5 - experimental + * @since 2.1 */ @BackpressureSupport(BackpressureKind.FULL) @SchedulerSupport(SchedulerSupport.NONE) @CheckReturnValue - @Experimental public final ParallelFlowable parallel(int parallelism) { ObjectHelper.verifyPositive(parallelism, "parallelism"); return ParallelFlowable.from(this, parallelism); @@ -10386,7 +10404,7 @@ public final ParallelFlowable parallel(int parallelism, int prefetch) { *

    * *

    - *
    Backpressure:
    + *
    Backpressure:
    *
    The returned {@code ConnectableFlowable} honors backpressure for each of its {@code Subscriber}s * and expects the source {@code Publisher} to honor backpressure as well. If this expectation is violated, * the operator will signal a {@code MissingBackpressureException} to its {@code Subscriber}s and disconnect.
    @@ -10480,7 +10498,7 @@ public final Flowable publish(Function, ? extends Pub *

    * *

    - *
    Backpressure:
    + *
    Backpressure:
    *
    The returned {@code ConnectableFlowable} honors backpressure for each of its {@code Subscriber}s * and expects the source {@code Publisher} to honor backpressure as well. If this expectation is violated, * the operator will signal a {@code MissingBackpressureException} to its {@code Subscriber}s and disconnect.
    @@ -10528,7 +10546,7 @@ public final Flowable rebatchRequests(int n) { } /** - * Returns a Maybe that applies a specified accumulator function to the first item emitted by a source + * Returns a Flowable that applies a specified accumulator function to the first item emitted by a source * Publisher, then feeds the result of that function along with the second item emitted by the source * Publisher into the same function, and so on until all items have been emitted by the source Publisher, * and emits the final result from the final call to your function as its sole item. @@ -10576,18 +10594,22 @@ public final Flowable reduce(BiFunction reducer) { * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method * that does a similar operation on lists. *

    - * Note that the {@code initialValue} is shared among all subscribers to the resulting Publisher + * Note that the {@code seed} is shared among all subscribers to the resulting Publisher * and may cause problems if it is mutable. To make sure each subscriber gets its own value, defer * the application of this operator via {@link #defer(Callable)}: *

    
    -     * Publisher<T> source = ...
    -     * Publisher.defer(() -> source.reduce(new ArrayList<>(), (list, item) -> list.add(item)));
    +     * Publisher<T> source = ...
    +     * Flowable.defer(() -> source.reduce(new ArrayList<>(), (list, item) -> list.add(item)));
          *
          * // alternatively, by using compose to stay fluent
          *
    -     * source.compose(o ->
    -     *     Publisher.defer(() -> o.reduce(new ArrayList<>(), (list, item) -> list.add(item)))
    -     * );
    +     * source.compose(o ->
    +     *     Flowable.defer(() -> o.reduce(new ArrayList<>(), (list, item) -> list.add(item)))
    +     * ).firstOrError();
    +     *
    +     * // or, by using reduceWith instead of reduce
    +     *
    +     * source.reduceWith(() -> new ArrayList<>(), (list, item) -> list.add(item)));
          * 
    *
    *
    Backpressure:
    @@ -10607,6 +10629,7 @@ public final Flowable reduce(BiFunction reducer) { * items emitted by the source Publisher * @see ReactiveX operators documentation: Reduce * @see Wikipedia: Fold (higher-order function) + * @see #reduceWith(Callable, BiFunction) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.UNBOUNDED_IN) @@ -10619,29 +10642,16 @@ public final Flowable reduce(R seed, BiFunction reducer) /** * Returns a Flowable that applies a specified accumulator function to the first item emitted by a source - * Publisher and a specified seed value, then feeds the result of that function along with the second item - * emitted by a Publisher into the same function, and so on until all items have been emitted by the - * source Publisher, emitting the final result from the final call to your function as its sole item. + * Publisher and a seed value derived from calling a specified seedSupplier, then feeds the result + * of that function along with the second item emitted by a Publisher into the same function, and so on until + * all items have been emitted by the source Publisher, emitting the final result from the final call to your + * function as its sole item. *

    * *

    * This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method * that does a similar operation on lists. - *

    - * Note that the {@code initialValue} is shared among all subscribers to the resulting Publisher - * and may cause problems if it is mutable. To make sure each subscriber gets its own value, defer - * the application of this operator via {@link #defer(Callable)}: - *

    
    -     * Publisher<T> source = ...
    -     * Publisher.defer(() -> source.reduce(new ArrayList<>(), (list, item) -> list.add(item)));
    -     *
    -     * // alternatively, by using compose to stay fluent
    -     *
    -     * source.compose(o ->
    -     *     Publisher.defer(() -> o.reduce(new ArrayList<>(), (list, item) -> list.add(item)))
    -     * );
    -     * 
    *
    *
    Backpressure:
    *
    The operator honors backpressure of its downstream consumer and consumes the @@ -10675,7 +10685,7 @@ public final Flowable reduceWith(Callable seedSupplier, BiFunction * *
    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    @@ -10698,7 +10708,7 @@ public final Flowable repeat() { *

    * *

    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    @@ -10733,7 +10743,7 @@ public final Flowable repeat(long times) { *

    * *

    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    @@ -10766,7 +10776,7 @@ public final Flowable repeatUntil(BooleanSupplier stop) { *

    * *

    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    @@ -11370,7 +11380,7 @@ public final ConnectableFlowable replay(final Scheduler scheduler) { * {@code [1, 2]} then succeeds the second time and emits {@code [1, 2, 3, 4, 5]} then the complete sequence * of emissions and notifications would be {@code [1, 2, 1, 2, 3, 4, 5, onComplete]}. *
    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    @@ -11393,7 +11403,7 @@ public final Flowable retry() { *

    * *

    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    @@ -11431,7 +11441,7 @@ public final Flowable retry(BiPredicate p * {@code [1, 2]} then succeeds the second time and emits {@code [1, 2, 3, 4, 5]} then the complete sequence * of emissions and notifications would be {@code [1, 2, 1, 2, 3, 4, 5, onComplete]}. *
    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    @@ -11454,7 +11464,7 @@ public final Flowable retry(long count) { * Retries at most times or until the predicate returns false, whichever happens first. * *
    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    @@ -11479,7 +11489,7 @@ public final Flowable retry(long times, Predicate predicat /** * Retries the current Flowable if the predicate returns true. *
    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    @@ -11499,7 +11509,7 @@ public final Flowable retry(Predicate predicate) { /** * Retries until the given stop function returns true. *
    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    @@ -11531,11 +11541,11 @@ public final Flowable retryUntil(final BooleanSupplier stop) { * This retries 3 times, each time incrementing the number of seconds it waits. * *
    
    -     *  Publisher.create((Subscriber s) -> {
    +     *  Flowable.create((Subscriber<? super String> s) -> {
          *      System.out.println("subscribing");
          *      s.onError(new RuntimeException("always fails"));
    -     *  }).retryWhen(attempts -> {
    -     *      return attempts.zipWith(Publisher.range(1, 3), (n, i) -> i).flatMap(i -> {
    +     *  }).retryWhen(attempts -> {
    +     *      return attempts.zipWith(Publisher.range(1, 3), (n, i) -> i).flatMap(i -> {
          *          System.out.println("delay retry by " + i + " second(s)");
          *          return Publisher.timer(i, TimeUnit.SECONDS);
          *      });
    @@ -11554,7 +11564,7 @@ public final Flowable retryUntil(final BooleanSupplier stop) {
          * subscribing
          * } 
    *
    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. * If this expectation is violated, the operator may throw an {@code IllegalStateException}.
    *
    Scheduler:
    @@ -11583,7 +11593,7 @@ public final Flowable retryWhen( * deals with exceptions thrown by a misbehaving Subscriber (that doesn't follow the * Reactive-Streams specification). *
    - *
    Backpressure:
    + *
    Backpressure:
    *
    This operator leaves the reactive world and the backpressure behavior depends on the Subscriber's behavior.
    *
    Scheduler:
    *
    {@code safeSubscribe} does not operate by default on a particular {@link Scheduler}.
    @@ -11816,7 +11826,7 @@ public final Flowable sample(Publisher sampler, boolean emitLast) { *

    * This sort of function is sometimes called an accumulator. *

    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. * Violating this expectation, a {@code MissingBackpressureException} may get signalled somewhere downstream.
    *
    Scheduler:
    @@ -11855,17 +11865,17 @@ public final Flowable scan(BiFunction accumulator) { * and may cause problems if it is mutable. To make sure each subscriber gets its own value, defer * the application of this operator via {@link #defer(Callable)}: *
    
    -     * Publisher<T> source = ...
    -     * Publisher.defer(() -> source.scan(new ArrayList<>(), (list, item) -> list.add(item)));
    +     * Publisher<T> source = ...
    +     * Flowable.defer(() -> source.scan(new ArrayList<>(), (list, item) -> list.add(item)));
          *
          * // alternatively, by using compose to stay fluent
          *
    -     * source.compose(o ->
    -     *     Publisher.defer(() -> o.scan(new ArrayList<>(), (list, item) -> list.add(item)))
    +     * source.compose(o ->
    +     *     Flowable.defer(() -> o.scan(new ArrayList<>(), (list, item) -> list.add(item)))
          * );
          * 
    *
    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. * Violating this expectation, a {@code MissingBackpressureException} may get signalled somewhere downstream.
    *
    Scheduler:
    @@ -11901,26 +11911,10 @@ public final Flowable scan(final R initialValue, BiFunction * This sort of function is sometimes called an accumulator. *

    - * Note that the Publisher that results from this method will emit {@code initialValue} as its first + * Note that the Publisher that results from this method will emit value returned by {@code seedSupplier} as its first * emitted item. - *

    - * Note that the {@code initialValue} is shared among all subscribers to the resulting Publisher - * and may cause problems if it is mutable. To make sure each subscriber gets its own value, defer - * the application of this operator via {@link #defer(Callable)}: - *

    
    -     * Publisher<T> source = ...
    -     * Publisher.defer(() -> source.scan(new ArrayList<>(), (list, item) -> list.add(item)));
    -     *
    -     * // alternatively, by using compose to stay fluent
    -     *
    -     * source.compose(o ->
    -     *     Publisher.defer(() -> o.scan(new ArrayList<>(), (list, item) -> list.add(item)))
    -     * );
    -     * 
    - *

    - * Unlike 1.x, this operator doesn't emit the seed value unless the upstream signals an event. *

    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator honors downstream backpressure and expects the source {@code Publisher} to honor backpressure as well. * Violating this expectation, a {@code MissingBackpressureException} may get signalled somewhere downstream.
    *
    Scheduler:
    @@ -12649,7 +12643,7 @@ public final Flowable strict() { * {@link io.reactivex.common.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} * and routed to the RxJavaFlowablePlugins.onError handler. *
    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no * backpressure is applied to it).
    *
    Scheduler:
    @@ -12674,7 +12668,7 @@ public final Disposable subscribe() { * {@link io.reactivex.common.exceptions.OnErrorNotImplementedException OnErrorNotImplementedException} * and routed to the RxJavaFlowablePlugins.onError handler. *
    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no * backpressure is applied to it).
    *
    Scheduler:
    @@ -12701,7 +12695,7 @@ public final Disposable subscribe(Consumer onNext) { * Subscribes to a Publisher and provides callbacks to handle the items it emits and any error * notification it issues. *
    - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no * backpressure is applied to it).
    *
    Scheduler:
    @@ -12731,7 +12725,7 @@ public final Disposable subscribe(Consumer onNext, Consumer - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no * backpressure is applied to it).
    *
    Scheduler:
    @@ -12766,7 +12760,7 @@ public final Disposable subscribe(Consumer onNext, Consumer - *
    Backpressure:
    + *
    Backpressure:
    *
    The operator consumes the source {@code Publisher} in an unbounded manner (i.e., no * backpressure is applied to it).
    *
    Scheduler:
    @@ -12895,10 +12889,10 @@ public final void subscribe(RelaxedSubscriber s) { * Subscriber as is. *

    Usage example: *

    
    -     * Flowable<Integer> source = Flowable.range(1, 10);
    +     * Flowable<Integer> source = Flowable.range(1, 10);
          * CompositeDisposable composite = new CompositeDisposable();
          *
    -     * ResourceSubscriber<Integer> rs = new ResourceSubscriber<>() {
    +     * ResourceSubscriber<Integer> rs = new ResourceSubscriber<>() {
          *     // ...
          * };
          *
    @@ -12928,6 +12922,10 @@ public final > E subscribeWith(E subscriber) {
         /**
          * Asynchronously subscribes Subscribers to this Publisher on the specified {@link Scheduler}.
          * 

    + * If there is a {@link #create(FlowableOnSubscribe, BackpressureStrategy)} type source up in the + * chain, it is recommended to use {@code subscribeOn(scheduler, false)} instead + * to avoid same-pool deadlock because requests may pile up behind a eager/blocking emitter. + *

    * *

    *
    Backpressure:
    @@ -12944,20 +12942,59 @@ public final > E subscribeWith(E subscriber) { * @see ReactiveX operators documentation: SubscribeOn * @see RxJava Threading Examples * @see #observeOn + * @see #subscribeOn(Scheduler, boolean) */ @CheckReturnValue @BackpressureSupport(BackpressureKind.PASS_THROUGH) @SchedulerSupport(SchedulerSupport.CUSTOM) - public final Flowable subscribeOn(Scheduler scheduler) { + public final Flowable subscribeOn(@NonNull Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); - return RxJavaFlowablePlugins.onAssembly(new FlowableSubscribeOn(this, scheduler, this instanceof FlowableCreate)); + return subscribeOn(scheduler, !(this instanceof FlowableCreate)); + } + + /** + * Asynchronously subscribes Subscribers to this Publisher on the specified {@link Scheduler} + * optionally reroutes requests from other threads to the same {@link Scheduler} thread. + *

    + * If there is a {@link #create(FlowableOnSubscribe, BackpressureStrategy)} type source up in the + * chain, it is recommended to have {@code requestOn} false to avoid same-pool deadlock + * because requests may pile up behind a eager/blocking emitter. + *

    + * + *

    + *
    Backpressure:
    + *
    The operator doesn't interfere with backpressure which is determined by the source {@code Publisher}'s backpressure + * behavior.
    + *
    Scheduler:
    + *
    You specify which {@link Scheduler} this operator will use
    + *
    + * + * @param scheduler + * the {@link Scheduler} to perform subscription actions on + * @param requestOn if true, requests are rerouted to the given Scheduler as well (strong pipelining) + * if false, requests coming from any thread are simply forwarded to + * the upstream on the same thread (weak pipelining) + * @return the source Publisher modified so that its subscriptions happen on the + * specified {@link Scheduler} + * @see ReactiveX operators documentation: SubscribeOn + * @see RxJava Threading Examples + * @see #observeOn + * @since 2.1.1 - experimental + */ + @CheckReturnValue + @BackpressureSupport(BackpressureKind.PASS_THROUGH) + @SchedulerSupport(SchedulerSupport.CUSTOM) + @Experimental + public final Flowable subscribeOn(@NonNull Scheduler scheduler, boolean requestOn) { + ObjectHelper.requireNonNull(scheduler, "scheduler is null"); + return RxJavaFlowablePlugins.onAssembly(new FlowableSubscribeOn(this, scheduler, requestOn)); } /** * Returns a Flowable that emits the items emitted by the source Publisher or the items of an alternate * Publisher if the source Publisher is empty. + *

    * - *

    *

    *
    Backpressure:
    *
    If the source {@code Publisher} is empty, the alternate {@code Publisher} is expected to honor backpressure. @@ -13390,7 +13427,6 @@ public final Flowable takeLast(long count, long time, TimeUnit unit, Schedule * unbounded manner (i.e., no backpressure is applied to it) but note that this may * lead to {@code OutOfMemoryError} due to internal buffer bloat. * Consider using {@link #takeLast(long, long, TimeUnit)} in this case.
    - * behavior.
    *
    Scheduler:
    *
    This version of {@code takeLast} operates by default on the {@code computation} {@link Scheduler}.
    *
    @@ -13421,7 +13457,6 @@ public final Flowable takeLast(long time, TimeUnit unit) { * unbounded manner (i.e., no backpressure is applied to it) but note that this may * lead to {@code OutOfMemoryError} due to internal buffer bloat. * Consider using {@link #takeLast(long, long, TimeUnit)} in this case.
    - * behavior.
    *
    Scheduler:
    *
    This version of {@code takeLast} operates by default on the {@code computation} {@link Scheduler}.
    *
    @@ -13786,7 +13821,6 @@ public final Flowable throttleLast(long intervalDuration, TimeUnit unit, Sche * *

    * Information on debounce vs throttle: - *

    *

      *
    • Debounce and Throttle: visual explanation
    • *
    • Debouncing: javascript methods
    • @@ -13828,7 +13862,6 @@ public final Flowable throttleWithTimeout(long timeout, TimeUnit unit) { * *

      * Information on debounce vs throttle: - *

      *

        *
      • Debounce and Throttle: visual explanation
      • *
      • Debouncing: javascript methods
      • @@ -14385,7 +14418,7 @@ public final Flowable> timestamp(final TimeUnit unit, final Scheduler s *

        * This allows fluent conversion to any other type. *

        - *
        Backpressure:
        + *
        Backpressure:
        *
        The backpressure behavior depends on what happens in the {@code converter} function.
        *
        Scheduler:
        *
        {@code to} does not operate by default on a particular {@link Scheduler}.
        @@ -15841,7 +15874,7 @@ public final Flowable withLatestFrom(Iterable> oth * Note that the {@code other} Iterable is evaluated as items are observed from the source Publisher; it is * not pre-consumed. This allows you to zip infinite streams on either side. *
        - *
        Backpressure:
        + *
        Backpressure:
        *
        The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
        @@ -15875,7 +15908,6 @@ public final Flowable zipWith(Iterable other, BiFunction - *

        * The operator subscribes to its sources in order they are specified and completes eagerly if * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling @@ -15890,7 +15922,7 @@ public final Flowable zipWith(Iterable other, BiFunction *

        - *
        Backpressure:
        + *
        Backpressure:
        *
        The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
        @@ -15923,7 +15955,6 @@ public final Flowable zipWith(Publisher other, BiFunction * Returns a Flowable that emits items that are the result of applying a specified function to pairs of * values, one each from the source Publisher and another specified Publisher. *

        - *

        * The operator subscribes to its sources in order they are specified and completes eagerly if * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling @@ -15938,7 +15969,7 @@ public final Flowable zipWith(Publisher other, BiFunction * * *

        - *
        Backpressure:
        + *
        Backpressure:
        *
        The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
        @@ -15974,7 +16005,6 @@ public final Flowable zipWith(Publisher other, * Returns a Flowable that emits items that are the result of applying a specified function to pairs of * values, one each from the source Publisher and another specified Publisher. *

        - *

        * The operator subscribes to its sources in order they are specified and completes eagerly if * one of the sources is shorter than the rest while cancelling the other sources. Therefore, it * is possible those other sources will never be able to run to completion (and thus not calling @@ -15989,7 +16019,7 @@ public final Flowable zipWith(Publisher other, * * *

        - *
        Backpressure:
        + *
        Backpressure:
        *
        The operator expects backpressure from the sources and honors backpressure from the downstream. * (I.e., zipping with {@link #interval(long, TimeUnit)} may result in MissingBackpressureException, use * one of the {@code onBackpressureX} to handle similar, backpressure-ignoring sources.
        @@ -16030,7 +16060,7 @@ public final Flowable zipWith(Publisher other, * Creates a TestSubscriber that requests Long.MAX_VALUE and subscribes * it to this Flowable. *
        - *
        Backpressure:
        + *
        Backpressure:
        *
        The returned TestSubscriber consumes this Flowable in an unbounded fashion.
        *
        Scheduler:
        *
        {@code test} does not operate by default on a particular {@link Scheduler}.
        @@ -16051,7 +16081,7 @@ public final TestSubscriber test() { // NoPMD * Creates a TestSubscriber with the given initial request amount and subscribes * it to this Flowable. *
        - *
        Backpressure:
        + *
        Backpressure:
        *
        The returned TestSubscriber requests the given {@code initialRequest} amount upfront.
        *
        Scheduler:
        *
        {@code test} does not operate by default on a particular {@link Scheduler}.
        @@ -16074,7 +16104,7 @@ public final TestSubscriber test(long initialRequest) { // NoPMD * optionally cancels it before the subscription and subscribes * it to this Flowable. *
        - *
        Backpressure:
        + *
        Backpressure:
        *
        The returned TestSubscriber requests the given {@code initialRequest} amount upfront.
        *
        Scheduler:
        *
        {@code test} does not operate by default on a particular {@link Scheduler}.
        diff --git a/flowable/src/main/java/io/reactivex/flowable/FlowableEmitter.java b/flowable/src/main/java/io/reactivex/flowable/FlowableEmitter.java index e0104e0..d76fead 100644 --- a/flowable/src/main/java/io/reactivex/flowable/FlowableEmitter.java +++ b/flowable/src/main/java/io/reactivex/flowable/FlowableEmitter.java @@ -65,4 +65,19 @@ public interface FlowableEmitter extends Emitter { */ @NonNull FlowableEmitter serialize(); + + /** + * Attempts to emit the specified {@code Throwable} error if the downstream + * hasn't cancelled the sequence or is otherwise terminated, returning false + * if the emission is not allowed to happen due to lifecycle restrictions. + *

        + * Unlike {@link #onError(Throwable)}, the {@code RxJavaPlugins.onError} is not called + * if the error could not be delivered. + *

        History: 2.1.1 - experimental + * @param t the throwable error to signal if possible + * @return true if successful, false if the downstream is not able to accept further + * events + * @since 2.2 + */ + boolean tryOnError(@NonNull Throwable t); } diff --git a/flowable/src/main/java/io/reactivex/flowable/ParallelFailureHandling.java b/flowable/src/main/java/io/reactivex/flowable/ParallelFailureHandling.java index 6353264..e1a09e2 100644 --- a/flowable/src/main/java/io/reactivex/flowable/ParallelFailureHandling.java +++ b/flowable/src/main/java/io/reactivex/flowable/ParallelFailureHandling.java @@ -13,14 +13,13 @@ package io.reactivex.flowable; -import io.reactivex.common.annotations.Experimental; import io.reactivex.common.functions.BiFunction; /** * Enumerations for handling failure within a parallel operator. - * @since 2.0.8 - experimental + *

        History: 2.0.8 - experimental + * @since 2.2 */ -@Experimental public enum ParallelFailureHandling implements BiFunction { /** * The current rail is stopped and the error is dropped. diff --git a/flowable/src/main/java/io/reactivex/flowable/ParallelFlowable.java b/flowable/src/main/java/io/reactivex/flowable/ParallelFlowable.java index 6da335b..ab3daf5 100644 --- a/flowable/src/main/java/io/reactivex/flowable/ParallelFlowable.java +++ b/flowable/src/main/java/io/reactivex/flowable/ParallelFlowable.java @@ -34,10 +34,10 @@ * Use {@code runOn()} to introduce where each 'rail' should run on thread-vise. * Use {@code sequential()} to merge the sources back into a single Flowable. * + *

        History: 2.0.5 - experimental * @param the value type - * @since 2.0.5 - experimental + * @since 2.2 */ -@Experimental public abstract class ParallelFlowable { /** diff --git a/flowable/src/main/java/io/reactivex/flowable/ParallelTransformer.java b/flowable/src/main/java/io/reactivex/flowable/ParallelTransformer.java index 42291d0..bc4ecff 100644 --- a/flowable/src/main/java/io/reactivex/flowable/ParallelTransformer.java +++ b/flowable/src/main/java/io/reactivex/flowable/ParallelTransformer.java @@ -18,12 +18,12 @@ /** * Interface to compose ParallelFlowable. * + *

        History: 2.0.8 - experimental * @param the upstream value type * @param the downstream value type * - * @since 2.0.8 - experimental + * @since 2.2 */ -@Experimental public interface ParallelTransformer { /** * Applies a function to the upstream ParallelFlowable and returns a ParallelFlowable with diff --git a/flowable/src/main/java/io/reactivex/flowable/RxJavaFlowablePlugins.java b/flowable/src/main/java/io/reactivex/flowable/RxJavaFlowablePlugins.java index 05cd51a..728652e 100644 --- a/flowable/src/main/java/io/reactivex/flowable/RxJavaFlowablePlugins.java +++ b/flowable/src/main/java/io/reactivex/flowable/RxJavaFlowablePlugins.java @@ -199,10 +199,10 @@ public static ConnectableFlowable onAssembly(@NonNull ConnectableFlowable /** * Sets the specific hook function. + *

        History: 2.0.6 - experimental * @param handler the hook function to set, null allowed - * @since 2.0.6 - experimental + * @since 2.1 */ - @Experimental @SuppressWarnings("rawtypes") public static void setOnParallelAssembly(@Nullable Function handler) { if (lockdown) { @@ -213,10 +213,10 @@ public static void setOnParallelAssembly(@Nullable FunctionHistory: 2.0.6 - experimental * @return the hook function, may be null - * @since 2.0.6 - experimental + * @since 2.1 */ - @Experimental @SuppressWarnings("rawtypes") @Nullable public static Function getOnParallelAssembly() { @@ -225,12 +225,12 @@ public static void setOnParallelAssembly(@Nullable FunctionHistory: 2.0.6 - experimental * @param the value type of the source * @param source the hook's input value * @return the value returned by the hook - * @since 2.0.6 - experimental + * @since 2.1 */ - @Experimental @SuppressWarnings({ "rawtypes", "unchecked" }) @NonNull public static ParallelFlowable onAssembly(@NonNull ParallelFlowable source) { @@ -244,12 +244,12 @@ public static ParallelFlowable onAssembly(@NonNull ParallelFlowable so /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#computation()} * except using {@code threadFactory} for thread creation. + *

        History: 2.0.5 - experimental * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. * @return the created Scheduler instance - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental @NonNull public static Scheduler createComputationScheduler(@NonNull ThreadFactory threadFactory) { return new ComputationScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); @@ -258,12 +258,12 @@ public static Scheduler createComputationScheduler(@NonNull ThreadFactory thread /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#io()} * except using {@code threadFactory} for thread creation. + *

        History: 2.0.5 - experimental * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. * @return the created Scheduler instance - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental @NonNull public static Scheduler createIoScheduler(@NonNull ThreadFactory threadFactory) { return new IoScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); @@ -272,12 +272,12 @@ public static Scheduler createIoScheduler(@NonNull ThreadFactory threadFactory) /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#newThread()} * except using {@code threadFactory} for thread creation. + *

        History: 2.0.5 - experimental * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. * @return the created Scheduler instance - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental @NonNull public static Scheduler createNewThreadScheduler(@NonNull ThreadFactory threadFactory) { return new NewThreadScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); @@ -286,12 +286,12 @@ public static Scheduler createNewThreadScheduler(@NonNull ThreadFactory threadFa /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#single()} * except using {@code threadFactory} for thread creation. + *

        History: 2.0.5 - experimental * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. * @return the created Scheduler instance - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental @NonNull public static Scheduler createSingleScheduler(@NonNull ThreadFactory threadFactory) { return new SingleScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); diff --git a/flowable/src/main/java/io/reactivex/flowable/extensions/FuseToFlowable.java b/flowable/src/main/java/io/reactivex/flowable/extensions/FuseToFlowable.java index b2fdc0f..b0810a1 100644 --- a/flowable/src/main/java/io/reactivex/flowable/extensions/FuseToFlowable.java +++ b/flowable/src/main/java/io/reactivex/flowable/extensions/FuseToFlowable.java @@ -20,8 +20,8 @@ * the operator goes from Flowable to some other reactive type and then the sequence calls * for toFlowable again: *

        - * Single<Integer> single = Flowable.range(1, 10).reduce((a, b) -> a + b);
        - * Flowable<Integer> flowable = single.toFlowable();
        + * Single<Integer> single = Flowable.range(1, 10).reduce((a, b) -> a + b);
        + * Flowable<Integer> flowable = single.toFlowable();
          * 
        * * The {@code Single.toFlowable()} will check for this interface and call the {@link #fuseToFlowable()} diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableBufferTimed.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableBufferTimed.java index 741c3dd..7869565 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableBufferTimed.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableBufferTimed.java @@ -460,12 +460,11 @@ public void onNext(T t) { if (b.size() < maxSize) { return; } - } - - if (restartTimerOnMaxSize) { buffer = null; producerIndex++; + } + if (restartTimerOnMaxSize) { timer.dispose(); } @@ -480,17 +479,13 @@ public void onNext(T t) { return; } - if (restartTimerOnMaxSize) { - synchronized (this) { - buffer = b; - consumerIndex++; - } + synchronized (this) { + buffer = b; + consumerIndex++; + } + if (restartTimerOnMaxSize) { timer = w.schedulePeriodically(this, timespan, timespan, unit); - } else { - synchronized (this) { - buffer = b; - } } } diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableCombineLatest.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableCombineLatest.java index 0701390..5a16969 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableCombineLatest.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableCombineLatest.java @@ -476,7 +476,7 @@ public R poll() throws Exception { return null; } T[] a = (T[])queue.poll(); - R r = combiner.apply(a); + R r = ObjectHelper.requireNonNull(combiner.apply(a), "The combiner returned a null value"); ((CombineLatestInnerSubscriber)e).requestOne(); return r; } diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableCreate.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableCreate.java index cb7d1f9..9f70b12 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableCreate.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableCreate.java @@ -128,21 +128,27 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - if (emitter.isCancelled() || done) { - RxJavaCommonPlugins.onError(t); - return; - } - if (t == null) { - t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); - } - if (error.addThrowable(t)) { - done = true; - drain(); - } else { + if (!tryOnError(t)) { RxJavaCommonPlugins.onError(t); } } + @Override + public boolean tryOnError(Throwable t) { + if (emitter.isCancelled() || done) { + return false; + } + if (t == null) { + t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); + } + if (error.addThrowable(t)) { + done = true; + drain(); + return true; + } + return false; + } + @Override public void onComplete() { if (emitter.isCancelled() || done) { @@ -244,6 +250,10 @@ abstract static class BaseEmitter @Override public void onComplete() { + complete(); + } + + protected void complete() { if (isCancelled()) { return; } @@ -255,19 +265,30 @@ public void onComplete() { } @Override - public void onError(Throwable e) { + public final void onError(Throwable e) { + if (!tryOnError(e)) { + RxJavaCommonPlugins.onError(e); + } + } + + @Override + public boolean tryOnError(Throwable e) { + return error(e); + } + + protected boolean error(Throwable e) { if (e == null) { e = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); } if (isCancelled()) { - RxJavaCommonPlugins.onError(e); - return; + return false; } try { actual.onError(e); } finally { serial.dispose(); } + return true; } @Override @@ -445,10 +466,9 @@ public void onNext(T t) { } @Override - public void onError(Throwable e) { + public boolean tryOnError(Throwable e) { if (done || isCancelled()) { - RxJavaCommonPlugins.onError(e); - return; + return false; } if (e == null) { @@ -458,6 +478,7 @@ public void onError(Throwable e) { error = e; done = true; drain(); + return true; } @Override @@ -506,9 +527,9 @@ void drain() { if (d && empty) { Throwable ex = error; if (ex != null) { - super.onError(ex); + error(ex); } else { - super.onComplete(); + complete(); } return; } @@ -535,9 +556,9 @@ void drain() { if (d && empty) { Throwable ex = error; if (ex != null) { - super.onError(ex); + error(ex); } else { - super.onComplete(); + complete(); } return; } @@ -588,10 +609,9 @@ public void onNext(T t) { } @Override - public void onError(Throwable e) { + public boolean tryOnError(Throwable e) { if (done || isCancelled()) { - RxJavaCommonPlugins.onError(e); - return; + return false; } if (e == null) { onError(new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources.")); @@ -599,6 +619,7 @@ public void onError(Throwable e) { error = e; done = true; drain(); + return true; } @Override @@ -647,9 +668,9 @@ void drain() { if (d && empty) { Throwable ex = error; if (ex != null) { - super.onError(ex); + error(ex); } else { - super.onComplete(); + complete(); } return; } @@ -676,9 +697,9 @@ void drain() { if (d && empty) { Throwable ex = error; if (ex != null) { - super.onError(ex); + error(ex); } else { - super.onComplete(); + complete(); } return; } @@ -696,4 +717,4 @@ void drain() { } } -} +} \ No newline at end of file diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableDoAfterNext.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableDoAfterNext.java index 96d86cc..c287b2c 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableDoAfterNext.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableDoAfterNext.java @@ -23,10 +23,10 @@ /** * Calls a consumer after pushing the current item to the downstream. + *

        History: 2.0.1 - experimental * @param the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class FlowableDoAfterNext extends AbstractFlowableWithUpstream { final Consumer onAfterNext; diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableDoFinally.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableDoFinally.java index a0e7329..f24d40a 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableDoFinally.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableDoFinally.java @@ -26,10 +26,10 @@ /** * Execute an action after an onError, onComplete or a cancel event. * + *

        History: 2.0.1 - experimental * @param the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class FlowableDoFinally extends AbstractFlowableWithUpstream { final Action onFinally; diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableDoOnEach.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableDoOnEach.java index 54ba64b..61c320d 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableDoOnEach.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableDoOnEach.java @@ -20,6 +20,7 @@ import io.reactivex.common.annotations.Nullable; import io.reactivex.common.exceptions.*; import io.reactivex.common.functions.*; +import io.reactivex.common.internal.utils.ExceptionHelper; import io.reactivex.flowable.Flowable; import io.reactivex.flowable.internal.subscribers.*; @@ -148,12 +149,34 @@ public int requestFusion(int mode) { @Nullable @Override - public T poll() throws Throwable { - T v = qs.poll(); + public T poll() throws Exception { + T v; + + try { + v = qs.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + try { + onError.accept(ex); + } catch (Throwable exc) { + throw new CompositeException(ex, exc); + } + throw ExceptionHelper.throwIfThrowable(ex); + } if (v != null) { try { - onNext.accept(v); + try { + onNext.accept(v); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + try { + onError.accept(ex); + } catch (Throwable exc) { + throw new CompositeException(ex, exc); + } + throw ExceptionHelper.throwIfThrowable(ex); + } } finally { onAfterTerminate.run(); } @@ -281,12 +304,34 @@ public int requestFusion(int mode) { @Nullable @Override - public T poll() throws Throwable { - T v = qs.poll(); + public T poll() throws Exception { + T v; + + try { + v = qs.poll(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + try { + onError.accept(ex); + } catch (Throwable exc) { + throw new CompositeException(ex, exc); + } + throw ExceptionHelper.throwIfThrowable(ex); + } if (v != null) { try { - onNext.accept(v); + try { + onNext.accept(v); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + try { + onError.accept(ex); + } catch (Throwable exc) { + throw new CompositeException(ex, exc); + } + throw ExceptionHelper.throwIfThrowable(ex); + } } finally { onAfterTerminate.run(); } diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableInternalHelper.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableInternalHelper.java index 3be1407..306f40d 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableInternalHelper.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableInternalHelper.java @@ -19,7 +19,7 @@ import io.reactivex.common.*; import io.reactivex.common.functions.*; -import io.reactivex.common.internal.functions.Functions; +import io.reactivex.common.internal.functions.*; import io.reactivex.flowable.*; /** @@ -77,7 +77,8 @@ static final class ItemDelayFunction implements Function> @Override public Publisher apply(final T v) throws Exception { - return new FlowableTakePublisher(itemDelay.apply(v), 1).map(Functions.justFunction(v)).defaultIfEmpty(v); + Publisher p = ObjectHelper.requireNonNull(itemDelay.apply(v), "The itemDelay returned a null Publisher"); + return new FlowableTakePublisher(p, 1).map(Functions.justFunction(v)).defaultIfEmpty(v); } } @@ -164,7 +165,7 @@ static final class FlatMapWithCombinerOuter implements Function apply(final T t) throws Exception { @SuppressWarnings("unchecked") - Publisher u = (Publisher)mapper.apply(t); + Publisher u = (Publisher)ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Publisher"); return new FlowableMapPublisher(u, new FlatMapWithCombinerInner(combiner, t)); } } @@ -184,7 +185,7 @@ static final class FlatMapIntoIterable implements Function @Override public Publisher apply(T t) throws Exception { - return new FlowableFromIterable(mapper.apply(t)); + return new FlowableFromIterable(ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Iterable")); } } @@ -317,7 +318,8 @@ static final class ReplayFunction implements Function, Publish @Override public Publisher apply(Flowable t) throws Exception { - return Flowable.fromPublisher(selector.apply(t)).observeOn(scheduler); + Publisher p = ObjectHelper.requireNonNull(selector.apply(t), "The selector returned a null Publisher"); + return Flowable.fromPublisher(p).observeOn(scheduler); } } } diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableInterval.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableInterval.java index 8058928..d819eff 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableInterval.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableInterval.java @@ -19,8 +19,10 @@ import org.reactivestreams.*; import io.reactivex.common.*; +import io.reactivex.common.Scheduler.Worker; import io.reactivex.common.exceptions.MissingBackpressureException; import io.reactivex.common.internal.disposables.DisposableHelper; +import io.reactivex.common.internal.schedulers.TrampolineScheduler; import io.reactivex.flowable.Flowable; import io.reactivex.flowable.internal.subscriptions.SubscriptionHelper; import io.reactivex.flowable.internal.utils.BackpressureHelper; @@ -43,9 +45,16 @@ public void subscribeActual(Subscriber s) { IntervalSubscriber is = new IntervalSubscriber(s); s.onSubscribe(is); - Disposable d = scheduler.schedulePeriodicallyDirect(is, initialDelay, period, unit); + Scheduler sch = scheduler; - is.setResource(d); + if (sch instanceof TrampolineScheduler) { + Worker worker = sch.createWorker(); + is.setResource(worker); + worker.schedulePeriodically(is, initialDelay, period, unit); + } else { + Disposable d = sch.schedulePeriodicallyDirect(is, initialDelay, period, unit); + is.setResource(d); + } } static final class IntervalSubscriber extends AtomicLong diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableIntervalRange.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableIntervalRange.java index 15d8372..1d60acc 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableIntervalRange.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableIntervalRange.java @@ -19,8 +19,10 @@ import org.reactivestreams.*; import io.reactivex.common.*; +import io.reactivex.common.Scheduler.Worker; import io.reactivex.common.exceptions.MissingBackpressureException; import io.reactivex.common.internal.disposables.DisposableHelper; +import io.reactivex.common.internal.schedulers.TrampolineScheduler; import io.reactivex.flowable.Flowable; import io.reactivex.flowable.internal.subscriptions.SubscriptionHelper; import io.reactivex.flowable.internal.utils.BackpressureHelper; @@ -47,9 +49,16 @@ public void subscribeActual(Subscriber s) { IntervalRangeSubscriber is = new IntervalRangeSubscriber(s, start, end); s.onSubscribe(is); - Disposable d = scheduler.schedulePeriodicallyDirect(is, initialDelay, period, unit); + Scheduler sch = scheduler; - is.setResource(d); + if (sch instanceof TrampolineScheduler) { + Worker worker = sch.createWorker(); + is.setResource(worker); + worker.schedulePeriodically(is, initialDelay, period, unit); + } else { + Disposable d = sch.schedulePeriodicallyDirect(is, initialDelay, period, unit); + is.setResource(d); + } } static final class IntervalRangeSubscriber extends AtomicLong diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableLast.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableLast.java index 3ec62ed..720ced1 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableLast.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableLast.java @@ -49,7 +49,7 @@ static final class LastSubscriber extends DeferredScalarSubscription Subscription upstream; - public LastSubscriber(Subscriber actual, T defaultItem, boolean errorOnEmpty) { + LastSubscriber(Subscriber actual, T defaultItem, boolean errorOnEmpty) { super(actual); this.defaultItem = defaultItem; this.errorOnEmpty = errorOnEmpty; diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableMapNotification.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableMapNotification.java index e8ffe45..aaae26c 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableMapNotification.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableMapNotification.java @@ -17,7 +17,7 @@ import org.reactivestreams.Subscriber; -import io.reactivex.common.exceptions.Exceptions; +import io.reactivex.common.exceptions.*; import io.reactivex.common.functions.Function; import io.reactivex.common.internal.functions.ObjectHelper; import io.reactivex.flowable.Flowable; @@ -87,7 +87,7 @@ public void onError(Throwable t) { p = ObjectHelper.requireNonNull(onErrorMapper.apply(t), "The onError publisher returned is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + actual.onError(new CompositeException(t, e)); return; } diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableReduceWith.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableReduceWith.java index ec019b1..516b777 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableReduceWith.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableReduceWith.java @@ -62,7 +62,7 @@ static final class ReduceWithSubscriber extends DeferredScalarSubscription boolean done; - public ReduceWithSubscriber(Subscriber actual, R value, BiFunction reducer) { + ReduceWithSubscriber(Subscriber actual, R value, BiFunction reducer) { super(actual); this.value = value; this.reducer = reducer; diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableRefCount.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableRefCount.java index 98aaa9c..de5a32a 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableRefCount.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableRefCount.java @@ -139,10 +139,10 @@ public void subscribeActual(final Subscriber subscriber) { source.connect(onSubscribe(subscriber, writeLocked)); } finally { // need to cover the case where the source is subscribed to - // outside of this class thus preventing the Action1 passed + // outside of this class thus preventing the Consumer passed // to source.connect above being called if (writeLocked.get()) { - // Action1 passed to source.connect was not called + // Consumer passed to source.connect was not called lock.unlock(); } } diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableSubscribeOn.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableSubscribeOn.java index b9bd964..5ec37c4 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableSubscribeOn.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableSubscribeOn.java @@ -35,10 +35,10 @@ public final class FlowableSubscribeOn extends AbstractFlowableWithUpstream source, Scheduler scheduler, boolean nonScheduledRequests) { + public FlowableSubscribeOn(Flowable source, Scheduler scheduler, boolean requestOn) { super(source); this.scheduler = scheduler; - this.nonScheduledRequests = nonScheduledRequests; + this.nonScheduledRequests = !requestOn; } @Override diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableUsing.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableUsing.java index 81d8393..347a407 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableUsing.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableUsing.java @@ -22,6 +22,7 @@ import io.reactivex.common.RxJavaCommonPlugins; import io.reactivex.common.exceptions.*; import io.reactivex.common.functions.*; +import io.reactivex.common.internal.functions.ObjectHelper; import io.reactivex.flowable.Flowable; import io.reactivex.flowable.internal.subscriptions.*; @@ -55,7 +56,7 @@ public void subscribeActual(Subscriber s) { Publisher source; try { - source = sourceSupplier.apply(resource); + source = ObjectHelper.requireNonNull(sourceSupplier.apply(resource), "The sourceSupplier returned a null Publisher"); } catch (Throwable e) { Exceptions.throwIfFatal(e); try { diff --git a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableWithLatestFromMany.java b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableWithLatestFromMany.java index 5e7fed2..48ff2b7 100644 --- a/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableWithLatestFromMany.java +++ b/flowable/src/main/java/io/reactivex/flowable/internal/operators/FlowableWithLatestFromMany.java @@ -169,7 +169,7 @@ public void onNext(T t) { R v; try { - v = ObjectHelper.requireNonNull(combiner.apply(objects), "combiner returned a null value"); + v = ObjectHelper.requireNonNull(combiner.apply(objects), "The combiner returned a null value"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); cancel(); @@ -298,7 +298,7 @@ public void dispose() { final class SingletonArrayFunc implements Function { @Override public R apply(T t) throws Exception { - return combiner.apply(new Object[] { t }); + return ObjectHelper.requireNonNull(combiner.apply(new Object[] { t }), "The combiner returned a null value"); } } } diff --git a/flowable/src/main/java/io/reactivex/flowable/package-info.java b/flowable/src/main/java/io/reactivex/flowable/package-info.java index 80f2b9b..fd5ce96 100644 --- a/flowable/src/main/java/io/reactivex/flowable/package-info.java +++ b/flowable/src/main/java/io/reactivex/flowable/package-info.java @@ -14,14 +14,13 @@ * limitations under the License. */ /** - * Base reactive classes: Flowable, Observable, Single and Completable; base reactive consumers; + * Base reactive classes: Flowable; base reactive consumers; * other common base interfaces. * *

        A library that enables subscribing to and composing asynchronous events and * callbacks.

        - *

        The Flowable/Subscriber, Observable/Observer, Single/SingleObserver and - * Completable/CompletableObserver interfaces and associated operators (in - * the {@code io.reactivex.internal.operators} package) are inspired by the + *

        The Flowable/Subscriber interfaces and associated operators (in + * the {@code io.reactivex.flowable.internal.operators} package) are inspired by the * Reactive Rx library in Microsoft .NET but designed and implemented on * the more advanced Reactive-Streams ( http://www.reactivestreams.org ) principles.

        *

        @@ -32,21 +31,16 @@ * *

        Compared with the Microsoft implementation: *

          - *
        • Observable == IObservable (base type)
        • - *
        • Observer == IObserver (event consumer)
        • *
        • Disposable == IDisposable (resource/cancellation management)
        • - *
        • Observable == Observable (factory methods)
        • + *
        • Publisher = IObservable (reactive source) *
        • Flowable == IAsyncEnumerable (backpressure)
        • *
        • Subscriber == IAsyncEnumerator
        • *
        - * The Single and Completable reactive base types have no equivalent in Rx.NET as of 3.x. - *

        *

        Services which intend on exposing data asynchronously and wish * to allow reactive processing and composition can implement the - * {@link io.reactivex.Flowable}, {@link io.reactivex.Observable}, {@link io.reactivex.Single} - * or {@link io.reactivex.Completable} class which then allow consumers to subscribe to them + * {@link io.reactivex.flowable.Flowable} class which then allow consumers to subscribe to it * and receive events.

        - *

        Usage examples can be found on the {@link io.reactivex.Flowable}/{@link io.reactivex.Observable} and {@link org.reactivestreams.Subscriber} classes.

        + *

        Usage examples can be found on the {@link io.reactivex.flowable.Flowable} and {@link org.reactivestreams.Subscriber} classes.

        */ package io.reactivex.flowable; diff --git a/flowable/src/main/java/io/reactivex/flowable/processors/AsyncProcessor.java b/flowable/src/main/java/io/reactivex/flowable/processors/AsyncProcessor.java index b908e05..bd1c0c9 100644 --- a/flowable/src/main/java/io/reactivex/flowable/processors/AsyncProcessor.java +++ b/flowable/src/main/java/io/reactivex/flowable/processors/AsyncProcessor.java @@ -22,7 +22,8 @@ import io.reactivex.flowable.internal.subscriptions.DeferredScalarSubscription; /** - * A Subject that emits the very last value followed by a completion event or the received error to Subscribers. + * Processor that emits the very last value followed by a completion event or the received error + * to {@link Subscriber}s. * *

        The implementation of onXXX methods are technically thread-safe but non-serialized calls * to them may lead to undefined state in the currently subscribed Subscribers. diff --git a/flowable/src/main/java/io/reactivex/flowable/processors/BehaviorProcessor.java b/flowable/src/main/java/io/reactivex/flowable/processors/BehaviorProcessor.java index 65ff0e6..82f7156 100644 --- a/flowable/src/main/java/io/reactivex/flowable/processors/BehaviorProcessor.java +++ b/flowable/src/main/java/io/reactivex/flowable/processors/BehaviorProcessor.java @@ -35,7 +35,6 @@ * *

        * Example usage: - *

        *

         {@code
         
           // observer will receive all events.
        @@ -226,11 +225,11 @@ public void onComplete() {
              * 

        * Calling with null will terminate the PublishProcessor and a NullPointerException * is signalled to the Subscribers. + *

        History: 2.0.8 - experimental * @param t the item to emit, not null * @return true if the item was emitted to all Subscribers - * @since 2.0.8 - experimental + * @since 2.1 */ - @Experimental public boolean offer(T t) { if (t == null) { onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); diff --git a/flowable/src/main/java/io/reactivex/flowable/processors/PublishProcessor.java b/flowable/src/main/java/io/reactivex/flowable/processors/PublishProcessor.java index 7f9f156..58c12b5 100644 --- a/flowable/src/main/java/io/reactivex/flowable/processors/PublishProcessor.java +++ b/flowable/src/main/java/io/reactivex/flowable/processors/PublishProcessor.java @@ -23,14 +23,14 @@ import io.reactivex.flowable.internal.utils.BackpressureHelper; /** - * A Subject that multicasts events to Subscribers that are currently subscribed to it. + * Processor that multicasts all subsequently observed items to its current {@link Subscriber}s. * *

        * * - *

        The subject does not coordinate backpressure for its subscribers and implements a weaker onSubscribe which - * calls requests Long.MAX_VALUE from the incoming Subscriptions. This makes it possible to subscribe the PublishSubject - * to multiple sources (note on serialization though) unlike the standard contract on Subscriber. Child subscribers, however, are not overflown but receive an + *

        The processor does not coordinate backpressure for its subscribers and implements a weaker onSubscribe which + * calls requests Long.MAX_VALUE from the incoming Subscriptions. This makes it possible to subscribe the PublishProcessor + * to multiple sources (note on serialization though) unlike the standard Subscriber contract. Child subscribers, however, are not overflown but receive an * IllegalStateException in case their requested amount is zero. * *

        The implementation of onXXX methods are technically thread-safe but non-serialized calls @@ -40,7 +40,6 @@ * {@code new} but must be created via the {@link #create()} method. * * Example usage: - *

        *

         {@code
         
           PublishProcessor processor = PublishProcessor.create();
        @@ -54,7 +53,7 @@
           processor.onComplete();
         
           } 
        - * @param  the value type multicast to Subscribers.
        + * @param  the value type multicasted to Subscribers.
          */
         public final class PublishProcessor extends FlowableProcessor {
             /** The terminated indicator for the subscribers array. */
        @@ -236,11 +235,11 @@ public void onComplete() {
              * 

        * Calling with null will terminate the PublishProcessor and a NullPointerException * is signalled to the Subscribers. + *

        History: 2.0.8 - experimental * @param t the item to emit, not null * @return true if the item was emitted to all Subscribers - * @since 2.0.8 - experimental + * @since 2.1 */ - @Experimental public boolean offer(T t) { if (t == null) { onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources.")); diff --git a/flowable/src/main/java/io/reactivex/flowable/processors/ReplayProcessor.java b/flowable/src/main/java/io/reactivex/flowable/processors/ReplayProcessor.java index 2f06b36..ce2dffe 100644 --- a/flowable/src/main/java/io/reactivex/flowable/processors/ReplayProcessor.java +++ b/flowable/src/main/java/io/reactivex/flowable/processors/ReplayProcessor.java @@ -43,7 +43,6 @@ * *

        * Example usage: - *

        *

         {@code
         
           ReplayProcessor processor = new ReplayProcessor();
        @@ -1051,6 +1050,11 @@ public T getValue() {
                         h = next;
                     }
         
        +            long limit = scheduler.now(unit) - maxAge;
        +            if (h.time < limit) {
        +                return null;
        +            }
        +
                     Object v = h.value;
                     if (v == null) {
                         return null;
        diff --git a/flowable/src/main/java/io/reactivex/flowable/processors/UnicastProcessor.java b/flowable/src/main/java/io/reactivex/flowable/processors/UnicastProcessor.java
        index 90f1574..d472dc0 100644
        --- a/flowable/src/main/java/io/reactivex/flowable/processors/UnicastProcessor.java
        +++ b/flowable/src/main/java/io/reactivex/flowable/processors/UnicastProcessor.java
        @@ -89,13 +89,13 @@ public static  UnicastProcessor create(int capacityHint) {
         
             /**
              * Creates an UnicastProcessor with default internal buffer capacity hint and delay error flag.
        +     * 

        History: 2.0.8 - experimental * @param the value type * @param delayError deliver pending onNext events before onError * @return an UnicastProcessor instance - * @since 2.0.8 - experimental + * @since 2.1 */ @CheckReturnValue - @Experimental public static UnicastProcessor create(boolean delayError) { return new UnicastProcessor(bufferSize(), null, delayError); } @@ -125,15 +125,15 @@ public static UnicastProcessor create(int capacityHint, Runnable onCancel *

        The callback, if not null, is called exactly once and * non-overlapped with any active replay. * + *

        History: 2.0.8 - experimental * @param the value type * @param capacityHint the hint to size the internal unbounded buffer * @param onCancelled the non null callback * @param delayError deliver pending onNext events before onError * @return an UnicastProcessor instance - * @since 2.0.8 - experimental + * @since 2.1 */ @CheckReturnValue - @Experimental public static UnicastProcessor create(int capacityHint, Runnable onCancelled, boolean delayError) { ObjectHelper.requireNonNull(onCancelled, "onTerminate"); return new UnicastProcessor(capacityHint, onCancelled, delayError); diff --git a/flowable/src/main/java/io/reactivex/flowable/subscribers/DefaultSubscriber.java b/flowable/src/main/java/io/reactivex/flowable/subscribers/DefaultSubscriber.java index cbe974d..bd22f06 100644 --- a/flowable/src/main/java/io/reactivex/flowable/subscribers/DefaultSubscriber.java +++ b/flowable/src/main/java/io/reactivex/flowable/subscribers/DefaultSubscriber.java @@ -49,10 +49,9 @@ * instead of the standard {@code subscribe()} method. * @param the value type * - *

        Example

        - * Disposable d =
        - *     Flowable.range(1, 5)
        - *     .subscribeWith(new DefaultSubscriber<Integer>() {
        + * 

        Example

        
        + * Flowable.range(1, 5)
        + *     .subscribeWith(new DefaultSubscriber<Integer>() {
          *         @Override public void onStart() {
          *             System.out.println("Start!");
          *             request(1);
        @@ -71,9 +70,7 @@
          *             System.out.println("Done!");
          *         }
          *     });
        - * // ...
        - * d.dispose();
        - * 
        + *
        */ public abstract class DefaultSubscriber implements RelaxedSubscriber { private Subscription s; diff --git a/flowable/src/main/java/io/reactivex/flowable/subscribers/DisposableSubscriber.java b/flowable/src/main/java/io/reactivex/flowable/subscribers/DisposableSubscriber.java index 33c73ca..f9d1aa3 100644 --- a/flowable/src/main/java/io/reactivex/flowable/subscribers/DisposableSubscriber.java +++ b/flowable/src/main/java/io/reactivex/flowable/subscribers/DisposableSubscriber.java @@ -47,10 +47,10 @@ * If for some reason this can't be avoided, use {@link io.reactivex.flowable.Flowable#safeSubscribe(org.reactivestreams.Subscriber)} * instead of the standard {@code subscribe()} method. * - *

        Example

        + * 

        Example

        
          * Disposable d =
          *     Flowable.range(1, 5)
        - *     .subscribeWith(new DisposableSubscriber<Integer>() {
        + *     .subscribeWith(new DisposableSubscriber<Integer>() {
          *         @Override public void onStart() {
          *             request(1);
          *         }
        @@ -70,7 +70,7 @@
          *     });
          * // ...
          * d.dispose();
        - * 
        + *
        * @param the received value type. */ public abstract class DisposableSubscriber implements RelaxedSubscriber, Disposable { diff --git a/flowable/src/main/java/io/reactivex/flowable/subscribers/ResourceSubscriber.java b/flowable/src/main/java/io/reactivex/flowable/subscribers/ResourceSubscriber.java index 2d96c2b..ff4b5aa 100644 --- a/flowable/src/main/java/io/reactivex/flowable/subscribers/ResourceSubscriber.java +++ b/flowable/src/main/java/io/reactivex/flowable/subscribers/ResourceSubscriber.java @@ -60,13 +60,13 @@ * If for some reason this can't be avoided, use {@link io.reactivex.flowable.Flowable#safeSubscribe(org.reactivestreams.Subscriber)} * instead of the standard {@code subscribe()} method. * - *

        Example

        + * 

        Example

        
          * Disposable d =
          *     Flowable.range(1, 5)
        - *     .subscribeWith(new ResourceSubscriber<Integer>() {
        + *     .subscribeWith(new ResourceSubscriber<Integer>() {
          *         @Override public void onStart() {
          *             add(Schedulers.single()
        - *                 .scheduleDirect(() -> System.out.println("Time!"),
        + *                 .scheduleDirect(() -> System.out.println("Time!"),
          *                     2, TimeUnit.SECONDS));
          *             request(1);
          *         }
        @@ -88,7 +88,7 @@
          *     });
          * // ...
          * d.dispose();
        - * 
        + *
        * * @param the value type */ diff --git a/flowable/src/main/java/io/reactivex/flowable/subscribers/TestSubscriber.java b/flowable/src/main/java/io/reactivex/flowable/subscribers/TestSubscriber.java index 04188f3..4630f02 100644 --- a/flowable/src/main/java/io/reactivex/flowable/subscribers/TestSubscriber.java +++ b/flowable/src/main/java/io/reactivex/flowable/subscribers/TestSubscriber.java @@ -18,7 +18,6 @@ import hu.akarnokd.reactivestreams.extensions.*; import io.reactivex.common.*; -import io.reactivex.common.annotations.Experimental; import io.reactivex.common.functions.Consumer; import io.reactivex.common.internal.utils.ExceptionHelper; import io.reactivex.flowable.internal.subscriptions.SubscriptionHelper; @@ -202,6 +201,7 @@ public void onNext(T t) { } catch (Throwable ex) { // Exceptions.throwIfFatal(e); TODO add fatal exceptions? errors.add(ex); + qs.cancel(); } return; } @@ -408,11 +408,11 @@ public final TestSubscriber assertOf(Consumer> chec /** * Calls {@link #request(long)} and returns this. + *

        History: 2.0.1 - experimental * @param n the request amount * @return this - * @since 2.0.1 - experimental + * @since 2.1 */ - @Experimental public final TestSubscriber requestMore(long n) { request(n); return this; diff --git a/flowable/src/test/java/io/reactivex/flowable/FlowableNullTests.java b/flowable/src/test/java/io/reactivex/flowable/FlowableNullTests.java index d0f270b..d9dac33 100644 --- a/flowable/src/test/java/io/reactivex/flowable/FlowableNullTests.java +++ b/flowable/src/test/java/io/reactivex/flowable/FlowableNullTests.java @@ -1342,6 +1342,7 @@ public Publisher call() { } @Test(expected = NullPointerException.class) + @Ignore("No longer crashes with NPE but signals it; tested elsewhere.") public void flatMapNotificationOnErrorReturnsNull() { Flowable.error(new TestException()).flatMap(new Function>() { @Override diff --git a/flowable/src/test/java/io/reactivex/flowable/FlowableTests.java b/flowable/src/test/java/io/reactivex/flowable/FlowableTests.java index 1d3f2cb..2f5d3da 100644 --- a/flowable/src/test/java/io/reactivex/flowable/FlowableTests.java +++ b/flowable/src/test/java/io/reactivex/flowable/FlowableTests.java @@ -1120,7 +1120,7 @@ public void testEmptyIsEmpty() { // public void testForEachWithError() { // Observable.error(new Exception("boo")) // // -// .forEach(new Action1() { +// .forEach(new Consumer() { // @Override // public void call(Object t) { // //do nothing diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableBufferTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableBufferTest.java index b4a29a9..c5df48c 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableBufferTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableBufferTest.java @@ -1972,4 +1972,45 @@ public void skipBackpressure() { .test() .assertResult(Arrays.asList(1, 2), Arrays.asList(4, 5), Arrays.asList(7, 8), Arrays.asList(10)); } + + @Test + public void withTimeAndSizeCapacityRace() { + for (int i = 0; i < 1000; i++) { + final TestScheduler scheduler = new TestScheduler(); + + final PublishProcessor ps = PublishProcessor.create(); + + TestSubscriber> ts = ps.buffer(1, TimeUnit.SECONDS, scheduler, 5).test(); + + ps.onNext(1); + ps.onNext(2); + ps.onNext(3); + ps.onNext(4); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(5); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + } + }; + + TestCommonHelper.race(r1, r2); + + ps.onComplete(); + + int items = 0; + for (List o : ts.values()) { + items += o.size(); + } + + assertEquals("Round: " + i, 5, items); + } + } } diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableCombineLatestTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableCombineLatestTest.java index 157ba67..e6e7508 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableCombineLatestTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableCombineLatestTest.java @@ -26,6 +26,7 @@ import org.mockito.*; import org.reactivestreams.*; +import hu.akarnokd.reactivestreams.extensions.FusedQueueSubscription; import io.reactivex.common.*; import io.reactivex.common.exceptions.*; import io.reactivex.common.functions.*; @@ -1550,4 +1551,21 @@ public Integer apply(Integer t1, Integer t2) throws Exception { pp2.onNext(2); ts.assertResult(3); } + + @Test + public void fusedNullCheck() { + TestSubscriber ts = SubscriberFusion.newTest(FusedQueueSubscription.ASYNC); + + Flowable.combineLatest(Flowable.just(1), Flowable.just(2), new BiFunction() { + @Override + public Integer apply(Integer t1, Integer t2) throws Exception { + return null; + } + }) + .subscribe(ts); + + ts + .assertOf(SubscriberFusion.assertFusionMode(FusedQueueSubscription.ASYNC)) + .assertFailureAndMessage(NullPointerException.class, "The combiner returned a null value"); + } } diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableCreateTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableCreateTest.java index d82299d..69898f5 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableCreateTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableCreateTest.java @@ -876,4 +876,59 @@ public void cancel() throws Exception { } } + + @Test + public void tryOnError() { + for (BackpressureStrategy strategy : BackpressureStrategy.values()) { + List errors = TestCommonHelper.trackPluginErrors(); + try { + final Boolean[] response = { null }; + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + e.onNext(1); + response[0] = e.tryOnError(new TestException()); + } + }, strategy) + .take(1) + .test() + .withTag(strategy.toString()) + .assertResult(1); + + assertFalse(response[0]); + + assertTrue(strategy + ": " + errors.toString(), errors.isEmpty()); + } finally { + RxJavaCommonPlugins.reset(); + } + } + } + + @Test + public void tryOnErrorSerialized() { + for (BackpressureStrategy strategy : BackpressureStrategy.values()) { + List errors = TestCommonHelper.trackPluginErrors(); + try { + final Boolean[] response = { null }; + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter e) throws Exception { + e = e.serialize(); + e.onNext(1); + response[0] = e.tryOnError(new TestException()); + } + }, strategy) + .take(1) + .test() + .withTag(strategy.toString()) + .assertResult(1); + + assertFalse(response[0]); + + assertTrue(strategy + ": " + errors.toString(), errors.isEmpty()); + } finally { + RxJavaCommonPlugins.reset(); + } + } + } } diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableDelayTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableDelayTest.java index d8ca594..a5da725 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableDelayTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableDelayTest.java @@ -1018,4 +1018,15 @@ public void onComplete() { } } + @Test + public void itemDelayReturnsNull() { + Flowable.just(1).delay(new Function>() { + @Override + public Publisher apply(Integer t) throws Exception { + return null; + } + }) + .test() + .assertFailureAndMessage(NullPointerException.class, "The itemDelay returned a null Publisher"); + } } diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableDoOnEachTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableDoOnEachTest.java index 06ae772..37d3524 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableDoOnEachTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableDoOnEachTest.java @@ -732,4 +732,197 @@ public Flowable apply(Flowable o) throws Exception { } }); } + + @Test + public void doOnNextDoOnErrorFused() { + ConnectableFlowable co = Flowable.just(1) + .doOnNext(new Consumer() { + @Override + public void accept(Integer v) throws Exception { + throw new TestException("First"); + } + }) + .doOnError(new Consumer() { + @Override + public void accept(Throwable e) throws Exception { + throw new TestException("Second"); + } + }) + .publish(); + + TestSubscriber ts = co.test(); + co.connect(); + + ts.assertFailure(CompositeException.class); + + TestHelper.assertError(ts, 0, TestException.class, "First"); + TestHelper.assertError(ts, 1, TestException.class, "Second"); + } + + @Test + public void doOnNextDoOnErrorCombinedFused() { + ConnectableFlowable co = Flowable.just(1) + .compose(new FlowableTransformer() { + @Override + public Publisher apply(Flowable v) { + return new FlowableDoOnEach(v, + new Consumer() { + @Override + public void accept(Integer v) throws Exception { + throw new TestException("First"); + } + }, + new Consumer() { + @Override + public void accept(Throwable e) throws Exception { + throw new TestException("Second"); + } + }, + Functions.EMPTY_ACTION + , + Functions.EMPTY_ACTION + ); + } + }) + .publish(); + + TestSubscriber ts = co.test(); + co.connect(); + + ts.assertFailure(CompositeException.class); + + TestHelper.assertError(ts, 0, TestException.class, "First"); + TestHelper.assertError(ts, 1, TestException.class, "Second"); + } + + @Test + public void doOnNextDoOnErrorFused2() { + ConnectableFlowable co = Flowable.just(1) + .doOnNext(new Consumer() { + @Override + public void accept(Integer v) throws Exception { + throw new TestException("First"); + } + }) + .doOnError(new Consumer() { + @Override + public void accept(Throwable e) throws Exception { + throw new TestException("Second"); + } + }) + .doOnError(new Consumer() { + @Override + public void accept(Throwable e) throws Exception { + throw new TestException("Third"); + } + }) + .publish(); + + TestSubscriber ts = co.test(); + co.connect(); + + ts.assertFailure(CompositeException.class); + + TestHelper.assertError(ts, 0, TestException.class, "First"); + TestHelper.assertError(ts, 1, TestException.class, "Second"); + TestHelper.assertError(ts, 2, TestException.class, "Third"); + } + + @Test + public void doOnNextDoOnErrorFusedConditional() { + ConnectableFlowable co = Flowable.just(1) + .doOnNext(new Consumer() { + @Override + public void accept(Integer v) throws Exception { + throw new TestException("First"); + } + }) + .doOnError(new Consumer() { + @Override + public void accept(Throwable e) throws Exception { + throw new TestException("Second"); + } + }) + .filter(Functions.alwaysTrue()) + .publish(); + + TestSubscriber ts = co.test(); + co.connect(); + + ts.assertFailure(CompositeException.class); + + TestHelper.assertError(ts, 0, TestException.class, "First"); + TestHelper.assertError(ts, 1, TestException.class, "Second"); + } + + @Test + public void doOnNextDoOnErrorFusedConditional2() { + ConnectableFlowable co = Flowable.just(1) + .doOnNext(new Consumer() { + @Override + public void accept(Integer v) throws Exception { + throw new TestException("First"); + } + }) + .doOnError(new Consumer() { + @Override + public void accept(Throwable e) throws Exception { + throw new TestException("Second"); + } + }) + .doOnError(new Consumer() { + @Override + public void accept(Throwable e) throws Exception { + throw new TestException("Third"); + } + }) + .filter(Functions.alwaysTrue()) + .publish(); + + TestSubscriber ts = co.test(); + co.connect(); + + ts.assertFailure(CompositeException.class); + + TestHelper.assertError(ts, 0, TestException.class, "First"); + TestHelper.assertError(ts, 1, TestException.class, "Second"); + TestHelper.assertError(ts, 2, TestException.class, "Third"); + } + + @Test + public void doOnNextDoOnErrorCombinedFusedConditional() { + ConnectableFlowable co = Flowable.just(1) + .compose(new FlowableTransformer() { + @Override + public Publisher apply(Flowable v) { + return new FlowableDoOnEach(v, + new Consumer() { + @Override + public void accept(Integer v) throws Exception { + throw new TestException("First"); + } + }, + new Consumer() { + @Override + public void accept(Throwable e) throws Exception { + throw new TestException("Second"); + } + }, + Functions.EMPTY_ACTION + , + Functions.EMPTY_ACTION + ); + } + }) + .filter(Functions.alwaysTrue()) + .publish(); + + TestSubscriber ts = co.test(); + co.connect(); + + ts.assertFailure(CompositeException.class); + + TestHelper.assertError(ts, 0, TestException.class, "First"); + TestHelper.assertError(ts, 1, TestException.class, "Second"); + } } diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableDoOnRequestTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableDoOnRequestTest.java index e5ba5c0..e8dc9a1 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableDoOnRequestTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableDoOnRequestTest.java @@ -91,7 +91,7 @@ public void onNext(Integer t) { public void dontRequestIfDownstreamRequestsLate() { // final List requested = new ArrayList(); // -// Action1 empty = Actions.empty(); +// Consumer empty = Functions.emptyConsumer(); // // final AtomicReference producer = new AtomicReference(); // diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableFlatMapTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableFlatMapTest.java index ae9baf0..84b9fe1 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableFlatMapTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableFlatMapTest.java @@ -263,7 +263,7 @@ public void testFlatMapTransformsOnErrorFuncThrows() { source.flatMap(just(onNext), funcThrow((Throwable) null, onError), just0(onComplete)).subscribe(o); - verify(o).onError(any(TestException.class)); + verify(o).onError(any(CompositeException.class)); verify(o, never()).onNext(any()); verify(o, never()).onComplete(); } @@ -996,4 +996,40 @@ public void run() { } } } + + @Test + public void iterableMapperFunctionReturnsNull() { + Flowable.just(1) + .flatMapIterable(new Function>() { + @Override + public Iterable apply(Integer v) throws Exception { + return null; + } + }, new BiFunction() { + @Override + public Object apply(Integer v, Object w) throws Exception { + return v; + } + }) + .test() + .assertFailureAndMessage(NullPointerException.class, "The mapper returned a null Iterable"); + } + + @Test + public void combinerMapperFunctionReturnsNull() { + Flowable.just(1) + .flatMap(new Function>() { + @Override + public Publisher apply(Integer v) throws Exception { + return null; + } + }, new BiFunction() { + @Override + public Object apply(Integer v, Object w) throws Exception { + return v; + } + }) + .test() + .assertFailureAndMessage(NullPointerException.class, "The mapper returned a null Publisher"); + } } diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableIntervalRangeTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableIntervalRangeTest.java index 2fda4ff..4fcbdc3 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableIntervalRangeTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableIntervalRangeTest.java @@ -109,4 +109,12 @@ public void take() { .awaitDone(5, TimeUnit.SECONDS) .assertResult(1L); } + + @Test(timeout = 2000) + public void cancel() { + Flowable.intervalRange(0, 20, 1, 1, TimeUnit.MILLISECONDS, Schedulers.trampoline()) + .take(10) + .test() + .assertResult(0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L); + } } diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableIntervalTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableIntervalTest.java new file mode 100644 index 0000000..3bb5464 --- /dev/null +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableIntervalTest.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * 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 io.reactivex.flowable.internal.operators; + +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import io.reactivex.common.Schedulers; +import io.reactivex.flowable.Flowable; + +public class FlowableIntervalTest { + + @Test(timeout = 2000) + public void cancel() { + Flowable.interval(1, TimeUnit.MILLISECONDS, Schedulers.trampoline()) + .take(10) + .test() + .assertResult(0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L); + } +} \ No newline at end of file diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableMapNotificationTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableMapNotificationTest.java index 5cfe720..70a4811 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableMapNotificationTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableMapNotificationTest.java @@ -16,8 +16,9 @@ import java.util.concurrent.Callable; import org.junit.Test; -import org.reactivestreams.Subscriber; +import org.reactivestreams.*; +import io.reactivex.common.exceptions.*; import io.reactivex.common.functions.Function; import io.reactivex.common.internal.functions.Functions; import io.reactivex.flowable.*; @@ -177,4 +178,22 @@ public Flowable apply(Flowable o) throws Exception { } }); } + + @Test + public void onErrorCrash() { + TestSubscriber ts = Flowable.error(new TestException("Outer")) + .flatMap(Functions.justFunction(Flowable.just(1)), + new Function>() { + @Override + public Publisher apply(Throwable t) throws Exception { + throw new TestException("Inner"); + } + }, + Functions.justCallable(Flowable.just(3))) + .test() + .assertFailure(CompositeException.class); + + TestHelper.assertError(ts, 0, TestException.class, "Outer"); + TestHelper.assertError(ts, 1, TestException.class, "Inner"); + } } diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableMapTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableMapTest.java index 864ccae..a11d57e 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableMapTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableMapTest.java @@ -371,10 +371,10 @@ public void verifyExceptionIsThrownIfThereIsNoExceptionHandler() { // } // }; // -// Action1 onNext = new Action1() { +// Consumer onNext = new Consumer() { // // @Override -// public void call(Object object) { +// public void accept(Object object) { // System.out.println(object.toString()); // } // }; diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableReplayTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableReplayTest.java index 023096c..5aa590f 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableReplayTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableReplayTest.java @@ -1745,4 +1745,17 @@ public void timedNoOutdatedData() { source.test().assertResult(); } + + @Test + public void replaySelectorReturnsNull() { + Flowable.just(1) + .replay(new Function, Publisher>() { + @Override + public Publisher apply(Flowable v) throws Exception { + return null; + } + }, Schedulers.trampoline()) + .test() + .assertFailureAndMessage(NullPointerException.class, "The selector returned a null Publisher"); + } } diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableSubscribeOnTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableSubscribeOnTest.java index acd73c9..4b774dc 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableSubscribeOnTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableSubscribeOnTest.java @@ -371,4 +371,51 @@ public void subscribe(FlowableEmitter s) throws Exception { .assertNoErrors() .assertComplete(); } + + @Test + public void nonScheduledRequestsNotSubsequentSubscribeOn() { + TestSubscriber ts = Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter s) throws Exception { + for (int i = 1; i < 1001; i++) { + s.onNext(i); + Thread.sleep(1); + } + s.onComplete(); + } + }, BackpressureStrategy.DROP) + .map(Functions.identity()) + .subscribeOn(Schedulers.single(), false) + .observeOn(Schedulers.computation()) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertNoErrors() + .assertComplete(); + + int c = ts.valueCount(); + + assertTrue("" + c, c > Flowable.bufferSize()); + } + + @Test + public void scheduledRequestsNotSubsequentSubscribeOn() { + Flowable.create(new FlowableOnSubscribe() { + @Override + public void subscribe(FlowableEmitter s) throws Exception { + for (int i = 1; i < 1001; i++) { + s.onNext(i); + Thread.sleep(1); + } + s.onComplete(); + } + }, BackpressureStrategy.DROP) + .map(Functions.identity()) + .subscribeOn(Schedulers.single(), true) + .observeOn(Schedulers.computation()) + .test() + .awaitDone(5, TimeUnit.SECONDS) + .assertValueCount(Flowable.bufferSize()) + .assertNoErrors() + .assertComplete(); + } } diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableUsingTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableUsingTest.java index e23f8dd..058af77 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableUsingTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableUsingTest.java @@ -626,4 +626,14 @@ public void accept(Object e) throws Exception { RxJavaCommonPlugins.reset(); } } + + @Test + public void sourceSupplierReturnsNull() { + Flowable.using(Functions.justCallable(1), + Functions.justFunction((Publisher)null), + Functions.emptyConsumer()) + .test() + .assertFailureAndMessage(NullPointerException.class, "The sourceSupplier returned a null Publisher") + ; + } } diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableWithLatestFromTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableWithLatestFromTest.java index 543fd2b..7613e68 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableWithLatestFromTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/FlowableWithLatestFromTest.java @@ -26,6 +26,7 @@ import io.reactivex.common.*; import io.reactivex.common.exceptions.TestException; import io.reactivex.common.functions.*; +import io.reactivex.common.internal.functions.Functions; import io.reactivex.common.internal.utils.CrashingMappedIterable; import io.reactivex.flowable.*; import io.reactivex.flowable.internal.subscriptions.BooleanSubscription; @@ -708,4 +709,12 @@ public Object apply(Object[] o) throws Exception { .test() .assertFailure(NullPointerException.class); } + + @Test + public void zeroOtherCombinerReturnsNull() { + Flowable.just(1) + .withLatestFrom(new Flowable[0], Functions.justFunction(null)) + .test() + .assertFailureAndMessage(NullPointerException.class, "The combiner returned a null value"); + } } diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/ParallelMapTryTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/ParallelMapTryTest.java index ed6b2bd..72c06dc 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/ParallelMapTryTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/ParallelMapTryTest.java @@ -348,4 +348,9 @@ public void mapInvalidSourceConditional() { RxJavaCommonPlugins.reset(); } } + + @Test + public void failureHandlingEnum() { + TestCommonHelper.checkEnum(ParallelFailureHandling.class); + } } diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/operators/ParallelRunOnTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/operators/ParallelRunOnTest.java index 34b888d..c8348f9 100644 --- a/flowable/src/test/java/io/reactivex/flowable/internal/operators/ParallelRunOnTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/internal/operators/ParallelRunOnTest.java @@ -277,4 +277,47 @@ public void run() { TestCommonHelper.race(r1, r2); } } + + @SuppressWarnings("unchecked") + @Test + public void normalCancelAfterRequest1() { + + TestSubscriber ts = new TestSubscriber(1) { + @Override + public void onNext(Integer t) { + super.onNext(t); + cancel(); + onComplete(); + } + }; + + Flowable.range(1, 5) + .parallel(1) + .runOn(ImmediateThinScheduler.INSTANCE) + .subscribe(new Subscriber[] { ts }); + + ts.assertResult(1); + } + + @SuppressWarnings("unchecked") + @Test + public void conditionalCancelAfterRequest1() { + + TestSubscriber ts = new TestSubscriber(1) { + @Override + public void onNext(Integer t) { + super.onNext(t); + cancel(); + onComplete(); + } + }; + + Flowable.range(1, 5) + .parallel(1) + .runOn(ImmediateThinScheduler.INSTANCE) + .filter(Functions.alwaysTrue()) + .subscribe(new Subscriber[] { ts }); + + ts.assertResult(1); + } } diff --git a/flowable/src/test/java/io/reactivex/flowable/internal/subscribers/StrictSubscriberTest.java b/flowable/src/test/java/io/reactivex/flowable/internal/subscribers/StrictSubscriberTest.java new file mode 100644 index 0000000..6d595eb --- /dev/null +++ b/flowable/src/test/java/io/reactivex/flowable/internal/subscribers/StrictSubscriberTest.java @@ -0,0 +1,334 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * 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 io.reactivex.flowable.internal.subscribers; + +import static org.junit.Assert.*; + +import java.util.*; + +import org.junit.Test; +import org.reactivestreams.*; + +import io.reactivex.common.exceptions.TestException; +import io.reactivex.flowable.Flowable; +import io.reactivex.flowable.internal.subscriptions.BooleanSubscription; +import io.reactivex.flowable.subscribers.TestSubscriber; + +public class StrictSubscriberTest { + + @Test + public void strictMode() { + final List list = new ArrayList(); + Subscriber sub = new Subscriber() { + + @Override + public void onSubscribe(Subscription s) { + s.request(10); + } + + @Override + public void onNext(Object t) { + list.add(t); + } + + @Override + public void onError(Throwable t) { + list.add(t); + } + + @Override + public void onComplete() { + list.add("Done"); + } + }; + + new Flowable() { + @Override + protected void subscribeActual(Subscriber s) { + s.onSubscribe(new BooleanSubscription()); + s.onNext(s); + } + }.subscribe(sub); + + assertTrue(list.toString(), list.get(0) instanceof StrictSubscriber); + } + + static final class SubscriberWrapper implements Subscriber { + final TestSubscriber tester; + + SubscriberWrapper(TestSubscriber tester) { + this.tester = tester; + } + + @Override + public void onSubscribe(Subscription s) { + tester.onSubscribe(s); + } + + @Override + public void onNext(T t) { + tester.onNext(t); + } + + @Override + public void onError(Throwable t) { + tester.onError(t); + } + + @Override + public void onComplete() { + tester.onComplete(); + } + } + + @Test + public void normalOnNext() { + TestSubscriber ts = new TestSubscriber(); + SubscriberWrapper wrapper = new SubscriberWrapper(ts); + + Flowable.range(1, 5).subscribe(wrapper); + + ts.assertResult(1, 2, 3, 4, 5); + } + + @Test + public void normalOnNextBackpressured() { + TestSubscriber ts = new TestSubscriber(0); + SubscriberWrapper wrapper = new SubscriberWrapper(ts); + + Flowable.range(1, 5).subscribe(wrapper); + + ts.assertEmpty() + .requestMore(1) + .assertValue(1) + .requestMore(2) + .assertValues(1, 2, 3) + .requestMore(2) + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void normalOnError() { + TestSubscriber ts = new TestSubscriber(); + SubscriberWrapper wrapper = new SubscriberWrapper(ts); + + Flowable.range(1, 5).concatWith(Flowable.error(new TestException())) + .subscribe(wrapper); + + ts.assertFailure(TestException.class, 1, 2, 3, 4, 5); + } + + @Test + public void deferredRequest() { + final List list = new ArrayList(); + Subscriber sub = new Subscriber() { + + @Override + public void onSubscribe(Subscription s) { + s.request(5); + list.add(0); + } + + @Override + public void onNext(Object t) { + list.add(t); + } + + @Override + public void onError(Throwable t) { + list.add(t); + } + + @Override + public void onComplete() { + list.add("Done"); + } + }; + + Flowable.range(1, 5).subscribe(sub); + + assertEquals(Arrays.asList(0, 1, 2, 3, 4, 5, "Done"), list); + } + + @Test + public void requestZero() { + final List list = new ArrayList(); + Subscriber sub = new Subscriber() { + + @Override + public void onSubscribe(Subscription s) { + s.request(0); + } + + @Override + public void onNext(Object t) { + list.add(t); + } + + @Override + public void onError(Throwable t) { + list.add(t); + } + + @Override + public void onComplete() { + list.add("Done"); + } + }; + + Flowable.range(1, 5).subscribe(sub); + + assertTrue(list.toString(), list.get(0) instanceof IllegalArgumentException); + assertTrue(list.toString(), list.get(0).toString().contains("3.9")); + } + + @Test + public void requestNegative() { + final List list = new ArrayList(); + Subscriber sub = new Subscriber() { + + @Override + public void onSubscribe(Subscription s) { + s.request(-99); + } + + @Override + public void onNext(Object t) { + list.add(t); + } + + @Override + public void onError(Throwable t) { + list.add(t); + } + + @Override + public void onComplete() { + list.add("Done"); + } + }; + + Flowable.range(1, 5).subscribe(sub); + + assertTrue(list.toString(), list.get(0) instanceof IllegalArgumentException); + assertTrue(list.toString(), list.get(0).toString().contains("3.9")); + } + + @Test + public void cancelAfterOnComplete() { + final List list = new ArrayList(); + Subscriber sub = new Subscriber() { + + Subscription s; + @Override + public void onSubscribe(Subscription s) { + this.s = s; + } + + @Override + public void onNext(Object t) { + list.add(t); + } + + @Override + public void onError(Throwable t) { + s.cancel(); + list.add(t); + } + + @Override + public void onComplete() { + s.cancel(); + list.add("Done"); + } + }; + + new Flowable() { + @Override + protected void subscribeActual(Subscriber s) { + BooleanSubscription b = new BooleanSubscription(); + s.onSubscribe(b); + s.onComplete(); + list.add(b.isCancelled()); + } + }.subscribe(sub); + + assertEquals(Arrays.asList("Done", false), list); + } + + @Test + public void cancelAfterOnError() { + final List list = new ArrayList(); + Subscriber sub = new Subscriber() { + + Subscription s; + @Override + public void onSubscribe(Subscription s) { + this.s = s; + } + + @Override + public void onNext(Object t) { + list.add(t); + } + + @Override + public void onError(Throwable t) { + s.cancel(); + list.add(t.getMessage()); + } + + @Override + public void onComplete() { + s.cancel(); + list.add("Done"); + } + }; + + new Flowable() { + @Override + protected void subscribeActual(Subscriber s) { + BooleanSubscription b = new BooleanSubscription(); + s.onSubscribe(b); + s.onError(new TestException("Forced failure")); + list.add(b.isCancelled()); + } + }.subscribe(sub); + + assertEquals(Arrays.asList("Forced failure", false), list); + } + + @Test + public void doubleOnSubscribe() { + TestSubscriber ts = new TestSubscriber(); + SubscriberWrapper wrapper = new SubscriberWrapper(ts); + + new Flowable() { + @Override + protected void subscribeActual(Subscriber s) { + BooleanSubscription b1 = new BooleanSubscription(); + s.onSubscribe(b1); + + BooleanSubscription b2 = new BooleanSubscription(); + s.onSubscribe(b2); + + assertTrue(b1.isCancelled()); + assertTrue(b2.isCancelled()); + } + }.subscribe(wrapper); + + ts.assertFailure(IllegalStateException.class); + assertTrue(ts.errors().toString(), ts.errors().get(0).getMessage().contains("2.12")); + } +} \ No newline at end of file diff --git a/flowable/src/test/java/io/reactivex/flowable/processors/ReplayProcessorTest.java b/flowable/src/test/java/io/reactivex/flowable/processors/ReplayProcessorTest.java index 79c08c6..873c4e2 100644 --- a/flowable/src/test/java/io/reactivex/flowable/processors/ReplayProcessorTest.java +++ b/flowable/src/test/java/io/reactivex/flowable/processors/ReplayProcessorTest.java @@ -1293,4 +1293,24 @@ public void timedNoOutdatedData() { source.test().assertResult(); } + + @Test + public void peekStateTimeAndSizeValueExpired() { + TestScheduler scheduler = new TestScheduler(); + ReplayProcessor rp = ReplayProcessor.createWithTime(1, TimeUnit.DAYS, scheduler); + + assertNull(rp.getValue()); + assertNull(rp.getValues(new Integer[2])[0]); + + rp.onNext(2); + + assertEquals((Integer)2, rp.getValue()); + assertEquals(2, rp.getValues()[0]); + + scheduler.advanceTimeBy(2, TimeUnit.DAYS); + + assertEquals(null, rp.getValue()); + assertEquals(0, rp.getValues().length); + assertNull(rp.getValues(new Integer[2])[0]); + } } diff --git a/gradle.properties b/gradle.properties index 5fee024..536532c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ release.scope=patch -release.version=0.1.0 -version=0.1.0 \ No newline at end of file +release.version=0.2.0 +version=0.2.0 \ No newline at end of file diff --git a/interop/src/main/java/io/reactivex/interop/RxJava3Interop.java b/interop/src/main/java/io/reactivex/interop/RxJava3Interop.java index e198053..5ab4c98 100644 --- a/interop/src/main/java/io/reactivex/interop/RxJava3Interop.java +++ b/interop/src/main/java/io/reactivex/interop/RxJava3Interop.java @@ -95,7 +95,7 @@ public static Flowable toFlowable(ObservableSource source, Backpressur *

        * *

        - *
        Backpressure:
        + *
        Backpressure:
        *
        The returned {@code Flowable} honors the backpressure of the downstream consumer.
        *
        Scheduler:
        *
        {@code toFlowable} does not operate by default on a particular {@link Scheduler}.
        @@ -138,7 +138,7 @@ public static Flowable toFlowable(MaybeSource source) { * Returns a Flowable which when subscribed to subscribes to this Completable and * relays the terminal events to the subscriber. *
        - *
        Backpressure:
        + *
        Backpressure:
        *
        The returned {@code Flowable} honors the backpressure of the downstream consumer.
        *
        Scheduler:
        *
        {@code toFlowable} does not operate by default on a particular {@link Scheduler}.
        @@ -159,7 +159,7 @@ public static Flowable toFlowable(CompletableSource source) { * Converts the given Flowable into a non-backpressured Observable. *
        *
        Backpressure:
        - *
        Publishers don't support backpressure thus the current Flowable is consumed in an unbounded + *
        Observables don't support backpressure thus the current Flowable is consumed in an unbounded * manner (by requesting Long.MAX_VALUE).
        *
        Scheduler:
        *
        {@code toObservable} does not operate by default on a particular {@link Scheduler}.
        @@ -962,7 +962,7 @@ public static Single> toSortedList(Flowable source, int capacityH * size thread pool: * *
        -     * Scheduler limitScheduler = Schedulers.computation().when(workers -> {
        +     * Scheduler limitScheduler = Schedulers.computation().when(workers -> {
              *  // use merge max concurrent to limit the number of concurrent
              *  // callbacks two at a time
              *  return Completable.merge(Flowable.merge(workers), 2);
        @@ -980,7 +980,7 @@ public static  Single> toSortedList(Flowable source, int capacityH
              * subscription to the second.
              * 
              * 
        -     * Scheduler limitScheduler = Schedulers.computation().when(workers -> {
        +     * Scheduler limitScheduler = Schedulers.computation().when(workers -> {
              *  // use merge max concurrent to limit the number of concurrent
              *  // Flowables two at a time
              *  return Completable.merge(Flowable.merge(workers, 2));
        @@ -993,9 +993,9 @@ public static  Single> toSortedList(Flowable source, int capacityH
              * bucket algorithm).
              * 
              * 
        -     * Scheduler slowScheduler = Schedulers.computation().when(workers -> {
        +     * Scheduler slowScheduler = Schedulers.computation().when(workers -> {
              *  // use concatenate to make each worker happen one at a time.
        -     *  return Completable.concat(workers.map(actions -> {
        +     *  return Completable.concat(workers.map(actions -> {
              *      // delay the starting of the next worker by 1 second.
              *      return Completable.merge(actions.delaySubscription(1, TimeUnit.SECONDS));
              *  }));
        diff --git a/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapCompletable.java b/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapCompletable.java
        index 43d5b8b..8ffd629 100644
        --- a/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapCompletable.java
        +++ b/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapCompletable.java
        @@ -75,6 +75,8 @@ static final class FlatMapCompletableMainSubscriber extends BasicIntFusedQueu
         
                 Subscription s;
         
        +        volatile boolean cancelled;
        +
                 FlatMapCompletableMainSubscriber(Subscriber observer,
                         Function mapper, boolean delayErrors,
                         int maxConcurrency) {
        @@ -120,7 +122,7 @@ public void onNext(T value) {
         
                     InnerConsumer inner = new InnerConsumer();
         
        -            if (set.add(inner)) {
        +            if (!cancelled && set.add(inner)) {
                         cs.subscribe(inner);
                     }
                 }
        @@ -167,6 +169,7 @@ public void onComplete() {
         
                 @Override
                 public void cancel() {
        +            cancelled = true;
                     s.cancel();
                     set.dispose();
                 }
        diff --git a/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapCompletableCompletable.java b/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapCompletableCompletable.java
        index ef6646d..1198dd6 100644
        --- a/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapCompletableCompletable.java
        +++ b/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapCompletableCompletable.java
        @@ -81,6 +81,8 @@ static final class FlatMapCompletableMainSubscriber extends AtomicInteger
         
                 Subscription s;
         
        +        volatile boolean disposed;
        +
                 FlatMapCompletableMainSubscriber(CompletableObserver observer,
                         Function mapper, boolean delayErrors,
                         int maxConcurrency) {
        @@ -126,7 +128,7 @@ public void onNext(T value) {
         
                     InnerObserver inner = new InnerObserver();
         
        -            if (set.add(inner)) {
        +            if (!disposed && set.add(inner)) {
                         cs.subscribe(inner);
                     }
                 }
        @@ -173,6 +175,7 @@ public void onComplete() {
         
                 @Override
                 public void dispose() {
        +            disposed = true;
                     s.cancel();
                     set.dispose();
                 }
        diff --git a/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapMaybe.java b/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapMaybe.java
        index 0593171..a2e8c8a 100644
        --- a/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapMaybe.java
        +++ b/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapMaybe.java
        @@ -132,7 +132,7 @@ public void onNext(T t) {
         
                     InnerObserver inner = new InnerObserver();
         
        -            if (set.add(inner)) {
        +            if (!cancelled && set.add(inner)) {
                         ms.subscribe(inner);
                     }
                 }
        diff --git a/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapSingle.java b/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapSingle.java
        index 9539d9c..7c474e0 100644
        --- a/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapSingle.java
        +++ b/interop/src/main/java/io/reactivex/interop/internal/operators/FlowableFlatMapSingle.java
        @@ -132,7 +132,7 @@ public void onNext(T t) {
         
                     InnerObserver inner = new InnerObserver();
         
        -            if (set.add(inner)) {
        +            if (!cancelled && set.add(inner)) {
                         ms.subscribe(inner);
                     }
                 }
        diff --git a/interop/src/test/java/io/reactivex/interop/XFlatMapTest.java b/interop/src/test/java/io/reactivex/interop/XFlatMapTest.java
        index d884b09..fe14bed 100644
        --- a/interop/src/test/java/io/reactivex/interop/XFlatMapTest.java
        +++ b/interop/src/test/java/io/reactivex/interop/XFlatMapTest.java
        @@ -32,7 +32,7 @@
         public class XFlatMapTest {
         
             @Rule
        -    public Retry retry = new Retry(3, 1000, true);
        +    public Retry retry = new Retry(5, 1000, true);
         
             static final int SLEEP_AFTER_CANCEL = 500;
         
        @@ -41,12 +41,23 @@ public class XFlatMapTest {
             void sleep() throws Exception {
                 cb.await();
                 try {
        +            long before = System.currentTimeMillis();
                     Thread.sleep(5000);
        +            throw new IllegalStateException("Was not interrupted in time?! " + (System.currentTimeMillis() - before));
                 } catch (InterruptedException ex) {
                     // ignored here
                 }
             }
         
        +    void beforeCancelSleep(TestConsumer ts) throws Exception {
        +        long before = System.currentTimeMillis();
        +        Thread.sleep(50);
        +        if (System.currentTimeMillis() - before > 100) {
        +            ts.dispose();
        +            throw new IllegalStateException("Overslept?" + (System.currentTimeMillis() - before));
        +        }
        +    }
        +
             @Test
             public void flowableFlowable() throws Exception {
                 List errors = TestCommonHelper.trackPluginErrors();
        @@ -64,7 +75,7 @@ public Publisher apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -95,7 +106,7 @@ public Single apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -126,7 +137,7 @@ public Maybe apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -157,7 +168,7 @@ public Completable apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -191,7 +202,7 @@ public Completable apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -222,7 +233,7 @@ public Observable apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -253,7 +264,7 @@ public Single apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -284,7 +295,7 @@ public Maybe apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -315,7 +326,7 @@ public Completable apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -347,7 +358,7 @@ public Completable apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -378,7 +389,7 @@ public Single apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -409,7 +420,7 @@ public Maybe apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -440,7 +451,7 @@ public Completable apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -472,7 +483,7 @@ public Completable apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -503,7 +514,7 @@ public Single apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -534,7 +545,7 @@ public Maybe apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -565,7 +576,7 @@ public Completable apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        @@ -597,7 +608,7 @@ public Completable apply(Integer v) throws Exception {
         
                     cb.await();
         
        -            Thread.sleep(50);
        +            beforeCancelSleep(ts);
         
                     ts.cancel();
         
        diff --git a/observable/src/main/java/io/reactivex/observable/Completable.java b/observable/src/main/java/io/reactivex/observable/Completable.java
        index aeac2a3..38fa100 100644
        --- a/observable/src/main/java/io/reactivex/observable/Completable.java
        +++ b/observable/src/main/java/io/reactivex/observable/Completable.java
        @@ -191,7 +191,6 @@ public static Completable concat(ObservableSource s
              *
              * });
              * 
        - *

        *

        *
        Scheduler:
        *
        {@code create} does not operate by default on a particular {@link Scheduler}.
        @@ -902,12 +901,12 @@ public final Throwable blockingGet(long timeout, TimeUnit unit) { *
        Scheduler:
        *
        {@code cache} does not operate by default on a particular {@link Scheduler}.
        *
        + *

        History: 2.0.4 - experimental * @return the new Completable instance - * @since 2.0.4 - experimental + * @since 2.1 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) - @Experimental public final Completable cache() { return RxJavaObservablePlugins.onAssembly(new CompletableCache(this)); } @@ -1178,13 +1177,13 @@ public final Completable doAfterTerminate(final Action onAfterTerminate) { *

        Scheduler:
        *
        {@code doFinally} does not operate by default on a particular {@link Scheduler}.
        *
        + *

        History: 2.0.1 - experimental * @param onFinally the action called when this Completable terminates or gets cancelled * @return the new Completable instance - * @since 2.0.1 - experimental + * @since 2.1 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) - @Experimental public final Completable doFinally(Action onFinally) { ObjectHelper.requireNonNull(onFinally, "onFinally is null"); return RxJavaObservablePlugins.onAssembly(new CompletableDoFinally(this, onFinally)); @@ -1510,10 +1509,10 @@ public final Observable startWith(ObservableSource other) { *

        Scheduler:
        *
        {@code hide} does not operate by default on a particular {@link Scheduler}.
        *
        + *

        History: 2.0.5 - experimental * @return the new Completable instance - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) public final Completable hide() { diff --git a/observable/src/main/java/io/reactivex/observable/CompletableEmitter.java b/observable/src/main/java/io/reactivex/observable/CompletableEmitter.java index 0ad6b8e..41ad854 100644 --- a/observable/src/main/java/io/reactivex/observable/CompletableEmitter.java +++ b/observable/src/main/java/io/reactivex/observable/CompletableEmitter.java @@ -57,4 +57,19 @@ public interface CompletableEmitter { * @return true if the downstream disposed the sequence */ boolean isDisposed(); + + /** + * Attempts to emit the specified {@code Throwable} error if the downstream + * hasn't cancelled the sequence or is otherwise terminated, returning false + * if the emission is not allowed to happen due to lifecycle restrictions. + *

        + * Unlike {@link #onError(Throwable)}, the {@code RxJavaPlugins.onError} is not called + * if the error could not be delivered. + *

        History: 2.1.1 - experimental + * @param t the throwable error to signal if possible + * @return true if successful, false if the downstream is not able to accept further + * events + * @since 2.2 + */ + boolean tryOnError(@NonNull Throwable t); } diff --git a/observable/src/main/java/io/reactivex/observable/ConnectableObservable.java b/observable/src/main/java/io/reactivex/observable/ConnectableObservable.java index c304113..ee609c7 100644 --- a/observable/src/main/java/io/reactivex/observable/ConnectableObservable.java +++ b/observable/src/main/java/io/reactivex/observable/ConnectableObservable.java @@ -108,7 +108,7 @@ public Observable autoConnect(int numberOfSubscribers) { * @param numberOfSubscribers the number of subscribers to await before calling connect * on the ConnectableObservable. A non-positive value indicates * an immediate connection. - * @param connection the callback Action1 that will receive the Subscription representing the + * @param connection the callback Consumer that will receive the Subscription representing the * established connection * @return an Observable that automatically connects to this ConnectableObservable * when the specified number of Subscribers subscribe to it and calls the diff --git a/observable/src/main/java/io/reactivex/observable/Maybe.java b/observable/src/main/java/io/reactivex/observable/Maybe.java index 90a6e29..096b921 100644 --- a/observable/src/main/java/io/reactivex/observable/Maybe.java +++ b/observable/src/main/java/io/reactivex/observable/Maybe.java @@ -413,7 +413,6 @@ public static Observable concatEager(ObservableSource - *

        *

        *
        Scheduler:
        *
        {@code create} does not operate by default on a particular {@link Scheduler}.
        @@ -778,7 +777,6 @@ public static Observable merge(ObservableSource * - *

        *

        *
        Scheduler:
        *
        {@code merge} does not operate by default on a particular {@link Scheduler}.
        @@ -2174,7 +2172,6 @@ public final Maybe delay(long delay, TimeUnit unit, Scheduler scheduler) { * Delays the emission of this Maybe until the given ObservableSource signals an item or completes. *

        * - *

        *

        *
        Scheduler:
        *
        This version of {@code delay} does not operate by default on a particular {@link Scheduler}.
        @@ -2200,7 +2197,7 @@ public final Maybe delay(ObservableSource delayIndicator) { /** * Returns a Maybe that delays the subscription to this Maybe * until the other ObservableSource emits an element or completes normally. - *

        + * *

        *
        Scheduler:
        *
        This method does not operate by default on a particular {@link Scheduler}.
        @@ -2275,13 +2272,13 @@ public final Maybe delaySubscription(long delay, TimeUnit unit, Scheduler sch *
        Scheduler:
        *
        {@code doAfterSuccess} does not operate by default on a particular {@link Scheduler}.
        *
        + *

        History: 2.0.1 - experimental * @param onAfterSuccess the Consumer that will be called after emitting an item from upstream to the downstream * @return the new Maybe instance - * @since 2.0.1 - experimental + * @since 2.1 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) - @Experimental public final Maybe doAfterSuccess(Consumer onAfterSuccess) { ObjectHelper.requireNonNull(onAfterSuccess, "doAfterSuccess is null"); return RxJavaObservablePlugins.onAssembly(new MaybeDoAfterSuccess(this, onAfterSuccess)); @@ -2328,13 +2325,13 @@ public final Maybe doAfterTerminate(Action onAfterTerminate) { *

        Scheduler:
        *
        {@code doFinally} does not operate by default on a particular {@link Scheduler}.
        *
        + *

        History: 2.0.1 - experimental * @param onFinally the action called when this Maybe terminates or gets cancelled * @return the new Maybe instance - * @since 2.0.1 - experimental + * @since 2.1 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) - @Experimental public final Maybe doFinally(Action onFinally) { ObjectHelper.requireNonNull(onFinally, "onFinally is null"); return RxJavaObservablePlugins.onAssembly(new MaybeDoFinally(this, onFinally)); @@ -2675,17 +2672,17 @@ public final Single flatMapSingle(final Function{@code flatMapSingleElement} does not operate by default on a particular {@link Scheduler}. *

        * + *

        History: 2.0.2 - experimental * @param the result value type * @param mapper * a function that, when applied to the item emitted by the source Maybe, returns a * Single * @return the new Maybe instance * @see ReactiveX operators documentation: FlatMap - * @since 2.0.2 - experimental + * @since 2.1 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) - @Experimental public final Maybe flatMapSingleElement(final Function> mapper) { ObjectHelper.requireNonNull(mapper, "mapper is null"); return RxJavaObservablePlugins.onAssembly(new MaybeFlatMapSingleElement(this, mapper)); @@ -2937,7 +2934,8 @@ public final Observable toObservable() { /** * Converts this Maybe into a Single instance composing cancellation - * through and turning an empty Maybe into a signal of NoSuchElementException. + * through and turning an empty Maybe into a Single that emits the given + * value through onSuccess. *

        *
        Scheduler:
        *
        {@code toSingle} does not operate by default on a particular {@link Scheduler}.
        @@ -3375,11 +3373,11 @@ public final Maybe retryUntil(final BooleanSupplier stop) { * This retries 3 times, each time incrementing the number of seconds it waits. * *
        
        -     *  ObservableSource.create((Observer s) -> {
        +     *  ObservableSource.create((Observer<? super String> s) -> {
              *      System.out.println("subscribing");
              *      s.onError(new RuntimeException("always fails"));
        -     *  }).retryWhen(attempts -> {
        -     *      return attempts.zipWith(ObservableSource.range(1, 3), (n, i) -> i).flatMap(i -> {
        +     *  }).retryWhen(attempts -> {
        +     *      return attempts.zipWith(ObservableSource.range(1, 3), (n, i) -> i).flatMap(i -> {
              *          System.out.println("delay retry by " + i + " second(s)");
              *          return ObservableSource.timer(i, TimeUnit.SECONDS);
              *      });
        @@ -3575,10 +3573,10 @@ public final Maybe subscribeOn(Scheduler scheduler) {
              * MaybeObserver as is.
              * 

        Usage example: *

        
        -     * Maybe<Integer> source = Maybe.just(1);
        +     * Maybe<Integer> source = Maybe.just(1);
              * CompositeDisposable composite = new CompositeDisposable();
              *
        -     * MaybeObserver<Integer> ms = new MaybeObserver<>() {
        +     * MaybeObserver<Integer> ms = new MaybeObserver<>() {
              *     // ...
              * };
              *
        @@ -3605,7 +3603,6 @@ public final > E subscribeWith(E observer) {
              * MaybeSource if the current Maybe is empty.
              * 

        * - *

        *

        *
        Scheduler:
        *
        {@code switchIfEmpty} does not operate by default on a particular {@link Scheduler}.
        diff --git a/observable/src/main/java/io/reactivex/observable/MaybeEmitter.java b/observable/src/main/java/io/reactivex/observable/MaybeEmitter.java index 9f616e6..645f478 100644 --- a/observable/src/main/java/io/reactivex/observable/MaybeEmitter.java +++ b/observable/src/main/java/io/reactivex/observable/MaybeEmitter.java @@ -65,4 +65,19 @@ public interface MaybeEmitter { * @return true if the downstream cancelled the sequence */ boolean isDisposed(); + + /** + * Attempts to emit the specified {@code Throwable} error if the downstream + * hasn't cancelled the sequence or is otherwise terminated, returning false + * if the emission is not allowed to happen due to lifecycle restrictions. + *

        + * Unlike {@link #onError(Throwable)}, the {@code RxJavaPlugins.onError} is not called + * if the error could not be delivered. + *

        History: 2.1.1 - experimental + * @param t the throwable error to signal if possible + * @return true if successful, false if the downstream is not able to accept further + * events + * @since 2.2 + */ + boolean tryOnError(@NonNull Throwable t); } diff --git a/observable/src/main/java/io/reactivex/observable/Observable.java b/observable/src/main/java/io/reactivex/observable/Observable.java index 8230009..be7f517 100644 --- a/observable/src/main/java/io/reactivex/observable/Observable.java +++ b/observable/src/main/java/io/reactivex/observable/Observable.java @@ -28,12 +28,12 @@ import io.reactivex.observable.observers.*; /** - * The Observable class that is designed similar to the Reactive-Streams Pattern, minus the backpressure, - * and offers factory methods, intermediate operators and the ability to consume reactive dataflows. + * The Observable class is the non-backpressured, optionally multi-valued base reactive class that + * offers factory methods, intermediate operators and the ability to consume synchronous + * and/or asynchronous reactive dataflows. *

        - * Reactive-Streams operates with {@code ObservableSource}s which {@code Observable} extends. Many operators - * therefore accept general {@code ObservableSource}s directly and allow direct interoperation with other - * Reactive-Streams implementations. + * Many operators in the class accept {@code ObservableSource}(s), the base reactive interface + * for such non-backpressured flows, which {@code Observable} itself implements as well. *

        * The Observable's operators, by default, run with a buffer size of 128 elements, * that can be overridden globally via the system parameter {@code rx2.buffer-size}. Most operators, however, have @@ -43,11 +43,49 @@ *

        * *

        - * For more information see the ReactiveX - * documentation. - * + * The design of this class was derived from the + * Reactive-Streams design and specification + * by removing any backpressure-related infrastructure and implementation detail, replacing the + * {@code org.reactivestreams.Subscription} with {@link Disposable} as the primary means to cancel + * a flow. + *

        + * The {@code Observable} follows the protocol + *

        
        + *      onSubscribe onNext* (onError | onComplete)?
        + * 
        + * where + * the stream can be disposed through the {@code Disposable} instance provided to consumers through + * {@code Observer.onSubscribe}. + *

        + * Unlike the {@code Observable} of version 1.x, {@link #subscribe(Observer)} does not allow external cancellation + * of a subscription and the {@code Observer} instance is expected to expose such capability. + *

        Example: + *

        
        + * Disposable d = Observable.just("Hello world!")
        + *     .delay(1, TimeUnit.SECONDS)
        + *     .subscribeWith(new DisposableObserver<String>() {
        + *         @Override public void onStart() {
        + *             System.out.println("Start!");
        + *         }
        + *         @Override public void onNext(Integer t) {
        + *             System.out.println(t);
        + *         }
        + *         @Override public void onError(Throwable t) {
        + *             t.printStackTrace();
        + *         }
        + *         @Override public void onComplete() {
        + *             System.out.println("Done!");
        + *         }
        + *     });
        + * 
        + * Thread.sleep(500);
        + * // the sequence now can be cancelled via dispose()
        + * d.dispose();
        + * 
        + * * @param * the type of the items emitted by the Observable + * @see io.reactivex.observable.observers.DisposableObserver */ public abstract class Observable implements ObservableSource { /** The default buffer size. */ @@ -137,6 +175,9 @@ public static int bufferSize() { * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). * If that input source is also synchronous, other sources after it will not be subscribed to. *

        + * If there are no ObservableSources provided, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + *

        * *

        *
        Scheduler:
        @@ -176,6 +217,9 @@ public static Observable combineLatest(Function + * If provided iterable of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + *

        * *

        *
        Scheduler:
        @@ -215,6 +259,9 @@ public static Observable combineLatest(Iterable + * If provided iterable of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + *

        * *

        *
        Scheduler:
        @@ -261,6 +308,9 @@ public static Observable combineLatest(Iterable + * If provided array of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + *

        * *

        *
        Scheduler:
        @@ -299,6 +349,9 @@ public static Observable combineLatest(ObservableSource[] * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). * If that input source is also synchronous, other sources after it will not be subscribed to. *

        + * If provided array of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + *

        * *

        *
        Scheduler:
        @@ -780,6 +833,8 @@ public static Observable combineLates * the source ObservableSources each time an item is received from any of the source ObservableSources, where this * aggregation is defined by a specified function. *

        + * + *

        * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a * {@code Function} passed to the method would trigger a {@code ClassCastException}. @@ -787,6 +842,9 @@ public static Observable combineLates * If any of the sources never produces an item but only terminates (normally or with an error), the * resulting sequence terminates immediately (normally or with all the errors accumulated till that point). * If that input source is also synchronous, other sources after it will not be subscribed to. + *

        + * If provided array of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. * *

        *
        Scheduler:
        @@ -818,6 +876,8 @@ public static Observable combineLatestDelayError(ObservableSource + * + *

        * Note on method signature: since Java doesn't allow creating a generic array with {@code new T[]}, the * implementation of this operator has to create an {@code Object[]} instead. Unfortunately, a * {@code Function} passed to the method would trigger a {@code ClassCastException}. @@ -825,6 +885,9 @@ public static Observable combineLatestDelayError(ObservableSource + * If there are no ObservableSources provided, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. * *

        *
        Scheduler:
        @@ -866,6 +929,9 @@ public static Observable combineLatestDelayError(Function + * If provided array of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + *

        * *

        *
        Scheduler:
        @@ -914,6 +980,9 @@ public static Observable combineLatestDelayError(ObservableSource + * If provided iterable of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + *

        * *

        *
        Scheduler:
        @@ -953,6 +1022,9 @@ public static Observable combineLatestDelayError(Iterable + * If provided iterable of ObservableSources is empty, the resulting sequence completes immediately without emitting + * any items and without any calls to the combiner function. + *

        * *

        *
        Scheduler:
        @@ -1217,7 +1289,7 @@ public static Observable concatArrayDelayError(ObservableSource - * + * *
        *
        Scheduler:
        *
        This method does not operate by default on a particular {@link Scheduler}.
        @@ -1538,7 +1610,7 @@ public static Observable empty() { * Returns an Observable that invokes an {@link Observer}'s {@link Observer#onError onError} method when the * Observer subscribes to it. *

        - * + * *

        *
        Scheduler:
        *
        {@code error} does not operate by default on a particular {@link Scheduler}.
        @@ -1563,7 +1635,7 @@ public static Observable error(Callable errorSupplie * Returns an Observable that invokes an {@link Observer}'s {@link Observer#onError onError} method when the * Observer subscribes to it. *

        - * + * *

        *
        Scheduler:
        *
        {@code error} does not operate by default on a particular {@link Scheduler}.
        @@ -1645,7 +1717,7 @@ public static Observable fromCallable(Callable supplier) { /** * Converts a {@link Future} into an ObservableSource. *

        - * + * *

        * You can convert any object that supports the {@link Future} interface into an ObservableSource that emits the * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} @@ -1678,7 +1750,7 @@ public static Observable fromFuture(Future future) { /** * Converts a {@link Future} into an ObservableSource, with a timeout on the Future. *

        - * + * *

        * You can convert any object that supports the {@link Future} interface into an ObservableSource that emits the * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} @@ -1716,7 +1788,7 @@ public static Observable fromFuture(Future future, long time /** * Converts a {@link Future} into an ObservableSource, with a timeout on the Future. *

        - * + * *

        * You can convert any object that supports the {@link Future} interface into an ObservableSource that emits the * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} @@ -1757,14 +1829,14 @@ public static Observable fromFuture(Future future, long time /** * Converts a {@link Future}, operating on a specified {@link Scheduler}, into an ObservableSource. *

        - * + * *

        * You can convert any object that supports the {@link Future} interface into an ObservableSource that emits the * return value of the {@link Future#get} method of that object, by passing the object into the {@code from} * method. *

        * Unlike 1.x, cancelling the Observable won't cancel the future. If necessary, one can use composition to achieve the - * cancellation effect: {@code futureObservableSource.doOnCancel(() -> future.cancel(true));}. + * cancellation effect: {@code futureObservableSource.doOnCancel(() -> future->ancel(true));}. *

        *
        Scheduler:
        *
        You specify which {@link Scheduler} this operator will use
        @@ -1792,7 +1864,7 @@ public static Observable fromFuture(Future future, Scheduler /** * Converts an {@link Iterable} sequence into an ObservableSource that emits the items in the sequence. *

        - * + * *

        *
        Scheduler:
        *
        {@code fromIterable} does not operate by default on a particular {@link Scheduler}.
        @@ -5064,7 +5136,6 @@ public final void blockingSubscribe(Consumer onNext, Consumeron the current thread. - *

        *

        *
        Scheduler:
        *
        {@code blockingSubscribe} does not operate by default on a particular {@link Scheduler}.
        @@ -6139,7 +6210,6 @@ public final Observable concatMapEagerDelayError(Function * *
        *
        Scheduler:
        @@ -6165,7 +6235,6 @@ public final Observable concatMapIterable(final Function * *
        *
        Scheduler:
        @@ -6294,7 +6363,6 @@ public final Observable debounce(Function *

        * Information on debounce vs throttle: - *

        *

        + *

        History: 2.0.1 - experimental * @param onAfterNext the Consumer that will be called after emitting an item from upstream to the downstream * @return the new Observable instance - * @since 2.0.1 - experimental + * @since 2.1 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) - @Experimental public final Observable doAfterNext(Consumer onAfterNext) { ObjectHelper.requireNonNull(onAfterNext, "onAfterNext is null"); return RxJavaObservablePlugins.onAssembly(new ObservableDoAfterNext(this, onAfterNext)); @@ -6848,16 +6915,16 @@ public final Observable doAfterTerminate(Action onFinally) { *

        *
        Scheduler:
        *
        {@code doFinally} does not operate by default on a particular {@link Scheduler}.
        - * Operator-fusion: + *
        Operator-fusion:
        *
        This operator supports boundary-limited synchronous or asynchronous queue-fusion.
        *
        + *

        History: 2.0.1 - experimental * @param onFinally the action called when this Observable terminates or gets cancelled * @return the new Observable instance - * @since 2.0.1 - experimental + * @since 2.1 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) - @Experimental public final Observable doFinally(Action onFinally) { ObjectHelper.requireNonNull(onFinally, "onFinally is null"); return RxJavaObservablePlugins.onAssembly(new ObservableDoFinally(this, onFinally)); @@ -7338,7 +7405,7 @@ public final Observable flatMap(Function - * + * *

        *
        Scheduler:
        *
        {@code flatMap} does not operate by default on a particular {@link Scheduler}.
        @@ -7371,7 +7438,7 @@ public final Observable flatMap(Function - * + * *
        *
        Scheduler:
        *
        {@code flatMap} does not operate by default on a particular {@link Scheduler}.
        @@ -7453,7 +7520,7 @@ public final Observable flatMap( * ObservableSource and then flattens the ObservableSources returned from these functions and emits the resulting items, * while limiting the maximum number of concurrent subscriptions to these ObservableSources. *

        - * + * *

        *
        Scheduler:
        *
        {@code flatMap} does not operate by default on a particular {@link Scheduler}.
        @@ -7495,7 +7562,7 @@ public final Observable flatMap( * ObservableSources and emitting the results of this merger, while limiting the maximum number of concurrent * subscriptions to these ObservableSources. *

        - * + * *

        *
        Scheduler:
        *
        {@code flatMap} does not operate by default on a particular {@link Scheduler}.
        @@ -7587,7 +7654,7 @@ public final Observable flatMap(Function - * + * *
        *
        Scheduler:
        *
        {@code flatMap} does not operate by default on a particular {@link Scheduler}.
        @@ -7624,7 +7691,7 @@ public final Observable flatMap(Function - * + * *
        *
        Scheduler:
        *
        {@code flatMap} does not operate by default on a particular {@link Scheduler}.
        @@ -7665,7 +7732,7 @@ public final Observable flatMap(final Function - * + * *
        *
        Scheduler:
        *
        {@code flatMap} does not operate by default on a particular {@link Scheduler}.
        @@ -8886,7 +8953,7 @@ public final Maybe reduce(BiFunction reducer) { } /** - * Returns an Observable that applies a specified accumulator function to the first item emitted by a source + * Returns a Single that applies a specified accumulator function to the first item emitted by a source * ObservableSource and a specified seed value, then feeds the result of that function along with the second item * emitted by an ObservableSource into the same function, and so on until all items have been emitted by the * source ObservableSource, emitting the final result from the final call to your function as its sole item. @@ -8897,18 +8964,22 @@ public final Maybe reduce(BiFunction reducer) { * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method * that does a similar operation on lists. *

        - * Note that the {@code initialValue} is shared among all subscribers to the resulting ObservableSource + * Note that the {@code seed} is shared among all subscribers to the resulting ObservableSource * and may cause problems if it is mutable. To make sure each subscriber gets its own value, defer * the application of this operator via {@link #defer(Callable)}: *

        
        -     * ObservableSource<T> source = ...
        -     * Observable.defer(() -> source.reduce(new ArrayList<>(), (list, item) -> list.add(item)));
        +     * SingleSource<T> source = ...
        +     * Single.defer(() -> source.reduce(new ArrayList<>(), (list, item) -> list.add(item)));
              *
              * // alternatively, by using compose to stay fluent
              *
        -     * source.compose(o ->
        -     *     Observable.defer(() -> o.reduce(new ArrayList<>(), (list, item) -> list.add(item)))
        -     * );
        +     * source.compose(o ->
        +     *     Observable.defer(() -> o.reduce(new ArrayList<>(), (list, item) -> list.add(item)).toObservable())
        +     * ).firstOrError();
        +     *
        +     * // or, by using reduceWith instead of reduce
        +     *
        +     * source.reduceWith(() -> new ArrayList<>(), (list, item) -> list.add(item)));
              * 
        *
        *
        Scheduler:
        @@ -8925,6 +8996,7 @@ public final Maybe reduce(BiFunction reducer) { * items emitted by the source ObservableSource * @see ReactiveX operators documentation: Reduce * @see Wikipedia: Fold (higher-order function) + * @see #reduceWith(Callable, BiFunction) */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -8936,29 +9008,16 @@ public final Single reduce(R seed, BiFunction reducer) { /** * Returns a Single that applies a specified accumulator function to the first item emitted by a source - * ObservableSource and a specified seed value, then feeds the result of that function along with the second item - * emitted by an ObservableSource into the same function, and so on until all items have been emitted by the - * source ObservableSource, emitting the final result from the final call to your function as its sole item. + * ObservableSource and a seed value derived from calling a specified seedSupplier, then feeds the result + * of that function along with the second item emitted by an ObservableSource into the same function, + * and so on until all items have been emitted by the source ObservableSource, emitting the final result + * from the final call to your function as its sole item. *

        * *

        * This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code inject} method * that does a similar operation on lists. - *

        - * Note that the {@code initialValue} is shared among all subscribers to the resulting ObservableSource - * and may cause problems if it is mutable. To make sure each subscriber gets its own value, defer - * the application of this operator via {@link #defer(Callable)}: - *

        
        -     * ObservableSource<T> source = ...
        -     * Observable.defer(() -> source.reduce(new ArrayList<>(), (list, item) -> list.add(item)));
        -     *
        -     * // alternatively, by using compose to stay fluent
        -     *
        -     * source.compose(o ->
        -     *     Observable.defer(() -> o.reduce(new ArrayList<>(), (list, item) -> list.add(item)))
        -     * );
        -     * 
        *
        *
        Scheduler:
        *
        {@code reduceWith} does not operate by default on a particular {@link Scheduler}.
        @@ -9726,11 +9785,11 @@ public final Observable retryUntil(final BooleanSupplier stop) { * This retries 3 times, each time incrementing the number of seconds it waits. * *
        
        -     *  ObservableSource.create((Observer s) -> {
        +     *  ObservableSource.create((Observer>? super String> s) -> {
              *      System.out.println("subscribing");
              *      s.onError(new RuntimeException("always fails"));
        -     *  }).retryWhen(attempts -> {
        -     *      return attempts.zipWith(ObservableSource.range(1, 3), (n, i) -> i).flatMap(i -> {
        +     *  }).retryWhen(attempts -> {
        +     *      return attempts.zipWith(ObservableSource.range(1, 3), (n, i) -> i).flatMap(i -> {
              *          System.out.println("delay retry by " + i + " second(s)");
              *          return ObservableSource.timer(i, TimeUnit.SECONDS);
              *      });
        @@ -9824,6 +9883,7 @@ public final Observable sample(long period, TimeUnit unit) {
              *  
        {@code sample} operates by default on the {@code computation} {@link Scheduler}.
        *
        * + *

        History: 2.0.5 - experimental * @param period * the sampling rate * @param unit @@ -9836,11 +9896,10 @@ public final Observable sample(long period, TimeUnit unit) { * if false, an unsampled last item is ignored. * @see ReactiveX operators documentation: Sample * @see #throttleLast(long, TimeUnit) - * @since 2.0.5 - experimental + * @since 2.1 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.COMPUTATION) - @Experimental public final Observable sample(long period, TimeUnit unit, boolean emitLast) { return sample(period, unit, Schedulers.computation(), emitLast); } @@ -9885,6 +9944,7 @@ public final Observable sample(long period, TimeUnit unit, Scheduler schedule *

        You specify which {@link Scheduler} this operator will use
        *
        * + *

        History: 2.0.5 - experimental * @param period * the sampling rate * @param unit @@ -9899,11 +9959,10 @@ public final Observable sample(long period, TimeUnit unit, Scheduler schedule * the specified time interval * @see ReactiveX operators documentation: Sample * @see #throttleLast(long, TimeUnit, Scheduler) - * @since 2.0.5 - experimental + * @since 2.1 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.CUSTOM) - @Experimental public final Observable sample(long period, TimeUnit unit, Scheduler scheduler, boolean emitLast) { ObjectHelper.requireNonNull(unit, "unit is null"); ObjectHelper.requireNonNull(scheduler, "scheduler is null"); @@ -9947,6 +10006,7 @@ public final Observable sample(ObservableSource sampler) { *

        This version of {@code sample} does not operate by default on a particular {@link Scheduler}.
        *
        * + *

        History: 2.0.5 - experimental * @param the element type of the sampler ObservableSource * @param sampler * the ObservableSource to use for sampling the source ObservableSource @@ -9957,11 +10017,10 @@ public final Observable sample(ObservableSource sampler) { * @return an Observable that emits the results of sampling the items emitted by this ObservableSource whenever * the {@code sampler} ObservableSource emits an item or completes * @see ReactiveX operators documentation: Sample - * @since 2.0.5 - experimental + * @since 2.1 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) - @Experimental public final Observable sample(ObservableSource sampler, boolean emitLast) { ObjectHelper.requireNonNull(sampler, "sampler is null"); return RxJavaObservablePlugins.onAssembly(new ObservableSampleWithObservable(this, sampler, emitLast)); @@ -10012,13 +10071,13 @@ public final Observable scan(BiFunction accumulator) { * and may cause problems if it is mutable. To make sure each subscriber gets its own value, defer * the application of this operator via {@link #defer(Callable)}: *

        
        -     * ObservableSource<T> source = ...
        -     * Observable.defer(() -> source.scan(new ArrayList<>(), (list, item) -> list.add(item)));
        +     * ObservableSource<T> source = ...
        +     * Observable.defer(() -> source.scan(new ArrayList<>(), (list, item) -> list.add(item)));
              *
              * // alternatively, by using compose to stay fluent
              *
        -     * source.compose(o ->
        -     *     Observable.defer(() -> o.scan(new ArrayList<>(), (list, item) -> list.add(item)))
        +     * source.compose(o ->
        +     *     Observable.defer(() -> o.scan(new ArrayList<>(), (list, item) -> list.add(item)))
              * );
              * 
        *
        @@ -10061,13 +10120,13 @@ public final Observable scan(final R initialValue, BiFunction - * ObservableSource<T> source = ... - * Observable.defer(() -> source.scan(new ArrayList<>(), (list, item) -> list.add(item))); + * ObservableSource<T> source = ... + * Observable.defer(() -> source.scan(new ArrayList<>(), (list, item) -> list.add(item))); * * // alternatively, by using compose to stay fluent * - * source.compose(o -> - * Observable.defer(() -> o.scan(new ArrayList<>(), (list, item) -> list.add(item))) + * source.compose(o -> + * Observable.defer(() -> o.scan(new ArrayList<>(), (list, item) -> list.add(item))) * ); *
        *
        @@ -10192,7 +10251,7 @@ public final Single single(T defaultItem) { * if this Observable completes without emitting any items or emits more than one item a * {@link NoSuchElementException} or {@code IllegalArgumentException} will be signalled respectively. *

        - * + * *

        *
        Scheduler:
        *
        {@code singleOrError} does not operate by default on a particular {@link Scheduler}.
        @@ -10847,10 +10906,10 @@ public final void subscribe(Observer observer) { * Observer as is. *

        Usage example: *

        
        -     * Observable<Integer> source = Observable.range(1, 10);
        +     * Observable<Integer> source = Observable.range(1, 10);
              * CompositeDisposable composite = new CompositeDisposable();
              *
        -     * ResourceObserver<Integer> rs = new ResourceObserver<>() {
        +     * ResourceObserver<Integer> rs = new ResourceObserver<>() {
              *     // ...
              * };
              *
        @@ -10900,8 +10959,8 @@ public final Observable subscribeOn(Scheduler scheduler) {
             /**
              * Returns an Observable that emits the items emitted by the source ObservableSource or the items of an alternate
              * ObservableSource if the source ObservableSource is empty.
        +     * 

        * - *

        *

        *
        Scheduler:
        *
        {@code switchIfEmpty} does not operate by default on a particular {@link Scheduler}.
        @@ -11000,15 +11059,15 @@ public final Observable switchMap(Function{@code switchMapSingle} does not operate by default on a particular {@link Scheduler}. *
        * + *

        History: 2.0.8 - experimental * @param the element type of the inner SingleSources and the output * @param mapper * a function that, when applied to an item emitted by the source ObservableSource, returns a * SingleSource * @return an Observable that emits the item emitted by the SingleSource returned from applying {@code func} to the most recently emitted item emitted by the source ObservableSource * @see ReactiveX operators documentation: FlatMap - * @since 2.0.8 + * @since 2.1 */ - @Experimental @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @NonNull @@ -11031,15 +11090,15 @@ public final Observable switchMapSingle(@NonNull Function{@code switchMapSingleDelayError} does not operate by default on a particular {@link Scheduler}. *

        * + *

        History: 2.0.8 - experimental * @param the element type of the inner SingleSources and the output * @param mapper * a function that, when applied to an item emitted by the source ObservableSource, returns a * SingleSource * @return an Observable that emits the item emitted by the SingleSource returned from applying {@code func} to the most recently emitted item emitted by the source ObservableSource * @see ReactiveX operators documentation: FlatMap - * @since 2.0.8 + * @since 2.1 */ - @Experimental @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @NonNull @@ -11677,7 +11736,6 @@ public final Observable throttleLast(long intervalDuration, TimeUnit unit, Sc * *

        * Information on debounce vs throttle: - *

        *

          *
        • Debounce and Throttle: visual explanation
        • *
        • Debouncing: javascript methods
        • @@ -11715,7 +11773,6 @@ public final Observable throttleWithTimeout(long timeout, TimeUnit unit) { * *

          * Information on debounce vs throttle: - *

          *

            *
          • Debounce and Throttle: visual explanation
          • *
          • Debouncing: javascript methods
          • @@ -13200,7 +13257,6 @@ public final Observable> window( *

            * *

            - * if left unconsumed. *
            Scheduler:
            *
            This version of {@code window} does not operate by default on a particular {@link Scheduler}.
            *
            diff --git a/observable/src/main/java/io/reactivex/observable/ObservableEmitter.java b/observable/src/main/java/io/reactivex/observable/ObservableEmitter.java index 700d836..be3dab0 100644 --- a/observable/src/main/java/io/reactivex/observable/ObservableEmitter.java +++ b/observable/src/main/java/io/reactivex/observable/ObservableEmitter.java @@ -56,4 +56,19 @@ public interface ObservableEmitter extends Emitter { */ @NonNull ObservableEmitter serialize(); + + /** + * Attempts to emit the specified {@code Throwable} error if the downstream + * hasn't cancelled the sequence or is otherwise terminated, returning false + * if the emission is not allowed to happen due to lifecycle restrictions. + *

            + * Unlike {@link #onError(Throwable)}, the {@code RxJavaPlugins.onError} is not called + * if the error could not be delivered. + *

            History: 2.1.1 - experimental + * @param t the throwable error to signal if possible + * @return true if successful, false if the downstream is not able to accept further + * events + * @since 2.2 + */ + boolean tryOnError(@NonNull Throwable t); } diff --git a/observable/src/main/java/io/reactivex/observable/RxJavaObservablePlugins.java b/observable/src/main/java/io/reactivex/observable/RxJavaObservablePlugins.java index 968bb22..ef1df48 100644 --- a/observable/src/main/java/io/reactivex/observable/RxJavaObservablePlugins.java +++ b/observable/src/main/java/io/reactivex/observable/RxJavaObservablePlugins.java @@ -447,12 +447,12 @@ public static Completable onAssembly(@NonNull Completable source) { /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#computation()} * except using {@code threadFactory} for thread creation. + *

            History: 2.0.5 - experimental * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. * @return the created Scheduler instance - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental @NonNull public static Scheduler createComputationScheduler(@NonNull ThreadFactory threadFactory) { return new ComputationScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); @@ -461,12 +461,12 @@ public static Scheduler createComputationScheduler(@NonNull ThreadFactory thread /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#io()} * except using {@code threadFactory} for thread creation. + *

            History: 2.0.5 - experimental * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. * @return the created Scheduler instance - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental @NonNull public static Scheduler createIoScheduler(@NonNull ThreadFactory threadFactory) { return new IoScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); @@ -475,12 +475,12 @@ public static Scheduler createIoScheduler(@NonNull ThreadFactory threadFactory) /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#newThread()} * except using {@code threadFactory} for thread creation. + *

            History: 2.0.5 - experimental * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. * @return the created Scheduler instance - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental @NonNull public static Scheduler createNewThreadScheduler(@NonNull ThreadFactory threadFactory) { return new NewThreadScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); @@ -489,12 +489,12 @@ public static Scheduler createNewThreadScheduler(@NonNull ThreadFactory threadFa /** * Create an instance of the default {@link Scheduler} used for {@link Schedulers#single()} * except using {@code threadFactory} for thread creation. + *

            History: 2.0.5 - experimental * @param threadFactory thread factory to use for creating worker threads. Note that this takes precedence over any * system properties for configuring new thread creation. Cannot be null. * @return the created Scheduler instance - * @since 2.0.5 - experimental + * @since 2.1 */ - @Experimental @NonNull public static Scheduler createSingleScheduler(@NonNull ThreadFactory threadFactory) { return new SingleScheduler(ObjectHelper.requireNonNull(threadFactory, "threadFactory is null")); diff --git a/observable/src/main/java/io/reactivex/observable/Single.java b/observable/src/main/java/io/reactivex/observable/Single.java index 101eaf8..b038b78 100644 --- a/observable/src/main/java/io/reactivex/observable/Single.java +++ b/observable/src/main/java/io/reactivex/observable/Single.java @@ -295,7 +295,6 @@ public static Observable concatArray(SingleSource... sources * * }); *

        - *

        *

        *
        Scheduler:
        *
        {@code create} does not operate by default on a particular {@link Scheduler}.
        @@ -530,7 +529,6 @@ public static Single fromFuture(Future future, Scheduler sch * Wraps a specific ObservableSource into a Single and signals its single element or error. *

        If the ObservableSource is empty, a NoSuchElementException is signalled. * If the source has more than one element, an IndexOutOfBoundsException is signalled. - *

        *

        *
        Scheduler:
        *
        {@code fromObservable} does not operate by default on a particular {@link Scheduler}.
        @@ -617,7 +615,6 @@ public static Observable merge(ObservableSource * - *

        *

        *
        Scheduler:
        *
        {@code merge} does not operate by default on a particular {@link Scheduler}.
        @@ -1524,14 +1521,13 @@ public final Observable concatWith(SingleSource other) { } /** - * Delays the emission of the success or error signal from the current Single by - * the specified amount. + * Delays the emission of the success signal from the current Single by the specified amount. *
        *
        Scheduler:
        *
        {@code delay} operates by default on the {@code computation} {@link Scheduler}.
        *
        * - * @param time the time amount to delay the signals + * @param time the time amount to delay the emission of the success signal * @param unit the time unit * @return the new Single instance * @since 2.0 @@ -1543,16 +1539,15 @@ public final Single delay(long time, TimeUnit unit) { } /** - * Delays the emission of the success or error signal from the current Single by - * the specified amount. + * Delays the emission of the success signal from the current Single by the specified amount. *
        *
        Scheduler:
        *
        you specify the {@link Scheduler} where the non-blocking wait and emission happens
        *
        * - * @param time the time amount to delay the signals + * @param time the time amount to delay the emission of the success signal * @param unit the time unit - * @param scheduler the target scheduler to use fro the non-blocking wait and emission + * @param scheduler the target scheduler to use for the non-blocking wait and emission * @return the new Single instance * @since 2.0 */ @@ -1676,13 +1671,13 @@ public final Single delaySubscription(long time, TimeUnit unit, Scheduler *
        Scheduler:
        *
        {@code doAfterSuccess} does not operate by default on a particular {@link Scheduler}.
        *
        + *

        History: 2.0.1 - experimental * @param onAfterSuccess the Consumer that will be called after emitting an item from upstream to the downstream * @return the new Single instance - * @since 2.0.1 - experimental + * @since 2.1 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) - @Experimental public final Single doAfterSuccess(Consumer onAfterSuccess) { ObjectHelper.requireNonNull(onAfterSuccess, "doAfterSuccess is null"); return RxJavaObservablePlugins.onAssembly(new SingleDoAfterSuccess(this, onAfterSuccess)); @@ -1690,7 +1685,7 @@ public final Single doAfterSuccess(Consumer onAfterSuccess) { /** * Registers an {@link Action} to be called after this Single invokes either onSuccess or onError. - * *

        Note that the {@code doAfterSuccess} action is shared between subscriptions and as such + * *

        Note that the {@code doAfterTerminate} action is shared between subscriptions and as such * should be thread-safe.

        *

        * @@ -1699,16 +1694,16 @@ public final Single doAfterSuccess(Consumer onAfterSuccess) { *

        {@code doAfterTerminate} does not operate by default on a particular {@link Scheduler}.
        *
        * + *

        History: 2.0.6 - experimental * @param onAfterTerminate * an {@link Action} to be invoked when the source Single finishes * @return a Single that emits the same items as the source Single, then invokes the * {@link Action} * @see ReactiveX operators documentation: Do - * @since 2.0.6 - experimental + * @since 2.1 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) - @Experimental public final Single doAfterTerminate(Action onAfterTerminate) { ObjectHelper.requireNonNull(onAfterTerminate, "onAfterTerminate is null"); return RxJavaObservablePlugins.onAssembly(new SingleDoAfterTerminate(this, onAfterTerminate)); @@ -1725,13 +1720,13 @@ public final Single doAfterTerminate(Action onAfterTerminate) { *

        Scheduler:
        *
        {@code doFinally} does not operate by default on a particular {@link Scheduler}.
        *
        + *

        History: 2.0.1 - experimental * @param onFinally the action called when this Single terminates or gets cancelled * @return the new Single instance - * @since 2.0.1 - experimental + * @since 2.1 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) - @Experimental public final Single doFinally(Action onFinally) { ObjectHelper.requireNonNull(onFinally, "onFinally is null"); return RxJavaObservablePlugins.onAssembly(new SingleDoFinally(this, onFinally)); @@ -2177,9 +2172,9 @@ public final Single onErrorReturnItem(final T value) { /** * Instructs a Single to pass control to another Single rather than invoking * {@link SingleObserver#onError(Throwable)} if it encounters an error. - *

        + *

        * - *

        + *

        * By default, when a Single encounters an error that prevents it from emitting the expected item to * its {@link SingleObserver}, the Single invokes its SingleObserver's {@code onError} method, and then quits * without invoking any more of its SingleObserver's methods. The {@code onErrorResumeNext} method changes this @@ -2189,7 +2184,7 @@ public final Single onErrorReturnItem(final T value) { * will invoke the SingleObserver's {@link SingleObserver#onSuccess onSuccess} method if it is able to do so. In such a case, * because no Single necessarily invokes {@code onError}, the SingleObserver may never know that an error * happened. - *

        + *

        * You can use this to prevent errors from propagating or to supply fallback data should errors be * encountered. *

        @@ -2211,9 +2206,9 @@ public final Single onErrorResumeNext(final Single resumeSingleI /** * Instructs a Single to pass control to another Single rather than invoking * {@link SingleObserver#onError(Throwable)} if it encounters an error. - *

        + *

        * - *

        + *

        * By default, when a Single encounters an error that prevents it from emitting the expected item to * its {@link SingleObserver}, the Single invokes its SingleObserver's {@code onError} method, and then quits * without invoking any more of its SingleObserver's methods. The {@code onErrorResumeNext} method changes this @@ -2223,7 +2218,7 @@ public final Single onErrorResumeNext(final Single resumeSingleI * will invoke the SingleObserver's {@link SingleObserver#onSuccess onSuccess} method if it is able to do so. In such a case, * because no Single necessarily invokes {@code onError}, the SingleObserver may never know that an error * happened. - *

        + *

        * You can use this to prevent errors from propagating or to supply fallback data should errors be * encountered. *

        @@ -2234,7 +2229,7 @@ public final Single onErrorResumeNext(final Single resumeSingleI * @param resumeFunctionInCaseOfError a function that returns a Single that will take control if source Single encounters an error. * @return the original Single, with appropriately modified behavior. * @see ReactiveX operators documentation: Catch - * @since .20 + * @since 2.0 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @@ -2534,10 +2529,10 @@ public final void subscribe(SingleObserver subscriber) { * SingleObserver as is. *

        Usage example: *

        
        -     * Single<Integer> source = Single.just(1);
        +     * Single<Integer> source = Single.just(1);
              * CompositeDisposable composite = new CompositeDisposable();
              *
        -     * class ResourceSingleObserver implements SingleObserver<Integer>, Disposable {
        +     * class ResourceSingleObserver implements SingleObserver<Integer>, Disposable {
              *     // ...
              * }
              *
        @@ -2859,14 +2854,14 @@ public final Observable toObservable() {
              *  
        Scheduler:
        *
        {@code unsubscribeOn} calls dispose() of the upstream on the {@link Scheduler} you specify.
        *
        + *

        History: 2.0.9 - experimental * @param scheduler the target scheduler where to execute the cancellation * @return the new Single instance * @throws NullPointerException if scheduler is null - * @since 2.0.9 - experimental + * @since 2.2 */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.CUSTOM) - @Experimental public final Single unsubscribeOn(final Scheduler scheduler) { ObjectHelper.requireNonNull(scheduler, "scheduler is null"); return RxJavaObservablePlugins.onAssembly(new SingleUnsubscribeOn(this, scheduler)); diff --git a/observable/src/main/java/io/reactivex/observable/SingleEmitter.java b/observable/src/main/java/io/reactivex/observable/SingleEmitter.java index 956787a..28c69bb 100644 --- a/observable/src/main/java/io/reactivex/observable/SingleEmitter.java +++ b/observable/src/main/java/io/reactivex/observable/SingleEmitter.java @@ -60,4 +60,19 @@ public interface SingleEmitter { * @return true if the downstream cancelled the sequence */ boolean isDisposed(); + + /** + * Attempts to emit the specified {@code Throwable} error if the downstream + * hasn't cancelled the sequence or is otherwise terminated, returning false + * if the emission is not allowed to happen due to lifecycle restrictions. + *

        + * Unlike {@link #onError(Throwable)}, the {@code RxJavaPlugins.onError} is not called + * if the error could not be delivered. + *

        History: 2.1.1 - experimental + * @param t the throwable error to signal if possible + * @return true if successful, false if the downstream is not able to accept further + * events + * @since 2.2 + */ + boolean tryOnError(@NonNull Throwable t); } diff --git a/observable/src/main/java/io/reactivex/observable/extensions/FuseToMaybe.java b/observable/src/main/java/io/reactivex/observable/extensions/FuseToMaybe.java index 43accba..11cfa90 100644 --- a/observable/src/main/java/io/reactivex/observable/extensions/FuseToMaybe.java +++ b/observable/src/main/java/io/reactivex/observable/extensions/FuseToMaybe.java @@ -20,8 +20,8 @@ * the operator goes from Maybe to some other reactive type and then the sequence calls * for toMaybe again: *

        - * Single<Integer> single = Maybe.just(1).isEmpty();
        - * Maybe<Integer> maybe = single.toMaybe();
        + * Single<Integer> single = Maybe.just(1).isEmpty();
        + * Maybe<Integer> maybe = single.toMaybe();
          * 
        * * The {@code Single.toMaybe()} will check for this interface and call the {@link #fuseToMaybe()} diff --git a/observable/src/main/java/io/reactivex/observable/extensions/FuseToObservable.java b/observable/src/main/java/io/reactivex/observable/extensions/FuseToObservable.java index 225fcfa..390f81c 100644 --- a/observable/src/main/java/io/reactivex/observable/extensions/FuseToObservable.java +++ b/observable/src/main/java/io/reactivex/observable/extensions/FuseToObservable.java @@ -20,8 +20,8 @@ * the operator goes from Observable to some other reactive type and then the sequence calls * for toObservable again: *
        - * Single<Integer> single = Observable.range(1, 10).reduce((a, b) -> a + b);
        - * Observable<Integer> observable = single.toObservable();
        + * Single<Integer> single = Observable.range(1, 10).reduce((a, b) -> a + b);
        + * Observable<Integer> observable = single.toObservable();
          * 
        * * The {@code Single.toObservable()} will check for this interface and call the {@link #fuseToObservable()} diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/CompletableCache.java b/observable/src/main/java/io/reactivex/observable/internal/operators/CompletableCache.java index fd0daa5..84d4b4b 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/CompletableCache.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/CompletableCache.java @@ -16,15 +16,14 @@ import java.util.concurrent.atomic.*; import io.reactivex.common.Disposable; -import io.reactivex.common.annotations.Experimental; import io.reactivex.observable.*; /** * Consume the upstream source exactly once and cache its terminal event. * - * @since 2.0.4 - experimental + *

        History: 2.0.4 - experimental + * @since 2.1 */ -@Experimental public final class CompletableCache extends Completable implements CompletableObserver { static final InnerCompletableCache[] EMPTY = new InnerCompletableCache[0]; diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/CompletableCreate.java b/observable/src/main/java/io/reactivex/observable/internal/operators/CompletableCreate.java index 02194d1..34ee444 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/CompletableCreate.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/CompletableCreate.java @@ -72,6 +72,13 @@ public void onComplete() { @Override public void onError(Throwable t) { + if (!tryOnError(t)) { + RxJavaCommonPlugins.onError(t); + } + } + + @Override + public boolean tryOnError(Throwable t) { if (t == null) { t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); } @@ -85,10 +92,10 @@ public void onError(Throwable t) { d.dispose(); } } - return; + return true; } } - RxJavaCommonPlugins.onError(t); + return false; } @Override diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/CompletableDoFinally.java b/observable/src/main/java/io/reactivex/observable/internal/operators/CompletableDoFinally.java index ea18dee..9c6abe5 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/CompletableDoFinally.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/CompletableDoFinally.java @@ -16,7 +16,6 @@ import java.util.concurrent.atomic.AtomicInteger; import io.reactivex.common.*; -import io.reactivex.common.annotations.Experimental; import io.reactivex.common.exceptions.Exceptions; import io.reactivex.common.functions.Action; import io.reactivex.common.internal.disposables.DisposableHelper; @@ -25,9 +24,9 @@ /** * Execute an action after an onError, onComplete or a dispose event. * - * @since 2.0.1 - experimental + *

        History: 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class CompletableDoFinally extends Completable { final CompletableSource source; diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeCreate.java b/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeCreate.java index 021e3da..897298a 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeCreate.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeCreate.java @@ -83,6 +83,13 @@ public void onSuccess(T value) { @Override public void onError(Throwable t) { + if (!tryOnError(t)) { + RxJavaCommonPlugins.onError(t); + } + } + + @Override + public boolean tryOnError(Throwable t) { if (t == null) { t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); } @@ -96,10 +103,10 @@ public void onError(Throwable t) { d.dispose(); } } - return; + return true; } } - RxJavaCommonPlugins.onError(t); + return false; } @Override diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeDoAfterSuccess.java b/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeDoAfterSuccess.java index c43abac..e81fa66 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeDoAfterSuccess.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeDoAfterSuccess.java @@ -14,7 +14,6 @@ package io.reactivex.observable.internal.operators; import io.reactivex.common.*; -import io.reactivex.common.annotations.Experimental; import io.reactivex.common.exceptions.Exceptions; import io.reactivex.common.functions.Consumer; import io.reactivex.common.internal.disposables.DisposableHelper; @@ -22,10 +21,10 @@ /** * Calls a consumer after pushing the current item to the downstream. + *

        History: 2.0.1 - experimental * @param the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class MaybeDoAfterSuccess extends AbstractMaybeWithUpstream { final Consumer onAfterSuccess; diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeDoFinally.java b/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeDoFinally.java index 0841353..8e3d3c3 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeDoFinally.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeDoFinally.java @@ -16,7 +16,6 @@ import java.util.concurrent.atomic.AtomicInteger; import io.reactivex.common.*; -import io.reactivex.common.annotations.Experimental; import io.reactivex.common.exceptions.Exceptions; import io.reactivex.common.functions.Action; import io.reactivex.common.internal.disposables.DisposableHelper; @@ -25,10 +24,10 @@ /** * Execute an action after an onSuccess, onError, onComplete or a dispose event. * + *

        History: 2.0.1 - experimental * @param the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class MaybeDoFinally extends AbstractMaybeWithUpstream { final Action onFinally; diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeFlatMapSingleElement.java b/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeFlatMapSingleElement.java index 1a55896..cfc0865 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeFlatMapSingleElement.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeFlatMapSingleElement.java @@ -16,7 +16,6 @@ import java.util.concurrent.atomic.AtomicReference; import io.reactivex.common.Disposable; -import io.reactivex.common.annotations.Experimental; import io.reactivex.common.exceptions.Exceptions; import io.reactivex.common.functions.Function; import io.reactivex.common.internal.disposables.DisposableHelper; @@ -28,9 +27,9 @@ * @param the input value type * @param the result value type * - * @since 2.0.2 - experimental + *

        History: 2.0.2 - experimental + * @since 2.1 */ -@Experimental public final class MaybeFlatMapSingleElement extends Maybe { final MaybeSource source; diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeZipArray.java b/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeZipArray.java index 1debc1f..2acb997 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeZipArray.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeZipArray.java @@ -191,7 +191,7 @@ public void onComplete() { final class SingletonArrayFunc implements Function { @Override public R apply(T t) throws Exception { - return zipper.apply(new Object[] { t }); + return ObjectHelper.requireNonNull(zipper.apply(new Object[] { t }), "The zipper returned a null value"); } } } diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeZipIterable.java b/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeZipIterable.java index eac3aa1..85e236c 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeZipIterable.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/MaybeZipIterable.java @@ -17,6 +17,7 @@ import io.reactivex.common.exceptions.Exceptions; import io.reactivex.common.functions.Function; +import io.reactivex.common.internal.functions.ObjectHelper; import io.reactivex.observable.*; import io.reactivex.observable.internal.disposables.EmptyDisposable; import io.reactivex.observable.internal.operators.MaybeZipArray.ZipCoordinator; @@ -81,7 +82,7 @@ protected void subscribeActual(MaybeObserver observer) { final class SingletonArrayFunc implements Function { @Override public R apply(T t) throws Exception { - return zipper.apply(new Object[] { t }); + return ObjectHelper.requireNonNull(zipper.apply(new Object[] { t }), "The zipper returned a null value"); } } } diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableBufferTimed.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableBufferTimed.java index 45ad096..47095cd 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableBufferTimed.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableBufferTimed.java @@ -459,12 +459,12 @@ public void onNext(T t) { if (b.size() < maxSize) { return; } - } - if (restartTimerOnMaxSize) { buffer = null; producerIndex++; + } + if (restartTimerOnMaxSize) { timer.dispose(); } @@ -479,17 +479,13 @@ public void onNext(T t) { return; } - if (restartTimerOnMaxSize) { - synchronized (this) { - buffer = b; - consumerIndex++; - } + synchronized (this) { + buffer = b; + consumerIndex++; + } + if (restartTimerOnMaxSize) { timer = w.schedulePeriodically(this, timespan, timespan, unit); - } else { - synchronized (this) { - buffer = b; - } } } diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableConcatMap.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableConcatMap.java index 8995627..8ca3d23 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableConcatMap.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableConcatMap.java @@ -201,6 +201,7 @@ void drain() { boolean empty = t == null; if (d && empty) { + disposed = true; actual.onComplete(); return; } @@ -366,7 +367,7 @@ public void onComplete() { @Override public boolean isDisposed() { - return d.isDisposed(); + return cancelled; } @Override @@ -399,7 +400,7 @@ void drain() { Throwable ex = error.get(); if (ex != null) { queue.clear(); - + cancelled = true; actual.onError(error.terminate()); return; } @@ -413,6 +414,7 @@ void drain() { v = queue.poll(); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); + cancelled = true; this.d.dispose(); error.addThrowable(ex); actual.onError(error.terminate()); @@ -422,6 +424,7 @@ void drain() { boolean empty = v == null; if (d && empty) { + cancelled = true; Throwable ex = error.terminate(); if (ex != null) { actual.onError(ex); @@ -439,6 +442,7 @@ void drain() { o = ObjectHelper.requireNonNull(mapper.apply(v), "The mapper returned a null ObservableSource"); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); + cancelled = true; this.d.dispose(); queue.clear(); error.addThrowable(ex); diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableCreate.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableCreate.java index f79767d..f3bc5aa 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableCreate.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableCreate.java @@ -69,6 +69,13 @@ public void onNext(T t) { @Override public void onError(Throwable t) { + if (!tryOnError(t)) { + RxJavaCommonPlugins.onError(t); + } + } + + @Override + public boolean tryOnError(Throwable t) { if (t == null) { t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); } @@ -78,9 +85,9 @@ public void onError(Throwable t) { } finally { dispose(); } - } else { - RxJavaCommonPlugins.onError(t); + return true; } + return false; } @Override @@ -173,9 +180,15 @@ public void onNext(T t) { @Override public void onError(Throwable t) { - if (emitter.isDisposed() || done) { + if (!tryOnError(t)) { RxJavaCommonPlugins.onError(t); - return; + } + } + + @Override + public boolean tryOnError(Throwable t) { + if (emitter.isDisposed() || done) { + return false; } if (t == null) { t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); @@ -183,9 +196,9 @@ public void onError(Throwable t) { if (error.addThrowable(t)) { done = true; drain(); - } else { - RxJavaCommonPlugins.onError(t); + return true; } + return false; } @Override @@ -267,4 +280,4 @@ public ObservableEmitter serialize() { } } -} +} \ No newline at end of file diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableDoAfterNext.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableDoAfterNext.java index 5df8bf7..1f270f0 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableDoAfterNext.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableDoAfterNext.java @@ -20,10 +20,10 @@ /** * Calls a consumer after pushing the current item to the downstream. + *

        History: 2.0.1 - experimental * @param the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class ObservableDoAfterNext extends AbstractObservableWithUpstream { final Consumer onAfterNext; diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableDoFinally.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableDoFinally.java index c56e16c..4b9bc03 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableDoFinally.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableDoFinally.java @@ -25,10 +25,10 @@ /** * Execute an action after an onError, onComplete or a dispose event. * + *

        History: 2.0.1 - experimental * @param the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class ObservableDoFinally extends AbstractObservableWithUpstream { final Action onFinally; diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapCompletable.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapCompletable.java index c9968c3..cb365e1 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapCompletable.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapCompletable.java @@ -64,6 +64,8 @@ static final class FlatMapCompletableMainObserver extends BasicIntQueueDispos Disposable d; + volatile boolean disposed; + FlatMapCompletableMainObserver(Observer observer, Function mapper, boolean delayErrors) { this.actual = observer; this.mapper = mapper; @@ -99,7 +101,7 @@ public void onNext(T value) { InnerObserver inner = new InnerObserver(); - if (set.add(inner)) { + if (!disposed && set.add(inner)) { cs.subscribe(inner); } } @@ -138,6 +140,7 @@ public void onComplete() { @Override public void dispose() { + disposed = true; d.dispose(); set.dispose(); } diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapCompletableCompletable.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapCompletableCompletable.java index 26d1e5d..58bc5c1 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapCompletableCompletable.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapCompletableCompletable.java @@ -69,6 +69,8 @@ static final class FlatMapCompletableMainObserver extends AtomicInteger imple Disposable d; + volatile boolean disposed; + FlatMapCompletableMainObserver(CompletableObserver observer, Function mapper, boolean delayErrors) { this.actual = observer; this.mapper = mapper; @@ -104,7 +106,7 @@ public void onNext(T value) { InnerObserver inner = new InnerObserver(); - if (set.add(inner)) { + if (!disposed && set.add(inner)) { cs.subscribe(inner); } } @@ -143,6 +145,7 @@ public void onComplete() { @Override public void dispose() { + disposed = true; d.dispose(); set.dispose(); } diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapMaybe.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapMaybe.java index 803600a..a396c67 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapMaybe.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapMaybe.java @@ -109,7 +109,7 @@ public void onNext(T t) { InnerObserver inner = new InnerObserver(); - if (set.add(inner)) { + if (!cancelled && set.add(inner)) { ms.subscribe(inner); } } diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapSingle.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapSingle.java index 73a0fbe..a609e2f 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapSingle.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableFlatMapSingle.java @@ -109,7 +109,7 @@ public void onNext(T t) { InnerObserver inner = new InnerObserver(); - if (set.add(inner)) { + if (!cancelled && set.add(inner)) { ms.subscribe(inner); } } diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableInternalHelper.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableInternalHelper.java index 6e03046..e227591 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableInternalHelper.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableInternalHelper.java @@ -74,7 +74,8 @@ static final class ItemDelayFunction implements Function apply(final T v) throws Exception { - return new ObservableTake(itemDelay.apply(v), 1).map(Functions.justFunction(v)).defaultIfEmpty(v); + ObservableSource o = ObjectHelper.requireNonNull(itemDelay.apply(v), "The itemDelay returned a null ObservableSource"); + return new ObservableTake(o, 1).map(Functions.justFunction(v)).defaultIfEmpty(v); } } @@ -162,7 +163,7 @@ static final class FlatMapWithCombinerOuter implements Function apply(final T t) throws Exception { @SuppressWarnings("unchecked") - ObservableSource u = (ObservableSource)mapper.apply(t); + ObservableSource u = (ObservableSource)ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null ObservableSource"); return new ObservableMap(u, new FlatMapWithCombinerInner(combiner, t)); } } @@ -182,7 +183,7 @@ static final class FlatMapIntoIterable implements Function apply(T t) throws Exception { - return new ObservableFromIterable(mapper.apply(t)); + return new ObservableFromIterable(ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null Iterable")); } } @@ -316,7 +317,7 @@ static final class ObservableMapper implements Function> { @Override public Observable apply(T t) throws Exception { return RxJavaObservablePlugins.onAssembly(new SingleToObservable( - ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null value"))); + ObjectHelper.requireNonNull(mapper.apply(t), "The mapper returned a null SingleSource"))); } } @@ -400,7 +401,8 @@ static final class ReplayFunction implements Function, Obser @Override public ObservableSource apply(Observable t) throws Exception { - return Observable.wrap(selector.apply(t)).observeOn(scheduler); + ObservableSource apply = ObjectHelper.requireNonNull(selector.apply(t), "The selector returned a null ObservableSource"); + return Observable.wrap(apply).observeOn(scheduler); } } } diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableInterval.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableInterval.java index e29d439..2609a6b 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableInterval.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableInterval.java @@ -17,7 +17,9 @@ import java.util.concurrent.atomic.AtomicReference; import io.reactivex.common.*; +import io.reactivex.common.Scheduler.Worker; import io.reactivex.common.internal.disposables.DisposableHelper; +import io.reactivex.common.internal.schedulers.TrampolineScheduler; import io.reactivex.observable.*; public final class ObservableInterval extends Observable { @@ -38,9 +40,16 @@ public void subscribeActual(Observer s) { IntervalObserver is = new IntervalObserver(s); s.onSubscribe(is); - Disposable d = scheduler.schedulePeriodicallyDirect(is, initialDelay, period, unit); + Scheduler sch = scheduler; - is.setResource(d); + if (sch instanceof TrampolineScheduler) { + Worker worker = sch.createWorker(); + is.setResource(worker); + worker.schedulePeriodically(is, initialDelay, period, unit); + } else { + Disposable d = sch.schedulePeriodicallyDirect(is, initialDelay, period, unit); + is.setResource(d); + } } static final class IntervalObserver diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableIntervalRange.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableIntervalRange.java index 765aeac..c235604 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableIntervalRange.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableIntervalRange.java @@ -17,7 +17,9 @@ import java.util.concurrent.atomic.AtomicReference; import io.reactivex.common.*; +import io.reactivex.common.Scheduler.Worker; import io.reactivex.common.internal.disposables.DisposableHelper; +import io.reactivex.common.internal.schedulers.TrampolineScheduler; import io.reactivex.observable.*; public final class ObservableIntervalRange extends Observable { @@ -42,9 +44,16 @@ public void subscribeActual(Observer s) { IntervalRangeObserver is = new IntervalRangeObserver(s, start, end); s.onSubscribe(is); - Disposable d = scheduler.schedulePeriodicallyDirect(is, initialDelay, period, unit); + Scheduler sch = scheduler; - is.setResource(d); + if (sch instanceof TrampolineScheduler) { + Worker worker = sch.createWorker(); + is.setResource(worker); + worker.schedulePeriodically(is, initialDelay, period, unit); + } else { + Disposable d = sch.schedulePeriodicallyDirect(is, initialDelay, period, unit); + is.setResource(d); + } } static final class IntervalRangeObserver diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableMapNotification.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableMapNotification.java index a93cc5c..05a16df 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableMapNotification.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableMapNotification.java @@ -16,7 +16,7 @@ import java.util.concurrent.Callable; import io.reactivex.common.Disposable; -import io.reactivex.common.exceptions.Exceptions; +import io.reactivex.common.exceptions.*; import io.reactivex.common.functions.Function; import io.reactivex.common.internal.disposables.DisposableHelper; import io.reactivex.common.internal.functions.ObjectHelper; @@ -106,7 +106,7 @@ public void onError(Throwable t) { p = ObjectHelper.requireNonNull(onErrorMapper.apply(t), "The onError Observable returned is null"); } catch (Throwable e) { Exceptions.throwIfFatal(e); - actual.onError(e); + actual.onError(new CompositeException(t, e)); return; } diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableRefCount.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableRefCount.java index ccc1832..79e8344 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableRefCount.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableRefCount.java @@ -68,10 +68,10 @@ public void subscribeActual(final Observer subscriber) { source.connect(onSubscribe(subscriber, writeLocked)); } finally { // need to cover the case where the source is subscribed to - // outside of this class thus preventing the Action1 passed + // outside of this class thus preventing the Consumer passed // to source.connect above being called if (writeLocked.get()) { - // Action1 passed to source.connect was not called + // Consumer passed to source.connect was not called lock.unlock(); } } diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableReplay.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableReplay.java index 7b69496..b90caf9 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableReplay.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableReplay.java @@ -21,6 +21,7 @@ import io.reactivex.common.exceptions.Exceptions; import io.reactivex.common.functions.*; import io.reactivex.common.internal.disposables.DisposableHelper; +import io.reactivex.common.internal.functions.ObjectHelper; import io.reactivex.common.internal.utils.ExceptionHelper; import io.reactivex.observable.*; import io.reactivex.observable.Observable; @@ -1025,8 +1026,8 @@ protected void subscribeActual(Observer child) { ConnectableObservable co; ObservableSource observable; try { - co = connectableFactory.call(); - observable = selector.apply(co); + co = ObjectHelper.requireNonNull(connectableFactory.call(), "The connectableFactory returned a null ConnectableObservable"); + observable = ObjectHelper.requireNonNull(selector.apply(co), "The selector returned a null ObservableSource"); } catch (Throwable e) { Exceptions.throwIfFatal(e); EmptyDisposable.error(e, child); diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableUsing.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableUsing.java index 22e3c31..9a6fad0 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableUsing.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableUsing.java @@ -20,6 +20,7 @@ import io.reactivex.common.exceptions.*; import io.reactivex.common.functions.*; import io.reactivex.common.internal.disposables.DisposableHelper; +import io.reactivex.common.internal.functions.ObjectHelper; import io.reactivex.observable.*; import io.reactivex.observable.internal.disposables.EmptyDisposable; @@ -53,7 +54,7 @@ public void subscribeActual(Observer s) { ObservableSource source; try { - source = sourceSupplier.apply(resource); + source = ObjectHelper.requireNonNull(sourceSupplier.apply(resource), "The sourceSupplier returned a null ObservableSource"); } catch (Throwable e) { Exceptions.throwIfFatal(e); try { diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableWithLatestFromMany.java b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableWithLatestFromMany.java index ad78236..9a6c5dc 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableWithLatestFromMany.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/ObservableWithLatestFromMany.java @@ -286,7 +286,7 @@ public void dispose() { final class SingletonArrayFunc implements Function { @Override public R apply(T t) throws Exception { - return combiner.apply(new Object[] { t }); + return ObjectHelper.requireNonNull(combiner.apply(new Object[] { t }), "The combiner returned a null value"); } } } diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/SingleCreate.java b/observable/src/main/java/io/reactivex/observable/internal/operators/SingleCreate.java index 1abb5c4..a61b9b0 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/SingleCreate.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/SingleCreate.java @@ -77,6 +77,13 @@ public void onSuccess(T value) { @Override public void onError(Throwable t) { + if (!tryOnError(t)) { + RxJavaCommonPlugins.onError(t); + } + } + + @Override + public boolean tryOnError(Throwable t) { if (t == null) { t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources."); } @@ -90,10 +97,10 @@ public void onError(Throwable t) { d.dispose(); } } - return; + return true; } } - RxJavaCommonPlugins.onError(t); + return false; } @Override diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/SingleDoAfterSuccess.java b/observable/src/main/java/io/reactivex/observable/internal/operators/SingleDoAfterSuccess.java index 97694f0..f799206 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/SingleDoAfterSuccess.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/SingleDoAfterSuccess.java @@ -14,7 +14,6 @@ package io.reactivex.observable.internal.operators; import io.reactivex.common.*; -import io.reactivex.common.annotations.Experimental; import io.reactivex.common.exceptions.Exceptions; import io.reactivex.common.functions.Consumer; import io.reactivex.common.internal.disposables.DisposableHelper; @@ -22,10 +21,10 @@ /** * Calls a consumer after pushing the current item to the downstream. + *

        History: 2.0.1 - experimental * @param the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class SingleDoAfterSuccess extends Single { final SingleSource source; diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/SingleDoFinally.java b/observable/src/main/java/io/reactivex/observable/internal/operators/SingleDoFinally.java index 21d1883..55f8bcf 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/SingleDoFinally.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/SingleDoFinally.java @@ -16,7 +16,6 @@ import java.util.concurrent.atomic.AtomicInteger; import io.reactivex.common.*; -import io.reactivex.common.annotations.Experimental; import io.reactivex.common.exceptions.Exceptions; import io.reactivex.common.functions.Action; import io.reactivex.common.internal.disposables.DisposableHelper; @@ -25,10 +24,10 @@ /** * Execute an action after an onSuccess, onError or a dispose event. * + *

        History: 2.0.1 - experimental * @param the value type - * @since 2.0.1 - experimental + * @since 2.1 */ -@Experimental public final class SingleDoFinally extends Single { final SingleSource source; diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/SingleMap.java b/observable/src/main/java/io/reactivex/observable/internal/operators/SingleMap.java index 248083d..e2f332d 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/SingleMap.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/SingleMap.java @@ -16,6 +16,7 @@ import io.reactivex.common.Disposable; import io.reactivex.common.exceptions.Exceptions; import io.reactivex.common.functions.Function; +import io.reactivex.common.internal.functions.ObjectHelper; import io.reactivex.observable.*; public final class SingleMap extends Single { @@ -53,7 +54,7 @@ public void onSubscribe(Disposable d) { public void onSuccess(T value) { R v; try { - v = mapper.apply(value); + v = ObjectHelper.requireNonNull(mapper.apply(value), "The mapper function returned a null value."); } catch (Throwable e) { Exceptions.throwIfFatal(e); onError(e); diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/SingleZipArray.java b/observable/src/main/java/io/reactivex/observable/internal/operators/SingleZipArray.java index 52ad6b8..8458bf7 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/SingleZipArray.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/SingleZipArray.java @@ -180,7 +180,7 @@ public void onError(Throwable e) { final class SingletonArrayFunc implements Function { @Override public R apply(T t) throws Exception { - return zipper.apply(new Object[] { t }); + return ObjectHelper.requireNonNull(zipper.apply(new Object[] { t }), "The zipper returned a null value"); } } } diff --git a/observable/src/main/java/io/reactivex/observable/internal/operators/SingleZipIterable.java b/observable/src/main/java/io/reactivex/observable/internal/operators/SingleZipIterable.java index fed2e31..5be70a8 100644 --- a/observable/src/main/java/io/reactivex/observable/internal/operators/SingleZipIterable.java +++ b/observable/src/main/java/io/reactivex/observable/internal/operators/SingleZipIterable.java @@ -17,6 +17,7 @@ import io.reactivex.common.exceptions.Exceptions; import io.reactivex.common.functions.Function; +import io.reactivex.common.internal.functions.ObjectHelper; import io.reactivex.observable.*; import io.reactivex.observable.internal.disposables.EmptyDisposable; import io.reactivex.observable.internal.operators.SingleZipArray.ZipCoordinator; @@ -81,7 +82,7 @@ protected void subscribeActual(SingleObserver observer) { final class SingletonArrayFunc implements Function { @Override public R apply(T t) throws Exception { - return zipper.apply(new Object[] { t }); + return ObjectHelper.requireNonNull(zipper.apply(new Object[] { t }), "The zipper returned a null value"); } } } diff --git a/observable/src/main/java/io/reactivex/observable/observers/DefaultObserver.java b/observable/src/main/java/io/reactivex/observable/observers/DefaultObserver.java index eceb601..c4508ae 100644 --- a/observable/src/main/java/io/reactivex/observable/observers/DefaultObserver.java +++ b/observable/src/main/java/io/reactivex/observable/observers/DefaultObserver.java @@ -38,10 +38,9 @@ * If for some reason this can't be avoided, use {@link io.reactivex.observable.Observable#safeSubscribe(io.reactivex.observable.Observer)} * instead of the standard {@code subscribe()} method. * - *

        Example

        - * Disposable d =
        - *     Observable.range(1, 5)
        - *     .subscribeWith(new DefaultObserver<Integer>() {
        + * 

        Example

        
        + * Observable.range(1, 5)
        + *     .subscribeWith(new DefaultObserver<Integer>() {
          *         @Override public void onStart() {
          *             System.out.println("Start!");
          *         }
        @@ -58,9 +57,7 @@
          *             System.out.println("Done!");
          *         }
          *     });
        - * // ...
        - * d.dispose();
        - * 
        + *
        * * @param the value type */ diff --git a/observable/src/main/java/io/reactivex/observable/observers/DisposableCompletableObserver.java b/observable/src/main/java/io/reactivex/observable/observers/DisposableCompletableObserver.java index aab6f47..aaca3bd 100644 --- a/observable/src/main/java/io/reactivex/observable/observers/DisposableCompletableObserver.java +++ b/observable/src/main/java/io/reactivex/observable/observers/DisposableCompletableObserver.java @@ -33,10 +33,10 @@ *

        Implementation of {@link #onStart()}, {@link #onError(Throwable)} and * {@link #onComplete()} are not allowed to throw any unchecked exceptions. * - *

        Example

        + * 

        Example

        
          * Disposable d =
          *     Completable.complete().delay(1, TimeUnit.SECONDS)
        - *     .subscribeWith(new DisposableMaybeObserver<Integer>() {
        + *     .subscribeWith(new DisposableMaybeObserver<Integer>() {
          *         @Override public void onStart() {
          *             System.out.println("Start!");
          *         }
        @@ -49,7 +49,7 @@
          *     });
          * // ...
          * d.dispose();
        - * 
        + *
        */ public abstract class DisposableCompletableObserver implements CompletableObserver, Disposable { final AtomicReference s = new AtomicReference(); diff --git a/observable/src/main/java/io/reactivex/observable/observers/DisposableMaybeObserver.java b/observable/src/main/java/io/reactivex/observable/observers/DisposableMaybeObserver.java index f760b98..8fde0dd 100644 --- a/observable/src/main/java/io/reactivex/observable/observers/DisposableMaybeObserver.java +++ b/observable/src/main/java/io/reactivex/observable/observers/DisposableMaybeObserver.java @@ -37,10 +37,10 @@ *

        Implementation of {@link #onStart()}, {@link #onSuccess(Object)}, {@link #onError(Throwable)} and * {@link #onComplete()} are not allowed to throw any unchecked exceptions. * - *

        Example

        + * 

        Example

        
          * Disposable d =
          *     Maybe.just(1).delay(1, TimeUnit.SECONDS)
        - *     .subscribeWith(new DisposableMaybeObserver<Integer>() {
        + *     .subscribeWith(new DisposableMaybeObserver<Integer>() {
          *         @Override public void onStart() {
          *             System.out.println("Start!");
          *         }
        @@ -56,7 +56,7 @@
          *     });
          * // ...
          * d.dispose();
        - * 
        + *
        * * * @param the received value type diff --git a/observable/src/main/java/io/reactivex/observable/observers/DisposableObserver.java b/observable/src/main/java/io/reactivex/observable/observers/DisposableObserver.java index d66af17..0c31544 100644 --- a/observable/src/main/java/io/reactivex/observable/observers/DisposableObserver.java +++ b/observable/src/main/java/io/reactivex/observable/observers/DisposableObserver.java @@ -38,10 +38,10 @@ * If for some reason this can't be avoided, use {@link io.reactivex.observable.Observable#safeSubscribe(io.reactivex.observable.Observer)} * instead of the standard {@code subscribe()} method. * - *

        Example

        + * 

        Example

        
          * Disposable d =
          *     Observable.range(1, 5)
        - *     .subscribeWith(new DisposableObserver<Integer>() {
        + *     .subscribeWith(new DisposableObserver<Integer>() {
          *         @Override public void onStart() {
          *             System.out.println("Start!");
          *         }
        @@ -60,7 +60,7 @@
          *     });
          * // ...
          * d.dispose();
        - * 
        + *
        * * @param the received value type */ diff --git a/observable/src/main/java/io/reactivex/observable/observers/DisposableSingleObserver.java b/observable/src/main/java/io/reactivex/observable/observers/DisposableSingleObserver.java index 4178366..b2014ac 100644 --- a/observable/src/main/java/io/reactivex/observable/observers/DisposableSingleObserver.java +++ b/observable/src/main/java/io/reactivex/observable/observers/DisposableSingleObserver.java @@ -33,10 +33,10 @@ *

        Implementation of {@link #onStart()}, {@link #onSuccess(Object)} and {@link #onError(Throwable)} * are not allowed to throw any unchecked exceptions. * - *

        Example

        + * 

        Example

        
          * Disposable d =
          *     Single.just(1).delay(1, TimeUnit.SECONDS)
        - *     .subscribeWith(new DisposableSingleObserver<Integer>() {
        + *     .subscribeWith(new DisposableSingleObserver<Integer>() {
          *         @Override public void onStart() {
          *             System.out.println("Start!");
          *         }
        @@ -49,7 +49,7 @@
          *     });
          * // ...
          * d.dispose();
        - * 
        + *
        * * @param the received value type */ diff --git a/observable/src/main/java/io/reactivex/observable/observers/ResourceCompletableObserver.java b/observable/src/main/java/io/reactivex/observable/observers/ResourceCompletableObserver.java index 20075d4..3bf7bcb 100644 --- a/observable/src/main/java/io/reactivex/observable/observers/ResourceCompletableObserver.java +++ b/observable/src/main/java/io/reactivex/observable/observers/ResourceCompletableObserver.java @@ -50,13 +50,13 @@ *

        Implementation of {@link #onStart()}, {@link #onError(Throwable)} * and {@link #onComplete()} are not allowed to throw any unchecked exceptions. * - *

        Example

        + * 

        Example

        
          * Disposable d =
          *     Completable.complete().delay(1, TimeUnit.SECONDS)
          *     .subscribeWith(new ResourceCompletableObserver() {
          *         @Override public void onStart() {
          *             add(Schedulers.single()
        - *                 .scheduleDirect(() -> System.out.println("Time!"),
        + *                 .scheduleDirect(() -> System.out.println("Time!"),
          *                     2, TimeUnit.SECONDS));
          *         }
          *         @Override public void onError(Throwable t) {
        @@ -70,7 +70,7 @@
          *     });
          * // ...
          * d.dispose();
        - * 
        + *
        */ public abstract class ResourceCompletableObserver implements CompletableObserver, Disposable { /** The active subscription. */ diff --git a/observable/src/main/java/io/reactivex/observable/observers/ResourceMaybeObserver.java b/observable/src/main/java/io/reactivex/observable/observers/ResourceMaybeObserver.java index 1b90055..2775fe6 100644 --- a/observable/src/main/java/io/reactivex/observable/observers/ResourceMaybeObserver.java +++ b/observable/src/main/java/io/reactivex/observable/observers/ResourceMaybeObserver.java @@ -54,13 +54,13 @@ *

        Implementation of {@link #onStart()}, {@link #onSuccess(Object)}, {@link #onError(Throwable)} * and {@link #onComplete()} are not allowed to throw any unchecked exceptions. * - *

        Example

        + * 

        Example

        
          * Disposable d =
          *     Maybe.just(1).delay(1, TimeUnit.SECONDS)
        - *     .subscribeWith(new ResourceMaybeObserver<Integer>() {
        + *     .subscribeWith(new ResourceMaybeObserver<Integer>() {
          *         @Override public void onStart() {
          *             add(Schedulers.single()
        - *                 .scheduleDirect(() -> System.out.println("Time!"),
        + *                 .scheduleDirect(() -> System.out.println("Time!"),
          *                     2, TimeUnit.SECONDS));
          *         }
          *         @Override public void onSuccess(Integer t) {
        @@ -78,7 +78,7 @@
          *     });
          * // ...
          * d.dispose();
        - * 
        + *
        * * @param the value type */ diff --git a/observable/src/main/java/io/reactivex/observable/observers/ResourceObserver.java b/observable/src/main/java/io/reactivex/observable/observers/ResourceObserver.java index 00b670b..be0bf0a 100644 --- a/observable/src/main/java/io/reactivex/observable/observers/ResourceObserver.java +++ b/observable/src/main/java/io/reactivex/observable/observers/ResourceObserver.java @@ -49,13 +49,13 @@ * If for some reason this can't be avoided, use {@link io.reactivex.observable.Observable#safeSubscribe(io.reactivex.observable.Observer)} * instead of the standard {@code subscribe()} method. * - *

        Example

        + * 

        Example

        
          * Disposable d =
          *     Observable.range(1, 5)
        - *     .subscribeWith(new ResourceObserver<Integer>() {
        + *     .subscribeWith(new ResourceObserver<Integer>() {
          *         @Override public void onStart() {
          *             add(Schedulers.single()
        - *                 .scheduleDirect(() -> System.out.println("Time!"),
        + *                 .scheduleDirect(() -> System.out.println("Time!"),
          *                     2, TimeUnit.SECONDS));
          *             request(1);
          *         }
        @@ -76,7 +76,7 @@
          *     });
          * // ...
          * d.dispose();
        - * 
        + *
        * * @param the value type */ diff --git a/observable/src/main/java/io/reactivex/observable/observers/ResourceSingleObserver.java b/observable/src/main/java/io/reactivex/observable/observers/ResourceSingleObserver.java index 3c689a3..6ff25ca 100644 --- a/observable/src/main/java/io/reactivex/observable/observers/ResourceSingleObserver.java +++ b/observable/src/main/java/io/reactivex/observable/observers/ResourceSingleObserver.java @@ -51,13 +51,13 @@ *

        Implementation of {@link #onStart()}, {@link #onSuccess(Object)} and {@link #onError(Throwable)} * are not allowed to throw any unchecked exceptions. * - *

        Example

        + * 

        Example

        
          * Disposable d =
          *     Single.just(1).delay(1, TimeUnit.SECONDS)
        - *     .subscribeWith(new ResourceSingleObserver<Integer>() {
        + *     .subscribeWith(new ResourceSingleObserver<Integer>() {
          *         @Override public void onStart() {
          *             add(Schedulers.single()
        - *                 .scheduleDirect(() -> System.out.println("Time!"),
        + *                 .scheduleDirect(() -> System.out.println("Time!"),
          *                     2, TimeUnit.SECONDS));
          *         }
          *         @Override public void onSuccess(Integer t) {
        @@ -71,7 +71,7 @@
          *     });
          * // ...
          * d.dispose();
        - * 
        + *
        * * @param the value type */ diff --git a/observable/src/main/java/io/reactivex/observable/observers/TestObserver.java b/observable/src/main/java/io/reactivex/observable/observers/TestObserver.java index e1a4d92..783521a 100644 --- a/observable/src/main/java/io/reactivex/observable/observers/TestObserver.java +++ b/observable/src/main/java/io/reactivex/observable/observers/TestObserver.java @@ -142,6 +142,7 @@ public void onNext(T t) { } catch (Throwable ex) { // Exceptions.throwIfFatal(e); TODO add fatal exceptions? errors.add(ex); + qs.dispose(); } return; } diff --git a/observable/src/main/java/io/reactivex/observable/package-info.java b/observable/src/main/java/io/reactivex/observable/package-info.java index 9e59a0f..5643269 100644 --- a/observable/src/main/java/io/reactivex/observable/package-info.java +++ b/observable/src/main/java/io/reactivex/observable/package-info.java @@ -14,14 +14,14 @@ * limitations under the License. */ /** - * Base reactive classes: Flowable, Observable, Single and Completable; base reactive consumers; + * Base reactive classes: Observable, Single and Completable; base reactive consumers; * other common base interfaces. * *

        A library that enables subscribing to and composing asynchronous events and * callbacks.

        - *

        The Flowable/Subscriber, Observable/Observer, Single/SingleObserver and + *

        The Observable/Observer, Single/SingleObserver and * Completable/CompletableObserver interfaces and associated operators (in - * the {@code io.reactivex.internal.operators} package) are inspired by the + * the {@code io.reactivex.observable.internal.operators} package) are inspired by the * Reactive Rx library in Microsoft .NET but designed and implemented on * the more advanced Reactive-Streams ( http://www.reactivestreams.org ) principles.

        *

        @@ -36,17 +36,15 @@ *

      • Observer == IObserver (event consumer)
      • *
      • Disposable == IDisposable (resource/cancellation management)
      • *
      • Observable == Observable (factory methods)
      • - *
      • Flowable == IAsyncEnumerable (backpressure)
      • - *
      • Subscriber == IAsyncEnumerator
      • * - * The Single and Completable reactive base types have no equivalent in Rx.NET as of 3.x. - *

        + * The Single, Maybe and Completable reactive base types have no equivalent in Rx.NET as of 3.x. *

        Services which intend on exposing data asynchronously and wish * to allow reactive processing and composition can implement the - * {@link io.reactivex.Flowable}, {@link io.reactivex.Observable}, {@link io.reactivex.Single} - * or {@link io.reactivex.Completable} class which then allow consumers to subscribe to them + * {@link io.reactivex.observable.Observable}, {@link io.reactivex.observable.Single}, + * {@link io.reactivex.observable.Maybe} or {@link io.reactivex.observable.Completable} + * class which then allow consumers to subscribe to them * and receive events.

        - *

        Usage examples can be found on the {@link io.reactivex.Flowable}/{@link io.reactivex.Observable} and {@link org.reactivestreams.Subscriber} classes.

        + *

        Usage examples can be found on the {@link io.reactivex.observable.Observable} class.

        */ package io.reactivex.observable; diff --git a/observable/src/main/java/io/reactivex/observable/subjects/BehaviorSubject.java b/observable/src/main/java/io/reactivex/observable/subjects/BehaviorSubject.java index cfec560..fcc31be 100644 --- a/observable/src/main/java/io/reactivex/observable/subjects/BehaviorSubject.java +++ b/observable/src/main/java/io/reactivex/observable/subjects/BehaviorSubject.java @@ -32,7 +32,6 @@ * *

        * Example usage: - *

        *

         {@code
         
           // observer will receive all 4 events (including "default").
        diff --git a/observable/src/main/java/io/reactivex/observable/subjects/CompletableSubject.java b/observable/src/main/java/io/reactivex/observable/subjects/CompletableSubject.java
        index 565cd83..037daa9 100644
        --- a/observable/src/main/java/io/reactivex/observable/subjects/CompletableSubject.java
        +++ b/observable/src/main/java/io/reactivex/observable/subjects/CompletableSubject.java
        @@ -28,9 +28,9 @@
          * 

        * The CompletableSubject doesn't store the Disposables coming through onSubscribe but * disposes them once the other onXXX methods were called (terminal state reached). - * @since 2.0.5 - experimental + *

        History: 2.0.5 - experimental + * @since 2.1 */ -@Experimental public final class CompletableSubject extends Completable implements CompletableObserver { final AtomicReference observers; diff --git a/observable/src/main/java/io/reactivex/observable/subjects/MaybeSubject.java b/observable/src/main/java/io/reactivex/observable/subjects/MaybeSubject.java index d4a1144..b511a7f 100644 --- a/observable/src/main/java/io/reactivex/observable/subjects/MaybeSubject.java +++ b/observable/src/main/java/io/reactivex/observable/subjects/MaybeSubject.java @@ -28,10 +28,10 @@ *

        * The MaybeSubject doesn't store the Disposables coming through onSubscribe but * disposes them once the other onXXX methods were called (terminal state reached). + *

        History: 2.0.5 - experimental * @param the value type received and emitted - * @since 2.0.5 - experimental + * @since 2.1 */ -@Experimental public final class MaybeSubject extends Maybe implements MaybeObserver { final AtomicReference[]> observers; diff --git a/observable/src/main/java/io/reactivex/observable/subjects/PublishSubject.java b/observable/src/main/java/io/reactivex/observable/subjects/PublishSubject.java index 7a0634f..d759880 100644 --- a/observable/src/main/java/io/reactivex/observable/subjects/PublishSubject.java +++ b/observable/src/main/java/io/reactivex/observable/subjects/PublishSubject.java @@ -26,7 +26,6 @@ * *

        * Example usage: - *

        *

         {@code
         
           PublishSubject subject = PublishSubject.create();
        diff --git a/observable/src/main/java/io/reactivex/observable/subjects/ReplaySubject.java b/observable/src/main/java/io/reactivex/observable/subjects/ReplaySubject.java
        index 84f5962..f79f917 100644
        --- a/observable/src/main/java/io/reactivex/observable/subjects/ReplaySubject.java
        +++ b/observable/src/main/java/io/reactivex/observable/subjects/ReplaySubject.java
        @@ -30,7 +30,6 @@
          * 
          * 

        * Example usage: - *

        *

         {@code
         
           ReplaySubject subject = new ReplaySubject<>();
        @@ -1016,6 +1015,11 @@ public T getValue() {
                         h = next;
                     }
         
        +            long limit = scheduler.now(unit) - maxAge;
        +            if (h.time < limit) {
        +                return null;
        +            }
        +
                     Object v = h.value;
                     if (v == null) {
                         return null;
        diff --git a/observable/src/main/java/io/reactivex/observable/subjects/SingleSubject.java b/observable/src/main/java/io/reactivex/observable/subjects/SingleSubject.java
        index bfe0fed..c361d19 100644
        --- a/observable/src/main/java/io/reactivex/observable/subjects/SingleSubject.java
        +++ b/observable/src/main/java/io/reactivex/observable/subjects/SingleSubject.java
        @@ -28,10 +28,10 @@
          * 

        * The SingleSubject doesn't store the Disposables coming through onSubscribe but * disposes them once the other onXXX methods were called (terminal state reached). + *

        History: 2.0.5 - experimental * @param the value type received and emitted - * @since 2.0.5 - experimental + * @since 2.1 */ -@Experimental public final class SingleSubject extends Single implements SingleObserver { final AtomicReference[]> observers; diff --git a/observable/src/main/java/io/reactivex/observable/subjects/Subject.java b/observable/src/main/java/io/reactivex/observable/subjects/Subject.java index b7b621c..be3b19a 100644 --- a/observable/src/main/java/io/reactivex/observable/subjects/Subject.java +++ b/observable/src/main/java/io/reactivex/observable/subjects/Subject.java @@ -37,7 +37,7 @@ public abstract class Subject extends Observable implements Observer { *

        The method is thread-safe. * @return true if the subject has reached a terminal state through an error event * @see #getThrowable() - * &see {@link #hasComplete()} + * @see #hasComplete() */ public abstract boolean hasThrowable(); diff --git a/observable/src/main/java/io/reactivex/observable/subjects/UnicastSubject.java b/observable/src/main/java/io/reactivex/observable/subjects/UnicastSubject.java index eccbd52..be38bee 100644 --- a/observable/src/main/java/io/reactivex/observable/subjects/UnicastSubject.java +++ b/observable/src/main/java/io/reactivex/observable/subjects/UnicastSubject.java @@ -118,15 +118,15 @@ public static UnicastSubject create(int capacityHint, Runnable onTerminat *

        The callback, if not null, is called exactly once and * non-overlapped with any active replay. * + *

        History: 2.0.8 - experimental * @param the value type * @param capacityHint the hint to size the internal unbounded buffer * @param onTerminate the callback to run when the Subject is terminated or cancelled, null not allowed * @param delayError deliver pending onNext events before onError * @return an UnicastSubject instance - * @since 2.0.8 - experimental + * @since 2.2 */ @CheckReturnValue - @Experimental public static UnicastSubject create(int capacityHint, Runnable onTerminate, boolean delayError) { return new UnicastSubject(capacityHint, onTerminate, delayError); } @@ -137,13 +137,13 @@ public static UnicastSubject create(int capacityHint, Runnable onTerminat *

        The callback, if not null, is called exactly once and * non-overlapped with any active replay. * + *

        History: 2.0.8 - experimental * @param the value type * @param delayError deliver pending onNext events before onError * @return an UnicastSubject instance - * @since 2.0.8 - experimental + * @since 2.2 */ @CheckReturnValue - @Experimental public static UnicastSubject create(boolean delayError) { return new UnicastSubject(bufferSize(), delayError); } diff --git a/observable/src/test/java/io/reactivex/observable/ObservableNullTests.java b/observable/src/test/java/io/reactivex/observable/ObservableNullTests.java index 6153875..873d8ba 100644 --- a/observable/src/test/java/io/reactivex/observable/ObservableNullTests.java +++ b/observable/src/test/java/io/reactivex/observable/ObservableNullTests.java @@ -1402,6 +1402,7 @@ public Observable call() { } @Test(expected = NullPointerException.class) + @Ignore("No longer crashes with NPE but signals it; tested elsewhere.") public void flatMapNotificationOnErrorReturnsNull() { Observable.error(new TestException()).flatMap(new Function>() { @Override diff --git a/observable/src/test/java/io/reactivex/observable/ObservableTest.java b/observable/src/test/java/io/reactivex/observable/ObservableTest.java index ae03186..94bac54 100644 --- a/observable/src/test/java/io/reactivex/observable/ObservableTest.java +++ b/observable/src/test/java/io/reactivex/observable/ObservableTest.java @@ -1142,9 +1142,9 @@ public void testEmptyIsEmpty() { // public void testForEachWithError() { // Observable.error(new Exception("boo")) // // -// .forEach(new Action1() { +// .forEach(new Consumer() { // @Override -// public void call(Object t) { +// public void accept(Object t) { // //do nothing // }}); // } diff --git a/observable/src/test/java/io/reactivex/observable/SingleTest.java b/observable/src/test/java/io/reactivex/observable/SingleTest.java index f504a21..38b615f 100644 --- a/observable/src/test/java/io/reactivex/observable/SingleTest.java +++ b/observable/src/test/java/io/reactivex/observable/SingleTest.java @@ -571,5 +571,15 @@ public void fromObservableError() { .assertFailure(RuntimeException.class) .assertErrorMessage("some error"); } + + @Test(expected = NullPointerException.class) + public void implementationThrows() { + new Single() { + @Override + protected void subscribeActual(SingleObserver observer) { + throw new NullPointerException(); + } + }.test(); + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/CompletableCreateTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/CompletableCreateTest.java index a5374bc..937ee3f 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/CompletableCreateTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/CompletableCreateTest.java @@ -16,6 +16,7 @@ import static org.junit.Assert.*; import java.io.IOException; +import java.util.List; import org.junit.Test; @@ -241,35 +242,25 @@ public void onComplete() { } @Test - public void onCompleteThrows2() { - Completable.create(new CompletableOnSubscribe() { - @Override - public void subscribe(CompletableEmitter e) throws Exception { - try { + public void tryOnError() { + List errors = TestCommonHelper.trackPluginErrors(); + try { + final Boolean[] response = { null }; + Completable.create(new CompletableOnSubscribe() { + @Override + public void subscribe(CompletableEmitter e) throws Exception { e.onComplete(); - fail("Should have thrown"); - } catch (TestException ex) { - // expected + response[0] = e.tryOnError(new TestException()); } + }) + .test() + .assertResult(); - assertTrue(e.isDisposed()); - } - }).subscribe(new CompletableObserver() { - - @Override - public void onSubscribe(Disposable d) { + assertFalse(response[0]); - } - - @Override - public void onError(Throwable e) { - - } - - @Override - public void onComplete() { - throw new TestException(); - } - }); + assertTrue(errors.toString(), errors.isEmpty()); + } finally { + RxJavaCommonPlugins.reset(); + } } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/CompletablePeekTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/CompletablePeekTest.java index 2480eff..ec686a3 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/CompletablePeekTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/CompletablePeekTest.java @@ -22,7 +22,9 @@ import io.reactivex.common.*; import io.reactivex.common.exceptions.TestException; import io.reactivex.common.functions.Action; -import io.reactivex.observable.Completable; +import io.reactivex.common.internal.functions.Functions; +import io.reactivex.observable.*; +import io.reactivex.observable.subjects.CompletableSubject; public class CompletablePeekTest { @@ -45,4 +47,9 @@ public void run() throws Exception { RxJavaCommonPlugins.reset(); } } + + @Test + public void disposed() { + TestHelper.checkDisposed(CompletableSubject.create().doOnComplete(Functions.EMPTY_ACTION)); + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/MaybeCreateTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/MaybeCreateTest.java index 1a57145..d66efd6 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/MaybeCreateTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/MaybeCreateTest.java @@ -16,6 +16,7 @@ import static org.junit.Assert.*; import java.io.IOException; +import java.util.List; import org.junit.Test; @@ -310,4 +311,27 @@ public void onComplete() { } }); } + + @Test + public void tryOnError() { + List errors = TestCommonHelper.trackPluginErrors(); + try { + final Boolean[] response = { null }; + Maybe.create(new MaybeOnSubscribe() { + @Override + public void subscribe(MaybeEmitter e) throws Exception { + e.onSuccess(1); + response[0] = e.tryOnError(new TestException()); + } + }) + .test() + .assertResult(1); + + assertFalse(response[0]); + + assertTrue(errors.toString(), errors.isEmpty()); + } finally { + RxJavaCommonPlugins.reset(); + } + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/MaybeZipArrayTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/MaybeZipArrayTest.java index 7e6c689..0fbe53c 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/MaybeZipArrayTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/MaybeZipArrayTest.java @@ -22,6 +22,7 @@ import io.reactivex.common.*; import io.reactivex.common.exceptions.TestException; import io.reactivex.common.functions.*; +import io.reactivex.common.internal.functions.Functions; import io.reactivex.observable.Maybe; import io.reactivex.observable.observers.TestObserver; import io.reactivex.observable.subjects.PublishSubject; @@ -160,4 +161,12 @@ public Object apply(Object[] v) { }, Maybe.just(1), null) .blockingGet(); } + + @SuppressWarnings("unchecked") + @Test + public void singleSourceZipperReturnsNull() { + Maybe.zipArray(Functions.justFunction(null), Maybe.just(1)) + .test() + .assertFailureAndMessage(NullPointerException.class, "The zipper returned a null value"); + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/MaybeZipIterableTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/MaybeZipIterableTest.java index 8bdcf0d..3590730 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/MaybeZipIterableTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/MaybeZipIterableTest.java @@ -22,6 +22,7 @@ import io.reactivex.common.*; import io.reactivex.common.exceptions.TestException; import io.reactivex.common.functions.Function; +import io.reactivex.common.internal.functions.Functions; import io.reactivex.common.internal.utils.CrashingMappedIterable; import io.reactivex.observable.Maybe; import io.reactivex.observable.observers.TestObserver; @@ -211,4 +212,12 @@ public Object apply(Object[] v) { }) .blockingGet(); } + + @SuppressWarnings("unchecked") + @Test + public void singleSourceZipperReturnsNull() { + Maybe.zipArray(Functions.justFunction(null), Maybe.just(1)) + .test() + .assertFailureAndMessage(NullPointerException.class, "The zipper returned a null value"); + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableBufferTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableBufferTest.java index 4a6dedf..c46dd77 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableBufferTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableBufferTest.java @@ -1397,4 +1397,45 @@ public void bufferTimedExactBoundedError() { to .assertFailure(TestException.class); } + + @Test + public void withTimeAndSizeCapacityRace() { + for (int i = 0; i < 1000; i++) { + final TestScheduler scheduler = new TestScheduler(); + + final PublishSubject ps = PublishSubject.create(); + + TestObserver> ts = ps.buffer(1, TimeUnit.SECONDS, scheduler, 5).test(); + + ps.onNext(1); + ps.onNext(2); + ps.onNext(3); + ps.onNext(4); + + Runnable r1 = new Runnable() { + @Override + public void run() { + ps.onNext(5); + } + }; + + Runnable r2 = new Runnable() { + @Override + public void run() { + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + } + }; + + TestCommonHelper.race(r1, r2); + + ps.onComplete(); + + int items = 0; + for (List o : ts.values()) { + items += o.size(); + } + + assertEquals("Round: " + i, 5, items); + } + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableConcatMapTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableConcatMapTest.java index 8c93f1b..173f344 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableConcatMapTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableConcatMapTest.java @@ -13,8 +13,7 @@ package io.reactivex.observable.internal.operators; - - +import static org.junit.Assert.*; import java.util.List; import java.util.concurrent.Callable; @@ -24,6 +23,7 @@ import io.reactivex.common.*; import io.reactivex.common.exceptions.TestException; import io.reactivex.common.functions.Function; +import io.reactivex.common.internal.functions.Functions; import io.reactivex.observable.*; import io.reactivex.observable.observers.TestObserver; import io.reactivex.observable.subjects.*; @@ -368,4 +368,66 @@ protected void subscribeActual(Observer observer) { RxJavaCommonPlugins.reset(); } } + + @SuppressWarnings("unchecked") + @Test + public void concatReportsDisposedOnComplete() { + final Disposable[] disposable = { null }; + + Observable.fromArray(Observable.just(1), Observable.just(2)) + .hide() + .concatMap(Functions.>identity()) + .subscribe(new Observer() { + + @Override + public void onSubscribe(Disposable d) { + disposable[0] = d; + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onComplete() { + } + }); + + assertTrue(disposable[0].isDisposed()); + } + + @Test + @SuppressWarnings("unchecked") + public void concatReportsDisposedOnError() { + final Disposable[] disposable = { null }; + + Observable.fromArray(Observable.just(1), Observable.error(new TestException())) + .hide() + .concatMap(Functions.>identity()) + .subscribe(new Observer() { + + @Override + public void onSubscribe(Disposable d) { + disposable[0] = d; + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onComplete() { + } + }); + + assertTrue(disposable[0].isDisposed()); + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableConcatTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableConcatTest.java index 5132a90..190fad2 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableConcatTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableConcatTest.java @@ -1041,4 +1041,119 @@ public void subscribe(ObservableEmitter s) throws Exception { assertEquals(1, calls[0]); } + + + @Test + public void concatReportsDisposedOnComplete() { + final Disposable[] disposable = { null }; + + Observable.concat(Observable.just(1), Observable.just(2)) + .subscribe(new Observer() { + + @Override + public void onSubscribe(Disposable d) { + disposable[0] = d; + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onComplete() { + } + }); + + assertTrue(disposable[0].isDisposed()); + } + + @Test + @SuppressWarnings("unchecked") + public void concatReportsDisposedOnCompleteDelayError() { + final Disposable[] disposable = { null }; + + Observable.concatArrayDelayError(Observable.just(1), Observable.just(2)) + .subscribe(new Observer() { + + @Override + public void onSubscribe(Disposable d) { + disposable[0] = d; + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onComplete() { + } + }); + + assertTrue(disposable[0].isDisposed()); + } + + @Test + public void concatReportsDisposedOnError() { + final Disposable[] disposable = { null }; + + Observable.concat(Observable.just(1), Observable.error(new TestException())) + .subscribe(new Observer() { + + @Override + public void onSubscribe(Disposable d) { + disposable[0] = d; + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onComplete() { + } + }); + + assertTrue(disposable[0].isDisposed()); + } + + @Test + @SuppressWarnings("unchecked") + public void concatReportsDisposedOnErrorDelayError() { + final Disposable[] disposable = { null }; + + Observable.concatArrayDelayError(Observable.just(1), Observable.error(new TestException())) + .subscribe(new Observer() { + + @Override + public void onSubscribe(Disposable d) { + disposable[0] = d; + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onComplete() { + } + }); + + assertTrue(disposable[0].isDisposed()); + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableCreateTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableCreateTest.java index c9a223c..1701d6b 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableCreateTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableCreateTest.java @@ -593,4 +593,53 @@ public void run() { .assertResult(); } } + + @Test + public void tryOnError() { + List errors = TestCommonHelper.trackPluginErrors(); + try { + final Boolean[] response = { null }; + Observable.create(new ObservableOnSubscribe() { + @Override + public void subscribe(ObservableEmitter e) throws Exception { + e.onNext(1); + response[0] = e.tryOnError(new TestException()); + } + }) + .take(1) + .test() + .assertResult(1); + + assertFalse(response[0]); + + assertTrue(errors.toString(), errors.isEmpty()); + } finally { + RxJavaCommonPlugins.reset(); + } + } + + @Test + public void tryOnErrorSerialized() { + List errors = TestCommonHelper.trackPluginErrors(); + try { + final Boolean[] response = { null }; + Observable.create(new ObservableOnSubscribe() { + @Override + public void subscribe(ObservableEmitter e) throws Exception { + e = e.serialize(); + e.onNext(1); + response[0] = e.tryOnError(new TestException()); + } + }) + .take(1) + .test() + .assertResult(1); + + assertFalse(response[0]); + + assertTrue(errors.toString(), errors.isEmpty()); + } finally { + RxJavaCommonPlugins.reset(); + } + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableDelayTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableDelayTest.java index 1675b9d..1735223 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableDelayTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableDelayTest.java @@ -965,4 +965,16 @@ public void onComplete() { // expected } } + + @Test + public void itemDelayReturnsNull() { + Observable.just(1).delay(new Function>() { + @Override + public Observable apply(Integer t) throws Exception { + return null; + } + }) + .test() + .assertFailureAndMessage(NullPointerException.class, "The itemDelay returned a null ObservableSource"); + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableFlatMapTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableFlatMapTest.java index 23193d9..21cf9cb 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableFlatMapTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableFlatMapTest.java @@ -264,7 +264,7 @@ public void testFlatMapTransformsOnErrorFuncThrows() { source.flatMap(just(onNext), funcThrow((Throwable) null, onError), just0(onComplete)).subscribe(o); - verify(o).onError(any(TestException.class)); + verify(o).onError(any(CompositeException.class)); verify(o, never()).onNext(any()); verify(o, never()).onComplete(); } @@ -856,4 +856,40 @@ public void run() { } } } + + @Test + public void iterableMapperFunctionReturnsNull() { + Observable.just(1) + .flatMapIterable(new Function>() { + @Override + public Iterable apply(Integer v) throws Exception { + return null; + } + }, new BiFunction() { + @Override + public Object apply(Integer v, Object w) throws Exception { + return v; + } + }) + .test() + .assertFailureAndMessage(NullPointerException.class, "The mapper returned a null Iterable"); + } + + @Test + public void combinerMapperFunctionReturnsNull() { + Observable.just(1) + .flatMap(new Function>() { + @Override + public Observable apply(Integer v) throws Exception { + return null; + } + }, new BiFunction() { + @Override + public Object apply(Integer v, Object w) throws Exception { + return v; + } + }) + .test() + .assertFailureAndMessage(NullPointerException.class, "The mapper returned a null ObservableSource"); + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableIntervalRangeTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableIntervalRangeTest.java index 679b164..1e347aa 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableIntervalRangeTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableIntervalRangeTest.java @@ -76,4 +76,12 @@ public void longOverflow() { public void dispose() { TestHelper.checkDisposed(Observable.intervalRange(1, 2, 1, 1, TimeUnit.MILLISECONDS)); } + + @Test(timeout = 2000) + public void cancel() { + Observable.intervalRange(0, 20, 1, 1, TimeUnit.MILLISECONDS, Schedulers.trampoline()) + .take(10) + .test() + .assertResult(0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L); + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableIntervalTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableIntervalTest.java index 52365eb..3bdd5bb 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableIntervalTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableIntervalTest.java @@ -19,7 +19,7 @@ import org.junit.Test; -import io.reactivex.common.TestScheduler; +import io.reactivex.common.*; import io.reactivex.observable.*; public class ObservableIntervalTest { @@ -28,4 +28,12 @@ public class ObservableIntervalTest { public void dispose() { TestHelper.checkDisposed(Observable.interval(1, TimeUnit.MILLISECONDS, new TestScheduler())); } + + @Test(timeout = 2000) + public void cancel() { + Observable.interval(1, TimeUnit.MILLISECONDS, Schedulers.trampoline()) + .take(10) + .test() + .assertResult(0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L); + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableMapNotificationTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableMapNotificationTest.java index 74524a6..92d87f2 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableMapNotificationTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableMapNotificationTest.java @@ -20,6 +20,7 @@ import org.junit.Test; import io.reactivex.common.Disposables; +import io.reactivex.common.exceptions.*; import io.reactivex.common.functions.Function; import io.reactivex.common.internal.functions.Functions; import io.reactivex.observable.*; @@ -87,4 +88,22 @@ public ObservableSource apply(Observable o) throws Exception { } }); } + + @Test + public void onErrorCrash() { + TestObserver ts = Observable.error(new TestException("Outer")) + .flatMap(Functions.justFunction(Observable.just(1)), + new Function>() { + @Override + public Observable apply(Throwable t) throws Exception { + throw new TestException("Inner"); + } + }, + Functions.justCallable(Observable.just(3))) + .test() + .assertFailure(CompositeException.class); + + TestHelper.assertError(ts, 0, TestException.class, "Outer"); + TestHelper.assertError(ts, 1, TestException.class, "Inner"); + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableMergeMaxConcurrentTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableMergeMaxConcurrentTest.java index 3d22b5f..8b976ef 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableMergeMaxConcurrentTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableMergeMaxConcurrentTest.java @@ -200,7 +200,7 @@ public void testSimpleAsyncLoop() { } } } - @Test(timeout = 10000) + @Test(timeout = 30000) public void testSimpleAsync() { for (int i = 1; i < 50; i++) { TestObserver ts = new TestObserver(); @@ -220,13 +220,13 @@ public void testSimpleAsync() { assertEquals(expected, actual); } } - @Test(timeout = 10000) + @Test(timeout = 30000) public void testSimpleOneLessAsyncLoop() { for (int i = 0; i < 200; i++) { testSimpleOneLessAsync(); } } - @Test(timeout = 10000) + @Test(timeout = 30000) public void testSimpleOneLessAsync() { long t = System.currentTimeMillis(); for (int i = 2; i < 50; i++) { diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableReplayTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableReplayTest.java index 7f27eae..e2e51a7 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableReplayTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableReplayTest.java @@ -1525,4 +1525,37 @@ public void timedNoOutdatedData() { source.test().assertResult(); } + + @Test + public void replaySelectorReturnsNullScheduled() { + Observable.just(1) + .replay(new Function, Observable>() { + @Override + public Observable apply(Observable v) throws Exception { + return null; + } + }, Schedulers.trampoline()) + .test() + .assertFailureAndMessage(NullPointerException.class, "The selector returned a null ObservableSource"); + } + + @Test + public void replaySelectorReturnsNull() { + Observable.just(1) + .replay(new Function, Observable>() { + @Override + public Observable apply(Observable v) throws Exception { + return null; + } + }) + .test() + .assertFailureAndMessage(NullPointerException.class, "The selector returned a null ObservableSource"); + } + + @Test + public void replaySelectorConnectableReturnsNull() { + ObservableReplay.multicastSelector(Functions.justCallable((ConnectableObservable)null), Functions.justFunction(Observable.just(1))) + .test() + .assertFailureAndMessage(NullPointerException.class, "The connectableFactory returned a null ConnectableObservable"); + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableUsingTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableUsingTest.java index 83ea9c4..c1c5f5a 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableUsingTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableUsingTest.java @@ -556,4 +556,14 @@ public void accept(Object e) throws Exception { RxJavaCommonPlugins.reset(); } } + + @Test + public void sourceSupplierReturnsNull() { + Observable.using(Functions.justCallable(1), + Functions.justFunction((Observable)null), + Functions.emptyConsumer()) + .test() + .assertFailureAndMessage(NullPointerException.class, "The sourceSupplier returned a null ObservableSource") + ; + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableWithLatestFromTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableWithLatestFromTest.java index 38b1946..8a92585 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableWithLatestFromTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/ObservableWithLatestFromTest.java @@ -25,6 +25,7 @@ import io.reactivex.common.*; import io.reactivex.common.exceptions.TestException; import io.reactivex.common.functions.*; +import io.reactivex.common.internal.functions.Functions; import io.reactivex.common.internal.utils.CrashingMappedIterable; import io.reactivex.observable.Observable; import io.reactivex.observable.Observer; @@ -647,4 +648,12 @@ public Object apply(Object[] o) throws Exception { .test() .assertFailure(NullPointerException.class); } + + @Test + public void zeroOtherCombinerReturnsNull() { + Observable.just(1) + .withLatestFrom(new Observable[0], Functions.justFunction(null)) + .test() + .assertFailureAndMessage(NullPointerException.class, "The combiner returned a null value"); + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/SingleCreateTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/SingleCreateTest.java index 739aa5f..7a42ad8 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/SingleCreateTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/SingleCreateTest.java @@ -16,6 +16,7 @@ import static org.junit.Assert.*; import java.io.IOException; +import java.util.List; import org.junit.Test; @@ -282,4 +283,27 @@ public void onError(Throwable e) { } }); } + + @Test + public void tryOnError() { + List errors = TestCommonHelper.trackPluginErrors(); + try { + final Boolean[] response = { null }; + Single.create(new SingleOnSubscribe() { + @Override + public void subscribe(SingleEmitter e) throws Exception { + e.onSuccess(1); + response[0] = e.tryOnError(new TestException()); + } + }) + .test() + .assertResult(1); + + assertFalse(response[0]); + + assertTrue(errors.toString(), errors.isEmpty()); + } finally { + RxJavaCommonPlugins.reset(); + } + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/SingleMapTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/SingleMapTest.java new file mode 100644 index 0000000..23bdb8a --- /dev/null +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/SingleMapTest.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2016-present, RxJava Contributors. + * + * 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 io.reactivex.observable.internal.operators; + +import org.junit.Test; + +import io.reactivex.common.functions.Function; +import io.reactivex.observable.*; + +public class SingleMapTest { + + @Test(expected = NullPointerException.class) + public void mapNull() { + Single.just(1).map(null); + } + + @Test + public void mapValue() { + Single.just(1).map(new Function() { + @Override + public Integer apply(final Integer integer) throws Exception { + if (integer == 1) { + return 2; + } + + return 1; + } + }) + .test() + .assertResult(2); + } + + @Test + public void mapValueNull() { + Single.just(1).map(new Function>() { + @Override + public SingleSource apply(final Integer integer) throws Exception { + return null; + } + }) + .test() + .assertNoValues() + .assertError(NullPointerException.class) + .assertErrorMessage("The mapper function returned a null value."); + } + + @Test + public void mapValueErrorThrown() { + Single.just(1).map(new Function>() { + @Override + public SingleSource apply(final Integer integer) throws Exception { + throw new RuntimeException("something went terribly wrong!"); + } + }) + .test() + .assertNoValues() + .assertError(RuntimeException.class) + .assertErrorMessage("something went terribly wrong!"); + } + + @Test + public void mapError() { + RuntimeException exception = new RuntimeException("test"); + + Single.error(exception).map(new Function() { + @Override + public Object apply(final Object integer) throws Exception { + return new Object(); + } + }) + .test() + .assertError(exception); + } +} \ No newline at end of file diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/SingleZipArrayTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/SingleZipArrayTest.java index 07722ba..8a0e1b4 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/SingleZipArrayTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/SingleZipArrayTest.java @@ -22,6 +22,7 @@ import io.reactivex.common.*; import io.reactivex.common.exceptions.TestException; import io.reactivex.common.functions.*; +import io.reactivex.common.internal.functions.Functions; import io.reactivex.observable.*; import io.reactivex.observable.observers.TestObserver; import io.reactivex.observable.subjects.PublishSubject; @@ -186,4 +187,12 @@ public Object apply(Object[] a) throws Exception { .test() .assertResult(2); } + + @SuppressWarnings("unchecked") + @Test + public void singleSourceZipperReturnsNull() { + Single.zipArray(Functions.justFunction(null), Single.just(1)) + .test() + .assertFailureAndMessage(NullPointerException.class, "The zipper returned a null value"); + } } diff --git a/observable/src/test/java/io/reactivex/observable/internal/operators/SingleZipIterableTest.java b/observable/src/test/java/io/reactivex/observable/internal/operators/SingleZipIterableTest.java index a2b12e2..f6e697c 100644 --- a/observable/src/test/java/io/reactivex/observable/internal/operators/SingleZipIterableTest.java +++ b/observable/src/test/java/io/reactivex/observable/internal/operators/SingleZipIterableTest.java @@ -22,6 +22,7 @@ import io.reactivex.common.*; import io.reactivex.common.exceptions.TestException; import io.reactivex.common.functions.Function; +import io.reactivex.common.internal.functions.Functions; import io.reactivex.common.internal.utils.CrashingMappedIterable; import io.reactivex.observable.*; import io.reactivex.observable.observers.TestObserver; @@ -235,4 +236,12 @@ public Object apply(Object[] a) throws Exception { .test() .assertResult(2); } + + @SuppressWarnings("unchecked") + @Test + public void singleSourceZipperReturnsNull() { + Single.zip(Arrays.asList(Single.just(1)), Functions.justFunction(null)) + .test() + .assertFailureAndMessage(NullPointerException.class, "The zipper returned a null value"); + } } diff --git a/observable/src/test/java/io/reactivex/observable/subjects/ReplaySubjectTest.java b/observable/src/test/java/io/reactivex/observable/subjects/ReplaySubjectTest.java index 4bedee3..8fe07fe 100644 --- a/observable/src/test/java/io/reactivex/observable/subjects/ReplaySubjectTest.java +++ b/observable/src/test/java/io/reactivex/observable/subjects/ReplaySubjectTest.java @@ -1182,4 +1182,24 @@ public void timedNoOutdatedData() { source.test().assertResult(); } + + @Test + public void peekStateTimeAndSizeValueExpired() { + TestScheduler scheduler = new TestScheduler(); + ReplaySubject rp = ReplaySubject.createWithTime(1, TimeUnit.DAYS, scheduler); + + assertNull(rp.getValue()); + assertNull(rp.getValues(new Integer[2])[0]); + + rp.onNext(2); + + assertEquals((Integer)2, rp.getValue()); + assertEquals(2, rp.getValues()[0]); + + scheduler.advanceTimeBy(2, TimeUnit.DAYS); + + assertEquals(null, rp.getValue()); + assertEquals(0, rp.getValues().length); + assertNull(rp.getValues(new Integer[2])[0]); + } }