Skip to content

Commit cc40f64

Browse files
authored
4.x: Streamable +blockingFirst,last[OrError],skip ,S.flattenAsStreamable (#8229)
1 parent b6b09c6 commit cc40f64

20 files changed

Lines changed: 1004 additions & 8 deletions

src/main/java/io/reactivex/rxjava4/core/Completable.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3228,6 +3228,8 @@ public final Future<Void> toFuture() {
32283228
* <p>
32293229
* <img width="640" height="293" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Completable.toStreamable.png" alt="">
32303230
* <dl>
3231+
* <dt><b>Backpressure:</b></dt>
3232+
* <dd>The returned {@code Streamable} honors the backpressure of the downstream consumer.</dd>
32313233
* <dt><b>Scheduler:</b></dt>
32323234
* <dd>{@code toStreamable} does not operate by default on a particular {@link Scheduler}.</dd>
32333235
* </dl>
@@ -3236,6 +3238,7 @@ public final Future<Void> toFuture() {
32363238
* @since 4.0.0
32373239
*/
32383240
@SchedulerSupport(SchedulerSupport.NONE)
3241+
@BackpressureSupport(BackpressureKind.FULL)
32393242
@CheckReturnValue
32403243
@NonNull
32413244
public final <@NonNull T> Streamable<T> toStreamable() {

src/main/java/io/reactivex/rxjava4/core/Maybe.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3896,14 +3896,17 @@ public final Single<T> toSingle() {
38963896
* <p>
38973897
* <img width="640" height="346" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Maybe.toStreamable.png" alt="">
38983898
* <dl>
3899+
* <dt><b>Backpressure:</b></dt>
3900+
* <dd>The returned {@code Streamable} honors the backpressure of the downstream consumer.</dd>
38993901
* <dt><b>Scheduler:</b></dt>
39003902
* <dd>{@code toStreamable} does not operate by default on a particular {@link Scheduler}.</dd>
39013903
* </dl>
39023904
* @return the new {@code Streamable} instance
39033905
* @since 4.0.0
39043906
*/
3905-
@CheckReturnValue
39063907
@SchedulerSupport(SchedulerSupport.NONE)
3908+
@BackpressureSupport(BackpressureKind.FULL)
3909+
@CheckReturnValue
39073910
@NonNull
39083911
public final Streamable<T> toStreamable() {
39093912
return RxJavaPlugins.onAssembly(new StreamableFromMaybe<>(this));

src/main/java/io/reactivex/rxjava4/core/Single.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import io.reactivex.rxjava4.internal.operators.mixed.*;
3434
import io.reactivex.rxjava4.internal.operators.observable.ObservableSingleSingle;
3535
import io.reactivex.rxjava4.internal.operators.single.*;
36-
import io.reactivex.rxjava4.internal.operators.streamable.StreamableFromSingle;
36+
import io.reactivex.rxjava4.internal.operators.streamable.*;
3737
import io.reactivex.rxjava4.observers.TestObserver;
3838
import io.reactivex.rxjava4.plugins.RxJavaPlugins;
3939
import io.reactivex.rxjava4.schedulers.*;
@@ -2976,6 +2976,37 @@ public final Maybe<T> filter(@NonNull Predicate<? super T> predicate) {
29762976
return RxJavaPlugins.onAssembly(new SingleFlatMapIterableObservable<>(this, mapper));
29772977
}
29782978

2979+
/**
2980+
* Maps the success value of the current {@code Single} into an {@link Iterable} and emits its items as a
2981+
* {@link Streamable} sequence.
2982+
* <p>
2983+
* <img width="640" height="373" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flattenAsStreamable.png" alt="">
2984+
* <dl>
2985+
* <dt><b>Backpressure:</b></dt>
2986+
* <dd>The operator honors backpressure from downstream.</dd>
2987+
* <dt><b>Scheduler:</b></dt>
2988+
* <dd>{@code flattenAsStreamable} does not operate by default on a particular {@link Scheduler}.</dd>
2989+
* </dl>
2990+
*
2991+
* @param <U>
2992+
* the type of item emitted by the resulting {@code Iterable}
2993+
* @param mapper
2994+
* a function that returns an {@code Iterable} sequence of values for when given an item emitted by the
2995+
* current {@code Single}
2996+
* @return the new {@code Streamable} instance
2997+
* @throws NullPointerException if {@code mapper} is {@code null}
2998+
* @see <a href="http://reactivex.io/documentation/operators/flatmap.html">ReactiveX operators documentation: FlatMap</a>
2999+
* @since 4.0.0
3000+
*/
3001+
@BackpressureSupport(BackpressureKind.FULL)
3002+
@CheckReturnValue
3003+
@NonNull
3004+
@SchedulerSupport(SchedulerSupport.NONE)
3005+
public final <@NonNull U> Streamable<U> flattenAsStreamable(@NonNull Function<? super T, @NonNull ? extends Iterable<? extends U>> mapper) {
3006+
Objects.requireNonNull(mapper, "mapper is null");
3007+
return RxJavaPlugins.onAssembly(new StreamableSingleFlattenAs<>(this, mapper));
3008+
}
3009+
29793010
/**
29803011
* Returns an {@link Observable} that is based on applying a specified function to the item emitted by the current {@code Single},
29813012
* where that function returns an {@link ObservableSource}.
@@ -4894,14 +4925,17 @@ public final Observable<T> toObservable() {
48944925
* <p>
48954926
* <img width="640" height="305" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/Single.toStreamable.v3.png" alt="">
48964927
* <dl>
4928+
* <dt><b>Backpressure:</b></dt>
4929+
* <dd>The returned {@code Streamable} honors the backpressure of the downstream consumer.</dd>
48974930
* <dt><b>Scheduler:</b></dt>
48984931
* <dd>{@code toStreamable} does not operate by default on a particular {@link Scheduler}.</dd>
48994932
* </dl>
49004933
* @return the new {@code Streamable} instance
49014934
* @since 4.0.0
49024935
*/
4903-
@CheckReturnValue
49044936
@SchedulerSupport(SchedulerSupport.NONE)
4937+
@BackpressureSupport(BackpressureKind.FULL)
4938+
@CheckReturnValue
49054939
@NonNull
49064940
public final Streamable<T> toStreamable() {
49074941
return RxJavaPlugins.onAssembly(new StreamableFromSingle<>(this));

src/main/java/io/reactivex/rxjava4/core/Streamable.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,19 @@ static <T> Streamable<List<T>> zip(Iterable<? extends Streamable<? extends T>> s
611611
// Operators
612612
// oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
613613

614+
/**
615+
* Blocks the current thread until this {@code Streamable} produces one item, which is then returned
616+
* @return the first item of this {@code Streamable}
617+
* @throws NoSuchElementException if the this {@code Streamable} is empty
618+
* @throws CancellationException if this {@code Streamable} failed with a checked exception
619+
* @throws RuntimeException if this {@code Streamable} failed with an unchecked exception
620+
*/
621+
@CheckReturnValue
622+
@NonNull
623+
default T blockingFirst() {
624+
return StreamableBlocking.blockingFirst(this);
625+
}
626+
614627
/**
615628
* Collects all upstream values via the use of a {@link Collector} configuration
616629
* and emits its resulting value as a single item of the returned {@code Streamable}.
@@ -732,6 +745,33 @@ default Streamable<T> intercept(StreamableInterceptConfig<T> config) {
732745
config.onStream(), config.onNext(), config.onCurrent(), config.onFinish()));
733746
}
734747

748+
/**
749+
* Signals the last item from the upstream {@code Streamable} or
750+
* the provided {@code defaultItem} if the upstream is empty, as a
751+
* {@link Single} instance.
752+
* @param defaultItem the item to signal if the upstream turns out to be empty
753+
* @return the new {@code Single} instance
754+
* @throws NullPointerException if {@code defaultItem} is {@code null}
755+
*/
756+
@CheckReturnValue
757+
@NonNull
758+
default Single<T> last(@NonNull T defaultItem) {
759+
Objects.requireNonNull(defaultItem, "defaultItem is null");
760+
return RxJavaPlugins.onAssembly(new StreamableLastAsSingle<>(this, defaultItem));
761+
}
762+
763+
/**
764+
* Signals the last item from the upstream {@code Streamable} or a
765+
* {@link NoSuchElementException} if the upstream is empty, as a
766+
* {@link Single} instance.
767+
* @return the new {@code Single} instance
768+
*/
769+
@CheckReturnValue
770+
@NonNull
771+
default Single<T> lastOrError() {
772+
return RxJavaPlugins.onAssembly(new StreamableLastAsSingle<>(this, null));
773+
}
774+
735775
/**
736776
* <strong>This method requires advanced knowledge about building operators, please consider
737777
* other standard composition methods first;</strong>
@@ -801,6 +841,21 @@ default Streamable<T> onErrorResumeNext(@NonNull Function<? super Throwable, ? e
801841
return RxJavaPlugins.onAssembly(new StreamableOnErrorResumeNext<>(this, fallbackMapper));
802842
}
803843

844+
/**
845+
* Skips the first {@code count} items and relays the rest to the downstream.
846+
* @param count the number of items to skip
847+
* @return the new {@Streamable} instance
848+
* @throws IllegalArgumentException if {@code count} is negative
849+
*/
850+
@CheckReturnValue
851+
@NonNull
852+
default Streamable<T> skip(long count) {
853+
if (count < 0) {
854+
throw new IllegalArgumentException("count >= 0 expected but it was " + count);
855+
}
856+
return RxJavaPlugins.onAssembly(new StreamableSkip<>(this, count));
857+
}
858+
804859
/**
805860
* Takes at most the given number of items from the upstream and relays it to the downstream,
806861
* then cancels the rest of the sequence.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2016-present, RxJava Contributors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
5+
* compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is
10+
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
11+
* the License for the specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package io.reactivex.rxjava4.internal.disposables;
15+
16+
import io.reactivex.rxjava4.disposables.Disposable;
17+
18+
/**
19+
* An extension to {@link Disposable} that allows not
20+
* implementing the {@link Disposable#isDisposed()} as it
21+
* is practically never needed or cannot be observed anyways.
22+
* @since 4.0.0
23+
*/
24+
public interface DisposableOnly extends Disposable {
25+
26+
@Override
27+
default boolean isDisposed() {
28+
throw new UnsupportedOperationException("The class " + this.getClass() + " does not support isDisposed");
29+
}
30+
}

src/main/java/io/reactivex/rxjava4/internal/operators/streamable/StageResumable.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,21 @@
2525
* one use/thread can signal {@link #ready()} to wake up another use/thread
2626
* on a {@link #await()} call.
2727
* @param <T> the element type of the notification pass-around
28+
* @since 4.0.0
2829
*/
2930
public final class StageResumable<T> extends AtomicReference<CompletableFuture<T>>
3031
implements BiConsumer<T, Throwable> {
3132

3233
@Serial
3334
private static final long serialVersionUID = -7518852864146380895L;
3435

36+
/**
37+
* When the producer has arranged the item transfer via some field or queue,
38+
* call this method and call {@link CompletableFuture#complete(Object)}
39+
* or {@link CompletableFuture#completeExceptionally(Throwable)} to
40+
* signal resumption for any current or upcoming {@link #await()} caller.
41+
* @return the {@code CompletableFuture} to complete in some way
42+
*/
3543
@CheckReturnValue
3644
@NonNull
3745
public CompletableFuture<T> ready() {
@@ -49,6 +57,12 @@ public CompletableFuture<T> ready() {
4957
return cf;
5058
}
5159

60+
/**
61+
* When the consumer is ready to receive an item, call this method
62+
* and apply a continuation function, such as {@link CompletableFuture#whenComplete(BiConsumer)}
63+
* to it to handle the signal and process any external data made ready.
64+
* @return the {@code CompletableFuture} to observe a completion value or exception
65+
*/
5266
@CheckReturnValue
5367
@NonNull
5468
public CompletableFuture<T> await() {
@@ -66,6 +80,10 @@ public CompletableFuture<T> await() {
6680
return cf.whenComplete(this);
6781
}
6882

83+
/// Used to clear any waiting [CompletableFuture] when the await finishes
84+
/// no concern to users and should not be called.
85+
/// @param t the completion value if any, ignored
86+
/// @param u the exception if any, ignored
6987
@Override
7088
public void accept(T t, Throwable u) {
7189
getAndSet(null);
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright (c) 2016-present, RxJava Contributors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
5+
* compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is
10+
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
11+
* the License for the specific language governing permissions and limitations under the License.
12+
*/
13+
14+
package io.reactivex.rxjava4.internal.operators.streamable;
15+
16+
import java.util.NoSuchElementException;
17+
import java.util.concurrent.CompletionException;
18+
19+
import io.reactivex.rxjava4.annotations.*;
20+
import io.reactivex.rxjava4.core.Streamable;
21+
import io.reactivex.rxjava4.disposables.CompositeDisposable;
22+
import io.reactivex.rxjava4.exceptions.Exceptions;
23+
import io.reactivex.rxjava4.internal.util.ExceptionHelper;
24+
25+
public record StreamableBlocking() {
26+
27+
/**
28+
* Consumes the first item and finishes the {@link Streamable},
29+
* throwing {@link NoSuchElementException} if the source is empty.
30+
* @param <T> the element type
31+
* @param source the source {@code Streamable}
32+
* @return the first item
33+
* @throws RuntimeException if the source signals an unchecked exception
34+
* @throws CompletionException if the source signals a checked exception
35+
*/
36+
@CheckReturnValue
37+
@NonNull
38+
public static <T> T blockingFirst(Streamable<T> source) {
39+
var streamer = source.stream(new CompositeDisposable());
40+
Throwable nextException = null;
41+
Throwable finishException = null;
42+
T result = null;
43+
try {
44+
if (streamer.awaitNext()) {
45+
result = streamer.current();
46+
}
47+
} catch (Throwable ex) {
48+
Exceptions.throwIfFatal(ex);
49+
nextException = ex;
50+
}
51+
try {
52+
streamer.awaitFinish();
53+
} catch (Throwable ex) {
54+
Exceptions.throwIfFatal(ex);
55+
finishException = ex;
56+
}
57+
58+
if (nextException != null || finishException != null) {
59+
throw ExceptionHelper.wrapOrThrow(ExceptionHelper.unwrapAndCombine(nextException, finishException));
60+
}
61+
if (result == null) {
62+
throw new NoSuchElementException();
63+
}
64+
return result;
65+
}
66+
67+
}

0 commit comments

Comments
 (0)