Skip to content

Commit ad73819

Browse files
committed
Merge pull request #3752 from akarnokd/SingleUsing1x
1.x: Single.using()
2 parents 662ce3b + 1be11d6 commit ad73819

File tree

4 files changed

+746
-10
lines changed

4 files changed

+746
-10
lines changed

src/main/java/rx/Single.java

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1597,6 +1597,30 @@ public final void unsafeSubscribe(Subscriber<? super T> subscriber) {
15971597
}
15981598
}
15991599

1600+
/**
1601+
* Subscribes an Observer to this single and returns a Subscription that allows
1602+
* unsubscription.
1603+
*
1604+
* @param observer the Observer to subscribe
1605+
* @return the Subscription that allows unsubscription
1606+
*/
1607+
public final Subscription subscribe(final Observer<? super T> observer) {
1608+
if (observer == null) {
1609+
throw new NullPointerException("observer is null");
1610+
}
1611+
return subscribe(new SingleSubscriber<T>() {
1612+
@Override
1613+
public void onSuccess(T value) {
1614+
observer.onNext(value);
1615+
observer.onCompleted();
1616+
}
1617+
@Override
1618+
public void onError(Throwable error) {
1619+
observer.onError(error);
1620+
}
1621+
});
1622+
}
1623+
16001624
/**
16011625
* Subscribes to a Single and provides a Subscriber that implements functions to handle the item the Single
16021626
* emits or any error notification it issues.
@@ -2541,4 +2565,75 @@ public final Single<T> retryWhen(final Func1<Observable<? extends Throwable>, ?
25412565
return toObservable().retryWhen(notificationHandler).toSingle();
25422566
}
25432567

2568+
/**
2569+
* Constructs an Single that creates a dependent resource object which is disposed of on unsubscription.
2570+
* <p>
2571+
* <img width="640" height="400" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/using.png" alt="">
2572+
* <dl>
2573+
* <dt><b>Scheduler:</b></dt>
2574+
* <dd>{@code using} does not operate by default on a particular {@link Scheduler}.</dd>
2575+
* </dl>
2576+
*
2577+
* @param resourceFactory
2578+
* the factory function to create a resource object that depends on the Single
2579+
* @param singleFactory
2580+
* the factory function to create a Single
2581+
* @param disposeAction
2582+
* the function that will dispose of the resource
2583+
* @return the Single whose lifetime controls the lifetime of the dependent resource object
2584+
* @see <a href="http://reactivex.io/documentation/operators/using.html">ReactiveX operators documentation: Using</a>
2585+
*/
2586+
@Experimental
2587+
public static <T, Resource> Single<T> using(
2588+
final Func0<Resource> resourceFactory,
2589+
final Func1<? super Resource, ? extends Single<? extends T>> observableFactory,
2590+
final Action1<? super Resource> disposeAction) {
2591+
return using(resourceFactory, observableFactory, disposeAction, false);
2592+
}
2593+
2594+
/**
2595+
* Constructs an Single that creates a dependent resource object which is disposed of just before
2596+
* termination if you have set {@code disposeEagerly} to {@code true} and unsubscription does not occur
2597+
* before termination. Otherwise resource disposal will occur on unsubscription. Eager disposal is
2598+
* particularly appropriate for a synchronous Single that resuses resources. {@code disposeAction} will
2599+
* only be called once per subscription.
2600+
* <p>
2601+
* <img width="640" height="400" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/using.png" alt="">
2602+
* <dl>
2603+
* <dt><b>Scheduler:</b></dt>
2604+
* <dd>{@code using} does not operate by default on a particular {@link Scheduler}.</dd>
2605+
* </dl>
2606+
*
2607+
* @warn "Backpressure Support" section missing from javadoc
2608+
* @param resourceFactory
2609+
* the factory function to create a resource object that depends on the Single
2610+
* @param singleFactory
2611+
* the factory function to create a Single
2612+
* @param disposeAction
2613+
* the function that will dispose of the resource
2614+
* @param disposeEagerly
2615+
* if {@code true} then disposal will happen either on unsubscription or just before emission of
2616+
* a terminal event ({@code onComplete} or {@code onError}).
2617+
* @return the Single whose lifetime controls the lifetime of the dependent resource object
2618+
* @see <a href="http://reactivex.io/documentation/operators/using.html">ReactiveX operators documentation: Using</a>
2619+
* @Experimental The behavior of this can change at any time.
2620+
* @since (if this graduates from Experimental/Beta to supported, replace this parenthetical with the release number)
2621+
*/
2622+
@Experimental
2623+
public static <T, Resource> Single<T> using(
2624+
final Func0<Resource> resourceFactory,
2625+
final Func1<? super Resource, ? extends Single<? extends T>> singleFactory,
2626+
final Action1<? super Resource> disposeAction, boolean disposeEagerly) {
2627+
if (resourceFactory == null) {
2628+
throw new NullPointerException("resourceFactory is null");
2629+
}
2630+
if (singleFactory == null) {
2631+
throw new NullPointerException("singleFactory is null");
2632+
}
2633+
if (disposeAction == null) {
2634+
throw new NullPointerException("disposeAction is null");
2635+
}
2636+
return create(new SingleOnSubscribeUsing<T, Resource>(resourceFactory, singleFactory, disposeAction, disposeEagerly));
2637+
}
2638+
25442639
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package rx.internal.operators;
2+
3+
import java.util.Arrays;
4+
5+
import rx.*;
6+
import rx.exceptions.*;
7+
import rx.functions.*;
8+
import rx.plugins.RxJavaPlugins;
9+
10+
/**
11+
* Generates a resource, derives a Single from it and disposes that resource once the
12+
* Single terminates.
13+
* @param <T> the value type of the Single
14+
* @param <Resource> the resource type
15+
*/
16+
public final class SingleOnSubscribeUsing<T, Resource> implements Single.OnSubscribe<T> {
17+
final Func0<Resource> resourceFactory;
18+
final Func1<? super Resource, ? extends Single<? extends T>> singleFactory;
19+
final Action1<? super Resource> disposeAction;
20+
final boolean disposeEagerly;
21+
22+
public SingleOnSubscribeUsing(Func0<Resource> resourceFactory,
23+
Func1<? super Resource, ? extends Single<? extends T>> observableFactory,
24+
Action1<? super Resource> disposeAction, boolean disposeEagerly) {
25+
this.resourceFactory = resourceFactory;
26+
this.singleFactory = observableFactory;
27+
this.disposeAction = disposeAction;
28+
this.disposeEagerly = disposeEagerly;
29+
}
30+
31+
@Override
32+
public void call(final SingleSubscriber<? super T> child) {
33+
final Resource resource;
34+
35+
try {
36+
resource = resourceFactory.call();
37+
} catch (Throwable ex) {
38+
Exceptions.throwIfFatal(ex);
39+
child.onError(ex);
40+
return;
41+
}
42+
43+
Single<? extends T> single;
44+
45+
try {
46+
single = singleFactory.call(resource);
47+
} catch (Throwable ex) {
48+
handleSubscriptionTimeError(child, resource, ex);
49+
return;
50+
}
51+
52+
if (single == null) {
53+
handleSubscriptionTimeError(child, resource, new NullPointerException("The single"));
54+
return;
55+
}
56+
57+
SingleSubscriber<T> parent = new SingleSubscriber<T>() {
58+
@Override
59+
public void onSuccess(T value) {
60+
if (disposeEagerly) {
61+
try {
62+
disposeAction.call(resource);
63+
} catch (Throwable ex) {
64+
Exceptions.throwIfFatal(ex);
65+
66+
child.onError(ex);
67+
return;
68+
}
69+
}
70+
71+
child.onSuccess(value);
72+
73+
if (!disposeEagerly) {
74+
try {
75+
disposeAction.call(resource);
76+
} catch (Throwable ex2) {
77+
Exceptions.throwIfFatal(ex2);
78+
RxJavaPlugins.getInstance().getErrorHandler().handleError(ex2);
79+
}
80+
}
81+
}
82+
83+
@Override
84+
public void onError(Throwable error) {
85+
handleSubscriptionTimeError(child, resource, error);
86+
}
87+
};
88+
child.add(parent);
89+
90+
single.subscribe(parent);
91+
}
92+
93+
void handleSubscriptionTimeError(SingleSubscriber<? super T> t, Resource resource, Throwable ex) {
94+
Exceptions.throwIfFatal(ex);
95+
96+
if (disposeEagerly) {
97+
try {
98+
disposeAction.call(resource);
99+
} catch (Throwable ex2) {
100+
Exceptions.throwIfFatal(ex2);
101+
ex = new CompositeException(Arrays.asList(ex, ex2));
102+
}
103+
}
104+
105+
t.onError(ex);
106+
107+
if (!disposeEagerly) {
108+
try {
109+
disposeAction.call(resource);
110+
} catch (Throwable ex2) {
111+
Exceptions.throwIfFatal(ex2);
112+
RxJavaPlugins.getInstance().getErrorHandler().handleError(ex2);
113+
}
114+
}
115+
}
116+
}

src/test/java/rx/SingleTest.java

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,28 @@
1212
*/
1313
package rx;
1414

15-
import org.junit.Test;
16-
import org.mockito.invocation.InvocationOnMock;
17-
import org.mockito.stubbing.Answer;
15+
import static org.junit.Assert.*;
16+
import static org.mockito.Matchers.*;
17+
import static org.mockito.Mockito.*;
1818

1919
import java.io.IOException;
2020
import java.util.*;
2121
import java.util.concurrent.*;
2222
import java.util.concurrent.atomic.*;
2323

24+
import org.junit.Test;
25+
import org.mockito.invocation.InvocationOnMock;
26+
import org.mockito.stubbing.Answer;
27+
2428
import rx.Single.OnSubscribe;
25-
import rx.exceptions.CompositeException;
29+
import rx.exceptions.*;
2630
import rx.functions.*;
2731
import rx.observers.TestSubscriber;
28-
import rx.schedulers.Schedulers;
29-
import rx.schedulers.TestScheduler;
32+
import rx.schedulers.*;
3033
import rx.singles.BlockingSingle;
3134
import rx.subjects.PublishSubject;
3235
import rx.subscriptions.Subscriptions;
3336

34-
import static org.junit.Assert.*;
35-
import static org.mockito.Matchers.eq;
36-
import static org.mockito.Mockito.*;
37-
3837
public class SingleTest {
3938

4039
@Test
@@ -1681,4 +1680,38 @@ public void takeUntilError_withSingle_shouldMatch() {
16811680
assertFalse(until.hasObservers());
16821681
assertFalse(ts.isUnsubscribed());
16831682
}
1683+
1684+
@Test
1685+
public void subscribeWithObserver() {
1686+
@SuppressWarnings("unchecked")
1687+
Observer<Integer> o = mock(Observer.class);
1688+
1689+
Single.just(1).subscribe(o);
1690+
1691+
verify(o).onNext(1);
1692+
verify(o).onCompleted();
1693+
verify(o, never()).onError(any(Throwable.class));
1694+
}
1695+
1696+
@Test
1697+
public void subscribeWithObserverAndGetError() {
1698+
@SuppressWarnings("unchecked")
1699+
Observer<Integer> o = mock(Observer.class);
1700+
1701+
Single.<Integer>error(new TestException()).subscribe(o);
1702+
1703+
verify(o, never()).onNext(anyInt());
1704+
verify(o, never()).onCompleted();
1705+
verify(o).onError(any(TestException.class));
1706+
}
1707+
1708+
@Test
1709+
public void subscribeWithNullObserver() {
1710+
try {
1711+
Single.just(1).subscribe((Observer<Integer>)null);
1712+
fail("Failed to throw NullPointerException");
1713+
} catch (NullPointerException ex) {
1714+
assertEquals("observer is null", ex.getMessage());
1715+
}
1716+
}
16841717
}

0 commit comments

Comments
 (0)