diff --git a/src/main/java/io/reactivex/rxjava3/core/Observable.java b/src/main/java/io/reactivex/rxjava3/core/Observable.java index 16e1454af3..0afdd29a93 100644 --- a/src/main/java/io/reactivex/rxjava3/core/Observable.java +++ b/src/main/java/io/reactivex/rxjava3/core/Observable.java @@ -15,6 +15,7 @@ import java.util.*; import java.util.concurrent.*; +import java.util.stream.*; import org.reactivestreams.Publisher; @@ -24,6 +25,7 @@ import io.reactivex.rxjava3.functions.*; import io.reactivex.rxjava3.internal.functions.*; import io.reactivex.rxjava3.internal.fuseable.ScalarSupplier; +import io.reactivex.rxjava3.internal.jdk8.*; import io.reactivex.rxjava3.internal.observers.*; import io.reactivex.rxjava3.internal.operators.flowable.*; import io.reactivex.rxjava3.internal.operators.mixed.*; @@ -1931,6 +1933,7 @@ public static Observable fromFuture(@NonNull Future future, * resulting ObservableSource * @return an Observable that emits each item in the source {@link Iterable} sequence * @see ReactiveX operators documentation: From + * @see #fromStream(Stream) */ @CheckReturnValue @NonNull @@ -5211,16 +5214,16 @@ public final Iterable blockingIterable() { *
{@code blockingIterable} does not operate by default on a particular {@link Scheduler}.
* * - * @param bufferSize the number of items to prefetch from the current Observable + * @param capacityHint the expected number of items to be buffered * @return an {@link Iterable} version of this {@code Observable} * @see ReactiveX documentation: To */ @CheckReturnValue @SchedulerSupport(SchedulerSupport.NONE) @NonNull - public final Iterable blockingIterable(int bufferSize) { - ObjectHelper.verifyPositive(bufferSize, "bufferSize"); - return new BlockingObservableIterable<>(this, bufferSize); + public final Iterable blockingIterable(int capacityHint) { + ObjectHelper.verifyPositive(capacityHint, "bufferSize"); + return new BlockingObservableIterable<>(this, capacityHint); } /** @@ -15910,4 +15913,502 @@ public final TestObserver test(boolean dispose) { // NoPMD subscribe(to); return to; } + + // ------------------------------------------------------------------------- + // JDK 8 Support + // ------------------------------------------------------------------------- + + /** + * Converts the existing value of the provided optional into a {@link #just(Object)} + * or an empty optional into an {@link #empty()} {@code Observable} instance. + *

+ * + *

+ * Note that the operator takes an already instantiated optional reference and does not + * by any means create this original optional. If the optional is to be created per + * consumer upon subscription, use {@link #defer(Supplier)} around {@code fromOptional}: + *


+     * Observable.defer(() -> Observable.fromOptional(createOptional()));
+     * 
+ *
+ *
Scheduler:
+ *
{@code fromOptional} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the element type of the optional value + * @param optional the optional value to convert into an {@code Observable} + * @return the new Observable instance + * @see #just(Object) + * @see #empty() + * @since 3.0.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public static Observable<@NonNull T> fromOptional(@NonNull Optional optional) { + Objects.requireNonNull(optional, "optional is null"); + return optional.map(Observable::just).orElseGet(Observable::empty); + } + + /** + * Signals the completion value or error of the given (hot) {@link CompletionStage}-based asynchronous calculation. + *

+ * + *

+ * Note that the operator takes an already instantiated, running or terminated {@code CompletionStage}. + * If the optional is to be created per consumer upon subscription, use {@link #defer(Supplier)} + * around {@code fromCompletionStage}: + *


+     * Observable.defer(() -> Observable.fromCompletionStage(createCompletionStage()));
+     * 
+ *

+ * If the {@code CompletionStage} completes with {@code null}, a {@link NullPointerException} is signaled. + *

+ * Canceling the flow can't cancel the execution of the {@code CompletionStage} because {@code CompletionStage} + * itself doesn't support cancellation. Instead, the operator detaches from the {@code CompletionStage}. + *

+ *
Scheduler:
+ *
{@code fromCompletionStage} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the element type of the CompletionStage + * @param stage the CompletionStage to convert to Observable and signal its terminal value or error + * @return the new Observable instance + * @since 3.0.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public static Observable<@NonNull T> fromCompletionStage(@NonNull CompletionStage stage) { + Objects.requireNonNull(stage, "stage is null"); + return RxJavaPlugins.onAssembly(new ObservableFromCompletionStage<>(stage)); + } + + /** + * Converts a {@link Stream} into a finite {@code Observable} and emits its items in the sequence. + *

+ * + *

+ * The operator closes the {@code Stream} upon cancellation and when it terminates. Exceptions raised when + * closing a {@code Stream} are routed to the global error handler ({@link RxJavaPlugins#onError(Throwable)}. + * If a {@code Stream} should not be closed, turn it into an {@link Iterable} and use {@link #fromIterable(Iterable)}: + *


+     * Stream<T> stream = ...
+     * Observable.fromIterable(stream::iterator);
+     * 
+ *

+ * Note that {@code Stream}s can be consumed only once; any subsequent attempt to consume a {@code Stream} + * will result in an {@link IllegalStateException}. + *

+ * Primitive streams are not supported and items have to be boxed manually (e.g., via {@link IntStream#boxed()}): + *


+     * IntStream intStream = IntStream.rangeClosed(1, 10);
+     * Observable.fromStream(intStream.boxed());
+     * 
+ *

+ * {@code Stream} does not support concurrent usage so creating and/or consuming the same instance multiple times + * from multiple threads can lead to undefined behavior. + *

+ *
Scheduler:
+ *
{@code fromStream} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the element type of the source {@code Stream} + * @param stream the {@code Stream} of values to emit + * @return the new Observable instance + * @since 3.0.0 + * @see #fromIterable(Iterable) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public static Observable<@NonNull T> fromStream(@NonNull Stream stream) { + Objects.requireNonNull(stream, "stream is null"); + return RxJavaPlugins.onAssembly(new ObservableFromStream<>(stream)); + } + + /** + * Maps each upstream value into an {@link Optional} and emits the contained item if not empty. + *

+ * + * + *

+ *
Scheduler:
+ *
{@code mapOptional} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the non-null output type + * @param mapper the function that receives the upstream item and should return a non-empty {@code Optional} + * to emit as the output or an empty {@code Optional} to skip to the next upstream value + * @return the new Observable instance + * @since 3.0.0 + * @see #map(Function) + * @see #filter(Predicate) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public final <@NonNull R> Observable mapOptional(@NonNull Function> mapper) { + Objects.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableMapOptional<>(this, mapper)); + } + + /** + * Collects the finite upstream's values into a container via a Stream {@link Collector} callback set and emits + * it as the success result. + *

+ * + * + *

+ *
Scheduler:
+ *
{@code collect} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param the non-null result type + * @param the intermediate container type used for the accumulation + * @param collector the interface defining the container supplier, accumulator and finisher functions; + * see {@link Collectors} for some standard implementations + * @return the new Single instance + * @since 3.0.0 + * @see Collectors + * @see #collect(Supplier, BiConsumer) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public final <@NonNull R, A> Single collect(@NonNull Collector collector) { + Objects.requireNonNull(collector, "collector is null"); + return RxJavaPlugins.onAssembly(new ObservableCollectWithCollectorSingle<>(this, collector)); + } + + /** + * Signals the first upstream item (or the default item if the upstream is empty) via + * a {@link CompletionStage}. + *

+ * + *

+ * The upstream can be canceled by converting the resulting {@code CompletionStage} into + * {@link CompletableFuture} via {@link CompletionStage#toCompletableFuture()} and + * calling {@link CompletableFuture#cancel(boolean)} on it. + * The upstream will be also cancelled if the resulting {@code CompletionStage} is converted to and + * completed manually by {@link CompletableFuture#complete(Object)} or {@link CompletableFuture#completeExceptionally(Throwable)}. + *

+ * {@code CompletionStage}s don't have a notion of emptyness and allow {@code null}s, therefore, one can either use + * a {@code defaultItem} of {@code null} or turn the flow into a sequence of {@link Optional}s and default to {@link Optional#empty()}: + *


+     * CompletionStage<Optional<T>> stage = source.map(Optional::of).firstStage(Optional.empty());
+     * 
+ *
+ *
Scheduler:
+ *
{@code firstStage} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param defaultItem the item to signal if the upstream is empty + * @return the new CompletionStage instance + * @since 3.0.0 + * @see #firstOrErrorStage() + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public final CompletionStage firstStage(@Nullable T defaultItem) { + return subscribeWith(new ObservableFirstStageObserver<>(true, defaultItem)); + } + + /** + * Signals the only expected upstream item (or the default item if the upstream is empty) + * or signals {@link IllegalArgumentException} if the upstream has more than one item + * via a {@link CompletionStage}. + *

+ * + *

+ * The upstream can be canceled by converting the resulting {@code CompletionStage} into + * {@link CompletableFuture} via {@link CompletionStage#toCompletableFuture()} and + * calling {@link CompletableFuture#cancel(boolean)} on it. + * The upstream will be also cancelled if the resulting {@code CompletionStage} is converted to and + * completed manually by {@link CompletableFuture#complete(Object)} or {@link CompletableFuture#completeExceptionally(Throwable)}. + *

+ * {@code CompletionStage}s don't have a notion of emptyness and allow {@code null}s, therefore, one can either use + * a {@code defaultItem} of {@code null} or turn the flow into a sequence of {@link Optional}s and default to {@link Optional#empty()}: + *


+     * CompletionStage<Optional<T>> stage = source.map(Optional::of).singleStage(Optional.empty());
+     * 
+ *
+ *
Scheduler:
+ *
{@code singleStage} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param defaultItem the item to signal if the upstream is empty + * @return the new CompletionStage instance + * @since 3.0.0 + * @see #singleOrErrorStage() + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public final CompletionStage singleStage(@Nullable T defaultItem) { + return subscribeWith(new ObservableSingleStageObserver<>(true, defaultItem)); + } + + /** + * Signals the last upstream item (or the default item if the upstream is empty) via + * a {@link CompletionStage}. + *

+ * + *

+ * The upstream can be canceled by converting the resulting {@code CompletionStage} into + * {@link CompletableFuture} via {@link CompletionStage#toCompletableFuture()} and + * calling {@link CompletableFuture#cancel(boolean)} on it. + * The upstream will be also cancelled if the resulting {@code CompletionStage} is converted to and + * completed manually by {@link CompletableFuture#complete(Object)} or {@link CompletableFuture#completeExceptionally(Throwable)}. + *

+ * {@code CompletionStage}s don't have a notion of emptyness and allow {@code null}s, therefore, one can either use + * a {@code defaultItem} of {@code null} or turn the flow into a sequence of {@link Optional}s and default to {@link Optional#empty()}: + *


+     * CompletionStage<Optional<T>> stage = source.map(Optional::of).lastStage(Optional.empty());
+     * 
+ *
+ *
Scheduler:
+ *
{@code lastStage} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @param defaultItem the item to signal if the upstream is empty + * @return the new CompletionStage instance + * @since 3.0.0 + * @see #lastOrErrorStage() + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public final CompletionStage lastStage(@Nullable T defaultItem) { + return subscribeWith(new ObservableLastStageObserver<>(true, defaultItem)); + } + + /** + * Signals the first upstream item or a {@link NoSuchElementException} if the upstream is empty via + * a {@link CompletionStage}. + *

+ * + *

+ * The upstream can be canceled by converting the resulting {@code CompletionStage} into + * {@link CompletableFuture} via {@link CompletionStage#toCompletableFuture()} and + * calling {@link CompletableFuture#cancel(boolean)} on it. + * The upstream will be also cancelled if the resulting {@code CompletionStage} is converted to and + * completed manually by {@link CompletableFuture#complete(Object)} or {@link CompletableFuture#completeExceptionally(Throwable)}. + *

+ *
Scheduler:
+ *
{@code firstOrErrorStage} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new CompletionStage instance + * @since 3.0.0 + * @see #firstStage(Object) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public final CompletionStage firstOrErrorStage() { + return subscribeWith(new ObservableFirstStageObserver<>(false, null)); + } + + /** + * Signals the only expected upstream item, a {@link NoSuchElementException} if the upstream is empty + * or signals {@link IllegalArgumentException} if the upstream has more than one item + * via a {@link CompletionStage}. + *

+ * + *

+ * The upstream can be canceled by converting the resulting {@code CompletionStage} into + * {@link CompletableFuture} via {@link CompletionStage#toCompletableFuture()} and + * calling {@link CompletableFuture#cancel(boolean)} on it. + * The upstream will be also cancelled if the resulting {@code CompletionStage} is converted to and + * completed manually by {@link CompletableFuture#complete(Object)} or {@link CompletableFuture#completeExceptionally(Throwable)}. + *

+ *
Scheduler:
+ *
{@code singleOrErrorStage} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new CompletionStage instance + * @since 3.0.0 + * @see #singleStage(Object) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public final CompletionStage singleOrErrorStage() { + return subscribeWith(new ObservableSingleStageObserver<>(false, null)); + } + + /** + * Signals the last upstream item or a {@link NoSuchElementException} if the upstream is empty via + * a {@link CompletionStage}. + *

+ * + *

+ * The upstream can be canceled by converting the resulting {@code CompletionStage} into + * {@link CompletableFuture} via {@link CompletionStage#toCompletableFuture()} and + * calling {@link CompletableFuture#cancel(boolean)} on it. + * The upstream will be also cancelled if the resulting {@code CompletionStage} is converted to and + * completed manually by {@link CompletableFuture#complete(Object)} or {@link CompletableFuture#completeExceptionally(Throwable)}. + *

+ *
Scheduler:
+ *
{@code lastOrErrorStage} does not operate by default on a particular {@link Scheduler}.
+ *
+ * @return the new CompletionStage instance + * @since 3.0.0 + * @see #lastStage(Object) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public final CompletionStage lastOrErrorStage() { + return subscribeWith(new ObservableLastStageObserver<>(false, null)); + } + + /** + * Creates a sequential {@link Stream} to consume or process this {@code Observable} in a blocking manner via + * the Java {@code Stream} API. + *

+ * + *

+ * Cancellation of the upstream is done via {@link Stream#close()}, therefore, it is strongly recommended the + * consumption is performed within a try-with-resources construct: + *


+     * Observable<Integer> source = Observable.range(1, 10)
+     *        .subscribeOn(Schedulers.computation());
+     *
+     * try (Stream<Integer> stream = source.blockingStream()) {
+     *     stream.limit(3).forEach(System.out::println);
+     * }
+     * 
+ *
+ *
Scheduler:
+ *
{@code blockingStream} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @return the new Stream instance + * @since 3.0.0 + * @see #blockingStream(int) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public final Stream blockingStream() { + return blockingStream(bufferSize()); + } + + /** + * Creates a sequential {@link Stream} to consume or process this {@code Observable} in a blocking manner via + * the Java {@code Stream} API. + *

+ * + *

+ * Cancellation of the upstream is done via {@link Stream#close()}, therefore, it is strongly recommended the + * consumption is performed within a try-with-resources construct: + *


+     * Observable<Integer> source = Observable.range(1, 10)
+     *        .subscribeOn(Schedulers.computation());
+     *
+     * try (Stream<Integer> stream = source.blockingStream(4)) {
+     *     stream.limit(3).forEach(System.out::println);
+     * }
+     * 
+ *
Scheduler:
+ *
{@code blockingStream} does not operate by default on a particular {@link Scheduler}.
+ * + * + * @param capacityHint the expected number of items to be buffered + * @return the new Stream instance + * @since 3.0.0 + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public final Stream blockingStream(int capacityHint) { + Iterator iterator = blockingIterable(capacityHint).iterator(); + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false) + .onClose(() -> ((Disposable)iterator).dispose()); + } + + /** + * Maps each upstream item into a {@link Stream} and emits the {@code Stream}'s items to the downstream in a sequential fashion. + *

+ * + *

+ * Due to the blocking and sequential nature of Java {@link Stream}s, the streams are mapped and consumed in a sequential fashion + * without interleaving (unlike a more general {@link #flatMap(Function)}). Therefore, {@code flatMapStream} and + * {@code concatMapStream} are identical operators and are provided as aliases. + *

+ * The operator closes the {@code Stream} upon cancellation and when it terminates. Exceptions raised when + * closing a {@code Stream} are routed to the global error handler ({@link RxJavaPlugins#onError(Throwable)}. + * If a {@code Stream} should not be closed, turn it into an {@link Iterable} and use {@link #concatMapIterable(Function)}: + *


+     * source.concatMapIterable(v -> createStream(v)::iterator);
+     * 
+ *

+ * Note that {@code Stream}s can be consumed only once; any subsequent attempt to consume a {@code Stream} + * will result in an {@link IllegalStateException}. + *

+ * Primitive streams are not supported and items have to be boxed manually (e.g., via {@link IntStream#boxed()}): + *


+     * source.concatMapStream(v -> IntStream.rangeClosed(v + 1, v + 10).boxed());
+     * 
+ *

+ * {@code Stream} does not support concurrent usage so creating and/or consuming the same instance multiple times + * from multiple threads can lead to undefined behavior. + *

+ *
Scheduler:
+ *
{@code concatMapStream} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the {@code Stream}s and the result + * @param mapper the function that receives an upstream item and should return a {@code Stream} whose elements + * will be emitted to the downstream + * @return the new Observable instance + * @see #concatMap(Function) + * @see #concatMapIterable(Function) + * @see #flatMapStream(Function) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public final <@NonNull R> Observable concatMapStream(@NonNull Function> mapper) { + return flatMapStream(mapper); + } + + /** + * Maps each upstream item into a {@link Stream} and emits the {@code Stream}'s items to the downstream in a sequential fashion. + *

+ * + *

+ * Due to the blocking and sequential nature of Java {@link Stream}s, the streams are mapped and consumed in a sequential fashion + * without interleaving (unlike a more general {@link #flatMap(Function)}). Therefore, {@code flatMapStream} and + * {@code concatMapStream} are identical operators and are provided as aliases. + *

+ * The operator closes the {@code Stream} upon cancellation and when it terminates. Exceptions raised when + * closing a {@code Stream} are routed to the global error handler ({@link RxJavaPlugins#onError(Throwable)}. + * If a {@code Stream} should not be closed, turn it into an {@link Iterable} and use {@link #flatMapIterable(Function)}: + *


+     * source.flatMapIterable(v -> createStream(v)::iterator);
+     * 
+ *

+ * Note that {@code Stream}s can be consumed only once; any subsequent attempt to consume a {@code Stream} + * will result in an {@link IllegalStateException}. + *

+ * Primitive streams are not supported and items have to be boxed manually (e.g., via {@link IntStream#boxed()}): + *


+     * source.flatMapStream(v -> IntStream.rangeClosed(v + 1, v + 10).boxed());
+     * 
+ *

+ * {@code Stream} does not support concurrent usage so creating and/or consuming the same instance multiple times + * from multiple threads can lead to undefined behavior. + *

+ *
Scheduler:
+ *
{@code flatMapStream} does not operate by default on a particular {@link Scheduler}.
+ *
+ * + * @param the element type of the {@code Stream}s and the result + * @param mapper the function that receives an upstream item and should return a {@code Stream} whose elements + * will be emitted to the downstream + * @return the new Observable instance + * @see #flatMap(Function) + * @see #flatMapIterable(Function) + */ + @CheckReturnValue + @SchedulerSupport(SchedulerSupport.NONE) + @NonNull + public final <@NonNull R> Observable flatMapStream(@NonNull Function> mapper) { + Objects.requireNonNull(mapper, "mapper is null"); + return RxJavaPlugins.onAssembly(new ObservableFlatMapStream<>(this, mapper)); + } } diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStream.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStream.java index ecc18fb154..55ec854ddf 100644 --- a/src/main/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStream.java +++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStream.java @@ -69,7 +69,7 @@ public static void subscribeStream(Subscriber s, Stream stream } if (s instanceof ConditionalSubscriber) { - s.onSubscribe(new StreamConditionalSubscription((ConditionalSubscriber)s, iterator, stream)); + s.onSubscribe(new StreamConditionalSubscription<>((ConditionalSubscriber)s, iterator, stream)); } else { s.onSubscribe(new StreamSubscription<>(s, iterator, stream)); } @@ -147,15 +147,23 @@ public T poll() { once = true; } else { if (!iterator.hasNext()) { + clear(); return null; } } - return Objects.requireNonNull(iterator.next(), "Iterator.next() returned a null value"); + return Objects.requireNonNull(iterator.next(), "The Stream's Iterator.next() returned a null value"); } @Override public boolean isEmpty() { - return iterator == null || !iterator.hasNext(); + Iterator it = iterator; + if (it != null) { + if (!once || it.hasNext()) { + return false; + } + clear(); + } + return true; } @Override diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollector.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollector.java new file mode 100644 index 0000000000..34132a01b3 --- /dev/null +++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollector.java @@ -0,0 +1,151 @@ +/** + * 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.rxjava3.internal.jdk8; + +import java.util.Objects; +import java.util.function.*; +import java.util.stream.Collector; + +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.core.*; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.exceptions.Exceptions; +import io.reactivex.rxjava3.internal.disposables.*; +import io.reactivex.rxjava3.internal.observers.DeferredScalarDisposable; +import io.reactivex.rxjava3.plugins.RxJavaPlugins; + +/** + * Collect items into a container defined by a Stream {@link Collector} callback set. + * + * @param the upstream value type + * @param
the intermediate accumulator type + * @param the result type + * @since 3.0.0 + */ +public final class ObservableCollectWithCollector extends Observable { + + final Observable source; + + final Collector collector; + + public ObservableCollectWithCollector(Observable source, Collector collector) { + this.source = source; + this.collector = collector; + } + + @Override + protected void subscribeActual(@NonNull Observer observer) { + A container; + BiConsumer accumulator; + Function finisher; + + try { + container = collector.supplier().get(); + accumulator = collector.accumulator(); + finisher = collector.finisher(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + source.subscribe(new CollectorObserver<>(observer, container, accumulator, finisher)); + } + + static final class CollectorObserver + extends DeferredScalarDisposable + implements Observer { + + private static final long serialVersionUID = -229544830565448758L; + + final BiConsumer accumulator; + + final Function finisher; + + Disposable upstream; + + boolean done; + + A container; + + CollectorObserver(Observer downstream, A container, BiConsumer accumulator, Function finisher) { + super(downstream); + this.container = container; + this.accumulator = accumulator; + this.finisher = finisher; + } + + @Override + public void onSubscribe(@NonNull Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + try { + accumulator.accept(container, t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + } else { + done = true; + upstream = DisposableHelper.DISPOSED; + this.container = null; + downstream.onError(t); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + + done = true; + upstream = DisposableHelper.DISPOSED; + A container = this.container; + this.container = null; + R result; + try { + result = Objects.requireNonNull(finisher.apply(container), "The finisher returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + complete(result); + } + + @Override + public void dispose() { + super.dispose(); + upstream.dispose(); + } + } +} diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollectorSingle.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollectorSingle.java new file mode 100644 index 0000000000..a919b02ab9 --- /dev/null +++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollectorSingle.java @@ -0,0 +1,159 @@ +/** + * 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.rxjava3.internal.jdk8; + +import java.util.Objects; +import java.util.function.*; +import java.util.stream.Collector; + +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.core.*; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.exceptions.Exceptions; +import io.reactivex.rxjava3.internal.disposables.*; +import io.reactivex.rxjava3.internal.fuseable.FuseToObservable; +import io.reactivex.rxjava3.plugins.RxJavaPlugins; + +/** + * Collect items into a container defined by a Stream {@link Collector} callback set. + * + * @param the upstream value type + * @param the intermediate accumulator type + * @param the result type + * @since 3.0.0 + */ +public final class ObservableCollectWithCollectorSingle extends Single implements FuseToObservable { + + final Observable source; + + final Collector collector; + + public ObservableCollectWithCollectorSingle(Observable source, Collector collector) { + this.source = source; + this.collector = collector; + } + + @Override + public Observable fuseToObservable() { + return new ObservableCollectWithCollector<>(source, collector); + } + + @Override + protected void subscribeActual(@NonNull SingleObserver observer) { + A container; + BiConsumer accumulator; + Function finisher; + + try { + container = collector.supplier().get(); + accumulator = collector.accumulator(); + finisher = collector.finisher(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + return; + } + + source.subscribe(new CollectorSingleObserver<>(observer, container, accumulator, finisher)); + } + + static final class CollectorSingleObserver implements Observer, Disposable { + + final SingleObserver downstream; + + final BiConsumer accumulator; + + final Function finisher; + + Disposable upstream; + + boolean done; + + A container; + + CollectorSingleObserver(SingleObserver downstream, A container, BiConsumer accumulator, Function finisher) { + this.downstream = downstream; + this.container = container; + this.accumulator = accumulator; + this.finisher = finisher; + } + + @Override + public void onSubscribe(@NonNull Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + try { + accumulator.accept(container, t); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + } + } + + @Override + public void onError(Throwable t) { + if (done) { + RxJavaPlugins.onError(t); + } else { + done = true; + upstream = DisposableHelper.DISPOSED; + this.container = null; + downstream.onError(t); + } + } + + @Override + public void onComplete() { + if (done) { + return; + } + + done = true; + upstream = DisposableHelper.DISPOSED; + A container = this.container; + this.container = null; + R result; + try { + result = Objects.requireNonNull(finisher.apply(container), "The finisher returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + return; + } + + downstream.onSuccess(result); + } + + @Override + public void dispose() { + upstream.dispose(); + upstream = DisposableHelper.DISPOSED; + } + + @Override + public boolean isDisposed() { + return upstream == DisposableHelper.DISPOSED; + } + } +} diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFirstStageObserver.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFirstStageObserver.java new file mode 100644 index 0000000000..cda0073067 --- /dev/null +++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFirstStageObserver.java @@ -0,0 +1,51 @@ +/** + * 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.rxjava3.internal.jdk8; + +import java.util.NoSuchElementException; + +/** + * Signals the first element of the source via the underlying CompletableFuture, + * signals the a default item if the upstream is empty or signals {@link NoSuchElementException}. + * + * @param the element type + * @since 3.0.0 + */ +public final class ObservableFirstStageObserver extends ObservableStageObserver { + + final boolean hasDefault; + + final T defaultItem; + + public ObservableFirstStageObserver(boolean hasDefault, T defaultItem) { + this.hasDefault = hasDefault; + this.defaultItem = defaultItem; + } + + @Override + public void onNext(T t) { + complete(t); + } + + @Override + public void onComplete() { + if (!isDone()) { + clear(); + if (hasDefault) { + complete(defaultItem); + } else { + completeExceptionally(new NoSuchElementException()); + } + } + } +} diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFlatMapStream.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFlatMapStream.java new file mode 100644 index 0000000000..9b7b446e28 --- /dev/null +++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFlatMapStream.java @@ -0,0 +1,162 @@ +/** + * 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.rxjava3.internal.jdk8; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; + +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.core.Observer; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.exceptions.Exceptions; +import io.reactivex.rxjava3.functions.*; +import io.reactivex.rxjava3.internal.disposables.*; +import io.reactivex.rxjava3.plugins.RxJavaPlugins; + +/** + * Maps the upstream values onto {@link Stream}s and emits their items in order to the downstream. + * + * @param the upstream element type + * @param the inner {@code Stream} and result element type + * @since 3.0.0 + */ +public final class ObservableFlatMapStream extends Observable { + + final Observable source; + + final Function> mapper; + + public ObservableFlatMapStream(Observable source, Function> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Observer observer) { + if (source instanceof Supplier) { + Stream stream = null; + try { + @SuppressWarnings("unchecked") + T t = ((Supplier)source).get(); + if (t != null) { + stream = Objects.requireNonNull(mapper.apply(t), "The mapper returned a null Stream"); + } + } catch (Throwable ex) { + EmptyDisposable.error(ex, observer); + return; + } + + if (stream != null) { + ObservableFromStream.subscribeStream(observer, stream); + } else { + EmptyDisposable.complete(observer); + } + } else { + source.subscribe(new FlatMapStreamObserver<>(observer, mapper)); + } + } + + static final class FlatMapStreamObserver extends AtomicInteger + implements Observer, Disposable { + + private static final long serialVersionUID = -5127032662980523968L; + + final Observer downstream; + + final Function> mapper; + + Disposable upstream; + + volatile boolean disposed; + + boolean done; + + FlatMapStreamObserver(Observer downstream, Function> mapper) { + this.downstream = downstream; + this.mapper = mapper; + } + + @Override + public void onSubscribe(@NonNull Disposable d) { + if (DisposableHelper.validate(this.upstream, d)) { + this.upstream = d; + + downstream.onSubscribe(this); + } + } + + @Override + public void onNext(@NonNull T t) { + if (done) { + return; + } + try { + try (Stream stream = Objects.requireNonNull(mapper.apply(t), "The mapper returned a null Stream")) { + Iterator it = stream.iterator(); + while (it.hasNext()) { + if (disposed) { + done = true; + break; + } + R value = Objects.requireNonNull(it.next(), "The Stream's Iterator.next retuned a null value"); + if (disposed) { + done = true; + break; + } + downstream.onNext(value); + if (disposed) { + done = true; + break; + } + } + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + upstream.dispose(); + onError(ex); + } + } + + @Override + public void onError(@NonNull Throwable e) { + if (done) { + RxJavaPlugins.onError(e); + } else { + done = true; + downstream.onError(e); + } + } + + @Override + public void onComplete() { + if (!done) { + done = true; + downstream.onComplete(); + } + } + + @Override + public void dispose() { + disposed = true; + upstream.dispose(); + } + + @Override + public boolean isDisposed() { + return disposed; + } + } +} diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromCompletionStage.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromCompletionStage.java new file mode 100644 index 0000000000..262da56026 --- /dev/null +++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromCompletionStage.java @@ -0,0 +1,92 @@ +/** + * 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.rxjava3.internal.jdk8; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; + +import io.reactivex.rxjava3.core.*; +import io.reactivex.rxjava3.internal.observers.DeferredScalarDisposable; + +/** + * Wrap a CompletionStage and signal its outcome. + * @param the element type + * @since 3.0.0 + */ +public final class ObservableFromCompletionStage extends Observable { + + final CompletionStage stage; + + public ObservableFromCompletionStage(CompletionStage stage) { + this.stage = stage; + } + + @Override + protected void subscribeActual(Observer observer) { + // We need an indirection because one can't detach from a whenComplete + // and cancellation should not hold onto the stage. + BiConsumerAtomicReference whenReference = new BiConsumerAtomicReference<>(); + CompletionStageHandler handler = new CompletionStageHandler<>(observer, whenReference); + whenReference.lazySet(handler); + + observer.onSubscribe(handler); + stage.whenComplete(whenReference); + } + + static final class CompletionStageHandler + extends DeferredScalarDisposable + implements BiConsumer { + + private static final long serialVersionUID = 4665335664328839859L; + + final BiConsumerAtomicReference whenReference; + + CompletionStageHandler(Observer downstream, BiConsumerAtomicReference whenReference) { + super(downstream); + this.whenReference = whenReference; + } + + @Override + public void accept(T item, Throwable error) { + if (error != null) { + downstream.onError(error); + } + else if (item != null) { + complete(item); + } else { + downstream.onError(new NullPointerException("The CompletionStage terminated with null.")); + } + } + + @Override + public void dispose() { + super.dispose(); + whenReference.set(null); + } + } + + static final class BiConsumerAtomicReference extends AtomicReference> + implements BiConsumer { + + private static final long serialVersionUID = 45838553147237545L; + + @Override + public void accept(T t, Throwable u) { + BiConsumer biConsumer = get(); + if (biConsumer != null) { + biConsumer.accept(t, u); + } + } + } +} diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromStream.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromStream.java new file mode 100644 index 0000000000..90d7681303 --- /dev/null +++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromStream.java @@ -0,0 +1,219 @@ +/** + * 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.rxjava3.internal.jdk8; + +import java.util.*; +import java.util.stream.Stream; + +import io.reactivex.rxjava3.annotations.*; +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.core.Observer; +import io.reactivex.rxjava3.exceptions.Exceptions; +import io.reactivex.rxjava3.internal.disposables.EmptyDisposable; +import io.reactivex.rxjava3.internal.fuseable.QueueDisposable; +import io.reactivex.rxjava3.plugins.RxJavaPlugins; + +/** + * Wraps a {@link Stream} and emits its values as an {@link Observable} sequence. + * @param the element type of the Stream + * @since 3.0.0 + */ +public final class ObservableFromStream extends Observable { + + final Stream stream; + + public ObservableFromStream(Stream stream) { + this.stream = stream; + } + + @Override + protected void subscribeActual(Observer observer) { + subscribeStream(observer, stream); + } + + /** + * Subscribes to the Stream. + * @param the element type of the flow + * @param observer the observer to drive + * @param stream the sequence to consume + */ + public static void subscribeStream(Observer observer, Stream stream) { + Iterator iterator; + try { + iterator = stream.iterator(); + + if (!iterator.hasNext()) { + EmptyDisposable.complete(observer); + closeSafely(stream); + return; + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + EmptyDisposable.error(ex, observer); + closeSafely(stream); + return; + } + + StreamDisposable disposable = new StreamDisposable<>(observer, iterator, stream); + observer.onSubscribe(disposable); + disposable.run(); + } + + static void closeSafely(AutoCloseable c) { + try { + c.close(); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + RxJavaPlugins.onError(ex); + } + } + + static final class StreamDisposable implements QueueDisposable { + + final Observer downstream; + + Iterator iterator; + + AutoCloseable closeable; + + volatile boolean disposed; + + boolean once; + + boolean outputFused; + + StreamDisposable(Observer downstream, Iterator iterator, AutoCloseable closeable) { + this.downstream = downstream; + this.iterator = iterator; + this.closeable = closeable; + } + + @Override + public void dispose() { + disposed = true; + run(); + } + + @Override + public boolean isDisposed() { + return disposed; + } + + @Override + public int requestFusion(int mode) { + if ((mode & SYNC) != 0) { + outputFused = true; + return SYNC; + } + return NONE; + } + + @Override + public boolean offer(@NonNull T value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean offer(@NonNull T v1, @NonNull T v2) { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + public T poll() { + if (iterator == null) { + return null; + } + if (!once) { + once = true; + } else { + if (!iterator.hasNext()) { + clear(); + return null; + } + } + return Objects.requireNonNull(iterator.next(), "The Stream's Iterator.next() returned a null value"); + } + + @Override + public boolean isEmpty() { + Iterator it = iterator; + if (it != null) { + if (!once || it.hasNext()) { + return false; + } + clear(); + } + return true; + } + + @Override + public void clear() { + iterator = null; + AutoCloseable c = closeable; + closeable = null; + if (c != null) { + closeSafely(c); + } + } + + public void run() { + if (outputFused) { + return; + } + Iterator iterator = this.iterator; + Observer downstream = this.downstream; + + for (;;) { + if (disposed) { + clear(); + break; + } + + T next; + try { + next = Objects.requireNonNull(iterator.next(), "The Stream's Iterator.next returned a null value"); + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + disposed = true; + continue; + } + + if (disposed) { + continue; + } + + downstream.onNext(next); + + if (disposed) { + continue; + } + + try { + if (iterator.hasNext()) { + continue; + } + } catch (Throwable ex) { + Exceptions.throwIfFatal(ex); + downstream.onError(ex); + disposed = true; + continue; + } + + downstream.onComplete(); + disposed = true; + } + } + } +} diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableLastStageObserver.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableLastStageObserver.java new file mode 100644 index 0000000000..e2f9dc2225 --- /dev/null +++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableLastStageObserver.java @@ -0,0 +1,54 @@ +/** + * 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.rxjava3.internal.jdk8; + +import java.util.NoSuchElementException; + +/** + * Signals the last element of the source via the underlying CompletableFuture, + * signals the a default item if the upstream is empty or signals {@link NoSuchElementException}. + * + * @param the element type + * @since 3.0.0 + */ +public final class ObservableLastStageObserver extends ObservableStageObserver { + + final boolean hasDefault; + + final T defaultItem; + + public ObservableLastStageObserver(boolean hasDefault, T defaultItem) { + this.hasDefault = hasDefault; + this.defaultItem = defaultItem; + } + + @Override + public void onNext(T t) { + value = t; + } + + @Override + public void onComplete() { + if (!isDone()) { + T v = value; + clear(); + if (v != null) { + complete(v); + } else if (hasDefault) { + complete(defaultItem); + } else { + completeExceptionally(new NoSuchElementException()); + } + } + } +} diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableMapOptional.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableMapOptional.java new file mode 100644 index 0000000000..eb134482be --- /dev/null +++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableMapOptional.java @@ -0,0 +1,96 @@ +/** + * 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.rxjava3.internal.jdk8; + +import java.util.*; + +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.core.Observer; +import io.reactivex.rxjava3.functions.Function; +import io.reactivex.rxjava3.internal.observers.BasicFuseableObserver; + +/** + * Map the upstream values into an Optional and emit its value if any. + * @param the upstream element type + * @param the output element type + * @since 3.0.0 + */ +public final class ObservableMapOptional extends Observable { + + final Observable source; + + final Function> mapper; + + public ObservableMapOptional(Observable source, Function> mapper) { + this.source = source; + this.mapper = mapper; + } + + @Override + protected void subscribeActual(Observer observer) { + source.subscribe(new MapOptionalObserver<>(observer, mapper)); + } + + static final class MapOptionalObserver extends BasicFuseableObserver { + + final Function> mapper; + + MapOptionalObserver(Observer downstream, Function> mapper) { + super(downstream); + this.mapper = mapper; + } + + @Override + public void onNext(T t) { + if (done) { + return; + } + + if (sourceMode != NONE) { + downstream.onNext(null); + return; + } + + Optional result; + try { + result = Objects.requireNonNull(mapper.apply(t), "The mapper returned a null Optional"); + } catch (Throwable ex) { + fail(ex); + return; + } + + if (result.isPresent()) { + downstream.onNext(result.get()); + } + } + + @Override + public int requestFusion(int mode) { + return transitiveBoundaryFusion(mode); + } + + @Override + public R poll() throws Throwable { + for (;;) { + T item = qd.poll(); + if (item == null) { + return null; + } + Optional result = Objects.requireNonNull(mapper.apply(item), "The mapper returned a null Optional"); + if (result.isPresent()) { + return result.get(); + } + } + } + } +} diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableSingleStageObserver.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableSingleStageObserver.java new file mode 100644 index 0000000000..fa8714397a --- /dev/null +++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableSingleStageObserver.java @@ -0,0 +1,60 @@ +/** + * 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.rxjava3.internal.jdk8; + +import java.util.NoSuchElementException; + +/** + * Signals the only element of the source via the underlying CompletableFuture, + * signals the a default item if the upstream is empty or signals {@link IllegalArgumentException} + * if the upstream has more than one item. + * + * @param the element type + * @since 3.0.0 + */ +public final class ObservableSingleStageObserver extends ObservableStageObserver { + + final boolean hasDefault; + + final T defaultItem; + + public ObservableSingleStageObserver(boolean hasDefault, T defaultItem) { + this.hasDefault = hasDefault; + this.defaultItem = defaultItem; + } + + @Override + public void onNext(T t) { + if (value != null) { + value = null; + completeExceptionally(new IllegalArgumentException("Sequence contains more than one element!")); + } else { + value = t; + } + } + + @Override + public void onComplete() { + if (!isDone()) { + T v = value; + clear(); + if (v != null) { + complete(v); + } else if (hasDefault) { + complete(defaultItem); + } else { + completeExceptionally(new NoSuchElementException()); + } + } + } +} diff --git a/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageObserver.java b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageObserver.java new file mode 100644 index 0000000000..54acae086f --- /dev/null +++ b/src/main/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageObserver.java @@ -0,0 +1,75 @@ +/** + * 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.rxjava3.internal.jdk8; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; + +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.core.Observer; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.internal.disposables.DisposableHelper; +import io.reactivex.rxjava3.plugins.RxJavaPlugins; + +/** + * Base class that extends CompletableFuture and provides basic infrastructure + * to notify watchers upon upstream signals. + * @param the element type + * @since 3.0.0 + */ +abstract class ObservableStageObserver extends CompletableFuture implements Observer { + + final AtomicReference upstream = new AtomicReference<>(); + + T value; + + @Override + public final void onSubscribe(@NonNull Disposable d) { + DisposableHelper.setOnce(upstream, d); + } + + @Override + public final void onError(Throwable t) { + clear(); + if (!completeExceptionally(t)) { + RxJavaPlugins.onError(t); + } + } + + protected final void disposeUpstream() { + DisposableHelper.dispose(upstream); + } + + protected final void clear() { + value = null; + upstream.lazySet(DisposableHelper.DISPOSED); + } + + @Override + public final boolean cancel(boolean mayInterruptIfRunning) { + disposeUpstream(); + return super.cancel(mayInterruptIfRunning); + } + + @Override + public final boolean complete(T value) { + disposeUpstream(); + return super.complete(value); + } + + @Override + public final boolean completeExceptionally(Throwable ex) { + disposeUpstream(); + return super.completeExceptionally(ex); + } +} diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStreamTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStreamTest.java index 940ac95c4d..7d5f7a0a74 100644 --- a/src/test/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStreamTest.java +++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/FlowableFromStreamTest.java @@ -184,6 +184,43 @@ public void onComplete() { assertTrue(q.isEmpty()); } + @Test + public void fusedPoll() throws Throwable { + AtomicReference> queue = new AtomicReference<>(); + AtomicInteger calls = new AtomicInteger(); + + Flowable.fromStream(Stream.of(1).onClose(() -> calls.getAndIncrement())) + .subscribe(new FlowableSubscriber() { + @Override + public void onSubscribe(@NonNull Subscription s) { + queue.set((SimpleQueue)s); + ((QueueSubscription)s).requestFusion(QueueFuseable.ANY); + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + }); + + SimpleQueue q = queue.get(); + + assertFalse(q.isEmpty()); + + assertEquals(1, q.poll()); + + assertTrue(q.isEmpty()); + + assertEquals(1, calls.get()); + } + @Test public void streamOfNull() { Flowable.fromStream(Stream.of((Integer)null)) @@ -512,4 +549,9 @@ public void closeCalledOnItemCrashConditional() { assertEquals(1, calls.get()); } + + @Test + public void badRequest() { + TestHelper.assertBadRequestReported(Flowable.fromStream(Stream.of(1))); + } } diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableBlockingStreamTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableBlockingStreamTest.java new file mode 100644 index 0000000000..b62bedb6f8 --- /dev/null +++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableBlockingStreamTest.java @@ -0,0 +1,107 @@ +/** + * 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.rxjava3.internal.jdk8; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.stream.*; + +import org.junit.Test; + +import io.reactivex.rxjava3.core.*; +import io.reactivex.rxjava3.exceptions.TestException; +import io.reactivex.rxjava3.processors.UnicastProcessor; +import io.reactivex.rxjava3.schedulers.Schedulers; + +public class ObservableBlockingStreamTest extends RxJavaTest { + + @Test + public void empty() { + try (Stream stream = Observable.empty().blockingStream()) { + assertEquals(0, stream.toArray().length); + } + } + + @Test + public void just() { + try (Stream stream = Observable.just(1).blockingStream()) { + assertArrayEquals(new Integer[] { 1 }, stream.toArray(Integer[]::new)); + } + } + + @Test + public void range() { + try (Stream stream = Observable.range(1, 5).blockingStream()) { + assertArrayEquals(new Integer[] { 1, 2, 3, 4, 5 }, stream.toArray(Integer[]::new)); + } + } + + @Test + public void rangeBackpressured() { + try (Stream stream = Observable.range(1, 5).blockingStream(1)) { + assertArrayEquals(new Integer[] { 1, 2, 3, 4, 5 }, stream.toArray(Integer[]::new)); + } + } + + @Test + public void rangeAsyncBackpressured() { + try (Stream stream = Observable.range(1, 1000).subscribeOn(Schedulers.computation()).blockingStream()) { + List list = stream.collect(Collectors.toList()); + + assertEquals(1000, list.size()); + for (int i = 1; i <= 1000; i++) { + assertEquals(i, list.get(i - 1).intValue()); + } + } + } + + @Test + public void rangeAsyncBackpressured1() { + try (Stream stream = Observable.range(1, 1000).subscribeOn(Schedulers.computation()).blockingStream(1)) { + List list = stream.collect(Collectors.toList()); + + assertEquals(1000, list.size()); + for (int i = 1; i <= 1000; i++) { + assertEquals(i, list.get(i - 1).intValue()); + } + } + } + + @Test + public void error() { + try (Stream stream = Observable.error(new TestException()).blockingStream()) { + stream.toArray(Integer[]::new); + fail("Should have thrown!"); + } catch (TestException expected) { + // expected + } + } + + @Test + public void close() { + UnicastProcessor up = UnicastProcessor.create(); + up.onNext(1); + up.onNext(2); + up.onNext(3); + up.onNext(4); + up.onNext(5); + + try (Stream stream = up.blockingStream()) { + assertArrayEquals(new Integer[] { 1, 2, 3 }, stream.limit(3).toArray(Integer[]::new)); + } + + assertFalse(up.hasSubscribers()); + } +} diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollectorTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollectorTest.java new file mode 100644 index 0000000000..923a7dbaa3 --- /dev/null +++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableCollectWithCollectorTest.java @@ -0,0 +1,444 @@ +/** + * 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.rxjava3.internal.jdk8; + +import static org.junit.Assert.assertFalse; + +import java.io.IOException; +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +import org.junit.Test; + +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.core.Observer; +import io.reactivex.rxjava3.core.RxJavaTest; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.exceptions.TestException; +import io.reactivex.rxjava3.observers.TestObserver; +import io.reactivex.rxjava3.processors.*; +import io.reactivex.rxjava3.subjects.PublishSubject; +import io.reactivex.rxjava3.testsupport.TestHelper; + +public class ObservableCollectWithCollectorTest extends RxJavaTest { + + @Test + public void basic() { + Observable.range(1, 5) + .collect(Collectors.toList()) + .test() + .assertResult(Arrays.asList(1, 2, 3, 4, 5)); + } + + @Test + public void empty() { + Observable.empty() + .collect(Collectors.toList()) + .test() + .assertResult(Collections.emptyList()); + } + + @Test + public void error() { + Observable.error(new TestException()) + .collect(Collectors.toList()) + .test() + .assertFailure(TestException.class); + } + + @Test + public void collectorSupplierCrash() { + Observable.range(1, 5) + .collect(new Collector() { + + @Override + public Supplier supplier() { + throw new TestException(); + } + + @Override + public BiConsumer accumulator() { + return (a, b) -> { }; + } + + @Override + public BinaryOperator combiner() { + return (a, b) -> a + b; + } + + @Override + public Function finisher() { + return a -> a; + } + + @Override + public Set characteristics() { + return Collections.emptySet(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void collectorAccumulatorCrash() { + BehaviorProcessor source = BehaviorProcessor.createDefault(1); + + source + .collect(new Collector() { + + @Override + public Supplier supplier() { + return () -> 1; + } + + @Override + public BiConsumer accumulator() { + return (a, b) -> { throw new TestException(); }; + } + + @Override + public BinaryOperator combiner() { + return (a, b) -> a + b; + } + + @Override + public Function finisher() { + return a -> a; + } + + @Override + public Set characteristics() { + return Collections.emptySet(); + } + }) + .test() + .assertFailure(TestException.class); + + assertFalse(source.hasSubscribers()); + } + + @Test + public void collectorFinisherCrash() { + Observable.range(1, 5) + .collect(new Collector() { + + @Override + public Supplier supplier() { + return () -> 1; + } + + @Override + public BiConsumer accumulator() { + return (a, b) -> { }; + } + + @Override + public BinaryOperator combiner() { + return (a, b) -> a + b; + } + + @Override + public Function finisher() { + return a -> { throw new TestException(); }; + } + + @Override + public Set characteristics() { + return Collections.emptySet(); + } + }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void collectorAccumulatorDropSignals() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Observable source = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onNext(2); + observer.onError(new IOException()); + observer.onComplete(); + } + }; + + source + .collect(new Collector() { + + @Override + public Supplier supplier() { + return () -> 1; + } + + @Override + public BiConsumer accumulator() { + return (a, b) -> { throw new TestException(); }; + } + + @Override + public BinaryOperator combiner() { + return (a, b) -> a + b; + } + + @Override + public Function finisher() { + return a -> a; + } + + @Override + public Set characteristics() { + return Collections.emptySet(); + } + }) + .test() + .assertFailure(TestException.class); + + TestHelper.assertUndeliverable(errors, 0, IOException.class); + }); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(PublishSubject.create() + .collect(Collectors.toList())); + } + + @Test + public void onSubscribe() { + TestHelper.checkDoubleOnSubscribeObservableToSingle(f -> f.collect(Collectors.toList())); + } + + @Test + public void basicToObservable() { + Observable.range(1, 5) + .collect(Collectors.toList()) + .toObservable() + .test() + .assertResult(Arrays.asList(1, 2, 3, 4, 5)); + } + + @Test + public void emptyToObservable() { + Observable.empty() + .collect(Collectors.toList()) + .toObservable() + .test() + .assertResult(Collections.emptyList()); + } + + @Test + public void errorToObservable() { + Observable.error(new TestException()) + .collect(Collectors.toList()) + .toObservable() + .test() + .assertFailure(TestException.class); + } + + @Test + public void collectorSupplierCrashToObservable() { + Observable.range(1, 5) + .collect(new Collector() { + + @Override + public Supplier supplier() { + throw new TestException(); + } + + @Override + public BiConsumer accumulator() { + return (a, b) -> { }; + } + + @Override + public BinaryOperator combiner() { + return (a, b) -> a + b; + } + + @Override + public Function finisher() { + return a -> a; + } + + @Override + public Set characteristics() { + return Collections.emptySet(); + } + }) + .toObservable() + .test() + .assertFailure(TestException.class); + } + + @Test + public void collectorAccumulatorCrashToObservable() { + BehaviorProcessor source = BehaviorProcessor.createDefault(1); + + source + .collect(new Collector() { + + @Override + public Supplier supplier() { + return () -> 1; + } + + @Override + public BiConsumer accumulator() { + return (a, b) -> { throw new TestException(); }; + } + + @Override + public BinaryOperator combiner() { + return (a, b) -> a + b; + } + + @Override + public Function finisher() { + return a -> a; + } + + @Override + public Set characteristics() { + return Collections.emptySet(); + } + }) + .toObservable() + .test() + .assertFailure(TestException.class); + + assertFalse(source.hasSubscribers()); + } + + @Test + public void collectorFinisherCrashToObservable() { + Observable.range(1, 5) + .collect(new Collector() { + + @Override + public Supplier supplier() { + return () -> 1; + } + + @Override + public BiConsumer accumulator() { + return (a, b) -> { }; + } + + @Override + public BinaryOperator combiner() { + return (a, b) -> a + b; + } + + @Override + public Function finisher() { + return a -> { throw new TestException(); }; + } + + @Override + public Set characteristics() { + return Collections.emptySet(); + } + }) + .toObservable() + .test() + .assertFailure(TestException.class); + } + + @Test + public void collectorAccumulatorDropSignalsToObservable() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Observable source = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onNext(2); + observer.onError(new IOException()); + observer.onComplete(); + } + }; + + source + .collect(new Collector() { + + @Override + public Supplier supplier() { + return () -> 1; + } + + @Override + public BiConsumer accumulator() { + return (a, b) -> { throw new TestException(); }; + } + + @Override + public BinaryOperator combiner() { + return (a, b) -> a + b; + } + + @Override + public Function finisher() { + return a -> a; + } + + @Override + public Set characteristics() { + return Collections.emptySet(); + } + }) + .toObservable() + .test() + .assertFailure(TestException.class); + + TestHelper.assertUndeliverable(errors, 0, IOException.class); + }); + } + + @Test + public void disposeToObservable() { + TestHelper.checkDisposed(PublishProcessor.create() + .collect(Collectors.toList()).toObservable()); + } + + @Test + public void onSubscribeToObservable() { + TestHelper.checkDoubleOnSubscribeObservable(f -> f.collect(Collectors.toList()).toObservable()); + } + + @Test + public void toObservableTake() { + Observable.range(1, 5) + .collect(Collectors.toList()) + .toObservable() + .take(1) + .test() + .assertResult(Arrays.asList(1, 2, 3, 4, 5)); + } + + @Test + public void disposeBeforeEnd() { + TestObserver> to = Observable.range(1, 5).concatWith(Observable.never()) + .collect(Collectors.toList()) + .test(); + + to.dispose(); + + to.assertEmpty(); + } +} diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFlatMapStreamTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFlatMapStreamTest.java new file mode 100644 index 0000000000..8341aa03dd --- /dev/null +++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFlatMapStreamTest.java @@ -0,0 +1,466 @@ +/** + * 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.rxjava3.internal.jdk8; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.*; + +import org.junit.Test; + +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.core.*; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.exceptions.TestException; +import io.reactivex.rxjava3.observers.TestObserver; +import io.reactivex.rxjava3.subjects.*; +import io.reactivex.rxjava3.testsupport.TestHelper; + +public class ObservableFlatMapStreamTest extends RxJavaTest { + + @Test + public void empty() { + Observable.empty() + .flatMapStream(v -> Stream.of(1, 2, 3, 4, 5)) + .test() + .assertResult(); + } + + @Test + public void emptyHidden() { + Observable.empty() + .hide() + .flatMapStream(v -> Stream.of(1, 2, 3, 4, 5)) + .test() + .assertResult(); + } + + @Test + public void just() { + Observable.just(1) + .flatMapStream(v -> Stream.of(v + 1, v + 2, v + 3, v + 4, v + 5)) + .test() + .assertResult(2, 3, 4, 5, 6); + } + + @Test + public void justHidden() { + Observable.just(1).hide() + .flatMapStream(v -> Stream.of(v + 1, v + 2, v + 3, v + 4, v + 5)) + .test() + .assertResult(2, 3, 4, 5, 6); + } + + @Test + public void error() { + Observable.error(new TestException()) + .flatMapStream(v -> Stream.of(1, 2, 3, 4, 5)) + .test() + .assertFailure(TestException.class); + } + + @Test + public void supplierFusedError() { + Observable.fromCallable(() -> { throw new TestException(); }) + .flatMapStream(v -> Stream.of(1, 2, 3, 4, 5)) + .test() + .assertFailure(TestException.class); + } + + @Test + public void errorHidden() { + Observable.error(new TestException()) + .hide() + .flatMapStream(v -> Stream.of(1, 2, 3, 4, 5)) + .test() + .assertFailure(TestException.class); + } + + @Test + public void range() { + Observable.range(1, 5) + .flatMapStream(v -> IntStream.range(v * 10, v * 10 + 5).boxed()) + .test() + .assertResult( + 10, 11, 12, 13, 14, + 20, 21, 22, 23, 24, + 30, 31, 32, 33, 34, + 40, 41, 42, 43, 44, + 50, 51, 52, 53, 54 + ); + } + + @Test + public void rangeHidden() { + Observable.range(1, 5) + .hide() + .flatMapStream(v -> IntStream.range(v * 10, v * 10 + 5).boxed()) + .test() + .assertResult( + 10, 11, 12, 13, 14, + 20, 21, 22, 23, 24, + 30, 31, 32, 33, 34, + 40, 41, 42, 43, 44, + 50, 51, 52, 53, 54 + ); + } + + @Test + public void rangeToEmpty() { + Observable.range(1, 5) + .flatMapStream(v -> Stream.of()) + .test() + .assertResult(); + } + + @Test + public void rangeTake() { + Observable.range(1, 5) + .flatMapStream(v -> IntStream.range(v * 10, v * 10 + 5).boxed()) + .take(12) + .test() + .assertResult( + 10, 11, 12, 13, 14, + 20, 21, 22, 23, 24, + 30, 31 + ); + } + + @Test + public void rangeTakeHidden() { + Observable.range(1, 5) + .hide() + .flatMapStream(v -> IntStream.range(v * 10, v * 10 + 5).boxed()) + .take(12) + .test() + .assertResult( + 10, 11, 12, 13, 14, + 20, 21, 22, 23, 24, + 30, 31 + ); + } + + @Test + public void upstreamCancelled() { + PublishSubject ps = PublishSubject.create(); + + AtomicInteger calls = new AtomicInteger(); + + TestObserver to = ps + .flatMapStream(v -> Stream.of(v + 1, v + 2).onClose(() -> calls.getAndIncrement())) + .take(1) + .test(); + + assertTrue(ps.hasObservers()); + + ps.onNext(1); + + to.assertResult(2); + + assertFalse(ps.hasObservers()); + + assertEquals(1, calls.get()); + } + + @Test + public void upstreamCancelledCloseCrash() throws Throwable { + TestHelper.withErrorTracking(errors -> { + PublishSubject ps = PublishSubject.create(); + + TestObserver to = ps + .flatMapStream(v -> Stream.of(v + 1, v + 2).onClose(() -> { throw new TestException(); })) + .take(1) + .test(); + + assertTrue(ps.hasObservers()); + + ps.onNext(1); + + to.assertResult(2); + + assertFalse(ps.hasObservers()); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + }); + } + + @Test + public void crossMap() { + Observable.range(1, 1000) + .flatMapStream(v -> IntStream.range(v * 1000, v * 1000 + 1000).boxed()) + .test() + .assertValueCount(1_000_000) + .assertNoErrors() + .assertComplete(); + } + + @Test + public void crossMapHidden() { + Observable.range(1, 1000) + .hide() + .flatMapStream(v -> IntStream.range(v * 1000, v * 1000 + 1000).boxed()) + .test() + .assertValueCount(1_000_000) + .assertNoErrors() + .assertComplete(); + } + + @Test + public void onSubscribe() { + TestHelper.checkDoubleOnSubscribeObservable(f -> f.flatMapStream(v -> Stream.of(1, 2))); + } + + @Test + public void mapperThrows() { + Observable.just(1).hide() + .concatMapStream(v -> { throw new TestException(); }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void mapperNull() { + Observable.just(1).hide() + .concatMapStream(v -> null) + .test() + .assertFailure(NullPointerException.class); + } + + @Test + public void streamNull() { + Observable.just(1).hide() + .concatMapStream(v -> Stream.of(1, null)) + .test() + .assertFailure(NullPointerException.class, 1); + } + + @Test + public void hasNextThrows() { + Observable.just(1).hide() + .concatMapStream(v -> Stream.generate(() -> { throw new TestException(); })) + .test() + .assertFailure(TestException.class); + } + + @Test + public void hasNextThrowsLater() { + AtomicInteger counter = new AtomicInteger(); + Observable.just(1).hide() + .concatMapStream(v -> Stream.generate(() -> { + if (counter.getAndIncrement() == 0) { + return 1; + } + throw new TestException(); + })) + .test() + .assertFailure(TestException.class, 1); + } + + @Test + public void mapperThrowsWhenUpstreamErrors() throws Throwable { + TestHelper.withErrorTracking(errors -> { + PublishSubject ps = PublishSubject.create(); + + AtomicInteger counter = new AtomicInteger(); + + TestObserver to = ps.hide() + .concatMapStream(v -> { + if (counter.getAndIncrement() == 0) { + return Stream.of(1, 2); + } + ps.onError(new IOException()); + throw new TestException(); + }) + .test(); + + ps.onNext(1); + ps.onNext(2); + + to + .assertFailure(IOException.class, 1, 2); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + }); + } + + @Test + public void cancelAfterIteratorNext() throws Exception { + TestObserver to = new TestObserver<>(); + + @SuppressWarnings("unchecked") + Stream stream = mock(Stream.class); + when(stream.iterator()).thenReturn(new Iterator() { + + @Override + public boolean hasNext() { + return true; + } + + @Override + public Integer next() { + to.dispose(); + return 1; + } + }); + + Observable.just(1) + .hide() + .concatMapStream(v -> stream) + .subscribe(to); + + to.assertEmpty(); + } + + @Test + public void cancelAfterIteratorHasNext() throws Exception { + TestObserver to = new TestObserver<>(); + + @SuppressWarnings("unchecked") + Stream stream = mock(Stream.class); + when(stream.iterator()).thenReturn(new Iterator() { + + @Override + public boolean hasNext() { + to.dispose(); + return true; + } + + @Override + public Integer next() { + return 1; + } + }); + + Observable.just(1) + .hide() + .concatMapStream(v -> stream) + .subscribe(to); + + to.assertEmpty(); + } + + @Test + public void asyncUpstreamFused() { + UnicastSubject us = UnicastSubject.create(); + + TestObserver to = us.flatMapStream(v -> Stream.of(1, 2)) + .test(); + + assertTrue(us.hasObservers()); + + us.onNext(1); + + to.assertValuesOnly(1, 2); + + us.onComplete(); + + to.assertResult(1, 2); + } + + @Test + public void asyncUpstreamFusionBoundary() { + UnicastSubject us = UnicastSubject.create(); + + TestObserver to = us + .map(v -> v + 1) + .flatMapStream(v -> Stream.of(1, 2)) + .test(); + + assertTrue(us.hasObservers()); + + us.onNext(1); + + to.assertValuesOnly(1, 2); + + us.onComplete(); + + to.assertResult(1, 2); + } + + @Test + public void fusedPollCrash() { + UnicastSubject us = UnicastSubject.create(); + + TestObserver to = us + .map(v -> { throw new TestException(); }) + .compose(TestHelper.observableStripBoundary()) + .flatMapStream(v -> Stream.of(1, 2)) + .test(); + + assertTrue(us.hasObservers()); + + us.onNext(1); + + assertFalse(us.hasObservers()); + + to.assertFailure(TestException.class); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(PublishSubject.create().flatMapStream(v -> Stream.of(1))); + } + + @Test + public void eventsIgnoredAfterCrash() { + AtomicInteger calls = new AtomicInteger(); + + new Observable() { + @Override + protected void subscribeActual(@NonNull Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onNext(2); + observer.onComplete(); + } + } + .flatMapStream(v -> { + calls.getAndIncrement(); + throw new TestException(); + }) + .take(1) + .test() + .assertFailure(TestException.class); + + assertEquals(1, calls.get()); + } + + @Test + public void eventsIgnoredAfterDispose() { + AtomicInteger calls = new AtomicInteger(); + + new Observable() { + @Override + protected void subscribeActual(@NonNull Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onNext(2); + observer.onComplete(); + } + } + .flatMapStream(v -> { + calls.getAndIncrement(); + return Stream.of(1); + }) + .take(1) + .test() + .assertResult(1); + + assertEquals(1, calls.get()); + } +} diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromCompletionStageTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromCompletionStageTest.java new file mode 100644 index 0000000000..612ab0724b --- /dev/null +++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromCompletionStageTest.java @@ -0,0 +1,65 @@ +/** + * 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.rxjava3.internal.jdk8; + +import java.util.concurrent.CompletableFuture; + +import org.junit.Test; + +import io.reactivex.rxjava3.core.*; +import io.reactivex.rxjava3.exceptions.TestException; +import io.reactivex.rxjava3.observers.TestObserver; + +public class ObservableFromCompletionStageTest extends RxJavaTest { + + @Test + public void syncSuccess() { + Observable.fromCompletionStage(CompletableFuture.completedFuture(1)) + .test() + .assertResult(1); + } + + @Test + public void syncFailure() { + CompletableFuture cf = new CompletableFuture<>(); + cf.completeExceptionally(new TestException()); + + Observable.fromCompletionStage(cf) + .test() + .assertFailure(TestException.class); + } + + @Test + public void syncNull() { + Observable.fromCompletionStage(CompletableFuture.completedFuture(null)) + .test() + .assertFailure(NullPointerException.class); + } + + @Test + public void cancel() { + CompletableFuture cf = new CompletableFuture<>(); + + TestObserver to = Observable.fromCompletionStage(cf) + .test(); + + to.assertEmpty(); + + to.dispose(); + + cf.complete(1); + + to.assertEmpty(); + } +} diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromOptionalTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromOptionalTest.java new file mode 100644 index 0000000000..e2e4059e70 --- /dev/null +++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromOptionalTest.java @@ -0,0 +1,38 @@ +/** + * 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.rxjava3.internal.jdk8; + +import java.util.Optional; + +import org.junit.Test; + +import io.reactivex.rxjava3.core.*; + +public class ObservableFromOptionalTest extends RxJavaTest { + + @Test + public void hasValue() { + Observable.fromOptional(Optional.of(1)) + .test() + .assertResult(1); + } + + @Test + public void empty() { + Observable.fromOptional(Optional.empty()) + .test() + .assertResult(); + } + +} diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromStreamTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromStreamTest.java new file mode 100644 index 0000000000..6074e54689 --- /dev/null +++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableFromStreamTest.java @@ -0,0 +1,488 @@ +/** + * 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.rxjava3.internal.jdk8; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.util.Iterator; +import java.util.concurrent.atomic.*; +import java.util.stream.*; + +import org.junit.Test; + +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.core.*; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.exceptions.TestException; +import io.reactivex.rxjava3.internal.fuseable.*; +import io.reactivex.rxjava3.observers.TestObserver; +import io.reactivex.rxjava3.testsupport.*; + +public class ObservableFromStreamTest extends RxJavaTest { + + @Test + public void empty() { + Observable.fromStream(Stream.of()) + .test() + .assertResult(); + } + + @Test + public void just() { + Observable.fromStream(Stream.of(1)) + .test() + .assertResult(1); + } + + @Test + public void many() { + Observable.fromStream(Stream.of(1, 2, 3, 4, 5)) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void noReuse() { + Observable source = Observable.fromStream(Stream.of(1, 2, 3, 4, 5)); + + source + .test() + .assertResult(1, 2, 3, 4, 5); + + source + .test() + .assertFailure(IllegalStateException.class); + } + + @Test + public void take() { + Observable.fromStream(IntStream.rangeClosed(1, 10).boxed()) + .take(5) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void emptyConditional() { + Observable.fromStream(Stream.of()) + .filter(v -> true) + .test() + .assertResult(); + } + + @Test + public void justConditional() { + Observable.fromStream(Stream.of(1)) + .filter(v -> true) + .test() + .assertResult(1); + } + + @Test + public void manyConditional() { + Observable.fromStream(Stream.of(1, 2, 3, 4, 5)) + .filter(v -> true) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void manyConditionalSkip() { + Observable.fromStream(IntStream.rangeClosed(1, 10).boxed()) + .filter(v -> v % 2 == 0) + .test() + .assertResult(2, 4, 6, 8, 10); + } + + @Test + public void takeConditional() { + Observable.fromStream(IntStream.rangeClosed(1, 10).boxed()) + .filter(v -> true) + .take(5) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void noOfferNoCrashAfterClear() throws Throwable { + AtomicReference> queue = new AtomicReference<>(); + + Observable.fromStream(IntStream.rangeClosed(1, 10).boxed()) + .subscribe(new Observer() { + @Override + public void onSubscribe(@NonNull Disposable d) { + queue.set((SimpleQueue)d); + ((QueueDisposable)d).requestFusion(QueueFuseable.ANY); + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + }); + + SimpleQueue q = queue.get(); + TestHelper.assertNoOffer(q); + + assertFalse(q.isEmpty()); + + q.clear(); + + assertNull(q.poll()); + + assertTrue(q.isEmpty()); + + q.clear(); + + assertNull(q.poll()); + + assertTrue(q.isEmpty()); + } + + @Test + public void fusedPoll() throws Throwable { + AtomicReference> queue = new AtomicReference<>(); + AtomicInteger calls = new AtomicInteger(); + + Observable.fromStream(Stream.of(1).onClose(() -> calls.getAndIncrement())) + .subscribe(new Observer() { + @Override + public void onSubscribe(@NonNull Disposable d) { + queue.set((SimpleQueue)d); + ((QueueDisposable)d).requestFusion(QueueFuseable.ANY); + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + }); + + SimpleQueue q = queue.get(); + + assertFalse(q.isEmpty()); + + assertEquals(1, q.poll()); + + assertTrue(q.isEmpty()); + + assertEquals(1, calls.get()); + } + + @Test + public void fusedPoll2() throws Throwable { + AtomicReference> queue = new AtomicReference<>(); + AtomicInteger calls = new AtomicInteger(); + + Observable.fromStream(Stream.of(1, 2).onClose(() -> calls.getAndIncrement())) + .subscribe(new Observer() { + @Override + public void onSubscribe(@NonNull Disposable d) { + queue.set((SimpleQueue)d); + ((QueueDisposable)d).requestFusion(QueueFuseable.ANY); + } + + @Override + public void onNext(Integer t) { + } + + @Override + public void onError(Throwable t) { + } + + @Override + public void onComplete() { + } + }); + + SimpleQueue q = queue.get(); + + assertFalse(q.isEmpty()); + + assertEquals(1, q.poll()); + + assertFalse(q.isEmpty()); + + assertEquals(2, q.poll()); + + assertTrue(q.isEmpty()); + + assertEquals(1, calls.get()); + } + + @Test + public void streamOfNull() { + Observable.fromStream(Stream.of((Integer)null)) + .test() + .assertFailure(NullPointerException.class); + } + + @Test + public void streamOfNullConditional() { + Observable.fromStream(Stream.of((Integer)null)) + .filter(v -> true) + .test() + .assertFailure(NullPointerException.class); + } + + @Test + public void syncFusionSupport() { + TestObserverEx to = new TestObserverEx<>(); + to.setInitialFusionMode(QueueFuseable.ANY); + + Observable.fromStream(IntStream.rangeClosed(1, 10).boxed()) + .subscribeWith(to) + .assertFuseable() + .assertFusionMode(QueueFuseable.SYNC) + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void asyncFusionNotSupported() { + TestObserverEx to = new TestObserverEx<>(); + to.setInitialFusionMode(QueueFuseable.ASYNC); + + Observable.fromStream(IntStream.rangeClosed(1, 10).boxed()) + .subscribeWith(to) + .assertFuseable() + .assertFusionMode(QueueFuseable.NONE) + .assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + } + + @Test + public void runToEndCloseCrash() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Stream stream = Stream.of(1, 2, 3, 4, 5).onClose(() -> { throw new TestException(); }); + + Observable.fromStream(stream) + .test() + .assertResult(1, 2, 3, 4, 5); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + }); + } + + @Test + public void takeCloseCrash() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Stream stream = Stream.of(1, 2, 3, 4, 5).onClose(() -> { throw new TestException(); }); + + Observable.fromStream(stream) + .take(3) + .test() + .assertResult(1, 2, 3); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + }); + } + + @Test + public void hasNextCrash() { + AtomicInteger v = new AtomicInteger(); + Observable.fromStream(Stream.generate(() -> { + int value = v.getAndIncrement(); + if (value == 1) { + throw new TestException(); + } + return value; + })) + .test() + .assertFailure(TestException.class, 0); + } + + @Test + public void hasNextCrashConditional() { + AtomicInteger counter = new AtomicInteger(); + Observable.fromStream(Stream.generate(() -> { + int value = counter.getAndIncrement(); + if (value == 1) { + throw new TestException(); + } + return value; + })) + .filter(v -> true) + .test() + .assertFailure(TestException.class, 0); + } + + @Test + public void closeCalledOnEmpty() { + AtomicInteger calls = new AtomicInteger(); + + Observable.fromStream(Stream.of().onClose(() -> calls.getAndIncrement())) + .test() + .assertResult(); + + assertEquals(1, calls.get()); + } + + @Test + public void closeCalledAfterItems() { + AtomicInteger calls = new AtomicInteger(); + + Observable.fromStream(Stream.of(1, 2, 3, 4, 5).onClose(() -> calls.getAndIncrement())) + .test() + .assertResult(1, 2, 3, 4, 5); + + assertEquals(1, calls.get()); + } + + @Test + public void closeCalledOnCancel() { + AtomicInteger calls = new AtomicInteger(); + + Observable.fromStream(Stream.of(1, 2, 3, 4, 5).onClose(() -> calls.getAndIncrement())) + .take(3) + .test() + .assertResult(1, 2, 3); + + assertEquals(1, calls.get()); + } + + @Test + public void closeCalledOnItemCrash() { + AtomicInteger calls = new AtomicInteger(); + AtomicInteger counter = new AtomicInteger(); + Observable.fromStream(Stream.generate(() -> { + int value = counter.getAndIncrement(); + if (value == 1) { + throw new TestException(); + } + return value; + }).onClose(() -> calls.getAndIncrement())) + .test() + .assertFailure(TestException.class, 0); + + assertEquals(1, calls.get()); + } + + @Test + public void closeCalledAfterItemsConditional() { + AtomicInteger calls = new AtomicInteger(); + + Observable.fromStream(Stream.of(1, 2, 3, 4, 5).onClose(() -> calls.getAndIncrement())) + .filter(v -> true) + .test() + .assertResult(1, 2, 3, 4, 5); + + assertEquals(1, calls.get()); + } + + @Test + public void closeCalledOnCancelConditional() { + AtomicInteger calls = new AtomicInteger(); + + Observable.fromStream(Stream.of(1, 2, 3, 4, 5).onClose(() -> calls.getAndIncrement())) + .filter(v -> true) + .take(3) + .test() + .assertResult(1, 2, 3); + + assertEquals(1, calls.get()); + } + + @Test + public void closeCalledOnItemCrashConditional() { + AtomicInteger calls = new AtomicInteger(); + AtomicInteger counter = new AtomicInteger(); + Observable.fromStream(Stream.generate(() -> { + int value = counter.getAndIncrement(); + if (value == 1) { + throw new TestException(); + } + return value; + }).onClose(() -> calls.getAndIncrement())) + .filter(v -> true) + .test() + .assertFailure(TestException.class, 0); + + assertEquals(1, calls.get()); + } + + @Test + public void dispose() { + TestHelper.checkDisposed(Observable.fromStream(Stream.of(1))); + } + + @Test + public void cancelAfterIteratorNext() throws Exception { + TestObserver to = new TestObserver<>(); + + @SuppressWarnings("unchecked") + Stream stream = mock(Stream.class); + when(stream.iterator()).thenReturn(new Iterator() { + + @Override + public boolean hasNext() { + return true; + } + + @Override + public Integer next() { + to.dispose(); + return 1; + } + }); + + Observable.fromStream(stream) + .subscribe(to); + + to.assertEmpty(); + } + + @Test + public void cancelAfterIteratorHasNext() throws Exception { + TestObserver to = new TestObserver<>(); + + @SuppressWarnings("unchecked") + Stream stream = mock(Stream.class); + when(stream.iterator()).thenReturn(new Iterator() { + + int calls; + + @Override + public boolean hasNext() { + if (++calls == 1) { + to.dispose(); + } + return true; + } + + @Override + public Integer next() { + return 1; + } + }); + + Observable.fromStream(stream) + .subscribe(to); + + to.assertEmpty(); + } +} diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableMapOptionalTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableMapOptionalTest.java new file mode 100644 index 0000000000..ba38417b12 --- /dev/null +++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableMapOptionalTest.java @@ -0,0 +1,394 @@ +/** + * 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.rxjava3.internal.jdk8; + +import static org.junit.Assert.assertFalse; + +import java.util.Optional; + +import org.junit.Test; + +import io.reactivex.rxjava3.core.*; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.exceptions.TestException; +import io.reactivex.rxjava3.functions.Function; +import io.reactivex.rxjava3.internal.fuseable.QueueFuseable; +import io.reactivex.rxjava3.subjects.*; +import io.reactivex.rxjava3.testsupport.TestHelper; + +public class ObservableMapOptionalTest extends RxJavaTest { + + static final Function> MODULO = v -> v % 2 == 0 ? Optional.of(v) : Optional.empty(); + + @Test + public void allPresent() { + Observable.range(1, 5) + .mapOptional(Optional::of) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void allEmpty() { + Observable.range(1, 5) + .mapOptional(v -> Optional.empty()) + .test() + .assertResult(); + } + + @Test + public void mixed() { + Observable.range(1, 10) + .mapOptional(MODULO) + .test() + .assertResult(2, 4, 6, 8, 10); + } + + @Test + public void mapperChash() { + BehaviorSubject source = BehaviorSubject.createDefault(1); + + source + .mapOptional(v -> { throw new TestException(); }) + .test() + .assertFailure(TestException.class); + + assertFalse(source.hasObservers()); + } + + @Test + public void mapperNull() { + BehaviorSubject source = BehaviorSubject.createDefault(1); + + source + .mapOptional(v -> null) + .test() + .assertFailure(NullPointerException.class); + + assertFalse(source.hasObservers()); + } + + @Test + public void crashDropsOnNexts() { + Observable source = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onNext(2); + } + }; + + source + .mapOptional(v -> { throw new TestException(); }) + .test() + .assertFailure(TestException.class); + } + + @Test + public void syncFusedAll() { + Observable.range(1, 5) + .mapOptional(Optional::of) + .to(TestHelper.testConsumer(false, QueueFuseable.SYNC)) + .assertFuseable() + .assertFusionMode(QueueFuseable.SYNC) + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void asyncFusedAll() { + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5); + + us + .mapOptional(Optional::of) + .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC)) + .assertFuseable() + .assertFusionMode(QueueFuseable.ASYNC) + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void boundaryFusedAll() { + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5); + + us + .mapOptional(Optional::of) + .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC | QueueFuseable.BOUNDARY)) + .assertFuseable() + .assertFusionMode(QueueFuseable.NONE) + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void syncFusedNone() { + Observable.range(1, 5) + .mapOptional(v -> Optional.empty()) + .to(TestHelper.testConsumer(false, QueueFuseable.SYNC)) + .assertFuseable() + .assertFusionMode(QueueFuseable.SYNC) + .assertResult(); + } + + @Test + public void asyncFusedNone() { + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5); + + us + .mapOptional(v -> Optional.empty()) + .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC)) + .assertFuseable() + .assertFusionMode(QueueFuseable.ASYNC) + .assertResult(); + } + + @Test + public void boundaryFusedNone() { + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5); + + us + .mapOptional(v -> Optional.empty()) + .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC | QueueFuseable.BOUNDARY)) + .assertFuseable() + .assertFusionMode(QueueFuseable.NONE) + .assertResult(); + } + + @Test + public void syncFusedMixed() { + Observable.range(1, 10) + .mapOptional(MODULO) + .to(TestHelper.testConsumer(false, QueueFuseable.SYNC)) + .assertFuseable() + .assertFusionMode(QueueFuseable.SYNC) + .assertResult(2, 4, 6, 8, 10); + } + + @Test + public void asyncFusedMixed() { + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + + us + .mapOptional(MODULO) + .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC)) + .assertFuseable() + .assertFusionMode(QueueFuseable.ASYNC) + .assertResult(2, 4, 6, 8, 10); + } + + @Test + public void boundaryFusedMixed() { + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + + us + .mapOptional(MODULO) + .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC | QueueFuseable.BOUNDARY)) + .assertFuseable() + .assertFusionMode(QueueFuseable.NONE) + .assertResult(2, 4, 6, 8, 10); + } + + @Test + public void allPresentConditional() { + Observable.range(1, 5) + .mapOptional(Optional::of) + .filter(v -> true) + .test() + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void allEmptyConditional() { + Observable.range(1, 5) + .mapOptional(v -> Optional.empty()) + .filter(v -> true) + .test() + .assertResult(); + } + + @Test + public void mixedConditional() { + Observable.range(1, 10) + .mapOptional(MODULO) + .filter(v -> true) + .test() + .assertResult(2, 4, 6, 8, 10); + } + + @Test + public void mapperChashConditional() { + BehaviorSubject source = BehaviorSubject.createDefault(1); + + source + .mapOptional(v -> { throw new TestException(); }) + .filter(v -> true) + .test() + .assertFailure(TestException.class); + + assertFalse(source.hasObservers()); + } + + @Test + public void mapperNullConditional() { + BehaviorSubject source = BehaviorSubject.createDefault(1); + + source + .mapOptional(v -> null) + .filter(v -> true) + .test() + .assertFailure(NullPointerException.class); + + assertFalse(source.hasObservers()); + } + + @Test + public void crashDropsOnNextsConditional() { + Observable source = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onNext(2); + } + }; + + source + .mapOptional(v -> { throw new TestException(); }) + .filter(v -> true) + .test() + .assertFailure(TestException.class); + } + + @Test + public void syncFusedAllConditional() { + Observable.range(1, 5) + .mapOptional(Optional::of) + .filter(v -> true) + .to(TestHelper.testConsumer(false, QueueFuseable.SYNC)) + .assertFuseable() + .assertFusionMode(QueueFuseable.SYNC) + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void asyncFusedAllConditional() { + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5); + + us + .mapOptional(Optional::of) + .filter(v -> true) + .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC)) + .assertFuseable() + .assertFusionMode(QueueFuseable.ASYNC) + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void boundaryFusedAllConditiona() { + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5); + + us + .mapOptional(Optional::of) + .filter(v -> true) + .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC | QueueFuseable.BOUNDARY)) + .assertFuseable() + .assertFusionMode(QueueFuseable.NONE) + .assertResult(1, 2, 3, 4, 5); + } + + @Test + public void syncFusedNoneConditional() { + Observable.range(1, 5) + .mapOptional(v -> Optional.empty()) + .filter(v -> true) + .to(TestHelper.testConsumer(false, QueueFuseable.SYNC)) + .assertFuseable() + .assertFusionMode(QueueFuseable.SYNC) + .assertResult(); + } + + @Test + public void asyncFusedNoneConditional() { + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5); + + us + .mapOptional(v -> Optional.empty()) + .filter(v -> true) + .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC)) + .assertFuseable() + .assertFusionMode(QueueFuseable.ASYNC) + .assertResult(); + } + + @Test + public void boundaryFusedNoneConditional() { + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5); + + us + .mapOptional(v -> Optional.empty()) + .filter(v -> true) + .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC | QueueFuseable.BOUNDARY)) + .assertFuseable() + .assertFusionMode(QueueFuseable.NONE) + .assertResult(); + } + + @Test + public void syncFusedMixedConditional() { + Observable.range(1, 10) + .mapOptional(MODULO) + .filter(v -> true) + .to(TestHelper.testConsumer(false, QueueFuseable.SYNC)) + .assertFuseable() + .assertFusionMode(QueueFuseable.SYNC) + .assertResult(2, 4, 6, 8, 10); + } + + @Test + public void asyncFusedMixedConditional() { + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + + us + .mapOptional(MODULO) + .filter(v -> true) + .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC)) + .assertFuseable() + .assertFusionMode(QueueFuseable.ASYNC) + .assertResult(2, 4, 6, 8, 10); + } + + @Test + public void boundaryFusedMixedConditional() { + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + + us + .mapOptional(MODULO) + .filter(v -> true) + .to(TestHelper.testConsumer(false, QueueFuseable.ASYNC | QueueFuseable.BOUNDARY)) + .assertFuseable() + .assertFusionMode(QueueFuseable.NONE) + .assertResult(2, 4, 6, 8, 10); + } +} diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageSubscriberOrDefaultTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageSubscriberOrDefaultTest.java new file mode 100644 index 0000000000..9b02622c9b --- /dev/null +++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageSubscriberOrDefaultTest.java @@ -0,0 +1,475 @@ +/** + * 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.rxjava3.internal.jdk8; + +import static org.junit.Assert.*; + +import java.util.concurrent.CompletableFuture; + +import org.junit.Test; + +import io.reactivex.rxjava3.core.*; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.exceptions.*; +import io.reactivex.rxjava3.subjects.*; +import io.reactivex.rxjava3.testsupport.TestHelper; + +public class ObservableStageSubscriberOrDefaultTest extends RxJavaTest { + + @Test + public void firstJust() throws Exception { + Integer v = Observable.just(1) + .firstStage(null) + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + } + + @Test + public void firstEmpty() throws Exception { + Integer v = Observable.empty() + .firstStage(2) + .toCompletableFuture() + .get(); + + assertEquals((Integer)2, v); + } + + @Test + public void firstCancels() throws Exception { + BehaviorSubject source = BehaviorSubject.createDefault(1); + + Integer v = source + .firstStage(null) + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + + assertFalse(source.hasObservers()); + } + + @Test + public void firstCompletableFutureCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .firstStage(null) + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.cancel(true); + + assertTrue(cf.isCancelled()); + + assertFalse(source.hasObservers()); + } + + @Test + public void firstCompletableManualCompleteCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .firstStage(null) + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.complete(1); + + assertTrue(cf.isDone()); + assertFalse(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + assertFalse(source.hasObservers()); + + assertEquals((Integer)1, cf.get()); + } + + @Test + public void firstCompletableManualCompleteExceptionallyCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .firstStage(null) + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.completeExceptionally(new TestException()); + + assertTrue(cf.isDone()); + assertTrue(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + assertFalse(source.hasObservers()); + + TestHelper.assertError(cf, TestException.class); + } + + @Test + public void firstError() throws Exception { + CompletableFuture cf = Observable.error(new TestException()) + .firstStage(null) + .toCompletableFuture(); + + assertTrue(cf.isDone()); + assertTrue(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + TestHelper.assertError(cf, TestException.class); + } + + @Test + public void firstSourceIgnoresCancel() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Integer v = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .firstStage(null) + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + }); + } + + @Test + public void firstDoubleOnSubscribe() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Integer v = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + } + } + .firstStage(null) + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + }); + } + + @Test + public void singleJust() throws Exception { + Integer v = Observable.just(1) + .singleStage(null) + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + } + + @Test + public void singleEmpty() throws Exception { + Integer v = Observable.empty() + .singleStage(2) + .toCompletableFuture() + .get(); + + assertEquals((Integer)2, v); + } + + @Test + public void singleTooManyCancels() throws Exception { + ReplaySubject source = ReplaySubject.create(); + source.onNext(1); + source.onNext(2); + + TestHelper.assertError(source + .singleStage(null) + .toCompletableFuture(), IllegalArgumentException.class); + + assertFalse(source.hasObservers()); + } + + @Test + public void singleCompletableFutureCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .singleStage(null) + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.cancel(true); + + assertTrue(cf.isCancelled()); + + assertFalse(source.hasObservers()); + } + + @Test + public void singleCompletableManualCompleteCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .singleStage(null) + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.complete(1); + + assertTrue(cf.isDone()); + assertFalse(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + assertFalse(source.hasObservers()); + + assertEquals((Integer)1, cf.get()); + } + + @Test + public void singleCompletableManualCompleteExceptionallyCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .singleStage(null) + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.completeExceptionally(new TestException()); + + assertTrue(cf.isDone()); + assertTrue(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + assertFalse(source.hasObservers()); + + TestHelper.assertError(cf, TestException.class); + } + + @Test + public void singleError() throws Exception { + CompletableFuture cf = Observable.error(new TestException()) + .singleStage(null) + .toCompletableFuture(); + + assertTrue(cf.isDone()); + assertTrue(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + TestHelper.assertError(cf, TestException.class); + } + + @Test + public void singleSourceIgnoresCancel() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Integer v = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onComplete(); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .singleStage(null) + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + }); + } + + @Test + public void singleDoubleOnSubscribe() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Integer v = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onComplete(); + } + } + .singleStage(null) + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + }); + } + + @Test + public void lastJust() throws Exception { + Integer v = Observable.just(1) + .lastStage(null) + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + } + + @Test + public void lastRange() throws Exception { + Integer v = Observable.range(1, 5) + .lastStage(null) + .toCompletableFuture() + .get(); + + assertEquals((Integer)5, v); + } + + @Test + public void lastEmpty() throws Exception { + Integer v = Observable.empty() + .lastStage(2) + .toCompletableFuture() + .get(); + + assertEquals((Integer)2, v); + } + + @Test + public void lastCompletableFutureCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .lastStage(null) + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.cancel(true); + + assertTrue(cf.isCancelled()); + + assertFalse(source.hasObservers()); + } + + @Test + public void lastCompletableManualCompleteCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .lastStage(null) + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.complete(1); + + assertTrue(cf.isDone()); + assertFalse(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + assertFalse(source.hasObservers()); + + assertEquals((Integer)1, cf.get()); + } + + @Test + public void lastCompletableManualCompleteExceptionallyCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .lastStage(null) + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.completeExceptionally(new TestException()); + + assertTrue(cf.isDone()); + assertTrue(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + assertFalse(source.hasObservers()); + + TestHelper.assertError(cf, TestException.class); + } + + @Test + public void lastError() throws Exception { + CompletableFuture cf = Observable.error(new TestException()) + .lastStage(null) + .toCompletableFuture(); + + assertTrue(cf.isDone()); + assertTrue(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + TestHelper.assertError(cf, TestException.class); + } + + @Test + public void lastSourceIgnoresCancel() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Integer v = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onComplete(); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .lastStage(null) + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + }); + } + + @Test + public void lastDoubleOnSubscribe() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Integer v = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onComplete(); + } + } + .lastStage(null) + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + }); + } +} diff --git a/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageSubscriberOrErrorTest.java b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageSubscriberOrErrorTest.java new file mode 100644 index 0000000000..d8e6fbe918 --- /dev/null +++ b/src/test/java/io/reactivex/rxjava3/internal/jdk8/ObservableStageSubscriberOrErrorTest.java @@ -0,0 +1,469 @@ +/** + * 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.rxjava3.internal.jdk8; + +import static org.junit.Assert.*; + +import java.util.NoSuchElementException; +import java.util.concurrent.CompletableFuture; + +import org.junit.Test; + +import io.reactivex.rxjava3.core.*; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.exceptions.*; +import io.reactivex.rxjava3.subjects.*; +import io.reactivex.rxjava3.testsupport.TestHelper; + +public class ObservableStageSubscriberOrErrorTest extends RxJavaTest { + + @Test + public void firstJust() throws Exception { + Integer v = Observable.just(1) + .firstOrErrorStage() + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + } + + @Test + public void firstEmpty() throws Exception { + TestHelper.assertError( + Observable.empty() + .firstOrErrorStage() + .toCompletableFuture(), NoSuchElementException.class); + } + + @Test + public void firstCancels() throws Exception { + BehaviorSubject source = BehaviorSubject.createDefault(1); + + Integer v = source + .firstOrErrorStage() + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + + assertFalse(source.hasObservers()); + } + + @Test + public void firstCompletableFutureCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .firstOrErrorStage() + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.cancel(true); + + assertTrue(cf.isCancelled()); + + assertFalse(source.hasObservers()); + } + + @Test + public void firstCompletableManualCompleteCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .firstOrErrorStage() + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.complete(1); + + assertTrue(cf.isDone()); + assertFalse(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + assertFalse(source.hasObservers()); + + assertEquals((Integer)1, cf.get()); + } + + @Test + public void firstCompletableManualCompleteExceptionallyCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .firstOrErrorStage() + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.completeExceptionally(new TestException()); + + assertTrue(cf.isDone()); + assertTrue(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + assertFalse(source.hasObservers()); + + TestHelper.assertError(cf, TestException.class); + } + + @Test + public void firstError() throws Exception { + CompletableFuture cf = Observable.error(new TestException()) + .firstOrErrorStage() + .toCompletableFuture(); + + assertTrue(cf.isDone()); + assertTrue(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + TestHelper.assertError(cf, TestException.class); + } + + @Test + public void firstSourceIgnoresCancel() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Integer v = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .firstOrErrorStage() + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + }); + } + + @Test + public void firstDoubleOnSubscribe() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Integer v = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + } + } + .firstOrErrorStage() + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + }); + } + + @Test + public void singleJust() throws Exception { + Integer v = Observable.just(1) + .singleOrErrorStage() + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + } + + @Test + public void singleEmpty() throws Exception { + TestHelper.assertError( + Observable.empty() + .singleOrErrorStage() + .toCompletableFuture(), NoSuchElementException.class); + } + + @Test + public void singleTooManyCancels() throws Exception { + ReplaySubject source = ReplaySubject.create(); + source.onNext(1); + source.onNext(2); + + TestHelper.assertError(source + .singleOrErrorStage() + .toCompletableFuture(), IllegalArgumentException.class); + + assertFalse(source.hasObservers()); + } + + @Test + public void singleCompletableFutureCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .singleOrErrorStage() + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.cancel(true); + + assertTrue(cf.isCancelled()); + + assertFalse(source.hasObservers()); + } + + @Test + public void singleCompletableManualCompleteCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .singleOrErrorStage() + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.complete(1); + + assertTrue(cf.isDone()); + assertFalse(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + assertFalse(source.hasObservers()); + + assertEquals((Integer)1, cf.get()); + } + + @Test + public void singleCompletableManualCompleteExceptionallyCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .singleOrErrorStage() + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.completeExceptionally(new TestException()); + + assertTrue(cf.isDone()); + assertTrue(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + assertFalse(source.hasObservers()); + + TestHelper.assertError(cf, TestException.class); + } + + @Test + public void singleError() throws Exception { + CompletableFuture cf = Observable.error(new TestException()) + .singleOrErrorStage() + .toCompletableFuture(); + + assertTrue(cf.isDone()); + assertTrue(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + TestHelper.assertError(cf, TestException.class); + } + + @Test + public void singleSourceIgnoresCancel() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Integer v = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onComplete(); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .singleOrErrorStage() + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + }); + } + + @Test + public void singleDoubleOnSubscribe() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Integer v = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onComplete(); + } + } + .singleOrErrorStage() + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + }); + } + + @Test + public void lastJust() throws Exception { + Integer v = Observable.just(1) + .lastOrErrorStage() + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + } + + @Test + public void lastRange() throws Exception { + Integer v = Observable.range(1, 5) + .lastOrErrorStage() + .toCompletableFuture() + .get(); + + assertEquals((Integer)5, v); + } + + @Test + public void lastEmpty() throws Exception { + TestHelper.assertError(Observable.empty() + .lastOrErrorStage() + .toCompletableFuture(), NoSuchElementException.class); + } + + @Test + public void lastCompletableFutureCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .lastOrErrorStage() + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.cancel(true); + + assertTrue(cf.isCancelled()); + + assertFalse(source.hasObservers()); + } + + @Test + public void lastCompletableManualCompleteCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .lastOrErrorStage() + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.complete(1); + + assertTrue(cf.isDone()); + assertFalse(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + assertFalse(source.hasObservers()); + + assertEquals((Integer)1, cf.get()); + } + + @Test + public void lastCompletableManualCompleteExceptionallyCancels() throws Exception { + PublishSubject source = PublishSubject.create(); + + CompletableFuture cf = source + .lastOrErrorStage() + .toCompletableFuture(); + + assertTrue(source.hasObservers()); + + cf.completeExceptionally(new TestException()); + + assertTrue(cf.isDone()); + assertTrue(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + assertFalse(source.hasObservers()); + + TestHelper.assertError(cf, TestException.class); + } + + @Test + public void lastError() throws Exception { + CompletableFuture cf = Observable.error(new TestException()) + .lastOrErrorStage() + .toCompletableFuture(); + + assertTrue(cf.isDone()); + assertTrue(cf.isCompletedExceptionally()); + assertFalse(cf.isCancelled()); + + TestHelper.assertError(cf, TestException.class); + } + + @Test + public void lastSourceIgnoresCancel() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Integer v = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onComplete(); + observer.onError(new TestException()); + observer.onComplete(); + } + } + .lastOrErrorStage() + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + + TestHelper.assertUndeliverable(errors, 0, TestException.class); + }); + } + + @Test + public void lastDoubleOnSubscribe() throws Throwable { + TestHelper.withErrorTracking(errors -> { + Integer v = new Observable() { + @Override + protected void subscribeActual(Observer observer) { + observer.onSubscribe(Disposable.empty()); + observer.onSubscribe(Disposable.empty()); + observer.onNext(1); + observer.onComplete(); + } + } + .lastOrErrorStage() + .toCompletableFuture() + .get(); + + assertEquals((Integer)1, v); + + TestHelper.assertError(errors, 0, ProtocolViolationException.class); + }); + } +} diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapEagerTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapEagerTest.java index e7625da6f9..0adfd655b0 100644 --- a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapEagerTest.java +++ b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableConcatMapEagerTest.java @@ -882,14 +882,14 @@ public Integer call() throws Exception { @Test public void innerErrorAfterPoll() { - final UnicastProcessor us = UnicastProcessor.create(); - us.onNext(1); + final UnicastProcessor up = UnicastProcessor.create(); + up.onNext(1); TestSubscriber ts = new TestSubscriber() { @Override public void onNext(Integer t) { super.onNext(t); - us.onError(new TestException()); + up.onError(new TestException()); } }; @@ -897,7 +897,7 @@ public void onNext(Integer t) { .concatMapEager(new Function>() { @Override public Flowable apply(Integer v) throws Exception { - return us; + return up; } }, 1, 128) .subscribe(ts); @@ -973,12 +973,12 @@ public Integer apply(Integer v) throws Exception { @Test public void fuseAndTake() { - UnicastProcessor us = UnicastProcessor.create(); + UnicastProcessor up = UnicastProcessor.create(); - us.onNext(1); - us.onComplete(); + up.onNext(1); + up.onComplete(); - us.concatMapEager(new Function>() { + up.concatMapEager(new Function>() { @Override public Flowable apply(Integer v) throws Exception { return Flowable.just(1); diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDistinctTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDistinctTest.java index 70682d9e03..87d5cda476 100644 --- a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDistinctTest.java +++ b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableDistinctTest.java @@ -128,13 +128,13 @@ public void fusedSync() { public void fusedAsync() { TestSubscriberEx ts = new TestSubscriberEx().setInitialFusionMode(QueueFuseable.ANY); - UnicastProcessor us = UnicastProcessor.create(); + UnicastProcessor up = UnicastProcessor.create(); - us + up .distinct() .subscribe(ts); - TestHelper.emit(us, 1, 1, 2, 1, 3, 2, 4, 5, 4); + TestHelper.emit(up, 1, 1, 2, 1, 3, 2, 4, 5, 4); ts.assertFusionMode(QueueFuseable.ASYNC) .assertResult(1, 2, 3, 4, 5); diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFilterTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFilterTest.java index 86ad5aa874..586a7b56c2 100644 --- a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFilterTest.java +++ b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableFilterTest.java @@ -557,9 +557,9 @@ public boolean test(Integer v) throws Exception { public void fusedAsync() { TestSubscriberEx ts = new TestSubscriberEx().setInitialFusionMode(QueueFuseable.ANY); - UnicastProcessor us = UnicastProcessor.create(); + UnicastProcessor up = UnicastProcessor.create(); - us + up .filter(new Predicate() { @Override public boolean test(Integer v) throws Exception { @@ -568,7 +568,7 @@ public boolean test(Integer v) throws Exception { }) .subscribe(ts); - TestHelper.emit(us, 1, 2, 3, 4, 5); + TestHelper.emit(up, 1, 2, 3, 4, 5); ts.assertFusionMode(QueueFuseable.ASYNC) .assertResult(2, 4); diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableMapTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableMapTest.java index c12aafa21d..1c0e6c58a3 100644 --- a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableMapTest.java +++ b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableMapTest.java @@ -583,13 +583,13 @@ public void fusedSync() { public void fusedAsync() { TestSubscriberEx ts = new TestSubscriberEx().setInitialFusionMode(QueueFuseable.ANY); - UnicastProcessor us = UnicastProcessor.create(); + UnicastProcessor up = UnicastProcessor.create(); - us + up .map(Functions.identity()) .subscribe(ts); - TestHelper.emit(us, 1, 2, 3, 4, 5); + TestHelper.emit(up, 1, 2, 3, 4, 5); ts.assertFusionMode(QueueFuseable.ASYNC) .assertResult(1, 2, 3, 4, 5); diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableObserveOnTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableObserveOnTest.java index 73efdfc426..7fbf19fa60 100644 --- a/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableObserveOnTest.java +++ b/src/test/java/io/reactivex/rxjava3/internal/operators/flowable/FlowableObserveOnTest.java @@ -1193,11 +1193,11 @@ public void inputSyncFused() { @Test public void inputAsyncFused() { - UnicastProcessor us = UnicastProcessor.create(); + UnicastProcessor up = UnicastProcessor.create(); - TestSubscriber ts = us.observeOn(Schedulers.single()).test(); + TestSubscriber ts = up.observeOn(Schedulers.single()).test(); - TestHelper.emit(us, 1, 2, 3, 4, 5); + TestHelper.emit(up, 1, 2, 3, 4, 5); ts .awaitDone(5, TimeUnit.SECONDS) @@ -1206,11 +1206,11 @@ public void inputAsyncFused() { @Test public void inputAsyncFusedError() { - UnicastProcessor us = UnicastProcessor.create(); + UnicastProcessor up = UnicastProcessor.create(); - TestSubscriber ts = us.observeOn(Schedulers.single()).test(); + TestSubscriber ts = up.observeOn(Schedulers.single()).test(); - us.onError(new TestException()); + up.onError(new TestException()); ts .awaitDone(5, TimeUnit.SECONDS) @@ -1219,11 +1219,11 @@ public void inputAsyncFusedError() { @Test public void inputAsyncFusedErrorDelayed() { - UnicastProcessor us = UnicastProcessor.create(); + UnicastProcessor up = UnicastProcessor.create(); - TestSubscriber ts = us.observeOn(Schedulers.single(), true).test(); + TestSubscriber ts = up.observeOn(Schedulers.single(), true).test(); - us.onError(new TestException()); + up.onError(new TestException()); ts .awaitDone(5, TimeUnit.SECONDS) @@ -1260,12 +1260,12 @@ public void outputFusedReject() { public void inputOutputAsyncFusedError() { TestSubscriberEx ts = new TestSubscriberEx().setInitialFusionMode(QueueFuseable.ANY); - UnicastProcessor us = UnicastProcessor.create(); + UnicastProcessor up = UnicastProcessor.create(); - us.observeOn(Schedulers.single()) + up.observeOn(Schedulers.single()) .subscribe(ts); - us.onError(new TestException()); + up.onError(new TestException()); ts .awaitDone(5, TimeUnit.SECONDS) @@ -1280,12 +1280,12 @@ public void inputOutputAsyncFusedError() { public void inputOutputAsyncFusedErrorDelayed() { TestSubscriberEx ts = new TestSubscriberEx().setInitialFusionMode(QueueFuseable.ANY); - UnicastProcessor us = UnicastProcessor.create(); + UnicastProcessor up = UnicastProcessor.create(); - us.observeOn(Schedulers.single(), true) + up.observeOn(Schedulers.single(), true) .subscribe(ts); - us.onError(new TestException()); + up.onError(new TestException()); ts .awaitDone(5, TimeUnit.SECONDS) @@ -1298,11 +1298,11 @@ public void inputOutputAsyncFusedErrorDelayed() { @Test public void outputFusedCancelReentrant() throws Exception { - final UnicastProcessor us = UnicastProcessor.create(); + final UnicastProcessor up = UnicastProcessor.create(); final CountDownLatch cdl = new CountDownLatch(1); - us.observeOn(Schedulers.single()) + up.observeOn(Schedulers.single()) .subscribe(new FlowableSubscriber() { Subscription upstream; int count; @@ -1315,7 +1315,7 @@ public void onSubscribe(Subscription s) { @Override public void onNext(Integer value) { if (++count == 1) { - us.onNext(2); + up.onNext(2); upstream.cancel(); cdl.countDown(); } @@ -1332,7 +1332,7 @@ public void onComplete() { } }); - us.onNext(1); + up.onNext(1); cdl.await(); } diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDistinctUntilChangedTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDistinctUntilChangedTest.java index da1d67e819..190506aed2 100644 --- a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDistinctUntilChangedTest.java +++ b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDistinctUntilChangedTest.java @@ -169,9 +169,9 @@ public boolean test(Integer a, Integer b) throws Exception { public void fusedAsync() { TestObserverEx to = new TestObserverEx<>(QueueFuseable.ANY); - UnicastSubject up = UnicastSubject.create(); + UnicastSubject us = UnicastSubject.create(); - up + us .distinctUntilChanged(new BiPredicate() { @Override public boolean test(Integer a, Integer b) throws Exception { @@ -180,7 +180,7 @@ public boolean test(Integer a, Integer b) throws Exception { }) .subscribe(to); - TestHelper.emit(up, 1, 2, 2, 3, 3, 4, 5); + TestHelper.emit(us, 1, 2, 2, 3, 3, 4, 5); to.assertFuseable() .assertFusionMode(QueueFuseable.ASYNC) diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoAfterNextTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoAfterNextTest.java index 5b6cd18987..411989a0f1 100644 --- a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoAfterNextTest.java +++ b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoAfterNextTest.java @@ -131,11 +131,11 @@ public void asyncFusedRejected() { public void asyncFused() { TestObserverEx to0 = new TestObserverEx<>(QueueFuseable.ASYNC); - UnicastSubject up = UnicastSubject.create(); + UnicastSubject us = UnicastSubject.create(); - TestHelper.emit(up, 1, 2, 3, 4, 5); + TestHelper.emit(us, 1, 2, 3, 4, 5); - up + us .doAfterNext(afterNext) .subscribe(to0); @@ -228,11 +228,11 @@ public void asyncFusedRejectedConditional() { public void asyncFusedConditional() { TestObserverEx to0 = new TestObserverEx<>(QueueFuseable.ASYNC); - UnicastSubject up = UnicastSubject.create(); + UnicastSubject us = UnicastSubject.create(); - TestHelper.emit(up, 1, 2, 3, 4, 5); + TestHelper.emit(us, 1, 2, 3, 4, 5); - up + us .doAfterNext(afterNext) .filter(Functions.alwaysTrue()) .subscribe(to0); diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoFinallyTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoFinallyTest.java index a0eeccb405..0cc21b8f13 100644 --- a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoFinallyTest.java +++ b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoFinallyTest.java @@ -129,10 +129,10 @@ public void syncFusedBoundary() { public void asyncFused() { TestObserverEx to = new TestObserverEx<>(QueueFuseable.ASYNC); - UnicastSubject up = UnicastSubject.create(); - TestHelper.emit(up, 1, 2, 3, 4, 5); + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5); - up + us .doFinally(this) .subscribe(to); @@ -146,10 +146,10 @@ public void asyncFused() { public void asyncFusedBoundary() { TestObserverEx to = new TestObserverEx<>(QueueFuseable.ASYNC | QueueFuseable.BOUNDARY); - UnicastSubject up = UnicastSubject.create(); - TestHelper.emit(up, 1, 2, 3, 4, 5); + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5); - up + us .doFinally(this) .subscribe(to); @@ -267,10 +267,10 @@ public void syncFusedBoundaryConditional() { public void asyncFusedConditional() { TestObserverEx to = new TestObserverEx<>(QueueFuseable.ASYNC); - UnicastSubject up = UnicastSubject.create(); - TestHelper.emit(up, 1, 2, 3, 4, 5); + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5); - up + us .doFinally(this) .filter(Functions.alwaysTrue()) .subscribe(to); @@ -285,10 +285,10 @@ public void asyncFusedConditional() { public void asyncFusedBoundaryConditional() { TestObserverEx to = new TestObserverEx<>(QueueFuseable.ASYNC | QueueFuseable.BOUNDARY); - UnicastSubject up = UnicastSubject.create(); - TestHelper.emit(up, 1, 2, 3, 4, 5); + UnicastSubject us = UnicastSubject.create(); + TestHelper.emit(us, 1, 2, 3, 4, 5); - up + us .doFinally(this) .filter(Functions.alwaysTrue()) .subscribe(to); diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoOnEachTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoOnEachTest.java index 0482d23906..6b8627b02b 100644 --- a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoOnEachTest.java +++ b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableDoOnEachTest.java @@ -596,9 +596,9 @@ public void fusedAsync() { final int[] call = { 0, 0 }; - UnicastSubject up = UnicastSubject.create(); + UnicastSubject us = UnicastSubject.create(); - up + us .doOnNext(new Consumer() { @Override public void accept(Integer v) throws Exception { @@ -613,7 +613,7 @@ public void run() throws Exception { }) .subscribe(to); - TestHelper.emit(up, 1, 2, 3, 4, 5); + TestHelper.emit(us, 1, 2, 3, 4, 5); to.assertFuseable() .assertFusionMode(QueueFuseable.ASYNC) @@ -630,9 +630,9 @@ public void fusedAsyncConditional() { final int[] call = { 0, 0 }; - UnicastSubject up = UnicastSubject.create(); + UnicastSubject us = UnicastSubject.create(); - up + us .doOnNext(new Consumer() { @Override public void accept(Integer v) throws Exception { @@ -648,7 +648,7 @@ public void run() throws Exception { .filter(Functions.alwaysTrue()) .subscribe(to); - TestHelper.emit(up, 1, 2, 3, 4, 5); + TestHelper.emit(us, 1, 2, 3, 4, 5); to.assertFuseable() .assertFusionMode(QueueFuseable.ASYNC) @@ -665,9 +665,9 @@ public void fusedAsyncConditional2() { final int[] call = { 0, 0 }; - UnicastSubject up = UnicastSubject.create(); + UnicastSubject us = UnicastSubject.create(); - up.hide() + us.hide() .doOnNext(new Consumer() { @Override public void accept(Integer v) throws Exception { @@ -683,7 +683,7 @@ public void run() throws Exception { .filter(Functions.alwaysTrue()) .subscribe(to); - TestHelper.emit(up, 1, 2, 3, 4, 5); + TestHelper.emit(us, 1, 2, 3, 4, 5); to.assertFuseable() .assertFusionMode(QueueFuseable.NONE) diff --git a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableObserveOnTest.java b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableObserveOnTest.java index 579b713313..2b8c2e0274 100644 --- a/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableObserveOnTest.java +++ b/src/test/java/io/reactivex/rxjava3/internal/operators/observable/ObservableObserveOnTest.java @@ -775,12 +775,12 @@ public void workerNotDisposedPrematurelySyncInNormalOut() { public void workerNotDisposedPrematurelyAsyncInNormalOut() { DisposeTrackingScheduler s = new DisposeTrackingScheduler(); - UnicastSubject up = UnicastSubject.create(); - up.onNext(1); - up.onComplete(); + UnicastSubject us = UnicastSubject.create(); + us.onNext(1); + us.onComplete(); Observable.concat( - up.observeOn(s), + us.observeOn(s), Observable.just(2) ) .test() diff --git a/src/test/java/io/reactivex/rxjava3/processors/UnicastProcessorTest.java b/src/test/java/io/reactivex/rxjava3/processors/UnicastProcessorTest.java index 53b7062028..002d6a17d3 100644 --- a/src/test/java/io/reactivex/rxjava3/processors/UnicastProcessorTest.java +++ b/src/test/java/io/reactivex/rxjava3/processors/UnicastProcessorTest.java @@ -131,14 +131,14 @@ public void run() { public void onTerminateCalledWhenOnError() { final AtomicBoolean didRunOnTerminate = new AtomicBoolean(); - UnicastProcessor us = UnicastProcessor.create(Observable.bufferSize(), new Runnable() { + UnicastProcessor up = UnicastProcessor.create(Observable.bufferSize(), new Runnable() { @Override public void run() { didRunOnTerminate.set(true); } }); assertFalse(didRunOnTerminate.get()); - us.onError(new RuntimeException("some error")); + up.onError(new RuntimeException("some error")); assertTrue(didRunOnTerminate.get()); } @@ -146,14 +146,14 @@ public void onTerminateCalledWhenOnError() { public void onTerminateCalledWhenOnComplete() { final AtomicBoolean didRunOnTerminate = new AtomicBoolean(); - UnicastProcessor us = UnicastProcessor.create(Observable.bufferSize(), new Runnable() { + UnicastProcessor up = UnicastProcessor.create(Observable.bufferSize(), new Runnable() { @Override public void run() { didRunOnTerminate.set(true); } }); assertFalse(didRunOnTerminate.get()); - us.onComplete(); + up.onComplete(); assertTrue(didRunOnTerminate.get()); } @@ -161,13 +161,13 @@ public void onTerminateCalledWhenOnComplete() { public void onTerminateCalledWhenCanceled() { final AtomicBoolean didRunOnTerminate = new AtomicBoolean(); - UnicastProcessor us = UnicastProcessor.create(Observable.bufferSize(), new Runnable() { + UnicastProcessor up = UnicastProcessor.create(Observable.bufferSize(), new Runnable() { @Override public void run() { didRunOnTerminate.set(true); } }); - final Disposable subscribe = us.subscribe(); + final Disposable subscribe = up.subscribe(); assertFalse(didRunOnTerminate.get()); subscribe.dispose(); @@ -327,7 +327,7 @@ public void run() { @Test public void subscribeRace() { for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { - final UnicastProcessor us = UnicastProcessor.create(); + final UnicastProcessor up = UnicastProcessor.create(); final TestSubscriberEx ts1 = new TestSubscriberEx<>(); final TestSubscriberEx ts2 = new TestSubscriberEx<>(); @@ -335,14 +335,14 @@ public void subscribeRace() { Runnable r1 = new Runnable() { @Override public void run() { - us.subscribe(ts1); + up.subscribe(ts1); } }; Runnable r2 = new Runnable() { @Override public void run() { - us.subscribe(ts2); + up.subscribe(ts2); } }; @@ -361,67 +361,67 @@ public void run() { @Test public void hasObservers() { - UnicastProcessor us = UnicastProcessor.create(); + UnicastProcessor up = UnicastProcessor.create(); - assertFalse(us.hasSubscribers()); + assertFalse(up.hasSubscribers()); - TestSubscriber ts = us.test(); + TestSubscriber ts = up.test(); - assertTrue(us.hasSubscribers()); + assertTrue(up.hasSubscribers()); ts.cancel(); - assertFalse(us.hasSubscribers()); + assertFalse(up.hasSubscribers()); } @Test public void drainFusedFailFast() { - UnicastProcessor us = UnicastProcessor.create(false); + UnicastProcessor up = UnicastProcessor.create(false); - TestSubscriberEx ts = us.to(TestHelper.testSubscriber(1, QueueFuseable.ANY, false)); + TestSubscriberEx ts = up.to(TestHelper.testSubscriber(1, QueueFuseable.ANY, false)); - us.done = true; - us.drainFused(ts); + up.done = true; + up.drainFused(ts); ts.assertResult(); } @Test public void drainFusedFailFastEmpty() { - UnicastProcessor us = UnicastProcessor.create(false); + UnicastProcessor up = UnicastProcessor.create(false); - TestSubscriberEx ts = us.to(TestHelper.testSubscriber(1, QueueFuseable.ANY, false)); + TestSubscriberEx ts = up.to(TestHelper.testSubscriber(1, QueueFuseable.ANY, false)); - us.drainFused(ts); + up.drainFused(ts); ts.assertEmpty(); } @Test public void checkTerminatedFailFastEmpty() { - UnicastProcessor us = UnicastProcessor.create(false); + UnicastProcessor up = UnicastProcessor.create(false); - TestSubscriberEx ts = us.to(TestHelper.testSubscriber(1, QueueFuseable.ANY, false)); + TestSubscriberEx ts = up.to(TestHelper.testSubscriber(1, QueueFuseable.ANY, false)); - us.checkTerminated(true, true, false, ts, us.queue); + up.checkTerminated(true, true, false, ts, up.queue); ts.assertEmpty(); } @Test public void alreadyCancelled() { - UnicastProcessor us = UnicastProcessor.create(false); + UnicastProcessor up = UnicastProcessor.create(false); - us.test().cancel(); + up.test().cancel(); BooleanSubscription bs = new BooleanSubscription(); - us.onSubscribe(bs); + up.onSubscribe(bs); assertTrue(bs.isCancelled()); List errors = TestHelper.trackPluginErrors(); try { - us.onError(new TestException()); + up.onError(new TestException()); TestHelper.assertUndeliverable(errors, 0, TestException.class); } finally { @@ -431,9 +431,9 @@ public void alreadyCancelled() { @Test public void unicastSubscriptionBadRequest() { - UnicastProcessor us = UnicastProcessor.create(false); + UnicastProcessor up = UnicastProcessor.create(false); - UnicastProcessor.UnicastQueueSubscription usc = (UnicastProcessor.UnicastQueueSubscription)us.wip; + UnicastProcessor.UnicastQueueSubscription usc = (UnicastProcessor.UnicastQueueSubscription)up.wip; List errors = TestHelper.trackPluginErrors(); try { @@ -449,17 +449,17 @@ public void fusedNoConcurrentCleanDueToCancel() { for (int j = 0; j < TestHelper.RACE_LONG_LOOPS; j++) { List errors = TestHelper.trackPluginErrors(); try { - final UnicastProcessor us = UnicastProcessor.create(); + final UnicastProcessor up = UnicastProcessor.create(); - TestObserver to = us + TestObserver to = up .observeOn(Schedulers.io()) .map(Functions.identity()) .observeOn(Schedulers.single()) .firstOrError() .test(); - for (int i = 0; us.hasSubscribers(); i++) { - us.onNext(i); + for (int i = 0; up.hasSubscribers(); i++) { + up.onNext(i); } to diff --git a/src/test/java/io/reactivex/rxjava3/subjects/UnicastSubjectTest.java b/src/test/java/io/reactivex/rxjava3/subjects/UnicastSubjectTest.java index 17e4cb0a72..ac000e4228 100644 --- a/src/test/java/io/reactivex/rxjava3/subjects/UnicastSubjectTest.java +++ b/src/test/java/io/reactivex/rxjava3/subjects/UnicastSubjectTest.java @@ -228,14 +228,14 @@ public void zeroCapacityHint() { public void completeCancelRace() { for (int i = 0; i < TestHelper.RACE_DEFAULT_LOOPS; i++) { final int[] calls = { 0 }; - final UnicastSubject up = UnicastSubject.create(100, new Runnable() { + final UnicastSubject us = UnicastSubject.create(100, new Runnable() { @Override public void run() { calls[0]++; } }); - final TestObserver to = up.test(); + final TestObserver to = us.test(); Runnable r1 = new Runnable() { @Override @@ -247,7 +247,7 @@ public void run() { Runnable r2 = new Runnable() { @Override public void run() { - up.onComplete(); + us.onComplete(); } }; diff --git a/src/test/java/io/reactivex/rxjava3/testsupport/TestObserverExTest.java b/src/test/java/io/reactivex/rxjava3/testsupport/TestObserverExTest.java index 9ee8a1cfc5..0d6edd6c3d 100644 --- a/src/test/java/io/reactivex/rxjava3/testsupport/TestObserverExTest.java +++ b/src/test/java/io/reactivex/rxjava3/testsupport/TestObserverExTest.java @@ -1094,16 +1094,16 @@ public void asyncQueueThrows() { TestObserverEx to = new TestObserverEx<>(); to.setInitialFusionMode(QueueFuseable.ANY); - UnicastSubject up = UnicastSubject.create(); + UnicastSubject us = UnicastSubject.create(); - up + us .map(new Function() { @Override public Object apply(Integer v) throws Exception { throw new TestException(); } }) .subscribe(to); - up.onNext(1); + us.onNext(1); to.assertSubscribed() .assertFuseable() @@ -1132,13 +1132,13 @@ public void asyncFusion() { TestObserverEx to = new TestObserverEx<>(); to.setInitialFusionMode(QueueFuseable.ANY); - UnicastSubject up = UnicastSubject.create(); + UnicastSubject us = UnicastSubject.create(); - up + us .subscribe(to); - up.onNext(1); - up.onComplete(); + us.onNext(1); + us.onComplete(); to.assertSubscribed() .assertFuseable() diff --git a/src/test/java/io/reactivex/rxjava3/validators/CheckLocalVariablesInTests.java b/src/test/java/io/reactivex/rxjava3/validators/CheckLocalVariablesInTests.java index 40a976a165..df6f0a378a 100644 --- a/src/test/java/io/reactivex/rxjava3/validators/CheckLocalVariablesInTests.java +++ b/src/test/java/io/reactivex/rxjava3/validators/CheckLocalVariablesInTests.java @@ -94,7 +94,15 @@ static void findPattern(String pattern, boolean checkMain) throws Exception { .append(fname) .append("#L").append(lineNum) .append(" ").append(line) - .append("\n"); + .append("\n") + .append(" at ") + .append(fname.replace(".java", "")) + .append(".method(") + .append(fname) + .append(":") + .append(lineNum) + .append(")\n"); + total++; } } @@ -111,9 +119,7 @@ static void findPattern(String pattern, boolean checkMain) throws Exception { } } if (total != 0) { - fail.append("Found ") - .append(total) - .append(" instances"); + fail.insert(0, "Found " + total + " instances"); System.out.println(fail); throw new AssertionError(fail.toString()); } @@ -149,6 +155,16 @@ public void publishProcessorAsPs() throws Exception { findPattern("PublishProcessor<.*>\\s+ps"); } + @Test + public void unicastSubjectAsUp() throws Exception { + findPattern("UnicastSubject<.*>\\s+up"); + } + + @Test + public void unicastProcessorAsUs() throws Exception { + findPattern("UnicastProcessor<.*>\\s+us"); + } + @Test public void behaviorProcessorAsBs() throws Exception { findPattern("BehaviorProcessor<.*>\\s+bs"); diff --git a/src/test/java/io/reactivex/rxjava3/validators/JavadocWording.java b/src/test/java/io/reactivex/rxjava3/validators/JavadocWording.java index e8219df148..4a4baaf8ae 100644 --- a/src/test/java/io/reactivex/rxjava3/validators/JavadocWording.java +++ b/src/test/java/io/reactivex/rxjava3/validators/JavadocWording.java @@ -57,8 +57,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception { && !m.signature.contains("Flowable") && !m.signature.contains("Observable") && !m.signature.contains("ObservableSource")) { - e.append("java.lang.RuntimeException: Maybe doc mentions onNext but no Flowable/Observable in signature\r\n at io.reactivex.") - .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Maybe doc mentions onNext but no Flowable/Observable in signature\r\n at io.reactivex.rxjava3.core.") + .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -74,8 +74,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception { && !m.signature.contains("Flowable") && !m.signature.contains("TestSubscriber") ) { - e.append("java.lang.RuntimeException: Maybe doc mentions Subscriber but not using Flowable\r\n at io.reactivex.") - .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Maybe doc mentions Subscriber but not using Flowable\r\n at io.reactivex.rxjava3.core.") + .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -90,8 +90,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception { if (!m.signature.contains("Publisher") && !m.signature.contains("Flowable") ) { - e.append("java.lang.RuntimeException: Maybe doc mentions Subscription but not using Flowable\r\n at io.reactivex.") - .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Maybe doc mentions Subscription but not using Flowable\r\n at io.reactivex.rxjava3.core.") + .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -108,8 +108,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception { && !m.signature.contains("TestObserver")) { if (idx < 5 || !m.javadoc.substring(idx - 5, idx + 8).equals("MaybeObserver")) { - e.append("java.lang.RuntimeException: Maybe doc mentions Observer but not using Observable\r\n at io.reactivex.") - .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Maybe doc mentions Observer but not using Observable\r\n at io.reactivex.rxjava3.core.") + .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } } @@ -124,8 +124,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception { if (idx >= 0) { if (!m.signature.contains("Publisher")) { if (idx == 0 || !m.javadoc.substring(idx - 1, idx + 9).equals("(Publisher")) { - e.append("java.lang.RuntimeException: Maybe doc mentions Publisher but not in the signature\r\n at io.reactivex.") - .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Maybe doc mentions Publisher but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } } @@ -139,8 +139,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception { int idx = m.javadoc.indexOf("Flowable", jdx); if (idx >= 0) { if (!m.signature.contains("Flowable")) { - e.append("java.lang.RuntimeException: Maybe doc mentions Flowable but not in the signature\r\n at io.reactivex.") - .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Maybe doc mentions Flowable but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; } else { @@ -154,7 +154,7 @@ public void maybeDocRefersToMaybeTypes() throws Exception { int j = m.javadoc.indexOf("#toSingle", jdx); int k = m.javadoc.indexOf("{@code Single", jdx); if (!m.signature.contains("Single") && (j + 3 != idx && k + 7 != idx)) { - e.append("java.lang.RuntimeException: Maybe doc mentions Single but not in the signature\r\n at io.reactivex.") + e.append("java.lang.RuntimeException: Maybe doc mentions Single but not in the signature\r\n at io.reactivex.rxjava3.core.") .append("Maybe(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -167,8 +167,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception { int idx = m.javadoc.indexOf("SingleSource", jdx); if (idx >= 0) { if (!m.signature.contains("SingleSource")) { - e.append("java.lang.RuntimeException: Maybe doc mentions SingleSource but not in the signature\r\n at io.reactivex.") - .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Maybe doc mentions SingleSource but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; } else { @@ -180,8 +180,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception { int idx = m.javadoc.indexOf("Observable", jdx); if (idx >= 0) { if (!m.signature.contains("Observable")) { - e.append("java.lang.RuntimeException: Maybe doc mentions Observable but not in the signature\r\n at io.reactivex.") - .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Maybe doc mentions Observable but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; } else { @@ -193,8 +193,8 @@ public void maybeDocRefersToMaybeTypes() throws Exception { int idx = m.javadoc.indexOf("ObservableSource", jdx); if (idx >= 0) { if (!m.signature.contains("ObservableSource")) { - e.append("java.lang.RuntimeException: Maybe doc mentions ObservableSource but not in the signature\r\n at io.reactivex.") - .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Maybe doc mentions ObservableSource but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; } else { @@ -233,8 +233,8 @@ public void flowableDocRefersToFlowableTypes() throws Exception { && !m.signature.contains("MaybeSource") && !m.signature.contains("Single") && !m.signature.contains("SingleSource")) { - e.append("java.lang.RuntimeException: Flowable doc mentions onSuccess\r\n at io.reactivex.") - .append("Flowable (Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Flowable doc mentions onSuccess\r\n at io.reactivex.rxjava3.core.") + .append("Flowable.method(Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -248,8 +248,8 @@ public void flowableDocRefersToFlowableTypes() throws Exception { if (idx >= 0) { if (!m.signature.contains("ObservableSource") && !m.signature.contains("Observable")) { - e.append("java.lang.RuntimeException: Flowable doc mentions Observer but not using Flowable\r\n at io.reactivex.") - .append("Flowable (Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Flowable doc mentions Observer but not using Flowable\r\n at io.reactivex.rxjava3.core.") + .append("Flowable.method(Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -273,8 +273,8 @@ public void flowableDocRefersToFlowableTypes() throws Exception { ) { CharSequence subSequence = m.javadoc.subSequence(idx - 6, idx + 11); if (idx < 6 || !subSequence.equals("{@link Disposable")) { - e.append("java.lang.RuntimeException: Flowable doc mentions Disposable but not using Flowable\r\n at io.reactivex.") - .append("Flowable (Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Flowable doc mentions Disposable but not using Flowable\r\n at io.reactivex.rxjava3.core.") + .append("Flowable.method(Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } } @@ -288,8 +288,8 @@ public void flowableDocRefersToFlowableTypes() throws Exception { int idx = m.javadoc.indexOf("Observable", jdx); if (idx >= 0) { if (!m.signature.contains("Observable")) { - e.append("java.lang.RuntimeException: Flowable doc mentions Observable but not in the signature\r\n at io.reactivex.") - .append("Flowable (Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Flowable doc mentions Observable but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Flowable.method(Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -302,8 +302,8 @@ public void flowableDocRefersToFlowableTypes() throws Exception { int idx = m.javadoc.indexOf("ObservableSource", jdx); if (idx >= 0) { if (!m.signature.contains("ObservableSource")) { - e.append("java.lang.RuntimeException: Flowable doc mentions ObservableSource but not in the signature\r\n at io.reactivex.") - .append("Flowable (Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Flowable doc mentions ObservableSource but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Flowable.method(Flowable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; } else { @@ -342,8 +342,8 @@ public void observableDocRefersToObservableTypes() throws Exception { && !m.signature.contains("MaybeSource") && !m.signature.contains("Single") && !m.signature.contains("SingleSource")) { - e.append("java.lang.RuntimeException: Observable doc mentions onSuccess\r\n at io.reactivex.") - .append("Observable (Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Observable doc mentions onSuccess\r\n at io.reactivex.rxjava3.core.") + .append("Observable.method(Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -358,8 +358,8 @@ public void observableDocRefersToObservableTypes() throws Exception { if (!m.signature.contains("Flowable") && !m.signature.contains("Publisher") ) { - e.append("java.lang.RuntimeException: Observable doc mentions Subscription but not using Flowable\r\n at io.reactivex.") - .append("Observable (Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Observable doc mentions Subscription but not using Flowable\r\n at io.reactivex.rxjava3.core.") + .append("Observable.method(Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -373,8 +373,8 @@ public void observableDocRefersToObservableTypes() throws Exception { if (idx >= 0) { if (!m.signature.contains("Flowable")) { if (idx < 6 || !m.javadoc.substring(idx - 6, idx + 8).equals("@link Flowable")) { - e.append("java.lang.RuntimeException: Observable doc mentions Flowable but not in the signature\r\n at io.reactivex.") - .append("Observable (Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Observable doc mentions Flowable but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Observable.method(Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } } @@ -388,8 +388,8 @@ public void observableDocRefersToObservableTypes() throws Exception { int idx = m.javadoc.indexOf("Publisher", jdx); if (idx >= 0) { if (!m.signature.contains("Publisher")) { - e.append("java.lang.RuntimeException: Observable doc mentions Publisher but not in the signature\r\n at io.reactivex.") - .append("Observable (Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Observable doc mentions Publisher but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Observable.method(Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; } else { @@ -402,8 +402,8 @@ public void observableDocRefersToObservableTypes() throws Exception { if (idx >= 0) { if (!m.signature.contains("Publisher") && !m.signature.contains("Flowable")) { - e.append("java.lang.RuntimeException: Observable doc mentions Subscriber but not using Flowable\r\n at io.reactivex.") - .append("Observable (Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Observable doc mentions Subscriber but not using Flowable\r\n at io.reactivex.rxjava3.core.") + .append("Observable.method(Observable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -443,8 +443,8 @@ public void singleDocRefersToSingleTypes() throws Exception { && !m.signature.contains("Flowable") && !m.signature.contains("Observable") && !m.signature.contains("ObservableSource")) { - e.append("java.lang.RuntimeException: Single doc mentions onNext but no Flowable/Observable in signature\r\n at io.reactivex.") - .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Single doc mentions onNext but no Flowable/Observable in signature\r\n at io.reactivex.rxjava3.core.") + .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -459,8 +459,8 @@ public void singleDocRefersToSingleTypes() throws Exception { if (!m.signature.contains("Publisher") && !m.signature.contains("Flowable") && !m.signature.contains("TestSubscriber")) { - e.append("java.lang.RuntimeException: Single doc mentions Subscriber but not using Flowable\r\n at io.reactivex.") - .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Single doc mentions Subscriber but not using Flowable\r\n at io.reactivex.rxjava3.core.") + .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -475,8 +475,8 @@ public void singleDocRefersToSingleTypes() throws Exception { if (!m.signature.contains("Flowable") && !m.signature.contains("Publisher") ) { - e.append("java.lang.RuntimeException: Single doc mentions Subscription but not using Flowable\r\n at io.reactivex.") - .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Single doc mentions Subscription but not using Flowable\r\n at io.reactivex.rxjava3.core.") + .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -493,8 +493,8 @@ public void singleDocRefersToSingleTypes() throws Exception { && !m.signature.contains("TestObserver")) { if (idx < 6 || !m.javadoc.substring(idx - 6, idx + 8).equals("SingleObserver")) { - e.append("java.lang.RuntimeException: Single doc mentions Observer but not using Observable\r\n at io.reactivex.") - .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Single doc mentions Observer but not using Observable\r\n at io.reactivex.rxjava3.core.") + .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } } @@ -509,8 +509,8 @@ public void singleDocRefersToSingleTypes() throws Exception { if (idx >= 0) { if (!m.signature.contains("Publisher")) { if (idx == 0 || !m.javadoc.substring(idx - 1, idx + 9).equals("(Publisher")) { - e.append("java.lang.RuntimeException: Single doc mentions Publisher but not in the signature\r\n at io.reactivex.") - .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Single doc mentions Publisher but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } } @@ -524,8 +524,8 @@ public void singleDocRefersToSingleTypes() throws Exception { int idx = m.javadoc.indexOf(" Flowable", jdx); if (idx >= 0) { if (!m.signature.contains("Flowable")) { - e.append("java.lang.RuntimeException: Single doc mentions Flowable but not in the signature\r\n at io.reactivex.") - .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Single doc mentions Flowable but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; } else { @@ -537,8 +537,8 @@ public void singleDocRefersToSingleTypes() throws Exception { int idx = m.javadoc.indexOf(" Maybe", jdx); if (idx >= 0) { if (!m.signature.contains("Maybe")) { - e.append("java.lang.RuntimeException: Single doc mentions Maybe but not in the signature\r\n at io.reactivex.") - .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Single doc mentions Maybe but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; } else { @@ -550,8 +550,8 @@ public void singleDocRefersToSingleTypes() throws Exception { int idx = m.javadoc.indexOf(" MaybeSource", jdx); if (idx >= 0) { if (!m.signature.contains("MaybeSource")) { - e.append("java.lang.RuntimeException: Single doc mentions SingleSource but not in the signature\r\n at io.reactivex.") - .append("Maybe (Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Single doc mentions SingleSource but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Maybe.method(Maybe.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; } else { @@ -563,8 +563,8 @@ public void singleDocRefersToSingleTypes() throws Exception { int idx = m.javadoc.indexOf(" Observable", jdx); if (idx >= 0) { if (!m.signature.contains("Observable")) { - e.append("java.lang.RuntimeException: Single doc mentions Observable but not in the signature\r\n at io.reactivex.") - .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Single doc mentions Observable but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; } else { @@ -576,8 +576,8 @@ public void singleDocRefersToSingleTypes() throws Exception { int idx = m.javadoc.indexOf(" ObservableSource", jdx); if (idx >= 0) { if (!m.signature.contains("ObservableSource")) { - e.append("java.lang.RuntimeException: Single doc mentions ObservableSource but not in the signature\r\n at io.reactivex.") - .append("Single (Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Single doc mentions ObservableSource but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Single.method(Single.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; } else { @@ -617,8 +617,8 @@ public void completableDocRefersToCompletableTypes() throws Exception { && !m.signature.contains("Flowable") && !m.signature.contains("Observable") && !m.signature.contains("ObservableSource")) { - e.append("java.lang.RuntimeException: Completable doc mentions onNext but no Flowable/Observable in signature\r\n at io.reactivex.") - .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Completable doc mentions onNext but no Flowable/Observable in signature\r\n at io.reactivex.rxjava3.core.") + .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -633,8 +633,8 @@ public void completableDocRefersToCompletableTypes() throws Exception { if (!m.signature.contains("Publisher") && !m.signature.contains("Flowable") && !m.signature.contains("TestSubscriber")) { - e.append("java.lang.RuntimeException: Completable doc mentions Subscriber but not using Flowable\r\n at io.reactivex.") - .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Completable doc mentions Subscriber but not using Flowable\r\n at io.reactivex.rxjava3.core.") + .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -649,8 +649,8 @@ public void completableDocRefersToCompletableTypes() throws Exception { if (!m.signature.contains("Flowable") && !m.signature.contains("Publisher") ) { - e.append("java.lang.RuntimeException: Completable doc mentions Subscription but not using Flowable\r\n at io.reactivex.") - .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Completable doc mentions Subscription but not using Flowable\r\n at io.reactivex.rxjava3.core.") + .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } jdx = idx + 6; @@ -667,8 +667,8 @@ public void completableDocRefersToCompletableTypes() throws Exception { && !m.signature.contains("TestObserver")) { if (idx < 11 || !m.javadoc.substring(idx - 11, idx + 8).equals("CompletableObserver")) { - e.append("java.lang.RuntimeException: Completable doc mentions Observer but not using Observable\r\n at io.reactivex.") - .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Completable doc mentions Observer but not using Observable\r\n at io.reactivex.rxjava3.core.") + .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } } @@ -683,8 +683,8 @@ public void completableDocRefersToCompletableTypes() throws Exception { if (idx >= 0) { if (!m.signature.contains("Publisher")) { if (idx == 0 || !m.javadoc.substring(idx - 1, idx + 9).equals("(Publisher")) { - e.append("java.lang.RuntimeException: Completable doc mentions Publisher but not in the signature\r\n at io.reactivex.") - .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Completable doc mentions Publisher but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } } @@ -700,8 +700,8 @@ public void completableDocRefersToCompletableTypes() throws Exception { if (!m.signature.contains("Flowable")) { Pattern p = Pattern.compile("@see\\s+#[A-Za-z0-9 _.,()]*Flowable"); if (!p.matcher(m.javadoc).find()) { - e.append("java.lang.RuntimeException: Completable doc mentions Flowable but not in the signature\r\n at io.reactivex.") - .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Completable doc mentions Flowable but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } } jdx = idx + 6; @@ -716,8 +716,8 @@ public void completableDocRefersToCompletableTypes() throws Exception { if (!m.signature.contains("Single")) { Pattern p = Pattern.compile("@see\\s+#[A-Za-z0-9 _.,()]*Single"); if (!p.matcher(m.javadoc).find()) { - e.append("java.lang.RuntimeException: Completable doc mentions Single but not in the signature\r\n at io.reactivex.") - .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Completable doc mentions Single but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } } jdx = idx + 6; @@ -732,8 +732,8 @@ public void completableDocRefersToCompletableTypes() throws Exception { if (!m.signature.contains("SingleSource")) { Pattern p = Pattern.compile("@see\\s+#[A-Za-z0-9 _.,()]*SingleSource"); if (!p.matcher(m.javadoc).find()) { - e.append("java.lang.RuntimeException: Completable doc mentions SingleSource but not in the signature\r\n at io.reactivex.") - .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Completable doc mentions SingleSource but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } } jdx = idx + 6; @@ -748,8 +748,8 @@ public void completableDocRefersToCompletableTypes() throws Exception { if (!m.signature.contains("Observable")) { Pattern p = Pattern.compile("@see\\s+#[A-Za-z0-9 _.,()]*Observable"); if (!p.matcher(m.javadoc).find()) { - e.append("java.lang.RuntimeException: Completable doc mentions Observable but not in the signature\r\n at io.reactivex.") - .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Completable doc mentions Observable but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } } jdx = idx + 6; @@ -764,8 +764,8 @@ public void completableDocRefersToCompletableTypes() throws Exception { if (!m.signature.contains("ObservableSource")) { Pattern p = Pattern.compile("@see\\s+#[A-Za-z0-9 _.,()]*ObservableSource"); if (!p.matcher(m.javadoc).find()) { - e.append("java.lang.RuntimeException: Completable doc mentions ObservableSource but not in the signature\r\n at io.reactivex.") - .append("Completable (Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); + e.append("java.lang.RuntimeException: Completable doc mentions ObservableSource but not in the signature\r\n at io.reactivex.rxjava3.core.") + .append("Completable.method(Completable.java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); } } jdx = idx + 6; @@ -807,9 +807,9 @@ static void aOrAn(StringBuilder e, RxMethod m, String wrongPre, String word, Str if (idx >= 0) { e.append("java.lang.RuntimeException: a/an typo ") .append(word) - .append("\r\n at io.reactivex.") + .append("\r\n at io.reactivex.rxjava3.core.") .append(baseTypeName) - .append(" (") + .append(".method(") .append(baseTypeName) .append(".java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); jdx = idx + 6; @@ -824,9 +824,9 @@ static void aOrAn(StringBuilder e, RxMethod m, String wrongPre, String word, Str if (idx >= 0) { e.append("java.lang.RuntimeException: a/an typo ") .append(word) - .append("\r\n at io.reactivex.") + .append("\r\n at io.reactivex.rxjava3.core.") .append(baseTypeName) - .append(" (") + .append(".method(") .append(baseTypeName) .append(".java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); jdx = idx + 6; @@ -841,9 +841,9 @@ static void aOrAn(StringBuilder e, RxMethod m, String wrongPre, String word, Str if (idx >= 0) { e.append("java.lang.RuntimeException: a/an typo ") .append(word) - .append("\r\n at io.reactivex.") + .append("\r\n at io.reactivex.rxjava3.core.") .append(baseTypeName) - .append(" (") + .append(".method(") .append(baseTypeName) .append(".java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); jdx = idx + 6; @@ -858,9 +858,9 @@ static void aOrAn(StringBuilder e, RxMethod m, String wrongPre, String word, Str if (idx >= 0) { e.append("java.lang.RuntimeException: a/an typo ") .append(word) - .append("\r\n at io.reactivex.") + .append("\r\n at io.reactivex.rxjava3.core.") .append(baseTypeName) - .append(" (") + .append(".method(") .append(baseTypeName) .append(".java:").append(m.javadocLine + lineNumber(m.javadoc, idx) - 1).append(")\r\n\r\n"); jdx = idx + 6; @@ -895,9 +895,9 @@ static void aOrAn(StringBuilder e, RxMethod m, String wrongPre, String word, Str if (idx >= 0) { e.append("java.lang.RuntimeException: a/an typo ") .append(word) - .append("\r\n at io.reactivex.") + .append("\r\n at io.reactivex.rxjava3.core.") .append(baseTypeName) - .append(" (") + .append(".method(") .append(baseTypeName) .append(".java:").append(m.javadocLine).append(")\r\n\r\n"); jdx = idx + wrongPre.length() + 1 + word.length(); @@ -923,9 +923,9 @@ static void missingClosingDD(StringBuilder e, RxMethod m, String baseTypeName) { jdx = idx2 + 5; } else { e.append("java.lang.RuntimeException: unbalanced
") - .append("\r\n at io.reactivex.") + .append("\r\n at io.reactivex.rxjava3.core.") .append(baseTypeName) - .append(" (") + .append(".method(") .append(baseTypeName) .append(".java:").append(m.javadocLine + lineNumber(m.javadoc, idx1) - 1).append(")\r\n\r\n"); break; @@ -936,9 +936,9 @@ static void missingClosingDD(StringBuilder e, RxMethod m, String baseTypeName) { static void backpressureMentionedWithoutAnnotation(StringBuilder e, RxMethod m, String baseTypeName) { if (m.backpressureDocLine > 0 && m.backpressureKind == null) { e.append("java.lang.RuntimeException: backpressure documented but not annotated ") - .append("\r\n at io.reactivex.") + .append("\r\n at io.reactivex.rxjava3.core.") .append(baseTypeName) - .append(" (") + .append(".method(") .append(baseTypeName) .append(".java:").append(m.backpressureDocLine).append(")\r\n\r\n"); } diff --git a/src/test/java/io/reactivex/rxjava3/validators/ParamValidationCheckerTest.java b/src/test/java/io/reactivex/rxjava3/validators/ParamValidationCheckerTest.java index 5bc4306492..d8c3e32636 100644 --- a/src/test/java/io/reactivex/rxjava3/validators/ParamValidationCheckerTest.java +++ b/src/test/java/io/reactivex/rxjava3/validators/ParamValidationCheckerTest.java @@ -509,6 +509,10 @@ public void checkParallelFlowable() { addOverride(new ParamOverride(Flowable.class, 0, ParamMode.ANY, "singleStage", Object.class)); addOverride(new ParamOverride(Flowable.class, 0, ParamMode.ANY, "lastStage", Object.class)); + addOverride(new ParamOverride(Observable.class, 0, ParamMode.ANY, "firstStage", Object.class)); + addOverride(new ParamOverride(Observable.class, 0, ParamMode.ANY, "singleStage", Object.class)); + addOverride(new ParamOverride(Observable.class, 0, ParamMode.ANY, "lastStage", Object.class)); + addOverride(new ParamOverride(Maybe.class, 0, ParamMode.ANY, "toCompletionStage", Object.class)); addOverride(new ParamOverride(Completable.class, 0, ParamMode.ANY, "toCompletionStage", Object.class));