18
18
import static org .junit .Assert .*;
19
19
import static org .mockito .Mockito .*;
20
20
21
- import java .lang . reflect . Array ;
21
+ import java .util . Arrays ;
22
22
import java .util .ArrayList ;
23
23
import java .util .List ;
24
24
import java .util .concurrent .CountDownLatch ;
25
+ import java .util .concurrent .atomic .AtomicBoolean ;
25
26
26
27
import org .junit .Assert ;
27
28
import org .junit .Before ;
30
31
import rx .Observable ;
31
32
import rx .Observer ;
32
33
import rx .Subscription ;
34
+ import rx .subscriptions .BooleanSubscription ;
33
35
import rx .util .AtomicObservableSubscription ;
34
- import rx .util .functions . Action1 ;
36
+ import rx .util .Exceptions ;
35
37
import rx .util .functions .Func1 ;
36
38
37
39
public final class OperationConcat {
38
40
39
41
/**
40
- * Combine the observable sequences from the list of Observables into one observable sequence without any transformation.
41
- *
42
- * @param sequences
43
- * An observable sequence of elements to project.
42
+ * Combine the observable sequences from the list of Observables into one
43
+ * observable sequence without any transformation. If either the outer
44
+ * observable or an inner observable calls onError, we will call onError.
45
+ *
46
+ * <p/>
47
+ *
48
+ * The outer observable might run on a separate thread from (one of) the
49
+ * inner observables; in this case care must be taken to avoid a deadlock.
50
+ * The Concat operation may block the outer thread while servicing an inner
51
+ * thread in order to ensure a well-defined ordering of elements; therefore
52
+ * none of the inner threads must be implemented in a way that might wait on
53
+ * the outer thread.
54
+ *
55
+ * @param sequences An observable sequence of elements to project.
44
56
* @return An observable sequence whose elements are the result of combining the output from the list of Observables.
45
57
*/
46
58
public static <T > Func1 <Observer <T >, Subscription > concat (final Observable <T >... sequences ) {
47
- return new Func1 <Observer <T >, Subscription >() {
48
-
49
- @ Override
50
- public Subscription call (Observer <T > observer ) {
51
- return new Concat <T >(sequences ).call (observer );
52
- }
53
- };
59
+ return concat (Observable .toObservable (sequences ));
54
60
}
55
61
56
62
public static <T > Func1 <Observer <T >, Subscription > concat (final List <Observable <T >> sequences ) {
57
- @ SuppressWarnings ("unchecked" )
58
- Observable <T >[] o = sequences .toArray ((Observable <T >[]) Array .newInstance (Observable .class , sequences .size ()));
59
- return concat (o );
63
+ return concat (Observable .toObservable (sequences ));
60
64
}
61
65
62
66
public static <T > Func1 <Observer <T >, Subscription > concat (final Observable <Observable <T >> sequences ) {
63
- final List <Observable <T >> list = new ArrayList <Observable <T >>();
64
- sequences .toList ().subscribe (new Action1 <List <Observable <T >>>() {
65
- @ Override
66
- public void call (List <Observable <T >> t1 ) {
67
- list .addAll (t1 );
68
- }
69
-
70
- });
71
-
72
- return concat (list );
73
- }
74
-
75
- private static class Concat <T > implements Func1 <Observer <T >, Subscription > {
76
- private final Observable <T >[] sequences ;
77
- private int num = 0 ;
78
- private int count = 0 ;
79
- private Subscription s ;
80
-
81
- Concat (final Observable <T >... sequences ) {
82
- this .sequences = sequences ;
83
- this .num = sequences .length - 1 ;
84
- }
85
-
86
- private final AtomicObservableSubscription Subscription = new AtomicObservableSubscription ();
87
-
88
- private final Subscription actualSubscription = new Subscription () {
67
+ return new Func1 <Observer <T >, Subscription >() {
89
68
90
69
@ Override
91
- public void unsubscribe () {
92
- if (null != s )
93
- s .unsubscribe ();
70
+ public Subscription call (Observer <T > observer ) {
71
+ return new ConcatSubscription <T >(sequences , observer );
94
72
}
95
73
};
74
+ }
96
75
97
- public Subscription call (Observer <T > observer ) {
98
- s = sequences [count ].subscribe (new ConcatObserver (observer ));
99
-
100
- return Subscription .wrap (actualSubscription );
101
- }
102
-
103
- private class ConcatObserver implements Observer <T > {
104
- private final Observer <T > observer ;
76
+ private static class ConcatSubscription <T > extends BooleanSubscription {
77
+ // Might be updated by an inner thread's onError during the outer
78
+ // thread's onNext, then read in the outer thread's onComplete.
79
+ final AtomicBoolean innerError = new AtomicBoolean (false );
105
80
106
- ConcatObserver (Observer <T > observer ) {
107
- this .observer = observer ;
108
- }
109
-
110
- @ Override
111
- public void onCompleted () {
112
- if (num == count )
113
- observer .onCompleted ();
114
- else {
115
- count ++;
116
- s = sequences [count ].subscribe (this );
81
+ public ConcatSubscription (Observable <Observable <T >> sequences , final Observer <T > observer ) {
82
+ final AtomicObservableSubscription outerSubscription = new AtomicObservableSubscription ();
83
+ outerSubscription .wrap (sequences .subscribe (new Observer <Observable <T >>() {
84
+ @ Override
85
+ public void onNext (Observable <T > nextSequence ) {
86
+ // We will not return from onNext until the inner observer completes.
87
+ // NB: while we are in onNext, the well-behaved outer observable will not call onError or onCompleted.
88
+ final CountDownLatch latch = new CountDownLatch (1 );
89
+ final AtomicObservableSubscription innerSubscription = new AtomicObservableSubscription ();
90
+ innerSubscription .wrap (nextSequence .subscribe (new Observer <T >() {
91
+ @ Override
92
+ public void onNext (T item ) {
93
+ // If the Concat's subscriber called unsubscribe() before the return of onNext, we must do so also.
94
+ observer .onNext (item );
95
+ if (isUnsubscribed ()) {
96
+ innerSubscription .unsubscribe ();
97
+ outerSubscription .unsubscribe ();
98
+ }
99
+ }
100
+ @ Override
101
+ public void onError (Exception e ) {
102
+ outerSubscription .unsubscribe ();
103
+ innerError .set (true );
104
+ observer .onError (e );
105
+ latch .countDown ();
106
+ }
107
+ @ Override
108
+ public void onCompleted () {
109
+ // Continue on to the next sequence
110
+ latch .countDown ();
111
+ }
112
+ }));
113
+ try {
114
+ latch .await ();
115
+ } catch (InterruptedException e ) {
116
+ Thread .currentThread ().interrupt ();
117
+ throw Exceptions .propagate (e );
118
+ }
117
119
}
118
- }
119
-
120
- @ Override
121
- public void onError (Exception e ) {
122
- observer . onError ( e );
123
-
124
- }
125
-
126
- @ Override
127
- public void onNext ( T args ) {
128
- observer . onNext ( args );
129
-
130
- }
131
- }
120
+ @ Override
121
+ public void onError ( Exception e ) {
122
+ // NB: a well-behaved observable will not interleave on{Next,Error,Completed} calls.
123
+ observer . onError (e );
124
+ }
125
+ @ Override
126
+ public void onCompleted () {
127
+ // NB: a well-behaved observable will not interleave on{Next,Error,Completed} calls.
128
+ if (! innerError . get ()) {
129
+ observer . onCompleted ();
130
+ }
131
+ }
132
+ }));
133
+ }
132
134
}
133
135
134
136
public static class UnitTest {
@@ -193,8 +195,8 @@ public void testConcatWithList() {
193
195
public void testConcatUnsubscribe () {
194
196
final CountDownLatch callOnce = new CountDownLatch (1 );
195
197
final CountDownLatch okToContinue = new CountDownLatch (1 );
196
- final TestObservable w1 = new TestObservable (null , null , "one" , "two" , "three" );
197
- final TestObservable w2 = new TestObservable (callOnce , okToContinue , "four" , "five" , "six" );
198
+ final TestObservable < String > w1 = new TestObservable < String > (null , null , "one" , "two" , "three" );
199
+ final TestObservable < String > w2 = new TestObservable < String > (callOnce , okToContinue , "four" , "five" , "six" );
198
200
199
201
@ SuppressWarnings ("unchecked" )
200
202
Observer <String > aObserver = mock (Observer .class );
@@ -256,7 +258,40 @@ public void unsubscribe() {
256
258
Assert .assertEquals (expected .length , index );
257
259
}
258
260
259
- private static class TestObservable extends Observable <String > {
261
+ @ Test
262
+ public void testBlockedObservableOfObservables () {
263
+ final String [] o = { "1" , "3" , "5" , "7" };
264
+ final String [] e = { "2" , "4" , "6" };
265
+ final Observable <String > odds = Observable .toObservable (o );
266
+ final Observable <String > even = Observable .toObservable (e );
267
+ final CountDownLatch callOnce = new CountDownLatch (1 );
268
+ final CountDownLatch okToContinue = new CountDownLatch (1 );
269
+ TestObservable <Observable <String >> observableOfObservables = new TestObservable <Observable <String >>(callOnce , okToContinue , odds , even );
270
+ Func1 <Observer <String >, Subscription > concatF = concat (observableOfObservables );
271
+ Observable <String > concat = Observable .create (concatF );
272
+ concat .subscribe (observer );
273
+ try {
274
+ //Block main thread to allow observables to serve up o1.
275
+ callOnce .await ();
276
+ } catch (Exception ex ) {
277
+ ex .printStackTrace ();
278
+ fail (ex .getMessage ());
279
+ }
280
+ // The concated observable should have served up all of the odds.
281
+ Assert .assertEquals (o .length , index );
282
+ try {
283
+ // unblock observables so it can serve up o2 and complete
284
+ okToContinue .countDown ();
285
+ observableOfObservables .t .join ();
286
+ } catch (Exception ex ) {
287
+ ex .printStackTrace ();
288
+ fail (ex .getMessage ());
289
+ }
290
+ // The concatenated observable should now have served up all the evens.
291
+ Assert .assertEquals (expected .length , index );
292
+ }
293
+
294
+ private static class TestObservable <T > extends Observable <T > {
260
295
261
296
private final Subscription s = new Subscription () {
262
297
@@ -266,28 +301,28 @@ public void unsubscribe() {
266
301
}
267
302
268
303
};
269
- private final String [] values ;
304
+ private final List < T > values ;
270
305
private Thread t = null ;
271
306
private int count = 0 ;
272
307
private boolean subscribed = true ;
273
308
private final CountDownLatch once ;
274
309
private final CountDownLatch okToContinue ;
275
310
276
- public TestObservable (CountDownLatch once , CountDownLatch okToContinue , String ... values ) {
277
- this .values = values ;
311
+ public TestObservable (CountDownLatch once , CountDownLatch okToContinue , T ... values ) {
312
+ this .values = Arrays . asList ( values ) ;
278
313
this .once = once ;
279
314
this .okToContinue = okToContinue ;
280
315
}
281
316
282
317
@ Override
283
- public Subscription subscribe (final Observer <String > observer ) {
318
+ public Subscription subscribe (final Observer <T > observer ) {
284
319
t = new Thread (new Runnable () {
285
320
286
321
@ Override
287
322
public void run () {
288
323
try {
289
- while (count < values .length && subscribed ) {
290
- observer .onNext (values [ count ] );
324
+ while (count < values .size () && subscribed ) {
325
+ observer .onNext (values . get ( count ) );
291
326
count ++;
292
327
//Unblock the main thread to call unsubscribe.
293
328
if (null != once )
0 commit comments