();
61 | for (final Method m : classToExtend.getMethods()) {
62 | for (Class c : m.getParameterTypes()) {
63 | if (Function.class.isAssignableFrom(c)) {
64 | funcMethods.add(m);
65 | names.add(m.getName());
66 | // break out of parameter-type loop
67 | break;
68 | }
69 | }
70 | }
71 |
72 | CallSiteArray callSites = new CallSiteArray(classToExtend, names.toArray(new String[0]));
73 | for (int index = 0; index < funcMethods.size(); index++) {
74 | methods.add(createMetaMethod(funcMethods.get(index), callSites, index));
75 | }
76 | }
77 |
78 | return methods;
79 | }
80 |
81 | private MetaMethod createMetaMethod(final Method m, final CallSiteArray callSites, final int callSiteIndex) {
82 | if (m.getDeclaringClass().equals(Observable.class) && m.getName().equals("create")) {
83 | return specialCasedOverrideForCreate(m);
84 | }
85 | return new MetaMethod() {
86 |
87 | @Override
88 | public int getModifiers() {
89 | return m.getModifiers();
90 | }
91 |
92 | @Override
93 | public String getName() {
94 | return m.getName();
95 | }
96 |
97 | @SuppressWarnings("rawtypes")
98 | @Override
99 | public Class getReturnType() {
100 | return m.getReturnType();
101 | }
102 |
103 | @Override
104 | public CachedClass getDeclaringClass() {
105 | return ReflectionCache.getCachedClass(m.getDeclaringClass());
106 | }
107 |
108 | @SuppressWarnings({ "rawtypes", "unchecked" })
109 | @Override
110 | public Object invoke(Object object, Object[] arguments) {
111 | // System.out.println("***** RxGroovyExtensionModule => invoked [" + getName() + "]: " + object + " args: " + arguments[0]);
112 | try {
113 | Object[] newArgs = new Object[arguments.length];
114 | for (int i = 0; i < arguments.length; i++) {
115 | final Object o = arguments[i];
116 | if (o instanceof Closure) {
117 | if (Action.class.isAssignableFrom(m.getParameterTypes()[i])) {
118 | newArgs[i] = new GroovyActionWrapper((Closure) o);
119 | } else if (OnSubscribe.class.isAssignableFrom(m.getParameterTypes()[i])) {
120 | newArgs[i] = new GroovyOnSubscribeWrapper((Closure) o);
121 | } else {
122 | newArgs[i] = new GroovyFunctionWrapper((Closure) o);
123 | }
124 | } else {
125 | newArgs[i] = o;
126 | }
127 | }
128 |
129 | return callSites.array[callSiteIndex].call(object, newArgs);
130 | } catch (Throwable e) {
131 | if (e instanceof RuntimeException) {
132 | // re-throw whatever was thrown to us
133 | throw (RuntimeException) e;
134 | } else {
135 | throw new RuntimeException(e);
136 | }
137 | }
138 | }
139 |
140 | @Override
141 | public CachedClass[] getParameterTypes() {
142 | if (parameterTypes == null) {
143 | getParametersTypes0();
144 | }
145 |
146 | return parameterTypes;
147 | }
148 |
149 | private synchronized void getParametersTypes0() {
150 | if (parameterTypes != null)
151 | return;
152 |
153 | Class [] npt = nativeParamTypes == null ? getPT() : nativeParamTypes;
154 |
155 | CachedClass[] pt = new CachedClass [npt.length];
156 | for (int i = 0; i != npt.length; ++i) {
157 | if (Function.class.isAssignableFrom(npt[i])) {
158 | // function type to be replaced by closure
159 | pt[i] = ReflectionCache.getCachedClass(Closure.class);
160 | } else {
161 | // non-function type
162 | pt[i] = ReflectionCache.getCachedClass(npt[i]);
163 | }
164 | }
165 |
166 | nativeParamTypes = npt;
167 | setParametersTypes(pt);
168 | }
169 |
170 | @Override
171 | protected Class[] getPT() {
172 | return m.getParameterTypes();
173 | }
174 | };
175 | }
176 |
177 | /**
178 | * Special case until we finish migrating off the deprecated 'create' method signature
179 | */
180 | private MetaMethod specialCasedOverrideForCreate(final Method m) {
181 | return new MetaMethod() {
182 |
183 | @Override
184 | public int getModifiers() {
185 | return m.getModifiers();
186 | }
187 |
188 | @Override
189 | public String getName() {
190 | return m.getName();
191 | }
192 |
193 | @Override
194 | public Class> getReturnType() {
195 | return m.getReturnType();
196 | }
197 |
198 | @Override
199 | public CachedClass getDeclaringClass() {
200 | return ReflectionCache.getCachedClass(m.getDeclaringClass());
201 | }
202 |
203 | @Override
204 | @SuppressWarnings("unchecked")
205 | public Object invoke(Object object, final Object[] arguments) {
206 | return Observable.create(new GroovyCreateWrapper((Closure) arguments[0]));
207 | }
208 |
209 | @Override
210 | public CachedClass[] getParameterTypes() {
211 | if (parameterTypes == null) {
212 | getParametersTypes0();
213 | }
214 |
215 | return parameterTypes;
216 | }
217 |
218 | private synchronized void getParametersTypes0() {
219 | if (parameterTypes != null)
220 | return;
221 |
222 | Class [] npt = nativeParamTypes == null ? getPT() : nativeParamTypes;
223 |
224 | CachedClass[] pt = new CachedClass [npt.length];
225 | for (int i = 0; i != npt.length; ++i) {
226 | if (Function.class.isAssignableFrom(npt[i])) {
227 | // function type to be replaced by closure
228 | pt[i] = ReflectionCache.getCachedClass(Closure.class);
229 | } else {
230 | // non-function type
231 | pt[i] = ReflectionCache.getCachedClass(npt[i]);
232 | }
233 | }
234 |
235 | nativeParamTypes = npt;
236 | setParametersTypes(pt);
237 | }
238 |
239 | @Override
240 | protected Class[] getPT() {
241 | return m.getParameterTypes();
242 | }
243 | };
244 | }
245 |
246 | }
247 |
--------------------------------------------------------------------------------
/src/main/java/rx/lang/groovy/RxGroovyPropertiesModuleFactory.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Netflix, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rx.lang.groovy;
17 |
18 | import java.util.Properties;
19 |
20 | import org.codehaus.groovy.runtime.m12n.ExtensionModule;
21 | import org.codehaus.groovy.runtime.m12n.PropertiesModuleFactory;
22 |
23 | /**
24 | * Factory for {@link RxGroovyExtensionModule} to add extension methods.
25 | *
26 | * This is loaded from /META-INF/services/org.codehaus.groovy.runtime.ExtensionModule
27 | *
28 | * The property is defined as: moduleFactory=rx.lang.groovy.RxGroovyPropertiesModuleFactory
29 | */
30 | public class RxGroovyPropertiesModuleFactory extends PropertiesModuleFactory {
31 |
32 | @Override
33 | public ExtensionModule newModule(Properties properties, ClassLoader classLoader) {
34 | return new RxGroovyExtensionModule();
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule:
--------------------------------------------------------------------------------
1 | moduleFactory=rx.lang.groovy.RxGroovyPropertiesModuleFactory
--------------------------------------------------------------------------------
/src/perf/java/rx/jmh/InputWithIncrementingInteger.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Netflix, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rx.jmh;
17 |
18 | import java.util.Iterator;
19 |
20 | import org.openjdk.jmh.annotations.Setup;
21 | import org.openjdk.jmh.infra.Blackhole;
22 |
23 | import rx.Observable;
24 | import rx.Observable.OnSubscribe;
25 | import rx.Observer;
26 | import rx.Subscriber;
27 |
28 | /**
29 | * Exposes an Observable and Observer that increments n Integers and consumes them in a Blackhole.
30 | */
31 | public abstract class InputWithIncrementingInteger {
32 | public Iterable iterable;
33 | public Observable observable;
34 | public Observable firehose;
35 | public Blackhole bh;
36 | public Observer observer;
37 |
38 | public abstract int getSize();
39 |
40 | @Setup
41 | public void setup(final Blackhole bh) {
42 | this.bh = bh;
43 | observable = Observable.range(0, getSize());
44 |
45 | firehose = Observable.create(new OnSubscribe() {
46 |
47 | @Override
48 | public void call(Subscriber super Integer> s) {
49 | for (int i = 0; i < getSize(); i++) {
50 | s.onNext(i);
51 | }
52 | s.onCompleted();
53 | }
54 |
55 | });
56 |
57 | iterable = new Iterable() {
58 |
59 | @Override
60 | public Iterator iterator() {
61 | return new Iterator() {
62 |
63 | int i = 0;
64 |
65 | @Override
66 | public boolean hasNext() {
67 | return i < getSize();
68 | }
69 |
70 | @Override
71 | public Integer next() {
72 | return i++;
73 | }
74 |
75 | @Override
76 | public void remove() {
77 |
78 | }
79 |
80 | };
81 | }
82 |
83 | };
84 | observer = new Observer() {
85 |
86 | @Override
87 | public void onCompleted() {
88 |
89 | }
90 |
91 | @Override
92 | public void onError(Throwable e) {
93 |
94 | }
95 |
96 | @Override
97 | public void onNext(Integer t) {
98 | bh.consume(t);
99 | }
100 |
101 | };
102 |
103 | }
104 |
105 | public LatchedObserver newLatchedObserver() {
106 | return new LatchedObserver(bh);
107 | }
108 |
109 | public Subscriber newSubscriber() {
110 | return new Subscriber() {
111 |
112 | @Override
113 | public void onCompleted() {
114 |
115 | }
116 |
117 | @Override
118 | public void onError(Throwable e) {
119 |
120 | }
121 |
122 | @Override
123 | public void onNext(Integer t) {
124 | bh.consume(t);
125 | }
126 |
127 | };
128 | }
129 |
130 | }
131 |
--------------------------------------------------------------------------------
/src/perf/java/rx/jmh/LatchedObserver.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2014 Netflix, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rx.jmh;
17 |
18 | import java.util.concurrent.CountDownLatch;
19 |
20 | import org.openjdk.jmh.infra.Blackhole;
21 |
22 | import rx.Observer;
23 |
24 | public class LatchedObserver implements Observer {
25 |
26 | public CountDownLatch latch = new CountDownLatch(1);
27 | private final Blackhole bh;
28 |
29 | public LatchedObserver(Blackhole bh) {
30 | this.bh = bh;
31 | }
32 |
33 | @Override
34 | public void onCompleted() {
35 | latch.countDown();
36 | }
37 |
38 | @Override
39 | public void onError(Throwable e) {
40 | latch.countDown();
41 | }
42 |
43 | @Override
44 | public void onNext(T t) {
45 | bh.consume(t);
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/perf/java/rx/jmh/README.txt:
--------------------------------------------------------------------------------
1 | For examples see:
2 |
3 | http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/
4 | https://github.com/nitsanw/jmh-samples/blob/master/src/main/java/org/openjdk/jmh/samples/
5 | https://github.com/nitsanw/jmh-samples/blob/master/src/main/java/org/openjdk/jmh/samples/JMHSample_15_Asymmetric.java
6 | https://github.com/nitsanw/jmh-samples/blob/master/src/main/java/org/openjdk/jmh/samples/JMHSample_09_Blackholes.java#L86
7 |
--------------------------------------------------------------------------------
/src/perf/java/rx/lang/groovy/ExtensionPerf.java:
--------------------------------------------------------------------------------
1 | package rx.lang.groovy;
2 |
3 | import org.openjdk.jmh.annotations.Benchmark;
4 | import org.openjdk.jmh.annotations.BenchmarkMode;
5 | import org.openjdk.jmh.annotations.Mode;
6 | import org.openjdk.jmh.annotations.OutputTimeUnit;
7 | import org.openjdk.jmh.annotations.Param;
8 | import org.openjdk.jmh.annotations.Scope;
9 | import org.openjdk.jmh.annotations.Setup;
10 | import org.openjdk.jmh.annotations.State;
11 |
12 | import rx.Observable;
13 | import rx.jmh.InputWithIncrementingInteger;
14 | import groovy.lang.GroovyShell;
15 | import groovy.lang.Script;
16 |
17 | import java.util.concurrent.TimeUnit;
18 |
19 | @BenchmarkMode(Mode.Throughput)
20 | @OutputTimeUnit(TimeUnit.SECONDS)
21 | public class ExtensionPerf {
22 | @State(Scope.Thread)
23 | public static class Input extends InputWithIncrementingInteger {
24 | public GroovyShell shell;
25 | public Script script;
26 |
27 | @Param({ "1", "1000", "1000000" })
28 | public int size;
29 |
30 | @Param({ "in.map({ it + 1})", "in.filter({ it != 0 })" })
31 | public String source;
32 |
33 | @Override
34 | public int getSize() {
35 | return size;
36 | }
37 |
38 | @Setup
39 | public void initGroovyStuff() {
40 | shell = new GroovyShell();
41 | script = shell.parse(source);
42 | shell.setVariable("in", firehose);
43 | }
44 | }
45 |
46 | @SuppressWarnings("unchecked")
47 | @Benchmark
48 | public void extension(Input input) throws InterruptedException {
49 | ((Observable) input.script.run()).subscribe(input.observer);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/perf/java/rx/lang/groovy/ObservableExtension.java:
--------------------------------------------------------------------------------
1 | package rx.lang.groovy;
2 |
3 | import rx.Observable;
4 | import rx.functions.Func1;
5 |
6 | public class ObservableExtension {
7 | public Observable filter(Observable self, Func1 super T, Boolean> predicate) {
8 | return self.filter(predicate);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/perf/java/rx/lang/groovy/README.md:
--------------------------------------------------------------------------------
1 | # JMH Benchmarks
2 |
3 | ### Run All
4 |
5 | ```
6 | ./gradlew benchmarks
7 | ```
8 |
9 | ### Run Specific Class
10 |
11 | ```
12 | ./gradlew benchmarks '-Pjmh=.*OperatorMapPerf.*'
13 | ```
14 |
15 | ### Arguments
16 |
17 | Optionally pass arguments for custom execution. Example:
18 |
19 | ```
20 | ./gradlew benchmarks '-Pjmh=-f 1 -tu s -bm thrpt -wi 5 -i 5 -r 1 .*OperatorMapPerf.*'
21 | ```
22 |
23 | gives output like this:
24 |
25 | ```
26 | # Warmup Iteration 1: 17541564.529 ops/s
27 | # Warmup Iteration 2: 20923290.222 ops/s
28 | # Warmup Iteration 3: 20743426.176 ops/s
29 | # Warmup Iteration 4: 23462116.103 ops/s
30 | # Warmup Iteration 5: 24283139.706 ops/s
31 | Iteration 1: 23317338.378 ops/s
32 | Iteration 2: 23478480.189 ops/s
33 | Iteration 3: 23186077.043 ops/s
34 | Iteration 4: 22120569.854 ops/s
35 | Iteration 5: 21581828.652 ops/s
36 |
37 | Result: 22736858.823 ±(99.9%) 3223211.259 ops/s [Average]
38 | Statistics: (min, avg, max) = (21581828.652, 22736858.823, 23478480.189), stdev = 837057.728
39 | Confidence interval (99.9%): [19513647.564, 25960070.082]
40 | ```
41 |
42 | To see all options:
43 |
44 | ```
45 | ./gradlew benchmarks '-Pjmh=-h'
46 | ```
47 |
--------------------------------------------------------------------------------
/src/perf/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule:
--------------------------------------------------------------------------------
1 | moduleName=groovy-extensions-perf
2 | moduleVersion=1.0
3 | extensionClasses=rx.lang.groovy.ObservableExtension
--------------------------------------------------------------------------------
/src/test/groovy/rx/lang/groovy/ObservableTests.groovy:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2013 Netflix, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package rx.lang.groovy
17 |
18 | import static org.junit.Assert.*
19 | import static org.mockito.Matchers.*
20 | import static org.mockito.Mockito.*
21 |
22 | import org.junit.Before
23 | import org.junit.Test
24 | import org.mockito.Mock
25 | import org.mockito.MockitoAnnotations
26 |
27 | import rx.Notification
28 | import rx.Observable
29 | import rx.Observer
30 | import rx.Subscriber
31 | import rx.Subscription
32 | import rx.Observable.OnSubscribe
33 | import rx.subscriptions.Subscriptions
34 |
35 | def class ObservableTests {
36 |
37 | @Mock
38 | ScriptAssertion a;
39 |
40 | @Mock
41 | Observer w;
42 |
43 | @Before
44 | public void before() {
45 | MockitoAnnotations.initMocks(this);
46 | }
47 |
48 | @Test
49 | public void testCreate() {
50 | Observable.create({it.onNext('hello');it.onCompleted();}).subscribe({ result -> a.received(result)});
51 | verify(a, times(1)).received("hello");
52 | }
53 |
54 | @Test
55 | public void testFilter() {
56 | Observable.from(1, 2, 3).filter({it >= 2}).subscribe({ result -> a.received(result)});
57 | verify(a, times(0)).received(1);
58 | verify(a, times(1)).received(2);
59 | verify(a, times(1)).received(3);
60 | }
61 |
62 | @Test
63 | public void testLast() {
64 | assertEquals("three", Observable.from("one", "two", "three").toBlocking().last())
65 | }
66 |
67 | @Test
68 | public void testLastWithPredicate() {
69 | assertEquals("two", Observable.from("one", "two", "three").toBlocking().last({ x -> x.length() == 3}))
70 | }
71 |
72 | @Test
73 | public void testMap1() {
74 | Observable.from(1).map({v -> 'hello_' + v}).subscribe({ result -> a.received(result)});
75 | verify(a, times(1)).received("hello_1");
76 | }
77 |
78 | @Test
79 | public void testMap2() {
80 | Observable.from(1, 2, 3).map({'hello_' + it}).subscribe({ result -> a.received(result)});
81 | verify(a, times(1)).received("hello_" + 1);
82 | verify(a, times(1)).received("hello_" + 2);
83 | verify(a, times(1)).received("hello_" + 3);
84 | }
85 |
86 | @Test
87 | public void testMaterialize() {
88 | Observable.from(1, 2, 3).materialize().subscribe({ result -> a.received(result)});
89 | // we expect 4 onNext calls: 3 for 1, 2, 3 ObservableNotification.OnNext and 1 for ObservableNotification.OnCompleted
90 | verify(a, times(4)).received(any(Notification.class));
91 | verify(a, times(0)).error(any(Exception.class));
92 | }
93 |
94 | @Test
95 | public void testMerge() {
96 | Observable.merge(
97 | Observable.from(1, 2, 3),
98 | Observable.merge(
99 | Observable.from(6),
100 | Observable.error(new NullPointerException()),
101 | Observable.from(7)),
102 | Observable.from(4, 5))
103 | .subscribe({ result -> a.received(result)}, { exception -> a.error(exception)});
104 |
105 | // executing synchronously so we can deterministically know what order things will come
106 | verify(a, times(1)).received(1);
107 | verify(a, times(1)).received(2);
108 | verify(a, times(1)).received(3);
109 | verify(a, times(0)).received(4); // the NPE will cause this sequence to be skipped
110 | verify(a, times(0)).received(5); // the NPE will cause this sequence to be skipped
111 | verify(a, times(1)).received(6); // this comes before the NPE so should exist
112 | verify(a, times(0)).received(7);// this comes in the sequence after the NPE
113 | verify(a, times(1)).error(any(NullPointerException.class));
114 | }
115 |
116 | @Test
117 | public void testScriptWithMaterialize() {
118 | new TestFactory().getObservable().materialize().subscribe({ result -> a.received(result)});
119 | // 2 times: once for hello_1 and once for onCompleted
120 | verify(a, times(2)).received(any(Notification.class));
121 | }
122 |
123 | @Test
124 | public void testScriptWithMerge() {
125 | TestFactory f = new TestFactory();
126 | Observable.merge(f.getObservable(), f.getObservable()).subscribe({ result -> a.received(result)});
127 | verify(a, times(1)).received("hello_1");
128 | verify(a, times(1)).received("hello_2");
129 | }
130 |
131 | @Test
132 | public void testFromWithIterable() {
133 | def list = [1, 2, 3, 4, 5]
134 | assertEquals(5, Observable.from(list).count().toBlocking().single());
135 | }
136 |
137 | @Test
138 | public void testFromWithObjects() {
139 | def list = [1, 2, 3, 4, 5]
140 | // this should now treat these as 2 objects so have a count of 2
141 | assertEquals(2, Observable.from(list, 6).count().toBlocking().single());
142 | }
143 |
144 | /**
145 | * Check that two different single arg methods are selected correctly
146 | */
147 | @Test
148 | public void testStartWith() {
149 | def list = [10, 11, 12, 13, 14]
150 | def startList = [1, 2, 3, 4, 5]
151 | assertEquals(6, Observable.from(list).startWith(0).count().toBlocking().single());
152 | assertEquals(10, Observable.from(list).startWith(startList).count().toBlocking().single());
153 | }
154 |
155 | @Test
156 | public void testScriptWithOnNext() {
157 | new TestFactory().getObservable().subscribe({ result -> a.received(result)});
158 | verify(a).received("hello_1");
159 | }
160 |
161 | @Test
162 | public void testSkipTake() {
163 | Observable.from(1, 2, 3).skip(1).take(1).subscribe({ result -> a.received(result)});
164 | verify(a, times(0)).received(1);
165 | verify(a, times(1)).received(2);
166 | verify(a, times(0)).received(3);
167 | }
168 |
169 | @Test
170 | public void testSkip() {
171 | Observable.from(1, 2, 3).skip(2).subscribe({ result -> a.received(result)});
172 | verify(a, times(0)).received(1);
173 | verify(a, times(0)).received(2);
174 | verify(a, times(1)).received(3);
175 | }
176 |
177 | @Test
178 | public void testTake() {
179 | Observable.from(1, 2, 3).take(2).subscribe({ result -> a.received(result)});
180 | verify(a, times(1)).received(1);
181 | verify(a, times(1)).received(2);
182 | verify(a, times(0)).received(3);
183 | }
184 |
185 | @Test
186 | public void testTakeLast() {
187 | new TestFactory().getObservable().takeLast(1).subscribe({ result -> a.received(result)});
188 | verify(a, times(1)).received("hello_1");
189 | }
190 |
191 | @Test
192 | public void testTakeWhileViaGroovy() {
193 | Observable.from(1, 2, 3).takeWhile( { x -> x < 3}).subscribe({ result -> a.received(result)});
194 | verify(a, times(1)).received(1);
195 | verify(a, times(1)).received(2);
196 | verify(a, times(0)).received(3);
197 | }
198 |
199 | @Test
200 | public void testToSortedList() {
201 | new TestFactory().getNumbers().toSortedList().subscribe({ result -> a.received(result)});
202 | verify(a, times(1)).received(Arrays.asList(1, 2, 3, 4, 5));
203 | }
204 |
205 | @Test
206 | public void testToSortedListWithFunction() {
207 | new TestFactory().getNumbers().toSortedList({a, b -> a - b}).subscribe({ result -> a.received(result)});
208 | verify(a, times(1)).received(Arrays.asList(1, 2, 3, 4, 5));
209 | }
210 |
211 | @Test
212 | public void testForEach() {
213 | Observable.create(new AsyncObservable()).toBlocking().forEach({ result -> a.received(result)});
214 | verify(a, times(1)).received(1);
215 | verify(a, times(1)).received(2);
216 | verify(a, times(1)).received(3);
217 | }
218 |
219 | @Test
220 | public void testForEachWithError() {
221 | try {
222 | Observable.create(new AsyncObservable()).toBlocking().forEach({ result -> throw new RuntimeException('err')});
223 | fail("we expect an exception to be thrown");
224 | }catch(Exception e) {
225 | // do nothing as we expect this
226 | }
227 | }
228 |
229 | @Test
230 | public void testLastOrDefault() {
231 | def val = Observable.from("one", "two").toBlocking().lastOrDefault("default", { x -> x.length() == 3})
232 | assertEquals("two", val)
233 | }
234 |
235 | @Test
236 | public void testLastOrDefault2() {
237 | def val = Observable.from("one", "two").toBlocking().lastOrDefault("default", { x -> x.length() > 3})
238 | assertEquals("default", val)
239 | }
240 |
241 | public void testSingle1() {
242 | def s = Observable.from("one").toBlocking().single({ x -> x.length() == 3})
243 | assertEquals("one", s)
244 | }
245 |
246 | @Test(expected = IllegalArgumentException.class)
247 | public void testSingle2() {
248 | Observable.from("one", "two").toBlocking().single({ x -> x.length() == 3})
249 | }
250 |
251 | @Test
252 | public void testDefer() {
253 | def obs = Observable.from(1, 2)
254 | Observable.defer({-> obs }).subscribe({ result -> a.received(result)})
255 | verify(a, times(1)).received(1);
256 | verify(a, times(1)).received(2);
257 |
258 | }
259 |
260 | @Test
261 | public void testAll() {
262 | Observable.from(1, 2, 3).all({ x -> x > 0 }).subscribe({ result -> a.received(result) });
263 | verify(a, times(1)).received(true);
264 | }
265 |
266 |
267 | @Test
268 | public void testZip() {
269 | Observable o1 = Observable.from(1, 2, 3);
270 | Observable o2 = Observable.from(4, 5, 6);
271 | Observable o3 = Observable.from(7, 8, 9);
272 |
273 | List values = Observable.zip(o1, o2, o3, {a, b, c -> [a, b, c] }).toList().toBlocking().single();
274 | assertEquals([1, 4, 7], values.get(0));
275 | assertEquals([2, 5, 8], values.get(1));
276 | assertEquals([3, 6, 9], values.get(2));
277 | }
278 |
279 | @Test
280 | public void testZipWithIterable() {
281 | Observable o1 = Observable.from(1, 2, 3);
282 | Observable o2 = Observable.from(4, 5, 6);
283 | Observable o3 = Observable.from(7, 8, 9);
284 |
285 | List values = Observable.zip([o1, o2, o3], {a, b, c -> [a, b, c] }).toList().toBlocking().single();
286 | assertEquals([1, 4, 7], values.get(0));
287 | assertEquals([2, 5, 8], values.get(1));
288 | assertEquals([3, 6, 9], values.get(2));
289 | }
290 |
291 | @Test
292 | public void testGroupBy() {
293 | int count=0;
294 |
295 | Observable.from("one", "two", "three", "four", "five", "six")
296 | .groupBy({String s -> s.length()})
297 | .flatMap({
298 | groupObservable ->
299 |
300 | return groupObservable.map({
301 | s ->
302 | return "Value: " + s + " Group: " + groupObservable.getKey();
303 | });
304 | }).toBlocking().forEach({
305 | s ->
306 | println(s);
307 | count++;
308 | })
309 |
310 | assertEquals(6, count);
311 | }
312 |
313 | @Test
314 | public void testToMap1() {
315 | Map actual = new HashMap();
316 |
317 | Observable.from("a", "bb", "ccc", "dddd")
318 | .toMap({String s -> s.length()})
319 | .toBlocking()
320 | .forEach({s -> actual.putAll(s); });
321 |
322 | Map expected = new HashMap();
323 | expected.put(1, "a");
324 | expected.put(2, "bb");
325 | expected.put(3, "ccc");
326 | expected.put(4, "dddd");
327 |
328 | assertEquals(expected, actual);
329 | }
330 |
331 | @Test
332 | public void testToMap2() {
333 | Map actual = new HashMap();
334 |
335 | Observable.from("a", "bb", "ccc", "dddd")
336 | .toMap({String s -> s.length()}, {String s -> s + s})
337 | .toBlocking()
338 | .forEach({s -> actual.putAll(s); });
339 |
340 | Map expected = new HashMap();
341 | expected.put(1, "aa");
342 | expected.put(2, "bbbb");
343 | expected.put(3, "cccccc");
344 | expected.put(4, "dddddddd");
345 |
346 | assertEquals(expected, actual);
347 | }
348 |
349 | @Test
350 | public void testToMap3() {
351 | Map actual = new HashMap();
352 |
353 | LinkedHashMap last3 = new LinkedHashMap() {
354 | public boolean removeEldestEntry(Map.Entry e) {
355 | return size() > 3;
356 | }
357 | };
358 |
359 | Observable.from("a", "bb", "ccc", "dddd")
360 | .toMap({String s -> s.length()}, {String s -> s + s}, { last3 })
361 | .toBlocking()
362 | .forEach({s -> actual.putAll(s); });
363 |
364 | Map expected = new HashMap();
365 | expected.put(2, "bbbb");
366 | expected.put(3, "cccccc");
367 | expected.put(4, "dddddddd");
368 |
369 | assertEquals(expected, actual);
370 | }
371 | @Test
372 | public void testToMultimap1() {
373 | Map actual = new HashMap();
374 |
375 | Observable.from("a", "b", "cc", "dd")
376 | .toMultimap({String s -> s.length()})
377 | .toBlocking()
378 | .forEach({s -> actual.putAll(s); });
379 |
380 | Map expected = new HashMap();
381 |
382 | expected.put(1, Arrays.asList("a", "b"));
383 | expected.put(2, Arrays.asList("cc", "dd"));
384 |
385 | assertEquals(expected, actual);
386 | }
387 |
388 | @Test
389 | public void testToMultimap2() {
390 | Map actual = new HashMap();
391 |
392 | Observable.from("a", "b", "cc", "dd")
393 | .toMultimap({String s -> s.length()}, {String s -> s + s})
394 | .toBlocking()
395 | .forEach({s -> actual.putAll(s); });
396 |
397 | Map expected = new HashMap();
398 |
399 | expected.put(1, Arrays.asList("aa", "bb"));
400 | expected.put(2, Arrays.asList("cccc", "dddd"));
401 |
402 | assertEquals(expected, actual);
403 | }
404 |
405 | @Test
406 | public void testToMultimap3() {
407 | Map actual = new HashMap();
408 |
409 | LinkedHashMap last1 = new LinkedHashMap() {
410 | public boolean removeEldestEntry(Map.Entry e) {
411 | return size() > 1;
412 | }
413 | };
414 |
415 | Observable.from("a", "b", "cc", "dd")
416 | .toMultimap({String s -> s.length()}, {String s -> s + s}, { last1 })
417 | .toBlocking()
418 | .forEach({s -> actual.putAll(s); });
419 |
420 | Map expected = new HashMap();
421 |
422 | expected.put(2, Arrays.asList("cccc", "dddd"));
423 |
424 | assertEquals(expected, actual);
425 | }
426 |
427 | @Test
428 | public void testToMultimap4() {
429 | Map actual = new HashMap();
430 |
431 | LinkedHashMap last1 = new LinkedHashMap() {
432 | public boolean removeEldestEntry(Map.Entry e) {
433 | return size() > 2;
434 | }
435 | };
436 |
437 | Observable.from("a", "b", "cc", "dd", "eee", "eee")
438 | .toMultimap({String s -> s.length()}, {String s -> s + s}, { last1 },
439 | {i -> i == 2 ? new ArrayList() : new HashSet() })
440 | .toBlocking()
441 | .forEach({s -> actual.putAll(s); });
442 |
443 | Map expected = new HashMap();
444 |
445 | expected.put(2, Arrays.asList("cccc", "dddd"));
446 | expected.put(3, new HashSet(Arrays.asList("eeeeee")));
447 |
448 | assertEquals(expected, actual);
449 | }
450 |
451 | def class AsyncObservable implements OnSubscribe {
452 |
453 | public void call(final Subscriber observer) {
454 | new Thread(new Runnable() {
455 | public void run() {
456 | try {
457 | Thread.sleep(50)
458 | }catch(Exception e) {
459 | // ignore
460 | }
461 | observer.onNext(1);
462 | observer.onNext(2);
463 | observer.onNext(3);
464 | observer.onCompleted();
465 | }
466 | }).start();
467 | }
468 | }
469 |
470 | def class TestFactory {
471 | int counter = 1;
472 |
473 | public Observable getNumbers() {
474 | return Observable.from(1, 3, 2, 5, 4);
475 | }
476 |
477 | public TestOnSubscribe getOnSubscribe() {
478 | return new TestOnSubscribe(counter++);
479 | }
480 |
481 | public Observable getObservable() {
482 | return Observable.create(getOnSubscribe());
483 | }
484 | }
485 |
486 | def interface ScriptAssertion {
487 | public void error(Exception o);
488 |
489 | public void received(Object o);
490 | }
491 |
492 | def class TestOnSubscribe implements OnSubscribe {
493 | private final int count;
494 |
495 | public TestOnSubscribe(int count) {
496 | this.count = count;
497 | }
498 |
499 | public void call(Subscriber observer) {
500 | observer.onNext("hello_" + count);
501 | observer.onCompleted();
502 | }
503 | }
504 | }
505 |
--------------------------------------------------------------------------------