Skip to content

Commit 7e8b750

Browse files
authored
4.x: Streamable ~ take native implementation (#8226)
* 4.x: Streamable ~ take native implementation * fix import wildcard, forgot to config eclipse
1 parent 24e05e9 commit 7e8b750

5 files changed

Lines changed: 124 additions & 16 deletions

File tree

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

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,12 @@
1515

1616
import java.util.*;
1717
import java.util.concurrent.*;
18-
import java.util.concurrent.atomic.AtomicLong;
1918
import java.util.stream.*;
2019

2120
import io.reactivex.rxjava4.annotations.*;
2221
import io.reactivex.rxjava4.core.config.*;
2322
import io.reactivex.rxjava4.disposables.*;
2423
import io.reactivex.rxjava4.functions.*;
25-
import io.reactivex.rxjava4.internal.functions.ObjectHelper;
2624
import io.reactivex.rxjava4.internal.operators.streamable.*;
2725
import io.reactivex.rxjava4.plugins.RxJavaPlugins;
2826
import io.reactivex.rxjava4.schedulers.Schedulers;
@@ -806,23 +804,22 @@ default Streamable<T> onErrorResumeNext(@NonNull Function<? super Throwable, ? e
806804
/**
807805
* Takes at most the given number of items from the upstream and relays it to the downstream,
808806
* then cancels the rest of the sequence.
809-
* @param n the maximum number of items to relay
807+
* <p>
808+
* Note that cancellation of the upstream happens when the downstream
809+
* calls {@link Streamer#next()} because unlike the push-based {@code take}
810+
* implementations, the current upstream value has to remain accessible until
811+
* the downstream calls {@code next} or {@link Streamer#finish()}.
812+
* @param count the maximum number of items to relay
810813
* @return the new {@code Streamable} instance
811-
* @throws IllegalArgumentException if {@code n} is non-positive
814+
* @throws IllegalArgumentException if {@code count} is negative
812815
*/
813816
@CheckReturnValue
814817
@NonNull
815-
default Streamable<T> take(long n) {
816-
ObjectHelper.verifyPositive(n, "n");
817-
return defer(() -> {
818-
var countdown = new AtomicLong(n);
819-
return transform((item, emitter, stopper) -> {
820-
emitter.emit(item);
821-
if (countdown.decrementAndGet() <= 0) {
822-
stopper.dispose();
823-
}
824-
});
825-
});
818+
default Streamable<T> take(long count) {
819+
if (count < 0) {
820+
throw new IllegalArgumentException("count >= 0 required but it was " + count);
821+
}
822+
return RxJavaPlugins.onAssembly(new StreamableTake<>(this, count));
826823
}
827824

828825
/**
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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.concurrent.CompletionStage;
17+
18+
import io.reactivex.rxjava4.annotations.NonNull;
19+
import io.reactivex.rxjava4.core.*;
20+
import io.reactivex.rxjava4.disposables.*;
21+
import io.reactivex.rxjava4.internal.fuseable.HasUpstreamStreamableSource;
22+
23+
public record StreamableTake<T>(Streamable<T> source, long count)
24+
implements Streamable<T>, HasUpstreamStreamableSource<T> {
25+
26+
@Override
27+
public @NonNull Streamer<@NonNull T> stream(@NonNull StreamerCancellation cancellation) {
28+
var dsc = cancellation.derive();
29+
return new TakeStreamer<>(source.stream(dsc), count, dsc);
30+
}
31+
32+
static final class TakeStreamer<T> implements Streamer<T> {
33+
final Streamer<T> upstream;
34+
35+
final Disposable upstreamDisposable;
36+
37+
long remaining;
38+
39+
TakeStreamer(Streamer<T> upstream, long count, Disposable upstreamDisposable) {
40+
this.upstream = upstream;
41+
this.upstreamDisposable = upstreamDisposable;
42+
this.remaining = count;
43+
}
44+
45+
@Override
46+
public @NonNull CompletionStage<Boolean> next() {
47+
if (remaining-- <= 0L) {
48+
upstreamDisposable.dispose();
49+
return NEXT_FALSE;
50+
}
51+
return upstream.next();
52+
}
53+
54+
@Override
55+
public @NonNull T current() {
56+
return upstream.current();
57+
}
58+
59+
@Override
60+
public @NonNull CompletionStage<Void> finish() {
61+
return upstream.finish();
62+
}
63+
}
64+
}

src/test/java/io/reactivex/rxjava4/internal/operators/streamable/StreamableTakeTest.java

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
import java.util.concurrent.atomic.AtomicBoolean;
2020

2121
import org.junit.jupiter.api.Test;
22-
import io.reactivex.rxjava4.core.Flowable;
22+
23+
import io.reactivex.rxjava4.core.*;
24+
import io.reactivex.rxjava4.exceptions.TestException;
25+
import io.reactivex.rxjava4.processors.DispatchStreamProcessor;
2326

2427
public class StreamableTakeTest extends StreamableBaseTest {
2528

@@ -52,4 +55,45 @@ public void fewer() throws Throwable {
5255

5356
assertFalse(isCancelled.get(), "Cancel was propagated!");
5457
}
58+
59+
@Test
60+
public void error() {
61+
Streamable.error(new TestException())
62+
.take(5)
63+
.test()
64+
.awaitDone(5, TimeUnit.SECONDS)
65+
.assertFailure(TestException.class);
66+
}
67+
68+
@Test
69+
public void doubleTake() {
70+
Streamable.range(1, 5)
71+
.take(3)
72+
.take(1)
73+
.test()
74+
.awaitDone(5, TimeUnit.SECONDS)
75+
.assertResult(1)
76+
;
77+
}
78+
79+
@Test
80+
public void cancelled() throws Throwable {
81+
var dsp = new DispatchStreamProcessor<>();
82+
83+
var ts = dsp.take(3).test();
84+
85+
ts.awaitOnSubscribe(1, TimeUnit.SECONDS);
86+
awaitStreamers(dsp, 1000);
87+
88+
dsp.next(1).toCompletableFuture().join();
89+
dsp.next(2).toCompletableFuture().join();
90+
dsp.next(3).toCompletableFuture().join();
91+
dsp.next(4).toCompletableFuture().join();
92+
93+
awaitNoStreamers(dsp, 1000);
94+
95+
ts
96+
.awaitDone(5, TimeUnit.SECONDS)
97+
.assertResult(1, 2, 3);
98+
}
5599
}

src/test/java/io/reactivex/rxjava4/validators/CheckParamValidationNamingTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ static void processFile(Class<?> clazz) throws Exception {
318318
if (linek.startsWith("public") || linek.startsWith("private")
319319
|| linek.startsWith("protected")
320320
|| linek.startsWith("static")
321+
|| linek.startsWith("default")
321322
|| linek.startsWith(baseClassName)) {
322323
break;
323324
}

src/test/java/io/reactivex/rxjava4/validators/CheckParamValidationTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,8 @@ public void checkStreamable() {
634634
addIgnore(new ParamIgnore(Streamable.class, "rangeLong", Long.TYPE, Long.TYPE));
635635
addIgnore(new ParamIgnore(Streamable.class, "intervalRange", Long.TYPE, Long.TYPE, Long.TYPE, Long.TYPE, TimeUnit.class, Scheduler.class));
636636
addIgnore(new ParamIgnore(Streamable.class, "intervalRange", Long.TYPE, Long.TYPE, Long.TYPE, Long.TYPE, TimeUnit.class, ExecutorService.class));
637+
// zero take is allowed
638+
addOverride(new ParamOverride(Streamable.class, 0, ParamMode.NON_NEGATIVE, "take", Long.TYPE));
637639

638640
// -----------------------------------------------------------------------------------
639641

0 commit comments

Comments
 (0)