19
19
import static org .mockito .Mockito .*;
20
20
21
21
import java .util .Arrays ;
22
- import java .util .HashMap ;
23
- import java .util .HashSet ;
24
22
import java .util .LinkedList ;
25
23
import java .util .List ;
26
24
import java .util .Map ;
27
- import java .util .Set ;
25
+ import java .util .concurrent . ConcurrentHashMap ;
28
26
import java .util .concurrent .atomic .AtomicBoolean ;
27
+ import java .util .concurrent .atomic .AtomicInteger ;
29
28
30
29
import org .junit .Test ;
31
30
import org .mockito .InOrder ;
31
+ import org .mockito .Matchers ;
32
32
33
33
import rx .Observable ;
34
34
import rx .Observer ;
52
52
public class OperationCombineLatest {
53
53
54
54
/**
55
- * Combines the two given Observables, emitting an item that aggregates the latest values of
56
- * each of the source Observables each time an item is received from either of the source
57
- * Observables, where the aggregation is defined by the given function.
58
- * @param w0 the first source Observable.
59
- * @param w1 the second source Observable.
60
- * @param combineLatestFunction the aggregation function that combines the source Observable
61
- * items.
62
- * @return a function from an Observer to a Subscription. This can be used to create an Observable.
55
+ * Combines the two given observables, emitting an event containing an aggregation of the latest values of each of the source observables
56
+ * each time an event is received from one of the source observables, where the aggregation is defined by the given function.
57
+ * @param w0
58
+ * The first source observable.
59
+ * @param w1
60
+ * The second source observable.
61
+ * @param combineLatestFunction
62
+ * The aggregation function used to combine the source observable values.
63
+ * @return A function from an observer to a subscription. This can be used to create an observable from.
63
64
*/
64
- public static <T0 , T1 , R > Func1 <Observer <R >, Subscription > combineLatest (Observable <T0 > w0 , Observable <T1 > w1 , Func2 <T0 , T1 , R > combineLatestFunction ) {
65
+ public static <T0 , T1 , R > Func1 <Observer <? super R >, Subscription > combineLatest (Observable <? super T0 > w0 , Observable <? super T1 > w1 , Func2 <? super T0 , ? super T1 , ? extends R > combineLatestFunction ) {
65
66
Aggregator <R > a = new Aggregator <R >(Functions .fromFunc (combineLatestFunction ));
66
67
a .addObserver (new CombineObserver <R , T0 >(a , w0 ));
67
68
a .addObserver (new CombineObserver <R , T1 >(a , w1 ));
@@ -71,7 +72,7 @@ public static <T0, T1, R> Func1<Observer<R>, Subscription> combineLatest(Observa
71
72
/**
72
73
* @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction)
73
74
*/
74
- public static <T0 , T1 , T2 , R > Func1 <Observer <R >, Subscription > combineLatest (Observable <T0 > w0 , Observable <T1 > w1 , Observable <T2 > w2 , Func3 <T0 , T1 , T2 , R > combineLatestFunction ) {
75
+ public static <T0 , T1 , T2 , R > Func1 <Observer <? super R >, Subscription > combineLatest (Observable <? super T0 > w0 , Observable <? super T1 > w1 , Observable <? super T2 > w2 , Func3 <? super T0 , ? super T1 , ? super T2 , ? extends R > combineLatestFunction ) {
75
76
Aggregator <R > a = new Aggregator <R >(Functions .fromFunc (combineLatestFunction ));
76
77
a .addObserver (new CombineObserver <R , T0 >(a , w0 ));
77
78
a .addObserver (new CombineObserver <R , T1 >(a , w1 ));
@@ -82,7 +83,7 @@ public static <T0, T1, T2, R> Func1<Observer<R>, Subscription> combineLatest(Obs
82
83
/**
83
84
* @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction)
84
85
*/
85
- public static <T0 , T1 , T2 , T3 , R > Func1 <Observer <R >, Subscription > combineLatest (Observable <T0 > w0 , Observable <T1 > w1 , Observable <T2 > w2 , Observable <T3 > w3 , Func4 <T0 , T1 , T2 , T3 , R > combineLatestFunction ) {
86
+ public static <T0 , T1 , T2 , T3 , R > Func1 <Observer <? super R >, Subscription > combineLatest (Observable <? super T0 > w0 , Observable <? super T1 > w1 , Observable <? super T2 > w2 , Observable <? super T3 > w3 , Func4 <? super T0 , ? super T1 , ? super T2 , ? super T3 , ? extends R > combineLatestFunction ) {
86
87
Aggregator <R > a = new Aggregator <R >(Functions .fromFunc (combineLatestFunction ));
87
88
a .addObserver (new CombineObserver <R , T0 >(a , w0 ));
88
89
a .addObserver (new CombineObserver <R , T1 >(a , w1 ));
@@ -92,16 +93,16 @@ public static <T0, T1, T2, T3, R> Func1<Observer<R>, Subscription> combineLatest
92
93
}
93
94
94
95
private static class CombineObserver <R , T > implements Observer <T > {
95
- final Observable <T > w ;
96
- final Aggregator <R > a ;
96
+ final Observable <? super T > w ;
97
+ final Aggregator <? super R > a ;
97
98
private Subscription subscription ;
98
99
99
- public CombineObserver (Aggregator <R > a , Observable <T > w ) {
100
+ public CombineObserver (Aggregator <? super R > a , Observable <? super T > w ) {
100
101
this .a = a ;
101
102
this .w = w ;
102
103
}
103
104
104
- public synchronized void startWatching () {
105
+ private void startWatching () {
105
106
if (subscription != null ) {
106
107
throw new RuntimeException ("This should only be called once." );
107
108
}
@@ -129,44 +130,28 @@ public void onNext(T args) {
129
130
* whenever we have received an event from one of the observables, as soon as each Observable has received
130
131
* at least one event.
131
132
*/
132
- private static class Aggregator <R > implements Func1 <Observer <R >, Subscription > {
133
+ private static class Aggregator <R > implements Func1 <Observer <? super R >, Subscription > {
133
134
134
- private Observer <R > observer ;
135
+ private volatile Observer <? super R > observer ;
135
136
136
- private final FuncN <R > combineLatestFunction ;
137
+ private final FuncN <? extends R > combineLatestFunction ;
137
138
private final AtomicBoolean running = new AtomicBoolean (true );
138
-
139
- // used as an internal lock for handling the latest values and the completed state of each observer
140
- private final Object lockObject = new Object ();
141
139
142
- /**
143
- * Store when an observer completes.
144
- * <p>
145
- * Note that access to this set MUST BE SYNCHRONIZED via 'lockObject' above.
146
- * */
147
- private final Set <CombineObserver <R , ?>> completed = new HashSet <CombineObserver <R , ?>>();
148
-
149
- /**
150
- * The latest value from each observer
151
- * <p>
152
- * Note that access to this set MUST BE SYNCHRONIZED via 'lockObject' above.
153
- * */
154
- private final Map <CombineObserver <R , ?>, Object > latestValue = new HashMap <CombineObserver <R , ?>, Object >();
140
+ // Stores how many observers have already completed
141
+ private final AtomicInteger numCompleted = new AtomicInteger (0 );
155
142
156
143
/**
157
- * Whether each observer has a latest value at all.
158
- * <p>
159
- * Note that access to this set MUST BE SYNCHRONIZED via 'lockObject' above.
160
- * */
161
- private final Set <CombineObserver <R , ?>> hasLatestValue = new HashSet <CombineObserver <R , ?>>();
162
-
144
+ * The latest value from each observer.
145
+ */
146
+ private final Map <CombineObserver <? extends R , ?>, Object > latestValue = new ConcurrentHashMap <CombineObserver <? extends R , ?>, Object >();
147
+
163
148
/**
164
149
* Ordered list of observers to combine.
165
150
* No synchronization is necessary as these can not be added or changed asynchronously.
166
151
*/
167
152
private final List <CombineObserver <R , ?>> observers = new LinkedList <CombineObserver <R , ?>>();
168
153
169
- public Aggregator (FuncN <R > combineLatestFunction ) {
154
+ public Aggregator (FuncN <? extends R > combineLatestFunction ) {
170
155
this .combineLatestFunction = combineLatestFunction ;
171
156
}
172
157
@@ -184,18 +169,15 @@ <T> void addObserver(CombineObserver<R, T> w) {
184
169
*
185
170
* @param w The observer that has completed.
186
171
*/
187
- <T > void complete (CombineObserver <R , T > w ) {
188
- synchronized (lockObject ) {
189
- // store that this CombineLatestObserver is completed
190
- completed .add (w );
191
- // if all CombineObservers are completed, we mark the whole thing as completed
192
- if (completed .size () == observers .size ()) {
193
- if (running .get ()) {
194
- // mark ourselves as done
195
- observer .onCompleted ();
196
- // just to ensure we stop processing in case we receive more onNext/complete/error calls after this
197
- running .set (false );
198
- }
172
+ <T > void complete (CombineObserver <? extends R , ? super T > w ) {
173
+ int completed = numCompleted .incrementAndGet ();
174
+ // if all CombineObservers are completed, we mark the whole thing as completed
175
+ if (completed == observers .size ()) {
176
+ if (running .get ()) {
177
+ // mark ourselves as done
178
+ observer .onCompleted ();
179
+ // just to ensure we stop processing in case we receive more onNext/complete/error calls after this
180
+ running .set (false );
199
181
}
200
182
}
201
183
}
@@ -217,7 +199,7 @@ void error(Exception e) {
217
199
* @param w
218
200
* @param arg
219
201
*/
220
- <T > void next (CombineObserver <R , T > w , T arg ) {
202
+ <T > void next (CombineObserver <? extends R , ? super T > w , T arg ) {
221
203
if (observer == null ) {
222
204
throw new RuntimeException ("This shouldn't be running if an Observer isn't registered" );
223
205
}
@@ -227,54 +209,48 @@ <T> void next(CombineObserver<R, T> w, T arg) {
227
209
return ;
228
210
}
229
211
230
- // define here so the variable is out of the synchronized scope
212
+ // remember this as the latest value for this observer
213
+ latestValue .put (w , arg );
214
+
215
+ if (latestValue .size () < observers .size ()) {
216
+ // we don't have a value yet for each observer to combine, so we don't have a combined value yet either
217
+ return ;
218
+ }
219
+
231
220
Object [] argsToCombineLatest = new Object [observers .size ()];
232
-
233
- // we synchronize everything that touches latest values
234
- synchronized (lockObject ) {
235
- // remember this as the latest value for this observer
236
- latestValue .put (w , arg );
237
-
238
- // remember that this observer now has a latest value set
239
- hasLatestValue .add (w );
240
-
241
- // if all observers in the 'observers' list have a value, invoke the combineLatestFunction
242
- for (CombineObserver <R , ?> rw : observers ) {
243
- if (!hasLatestValue .contains (rw )) {
244
- // we don't have a value yet for each observer to combine, so we don't have a combined value yet either
245
- return ;
246
- }
247
- }
248
- // if we get to here this means all the queues have data
249
- int i = 0 ;
250
- for (CombineObserver <R , ?> _w : observers ) {
251
- argsToCombineLatest [i ++] = latestValue .get (_w );
252
- }
221
+ int i = 0 ;
222
+ for (CombineObserver <R , ?> _w : observers ) {
223
+ argsToCombineLatest [i ++] = latestValue .get (_w );
224
+ }
225
+
226
+ try {
227
+ R combinedValue = combineLatestFunction .call (argsToCombineLatest );
228
+ observer .onNext (combinedValue );
229
+ } catch (Exception ex ) {
230
+ observer .onError (ex );
253
231
}
254
- // if we did not return above from the synchronized block we can now invoke the combineLatestFunction with all of the args
255
- // we do this outside the synchronized block as it is now safe to call this concurrently and don't need to block other threads from calling
256
- // this 'next' method while another thread finishes calling this combineLatestFunction
257
- observer .onNext (combineLatestFunction .call (argsToCombineLatest ));
258
232
}
259
233
260
234
@ Override
261
- public Subscription call (Observer <R > observer ) {
235
+ public Subscription call (Observer <? super R > observer ) {
262
236
if (this .observer != null ) {
263
237
throw new IllegalStateException ("Only one Observer can subscribe to this Observable." );
264
238
}
265
- this .observer = observer ;
239
+
240
+ AtomicObservableSubscription subscription = new AtomicObservableSubscription (new Subscription () {
241
+ @ Override
242
+ public void unsubscribe () {
243
+ stop ();
244
+ }
245
+ });
246
+ this .observer = new SynchronizedObserver <R >(observer , subscription );
266
247
267
248
/* start the observers */
268
249
for (CombineObserver <R , ?> rw : observers ) {
269
250
rw .startWatching ();
270
251
}
271
252
272
- return new Subscription () {
273
- @ Override
274
- public void unsubscribe () {
275
- stop ();
276
- }
277
- };
253
+ return subscription ;
278
254
}
279
255
280
256
private void stop () {
@@ -291,10 +267,33 @@ private void stop() {
291
267
292
268
public static class UnitTest {
293
269
294
- @ SuppressWarnings ("unchecked" )
295
- /* mock calls don't do generics */
270
+ @ Test
271
+ public void testCombineLatestWithFunctionThatThrowsAnException () {
272
+ @ SuppressWarnings ("unchecked" ) // mock calls don't do generics
273
+ Observer <String > w = mock (Observer .class );
274
+
275
+ TestObservable w1 = new TestObservable ();
276
+ TestObservable w2 = new TestObservable ();
277
+
278
+ Observable <String > combined = Observable .create (combineLatest (w1 , w2 , new Func2 <String , String , String >() {
279
+ @ Override
280
+ public String call (String v1 , String v2 ) {
281
+ throw new RuntimeException ("I don't work." );
282
+ }
283
+ }));
284
+ combined .subscribe (w );
285
+
286
+ w1 .Observer .onNext ("first value of w1" );
287
+ w2 .Observer .onNext ("first value of w2" );
288
+
289
+ verify (w , never ()).onNext (anyString ());
290
+ verify (w , never ()).onCompleted ();
291
+ verify (w , times (1 )).onError (Matchers .<RuntimeException >any ());
292
+ }
293
+
296
294
@ Test
297
295
public void testCombineLatestDifferentLengthObservableSequences1 () {
296
+ @ SuppressWarnings ("unchecked" ) // mock calls don't do generics
298
297
Observer <String > w = mock (Observer .class );
299
298
300
299
TestObservable w1 = new TestObservable ();
0 commit comments