Skip to content

Commit 73de5f7

Browse files
authored
4.x: Streamable Ops: doOnNext, intercept, lift, to; test cleanup (#8209)
1 parent bb26a05 commit 73de5f7

25 files changed

Lines changed: 804 additions & 107 deletions

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

Lines changed: 121 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import java.util.stream.Stream;
2020

2121
import io.reactivex.rxjava4.annotations.*;
22-
import io.reactivex.rxjava4.core.config.StandardConcurrentConfig;
22+
import io.reactivex.rxjava4.core.config.*;
2323
import io.reactivex.rxjava4.disposables.*;
2424
import io.reactivex.rxjava4.exceptions.Exceptions;
2525
import io.reactivex.rxjava4.functions.*;
@@ -37,6 +37,7 @@
3737
* @param <T> the element type of the stream.
3838
* @since 4.0.0
3939
*/
40+
@FunctionalInterface
4041
public interface Streamable<@NonNull T> {
4142

4243
// oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
@@ -394,52 +395,83 @@ static Streamable<Long> rangeLong(long start, long count) {
394395
// oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
395396

396397
/**
397-
* Converts the streamable into a Flowable representation, running
398-
* on the default Executors.newVirtualThreadPerTaskExecutor() virtual thread.
399-
* @return the new Flowable instance
398+
* Calls the given consumer whenever an upstream item becomes available.
399+
* @param consumer the callback to invoke with the next item from upstream
400+
* @return the new {@code Streamable} instance
401+
* @throws NullPointerException if {@code consumer} is {@code null}
402+
*/
403+
default Streamable<T> doOnNext(Consumer<? super T> consumer) {
404+
Objects.requireNonNull(consumer, "consumer is null");
405+
return intercept(new StreamableInterceptConfig<>(v -> { consumer.accept(v); return v; } ));
406+
}
407+
408+
/**
409+
* Maps each upstream item onto a {@code Streamable} and runs them concurrently while
410+
* relaying inner items as first-come-first-served manner.
411+
* @param <R> the element type of the output sequence
412+
* @param mapper the function that turns an upstream item into a {@code Streamable} inner sequence
413+
* @param config the configuration record for this operator
414+
* @return the new {@code Streamable} instance
415+
* @throws NullPointerException if {@code mapper} or {@code config} is {@code null}
400416
*/
401417
@CheckReturnValue
402418
@NonNull
403-
default Flowable<T> toFlowable() {
404-
return toFlowable(Executors.newVirtualThreadPerTaskExecutor());
419+
default <R> Streamable<R> flatMap(@NonNull Function<? super T, ? extends Streamable<? extends R>> mapper,
420+
@NonNull StandardConcurrentConfig config) {
421+
Objects.requireNonNull(mapper, "mapper is null");
422+
Objects.requireNonNull(config, "config is null");
423+
return RxJavaPlugins.onAssembly(new StreamableFlatMap<>(this, mapper, config.maxConcurrency()));
405424
}
406425

407426
/**
408-
* Converts the streamable into a Flowable representation, running
409-
* on the provided executor service.
410-
* @param executor the executor to use
411-
* @return the new Flowable instance
427+
* Hides the identity of this {@code Streamable} and its {@link Streamer}.
428+
* <p>
429+
* Use it to break optimizations or hide concrete implementations.
430+
* @return the new {@code Streamable} instance
412431
*/
413432
@CheckReturnValue
414433
@NonNull
415-
default Flowable<T> toFlowable(@NonNull ExecutorService executor) {
416-
Objects.requireNonNull(executor, "executir is null");
417-
var me = this;
418-
return Flowable.virtualCreate(emitter -> me.forEach(emitter::emit).await(emitter.canceller()), executor);
434+
default Streamable<T> hide() {
435+
return RxJavaPlugins.onAssembly(new StreamableHide<>(this));
419436
}
420437

421438
/**
422-
* Transforms the upstream sequence into zero or more elements for the downstream.
423-
* @param <R> the result element type
424-
* @param transformer the interface to implement the transforming logic
439+
* Intercepts the lifecycle method calls of {@code Streamable} and {@link Streamer}
440+
* and allows the modification of them via Function callbacks.
441+
* @param config the configuration record for this operator
425442
* @return the new {@code Streamable} instance
443+
* @thros NullPointerException if {@code config} is {@code null}
426444
*/
427445
@CheckReturnValue
428446
@NonNull
429-
default <@NonNull R> Streamable<R> transform(@NonNull VirtualTransformer<T, R> transformer) {
430-
return transform(transformer, Executors.newVirtualThreadPerTaskExecutor());
447+
default Streamable<T> intercept(StreamableInterceptConfig<T> config) {
448+
Objects.requireNonNull(config, "config is null");
449+
return RxJavaPlugins.onAssembly(new StreamableIntercept<>(this,
450+
config.onStream(), config.onNext(), config.onCurrent(), config.onFinish()));
431451
}
432452

433453
/**
434-
* Hides the identity of this {@code Streamable} and its {@link Streamer}.
454+
* <strong>This method requires advanced knowledge about building operators, please consider
455+
* other standard composition methods first;</strong>
456+
* Returns a {@code Streamable} instance which when its {@link #stream(DisposableContainer)} is invoked,
457+
* applies the specified operator callback to the upstream {@link Streamer} to produce
458+
* an actual {@code Streamer} instance to be handed downstream.
435459
* <p>
436-
* Use it to break optimizations or hide concrete implementations.
460+
* Use it to implement operators without creating the surrounding {@code Streamable} class.
461+
* <p>
462+
* If the {@code lifter} returns {@code null} or throws, the downstream will receive a
463+
* standard error streamer.
464+
* @param <R> the downstream type of the sequence
465+
* @param lifter the callback that will be invoked with the upstream {@code Streamer} and is expected
466+
* to produce a {@code Streamer} for the downstream.
437467
* @return the new {@code Streamable} instance
468+
* @throws NullPointerException if {@code lifter} is {@code null}
438469
*/
439470
@CheckReturnValue
440471
@NonNull
441-
default Streamable<T> hide() {
442-
return RxJavaPlugins.onAssembly(new StreamableHide<>(this));
472+
default <@NonNull R> Streamable<R> lift(@NonNull StreamableOperator<? super T, ? extends R> lifter) {
473+
Objects.requireNonNull(lifter, "lifter is null");
474+
return RxJavaPlugins.onAssembly(new StreamableLift<T, R>(this, lifter));
443475
}
444476

445477
/**
@@ -472,27 +504,6 @@ default Streamable<T> hide() {
472504
return RxJavaPlugins.onAssembly(new StreamableMapOptional<>(this, mapper));
473505
}
474506

475-
/**
476-
* Transforms the upstream sequence into zero or more elements for the downstream.
477-
* @param <R> the result element type
478-
* @param transformer the interface to implement the transforming logic
479-
* @param executor where to run the transform and blocking operations
480-
* @return the new Streamable instance
481-
*/
482-
@CheckReturnValue
483-
@NonNull
484-
default <@NonNull R> Streamable<R> transform(@NonNull VirtualTransformer<T, R> transformer,
485-
@NonNull ExecutorService executor) {
486-
Objects.requireNonNull(transformer, "transformer is null");
487-
Objects.requireNonNull(executor, "executor is null");
488-
var me = this;
489-
return create(emitter -> me.forEach((item, stopper) -> {
490-
// System.out.println("item " + item);
491-
transformer.transform(item, emitter, stopper);
492-
}, emitter.canceller(), executor)
493-
.await(emitter.canceller()), executor);
494-
}
495-
496507
/**
497508
* Takes at most the given number of items from the upstream and relays it to the downstream,
498509
* then cancels the rest of the sequence.
@@ -515,21 +526,76 @@ default Streamable<T> take(long n) {
515526
}
516527

517528
/**
518-
* Maps each upstream item onto a {@code Streamable} and runs them concurrently while
519-
* relaying inner items as first-come-first-served manner.
520-
* @param <R> the element type of the output sequence
521-
* @param mapper the function that turns an upstream item into a {@code Streamable} inner sequence
522-
* @param config the configuration record for this operator
529+
* Calls the specified converter function during assembly time and returns its resulting value.
530+
* <p>
531+
* This allows fluent conversion to any other type.
532+
* @param <R> the resulting object type
533+
* @param converter the function that receives the current {@code Observable} instance and returns a value
534+
* @return the converted value
535+
* @throws NullPointerException if {@code converter} is {@code null}
536+
*/
537+
@CheckReturnValue
538+
@NonNull
539+
default <@NonNull R> R to(@NonNull StreamableConverter<T, ? extends R> converter) {
540+
return Objects.requireNonNull(converter, "converter is null").apply(this);
541+
}
542+
543+
/**
544+
* Converts the streamable into a Flowable representation, running
545+
* on the default Executors.newVirtualThreadPerTaskExecutor() virtual thread.
546+
* @return the new Flowable instance
547+
*/
548+
@CheckReturnValue
549+
@NonNull
550+
default Flowable<T> toFlowable() {
551+
return toFlowable(Executors.newVirtualThreadPerTaskExecutor());
552+
}
553+
554+
/**
555+
* Converts the streamable into a Flowable representation, running
556+
* on the provided executor service.
557+
* @param executor the executor to use
558+
* @return the new Flowable instance
559+
*/
560+
@CheckReturnValue
561+
@NonNull
562+
default Flowable<T> toFlowable(@NonNull ExecutorService executor) {
563+
Objects.requireNonNull(executor, "executir is null");
564+
var me = this;
565+
return Flowable.virtualCreate(emitter -> me.forEach(emitter::emit).await(emitter.canceller()), executor);
566+
}
567+
568+
/**
569+
* Transforms the upstream sequence into zero or more elements for the downstream.
570+
* @param <R> the result element type
571+
* @param transformer the interface to implement the transforming logic
523572
* @return the new {@code Streamable} instance
524-
* @throws NullPointerException if {@code mapper} or {@code config} is {@code null}
525573
*/
526574
@CheckReturnValue
527575
@NonNull
528-
default <R> Streamable<R> flatMap(@NonNull Function<? super T, ? extends Streamable<? extends R>> mapper,
529-
@NonNull StandardConcurrentConfig config) {
530-
Objects.requireNonNull(mapper, "mapper is null");
531-
Objects.requireNonNull(config, "config is null");
532-
return RxJavaPlugins.onAssembly(new StreamableFlatMap<>(this, mapper, config.maxConcurrency()));
576+
default <@NonNull R> Streamable<R> transform(@NonNull VirtualTransformer<T, R> transformer) {
577+
return transform(transformer, Executors.newVirtualThreadPerTaskExecutor());
578+
}
579+
580+
/**
581+
* Transforms the upstream sequence into zero or more elements for the downstream.
582+
* @param <R> the result element type
583+
* @param transformer the interface to implement the transforming logic
584+
* @param executor where to run the transform and blocking operations
585+
* @return the new Streamable instance
586+
*/
587+
@CheckReturnValue
588+
@NonNull
589+
default <@NonNull R> Streamable<R> transform(@NonNull VirtualTransformer<T, R> transformer,
590+
@NonNull ExecutorService executor) {
591+
Objects.requireNonNull(transformer, "transformer is null");
592+
Objects.requireNonNull(executor, "executor is null");
593+
var me = this;
594+
return create(emitter -> me.forEach((item, stopper) -> {
595+
// System.out.println("item " + item);
596+
transformer.transform(item, emitter, stopper);
597+
}, emitter.canceller(), executor)
598+
.await(emitter.canceller()), executor);
533599
}
534600

535601
// oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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.core;
15+
16+
import io.reactivex.rxjava4.annotations.NonNull;
17+
18+
/**
19+
* Convenience interface and callback used by the {@link Streamable#to} operator to turn an {@link Streamable} into another
20+
* value fluently.
21+
* @param <T> the upstream type
22+
* @param <R> the output type
23+
* @since 4.0.0
24+
*/
25+
@FunctionalInterface
26+
public interface StreamableConverter<@NonNull T, @NonNull R> {
27+
/**
28+
* Applies a function to the upstream {@link Streamable} and returns a converted value of type {@code R}.
29+
*
30+
* @param upstream the upstream {@code Streamable} instance
31+
* @return the converted value
32+
*/
33+
R apply(@NonNull Streamable<T> upstream);
34+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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.core;
15+
16+
import io.reactivex.rxjava4.annotations.NonNull;
17+
import io.reactivex.rxjava4.disposables.DisposableContainer;
18+
19+
/**
20+
* Interface to map/wrap an upstream {@link Streamer} to an downstream {@code Streamer}.
21+
*
22+
* @param <T> the value type of the upstream
23+
* @param <R> the value type of the downstream
24+
* @since 4.0.0
25+
*/
26+
@FunctionalInterface
27+
public interface StreamableOperator<@NonNull T, @NonNull R> {
28+
/**
29+
* Applies a function to the upstream {@link Streamer} and returns a new downstream {@code Streamer}.
30+
* @param container the {@link DisposableContainer} handling the cancellation propagation for the downstream
31+
* @param streamer the upstream {@code Streamer} instance
32+
* @return the downstream {@code Streamer} instance
33+
* @throws Throwable on failure
34+
*/
35+
@NonNull
36+
Streamer<? extends R> apply(@NonNull DisposableContainer container,
37+
@NonNull Streamer<? extends T> streamer) throws Throwable;
38+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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.core.config;
15+
16+
import java.util.Objects;
17+
import java.util.concurrent.CompletionStage;
18+
19+
import io.reactivex.rxjava4.annotations.NonNull;
20+
import io.reactivex.rxjava4.core.*;
21+
import io.reactivex.rxjava4.disposables.DisposableContainer;
22+
import io.reactivex.rxjava4.functions.*;
23+
24+
/**
25+
* Configuration record the intercept() operator with various lifecylce-stage transforming callbacks
26+
* @param <T> the element type of the sequence
27+
* @param onStream called when the {@link Streamable#stream(io.reactivex.rxjava4.disposables.DisposableContainer)} is invoked
28+
* @param onNext called when the {@link Streamer#next(io.reactivex.rxjava4.disposables.DisposableContainer)} is invoked
29+
* @param onCurrent called when the {@link Streamer#current()} is invoked
30+
* @param onFinish called when the {@link Streamer#finish(io.reactivex.rxjava4.disposables.DisposableContainer)} is invoked
31+
* @since 4.0.0
32+
*/
33+
public record StreamableInterceptConfig<T>(
34+
@NonNull BiFunction<? super DisposableContainer, ? super Streamer<? extends T>, ? extends Streamer<? extends T>> onStream,
35+
@NonNull BiFunction<? super DisposableContainer, ? super CompletionStage<Boolean>, ? extends CompletionStage<Boolean>> onNext,
36+
@NonNull Function<? super T, ? extends T> onCurrent,
37+
@NonNull BiFunction<? super DisposableContainer, ? super CompletionStage<Void>, ? extends CompletionStage<Void>> onFinish
38+
) {
39+
40+
/**
41+
* Constructs a configuration with a custom {@link #onNext} intercept and everything else is pass-through.
42+
* @param onNext the callback for intercepting the {@code next()} calls
43+
*/
44+
public StreamableInterceptConfig(@NonNull BiFunction<? super DisposableContainer, ? super CompletionStage<Boolean>, ? extends CompletionStage<Boolean>> onNext) {
45+
this((_, v) -> v, onNext, v -> v, (_, v) -> v);
46+
}
47+
48+
/**
49+
* Constructs a configuration with a custom {@link #onCurrent} intercept and everything else is pass-through.
50+
* @param onCurrent the callback for when an item is ready
51+
*/
52+
public StreamableInterceptConfig(@NonNull Function<? super T, ? extends T> onCurrent) {
53+
this((_, v) -> v, (_, v) -> v, onCurrent, (_, v) -> v);
54+
}
55+
56+
/**
57+
* Constructs a fully configured record.
58+
* @param onStream called when the {@link Streamable#stream(io.reactivex.rxjava4.disposables.DisposableContainer)} is invoked
59+
* @param onNext called when the {@link Streamer#next(io.reactivex.rxjava4.disposables.DisposableContainer)} is invoked
60+
* @param onCurrent called when the {@link Streamer#current()} is invoked
61+
* @param onFinish called when the {@link Streamer#finish(io.reactivex.rxjava4.disposables.DisposableContainer)} is invoked
62+
*/
63+
public StreamableInterceptConfig {
64+
Objects.requireNonNull(onStream, "onStream is null");
65+
Objects.requireNonNull(onNext, "onNext is null");
66+
Objects.requireNonNull(onCurrent, "onCurrent is null");
67+
Objects.requireNonNull(onFinish, "onFinish is null");
68+
}
69+
}

0 commit comments

Comments
 (0)