diff --git a/CHANGES.md b/CHANGES.md index dabf343132..8e30cb39fd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,148 @@ # RxJava Releases # +### Version 0.15.1 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.15.1%22)) ### + +This release should be additive functionality and bug fixes. + +* [Pull 510](https://github.com/Netflix/RxJava/pull/506) Operators: And, Then, When +* [Pull 514](https://github.com/Netflix/RxJava/pull/514) Operator: Join +* [Pull 525](https://github.com/Netflix/RxJava/pull/526) Operators: toMap/toMultiMap +* [Pull 510](https://github.com/Netflix/RxJava/pull/510) BugFix: Zip +* [Pull 512](https://github.com/Netflix/RxJava/pull/512) Scala Adaptor Details +* [Pull 512](https://github.com/Netflix/RxJava/pull/529) Scala fixes +* [Pull 508](https://github.com/Netflix/RxJava/pull/508) Empty subscribe +* [Pull 522](https://github.com/Netflix/RxJava/pull/522) Unsubscribe from takeLast +* [Pull 525](https://github.com/Netflix/RxJava/pull/525) BugFix: Handling of Terminal State for Behavior/Publish Subjects + +### Version 0.15.0 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.15.0%22)) ### + +This release contains a refactor of the Scala Bindings by @headinthebox that results in some breaking changes. +The previous solution ended up not working well in all cases for idiomatic Scala usage. Java/Scala interop has been changed and is no longer transparent so as to optimize for native Scala usage. +Read the [rxjava-scala README](https://github.com/Netflix/RxJava/tree/master/language-adaptors/rxjava-scala) for more information. + +* [Pull 503](https://github.com/Netflix/RxJava/pull/503) New Scala Bindings +* [Pull 502](https://github.com/Netflix/RxJava/pull/502) Fix ObserveOn and add ParallelMerge Scheduler overload +* [Pull 499](https://github.com/Netflix/RxJava/pull/499) ObserveOn Refactor +* [Pull 492](https://github.com/Netflix/RxJava/pull/492) Implement the scheduler overloads for Range, From, StartWith +* [Pull 496](https://github.com/Netflix/RxJava/pull/496) Add contravariant for min and max + +### Version 0.14.11 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.14.11%22)) ### + +* [Pull 486](https://github.com/Netflix/RxJava/pull/486) BugFix: AsyncSubject +* [Pull 483](https://github.com/Netflix/RxJava/pull/483) Tweaks to DoOnEach and added DoOnError/DoOnCompleted + +This has a very slight breaking change by removing one `doOnEach` overload. The version was not bumped from 0.14 to 0.15 as it is so minor and the offending method was just released in the previous version. + +### Version 0.14.10 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.14.10%22)) ### + +* [Pull 481](https://github.com/Netflix/RxJava/pull/481) Operator: Using +* [Pull 480](https://github.com/Netflix/RxJava/pull/480) BugFix: Emit an IllegalArgumentException instead of ArithmeticException if the observable is empty +* [Pull 479](https://github.com/Netflix/RxJava/pull/479) Operator: DoOnEach +* [Pull 478](https://github.com/Netflix/RxJava/pull/478) Operator: Min, MinBy, Max, MaxBy +* [Pull 463](https://github.com/Netflix/RxJava/pull/463) Add Timeout Overloads + +### Version 0.14.9 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.14.9%22)) ### + +* [Pull 477](https://github.com/Netflix/RxJava/pull/477) BugFix: CompositeSubscription +* [Pull 476](https://github.com/Netflix/RxJava/pull/476) BugFix: Don't emit null onComplete when no onNext received in AsyncSubject +* [Pull 474](https://github.com/Netflix/RxJava/pull/474) BugFix: Reduce an empty observable +* [Pull 474](https://github.com/Netflix/RxJava/pull/474) BugFix: non-deterministic unit test +* [Pull 472](https://github.com/Netflix/RxJava/pull/472) BugFix: Issue 431 Unsubscribe with Schedulers.newThread +* [Pull 470](https://github.com/Netflix/RxJava/pull/470) Operator: Last + +### Version 0.14.8 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.14.8%22)) ### + +* [Pull 460](https://github.com/Netflix/RxJava/pull/460) Operator: Amb +* [Pull 466](https://github.com/Netflix/RxJava/pull/466) Refactor Unit Tests + +### Version 0.14.7 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.14.7%22)) ### + +* [Pull 459](https://github.com/Netflix/RxJava/pull/459) Fix multiple unsubscribe behavior +* [Pull 458](https://github.com/Netflix/RxJava/pull/458) rxjava-android: OperationObserveFromAndroidComponent +* [Pull 453](https://github.com/Netflix/RxJava/pull/453) Fix error handling in map operator +* [Pull 450](https://github.com/Netflix/RxJava/pull/450) Operator: TimeInterval +* [Pull 452](https://github.com/Netflix/RxJava/pull/451) Scheduler Overload of Just/Return Operator +* [Pull 433](https://github.com/Netflix/RxJava/pull/433) Fixes: Next Operator +* [Commit d64a8c5](https://github.com/Netflix/RxJava/commit/d64a8c5f73d8d1a5de1861e0d20f12609b408880) Update rxjava-apache-http to Apache HttpAsyncClient 4.0 GA + +### Version 0.14.6 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.14.6%22)) ### + +* [Pull 441](https://github.com/Netflix/RxJava/pull/441) Fixed the issue that 'take' does not call 'onError' +* [Pull 443](https://github.com/Netflix/RxJava/pull/443) OperationSwitch notify onComplete() too early. +* [Pull 434](https://github.com/Netflix/RxJava/pull/434) Timeout operator and SerialSubscription +* [Pull 447](https://github.com/Netflix/RxJava/pull/447) Caching the result of 'isInternalImplementation' + +### Version 0.14.5 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.14.5%22)) ### + +* [Pull 438](https://github.com/Netflix/RxJava/pull/438) Kotlin Language Adaptor + +### Version 0.14.4 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.14.4%22)) ### + +* [Issue 428](https://github.com/Netflix/RxJava/issues/428) Fix: buffer() using TimeAndSizeBasedChunks incorrectly forces thread into interrupted state +* [Pull 435](https://github.com/Netflix/RxJava/pull/435) rx-apache-http recognizes "Transfer-Encoding: chunked" as an HTTP stream +* [Pull 437](https://github.com/Netflix/RxJava/pull/437) Fixes: Scheduler and Merge + + +### Version 0.14.3 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.14.3%22)) ### + +* [Pull 407](https://github.com/Netflix/RxJava/pull/407) Operator: RefCount +* [Pull 410](https://github.com/Netflix/RxJava/pull/410) Operator: Contains +* [Pull 411](https://github.com/Netflix/RxJava/pull/411) Unit Test fix: update counter before triggering latch +* [Pull 413](https://github.com/Netflix/RxJava/pull/413) Fixed the issues of takeLast(items, 0) and null values +* [Pull 414](https://github.com/Netflix/RxJava/pull/414) Operator: SkipLast +* [Pull 415](https://github.com/Netflix/RxJava/pull/415) Operator: Empty with scheduler +* [Pull 416](https://github.com/Netflix/RxJava/pull/416) Operator: Throw with scheduler +* [Pull 420](https://github.com/Netflix/RxJava/pull/420) Scala Adaptor Improvements +* [Pull 422](https://github.com/Netflix/RxJava/pull/422) JRuby function wrapping support +* [Pull 424](https://github.com/Netflix/RxJava/pull/424) Operator: IgnoreElements +* [Pull 426](https://github.com/Netflix/RxJava/pull/426) PublishSubject ReSubscribe for publish().refCount() Behavior + +### Version 0.14.2 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.14.2%22)) ### + +* [Pull 403](https://github.com/Netflix/RxJava/pull/403) Operators: Cast and OfType +* [Pull 401](https://github.com/Netflix/RxJava/pull/401) Operator: DefaultIfEmpty +* [Pull 409](https://github.com/Netflix/RxJava/pull/409) Operator: Synchronize with object + +### Version 0.14.1 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.14.1%22)) ### + +* [Pull 402](https://github.com/Netflix/RxJava/pull/402) rxjava-apache-http improvements + +### Version 0.14.0 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.14.0%22)) ### + +Further progress to the Scala adaptor and a handful of new operators. + +Bump to 0.14.0 due to small breaking change to `distinct` operator removing overloaded methods with `Comparator`. These methods were added in 0.13.2 and determined to be incorrect. + +This release also includes a new contrib module, [rxjava-apache-http](https://github.com/Netflix/RxJava/tree/master/rxjava-contrib/rxjava-apache-http) that provides an Observable API to the Apache HttpAsyncClient. + +* [Pull 396](https://github.com/Netflix/RxJava/pull/396) Add missing methods to Scala Adaptor +* [Pull 390](https://github.com/Netflix/RxJava/pull/390) Operators: ElementAt and ElementAtOrDefault +* [Pull 398](https://github.com/Netflix/RxJava/pull/398) Operators: IsEmpty and Exists (instead of Any) +* [Pull 397](https://github.com/Netflix/RxJava/pull/397) Observable API for Apache HttpAsyncClient 4.0 +* [Pull 400](https://github.com/Netflix/RxJava/pull/400) Removing `comparator` overloads of `distinct` + +### Version 0.13.5 + +* Upload to Sonatype failed so version skipped + +### Version 0.13.4 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.13.4%22)) ### + +* [Pull 393](https://github.com/Netflix/RxJava/pull/393) Parallel Operator & ObserveOn/ScheduledObserver Fixes +* [Pull 394](https://github.com/Netflix/RxJava/pull/394) Change Interval and Sample default Scheduler +* [Pull 391](https://github.com/Netflix/RxJava/pull/391) Fix OSGI support for rxjava-scala + +### Version 0.13.3 + +* Upload to Sonatype failed so version skipped + +### Version 0.13.2 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.13.2%22)) ### + +* [Pull 389](https://github.com/Netflix/RxJava/pull/389) Scala Adaptor Improvements +* [Pull 382](https://github.com/Netflix/RxJava/pull/382) Removing deprecated RxImplicits from rxjava-scala +* [Pull 381](https://github.com/Netflix/RxJava/pull/381) Operator: mapWithIndex +* [Pull 380](https://github.com/Netflix/RxJava/pull/380) Implemented `distinct` and `distinctUntilChanged` variants using a comparator +* [Pull 379](https://github.com/Netflix/RxJava/pull/379) Make `interval` work with multiple subscribers + ### Version 0.13.1 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.13.1%22)) ### This release includes a new Scala adaptor as part of the effort from issue https://github.com/Netflix/RxJava/issues/336 pursuing idiomatic Scala support. diff --git a/gradle.properties b/gradle.properties index 6d6ed65604..92fc8fd0ca 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=0.13.2-SNAPSHOT +version=0.15.2-SNAPSHOT diff --git a/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy b/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy index ceeb25dcf6..9c6e244149 100644 --- a/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy +++ b/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy @@ -345,6 +345,143 @@ def class ObservableTests { assertEquals(6, count); } + @Test + public void testToMap1() { + Map actual = new HashMap(); + + Observable.from("a", "bb", "ccc", "dddd") + .toMap({String s -> s.length()}) + .toBlockingObservable() + .forEach({s -> actual.putAll(s); }); + + Map expected = new HashMap(); + expected.put(1, "a"); + expected.put(2, "bb"); + expected.put(3, "ccc"); + expected.put(4, "dddd"); + + assertEquals(expected, actual); + } + + @Test + public void testToMap2() { + Map actual = new HashMap(); + + Observable.from("a", "bb", "ccc", "dddd") + .toMap({String s -> s.length()}, {String s -> s + s}) + .toBlockingObservable() + .forEach({s -> actual.putAll(s); }); + + Map expected = new HashMap(); + expected.put(1, "aa"); + expected.put(2, "bbbb"); + expected.put(3, "cccccc"); + expected.put(4, "dddddddd"); + + assertEquals(expected, actual); + } + + @Test + public void testToMap3() { + Map actual = new HashMap(); + + LinkedHashMap last3 = new LinkedHashMap() { + public boolean removeEldestEntry(Map.Entry e) { + return size() > 3; + } + }; + + Observable.from("a", "bb", "ccc", "dddd") + .toMap({String s -> s.length()}, {String s -> s + s}, { last3 }) + .toBlockingObservable() + .forEach({s -> actual.putAll(s); }); + + Map expected = new HashMap(); + expected.put(2, "bbbb"); + expected.put(3, "cccccc"); + expected.put(4, "dddddddd"); + + assertEquals(expected, actual); + } + @Test + public void testToMultimap1() { + Map actual = new HashMap(); + + Observable.from("a", "b", "cc", "dd") + .toMultimap({String s -> s.length()}) + .toBlockingObservable() + .forEach({s -> actual.putAll(s); }); + + Map expected = new HashMap(); + + expected.put(1, Arrays.asList("a", "b")); + expected.put(2, Arrays.asList("cc", "dd")); + + assertEquals(expected, actual); + } + + @Test + public void testToMultimap2() { + Map actual = new HashMap(); + + Observable.from("a", "b", "cc", "dd") + .toMultimap({String s -> s.length()}, {String s -> s + s}) + .toBlockingObservable() + .forEach({s -> actual.putAll(s); }); + + Map expected = new HashMap(); + + expected.put(1, Arrays.asList("aa", "bb")); + expected.put(2, Arrays.asList("cccc", "dddd")); + + assertEquals(expected, actual); + } + + @Test + public void testToMultimap3() { + Map actual = new HashMap(); + + LinkedHashMap last1 = new LinkedHashMap() { + public boolean removeEldestEntry(Map.Entry e) { + return size() > 1; + } + }; + + Observable.from("a", "b", "cc", "dd") + .toMultimap({String s -> s.length()}, {String s -> s + s}, { last1 }) + .toBlockingObservable() + .forEach({s -> actual.putAll(s); }); + + Map expected = new HashMap(); + + expected.put(2, Arrays.asList("cccc", "dddd")); + + assertEquals(expected, actual); + } + + @Test + public void testToMultimap4() { + Map actual = new HashMap(); + + LinkedHashMap last1 = new LinkedHashMap() { + public boolean removeEldestEntry(Map.Entry e) { + return size() > 2; + } + }; + + Observable.from("a", "b", "cc", "dd", "eee", "eee") + .toMultimap({String s -> s.length()}, {String s -> s + s}, { last1 }, + {i -> i == 2 ? new ArrayList() : new HashSet() }) + .toBlockingObservable() + .forEach({s -> actual.putAll(s); }); + + Map expected = new HashMap(); + + expected.put(2, Arrays.asList("cccc", "dddd")); + expected.put(3, new HashSet(Arrays.asList("eeeeee"))); + + assertEquals(expected, actual); + } def class AsyncObservable implements OnSubscribeFunc { diff --git a/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/TestParallel.groovy b/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/TestParallel.groovy new file mode 100644 index 0000000000..509b7b0ca5 --- /dev/null +++ b/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/TestParallel.groovy @@ -0,0 +1,36 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.groovy + +import org.junit.Test + +import rx.Observable +import rx.Scheduler +import rx.concurrency.Schedulers +import rx.util.functions.Func1 + +class TestParallel { + + @Test + public void testParallelOperator() { + Observable.range(0, 100) + .parallel({ + it.map({ return it; }) + }) + .toBlockingObservable() + .forEach({ println("T: " + it + " Thread: " + Thread.currentThread()); }); + } +} diff --git a/language-adaptors/rxjava-jruby/README.md b/language-adaptors/rxjava-jruby/README.md new file mode 100644 index 0000000000..da75bac1fb --- /dev/null +++ b/language-adaptors/rxjava-jruby/README.md @@ -0,0 +1,45 @@ +# JRuby Adaptor for RxJava + +This adaptor improves the success and performance of RxJava when Ruby `Proc` is passed to an RxJava method. + +This enables correct and efficient execution of code such as: + +```ruby + Observable.from("one", "two", "three"). + take(2). + subscribe {|val| puts val} +``` + +# Usage + +Require the JAR file as usual. After requiring the JAR, you must also require the interop code: + +```ruby +require "rx/lang/jruby/interop" +``` + +# Binaries + +Binaries and dependency information for Maven, Ivy, Gradle and others can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22rxjava-jruby%22). + +Example for Maven: + +```xml + + com.netflix.rxjava + rxjava-jruby + x.y.z + +``` + +and for Ivy: + +```xml + +``` + +and for Gradle: + +```groovy +compile 'com.netflix.rxjava:rxjava-jruby:x.y.z' +``` diff --git a/language-adaptors/rxjava-jruby/build.gradle b/language-adaptors/rxjava-jruby/build.gradle new file mode 100644 index 0000000000..0369d6e36e --- /dev/null +++ b/language-adaptors/rxjava-jruby/build.gradle @@ -0,0 +1,49 @@ +apply plugin: 'osgi' + +repositories { + maven { + url 'http://deux.gemjars.org' + } + mavenCentral() +} + +configurations { + rspec +} + +sourceSets { + test { + resources { + srcDir 'src/spec/ruby' + } + } +} + +dependencies { + compile project(':rxjava-core') + compile 'org.jruby:jruby:1.7+' + provided 'junit:junit-dep:4.10' + provided 'org.mockito:mockito-core:1.8.5' + rspec 'org.jruby:jruby-complete:1.7.4' + rspec 'org.rubygems:rspec:2.14.1' +} + +/* // benjchristensen => commenting out until internal Netflix build system can handle ruby gems +task(rspec, type: JavaExec) { + main 'org.jruby.Main' + classpath configurations.rspec + runtimeClasspath + args 'classpath:bin/rspec', 'src/spec/ruby' +} + +tasks.build.dependsOn << 'rspec' +*/ + +jar { + manifest { + name = 'rxjava-jruby' + instruction 'Bundle-Vendor', 'Netflix' + instruction 'Bundle-DocURL', 'https://github.com/Netflix/RxJava' + instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' + instruction 'Fragment-Host', 'com.netflix.rxjava.core' + } +} diff --git a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyActionWrapper.java b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyActionWrapper.java new file mode 100644 index 0000000000..6beb4f1d59 --- /dev/null +++ b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyActionWrapper.java @@ -0,0 +1,77 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.jruby; + +import org.jruby.RubyProc; +import org.jruby.Ruby; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.javasupport.JavaUtil; + +import rx.util.functions.Action; +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Action2; +import rx.util.functions.Action3; + +/** + * Concrete wrapper that accepts a {@link RubyProc} and produces any needed Rx {@link Action}. + * + * @param + * @param + * @param + * @param + */ +public class JRubyActionWrapper implements Action, Action0, Action1, Action2, Action3 { + + private final RubyProc proc; + private final ThreadContext context; + private final Ruby runtime; + + public JRubyActionWrapper(ThreadContext context, RubyProc proc) { + this.proc = proc; + this.context = context; + this.runtime = context.getRuntime(); + } + + @Override + public void call() { + IRubyObject[] array = new IRubyObject[0]; + proc.call(context, array); + } + + @Override + public void call(T1 t1) { + IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1)}; + proc.call(context, array); + } + + @Override + public void call(T1 t1, T2 t2) { + IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1), + JavaUtil.convertJavaToRuby(runtime, t2)}; + proc.call(context, array); + } + + @Override + public void call(T1 t1, T2 t2, T3 t3) { + IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1), + JavaUtil.convertJavaToRuby(runtime, t2), + JavaUtil.convertJavaToRuby(runtime, t3)}; + proc.call(context, array); + } + +} diff --git a/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java new file mode 100644 index 0000000000..f049827d8e --- /dev/null +++ b/language-adaptors/rxjava-jruby/src/main/java/rx/lang/jruby/JRubyFunctionWrapper.java @@ -0,0 +1,173 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.jruby; + +import org.jruby.RubyProc; +import org.jruby.Ruby; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.javasupport.JavaUtil; + +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.Func4; +import rx.util.functions.Func5; +import rx.util.functions.Func6; +import rx.util.functions.Func7; +import rx.util.functions.Func8; +import rx.util.functions.Func9; +import rx.util.functions.FuncN; +import rx.util.functions.Function; + +/** + * Concrete wrapper that accepts a {@link RubyProc} and produces any needed Rx {@link Function}. + * + * @param + * @param + * @param + * @param + * @param + */ +public class JRubyFunctionWrapper implements + Func0, + Func1, + Func2, + Func3, + Func4, + Func5, + Func6, + Func7, + Func8, + Func9, + FuncN { + + private final RubyProc proc; + private final ThreadContext context; + private final Ruby runtime; + + public JRubyFunctionWrapper(ThreadContext context, RubyProc proc) { + this.proc = proc; + this.context = context; + this.runtime = context.getRuntime(); + } + + @Override + public R call() { + IRubyObject[] array = new IRubyObject[0]; + return (R) proc.call(context, array); + } + + @Override + public R call(T1 t1) { + IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1)}; + return (R) proc.call(context, array); + } + + @Override + public R call(T1 t1, T2 t2) { + IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1), + JavaUtil.convertJavaToRuby(runtime, t2)}; + return (R) proc.call(context, array); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3) { + IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1), + JavaUtil.convertJavaToRuby(runtime, t2), + JavaUtil.convertJavaToRuby(runtime, t3)}; + return (R) proc.call(context, array); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4) { + IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1), + JavaUtil.convertJavaToRuby(runtime, t2), + JavaUtil.convertJavaToRuby(runtime, t3), + JavaUtil.convertJavaToRuby(runtime, t4)}; + return (R) proc.call(context, array); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) { + IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1), + JavaUtil.convertJavaToRuby(runtime, t2), + JavaUtil.convertJavaToRuby(runtime, t3), + JavaUtil.convertJavaToRuby(runtime, t4), + JavaUtil.convertJavaToRuby(runtime, t5)}; + return (R) proc.call(context, array); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) { + IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1), + JavaUtil.convertJavaToRuby(runtime, t2), + JavaUtil.convertJavaToRuby(runtime, t3), + JavaUtil.convertJavaToRuby(runtime, t4), + JavaUtil.convertJavaToRuby(runtime, t5), + JavaUtil.convertJavaToRuby(runtime, t6)}; + return (R) proc.call(context, array); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) { + IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1), + JavaUtil.convertJavaToRuby(runtime, t2), + JavaUtil.convertJavaToRuby(runtime, t3), + JavaUtil.convertJavaToRuby(runtime, t4), + JavaUtil.convertJavaToRuby(runtime, t5), + JavaUtil.convertJavaToRuby(runtime, t6), + JavaUtil.convertJavaToRuby(runtime, t7)}; + return (R) proc.call(context, array); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) { + IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1), + JavaUtil.convertJavaToRuby(runtime, t2), + JavaUtil.convertJavaToRuby(runtime, t3), + JavaUtil.convertJavaToRuby(runtime, t4), + JavaUtil.convertJavaToRuby(runtime, t5), + JavaUtil.convertJavaToRuby(runtime, t6), + JavaUtil.convertJavaToRuby(runtime, t7), + JavaUtil.convertJavaToRuby(runtime, t8)}; + return (R) proc.call(context, array); + } + + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9) { + IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1), + JavaUtil.convertJavaToRuby(runtime, t2), + JavaUtil.convertJavaToRuby(runtime, t3), + JavaUtil.convertJavaToRuby(runtime, t4), + JavaUtil.convertJavaToRuby(runtime, t5), + JavaUtil.convertJavaToRuby(runtime, t6), + JavaUtil.convertJavaToRuby(runtime, t7), + JavaUtil.convertJavaToRuby(runtime, t8), + JavaUtil.convertJavaToRuby(runtime, t9)}; + return (R) proc.call(context, array); + } + + @Override + public R call(Object... args) { + IRubyObject[] array = new IRubyObject[args.length]; + for (int i = 0; i < args.length; i++) { + array[i] = JavaUtil.convertJavaToRuby(runtime, args[i]); + } + return (R) proc.call(context, array); + } +} diff --git a/language-adaptors/rxjava-jruby/src/main/resources/rx/lang/jruby/interop.rb b/language-adaptors/rxjava-jruby/src/main/resources/rx/lang/jruby/interop.rb new file mode 100644 index 0000000000..a080639728 --- /dev/null +++ b/language-adaptors/rxjava-jruby/src/main/resources/rx/lang/jruby/interop.rb @@ -0,0 +1,115 @@ +# Copyright 2013 Mike Ragalie +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +module Rx + module Lang + module Jruby + class Interop + WRAPPERS = { + Java::RxUtilFunctions::Action => Java::RxLangJruby::JRubyActionWrapper, + Java::Rx::Observable::OnSubscribeFunc => false + } + + WRAPPERS.default = Java::RxLangJruby::JRubyFunctionWrapper + + KLASSES = [Java::Rx::Observable, Java::RxObservables::BlockingObservable] + FUNCTION = Java::RxUtilFunctions::Function.java_class + RUNTIME = JRuby.runtime + + def self.instance + @instance ||= new + end + + def initialize + KLASSES.each do |klass| + function_methods = (klass.java_class.declared_instance_methods + klass.java_class.declared_class_methods).select do |method| + method.public? && method.parameter_types.any? {|type| FUNCTION.assignable_from?(type)} + end + + parameter_types = {} + function_methods.each do |method| + parameter_types[[method.name, method.static?]] ||= [] + + method.parameter_types.each_with_index do |type, idx| + next unless FUNCTION.assignable_from?(type) + + constructor = WRAPPERS.find do |java_class, wrapper| + type.ruby_class.ancestors.include?(java_class) + end + + # Skip OnSubscribeFuncs + next if constructor && constructor.last == false + + constructor = (constructor && constructor.last) || WRAPPERS.default + + parameter_types[[method.name, method.static?]][idx] ||= [] + parameter_types[[method.name, method.static?]][idx] << constructor + end + end + + parameter_types.each_pair do |_, types| + types.map! do |type| + next type.first if type && type.uniq.length == 1 + nil + end + end + + parameter_types.each_pair do |(method_name, static), types| + next if types.all?(&:nil?) + + klass_to_open = static ? klass.singleton_class : klass + + [method_name, underscore(method_name)].uniq.each do |name| + klass_to_open.send(:alias_method, "#{name}_without_wrapping", name) + klass_to_open.send(:define_method, name) do |*args, &block| + context = RUNTIME.get_current_context + + args = args.each_with_index.map do |arg, idx| + if arg.is_a?(Proc) && types[idx] + types[idx].new(context, arg) + else + arg + end + end + + if block && types[args.length] + block = types[args.length].new(context, block) + end + + send("#{name}_without_wrapping", *(args + [block].compact)) + end + end + end + end + end + + private + + # File activesupport/lib/active_support/inflector/methods.rb, line 89 + def underscore(camel_cased_word) + word = camel_cased_word.to_s.dup + word.gsub!('::', '/') + word.gsub!(/(?:([A-Za-z\d])|^)((?=a)b)(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" } + word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2') + word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') + word.tr!("-", "_") + word.downcase! + word + end + end + end + end +end + +Rx::Lang::Jruby::Interop.instance diff --git a/language-adaptors/rxjava-jruby/src/spec/ruby/rx/lang/jruby/interop_spec.rb b/language-adaptors/rxjava-jruby/src/spec/ruby/rx/lang/jruby/interop_spec.rb new file mode 100644 index 0000000000..222caf34ed --- /dev/null +++ b/language-adaptors/rxjava-jruby/src/spec/ruby/rx/lang/jruby/interop_spec.rb @@ -0,0 +1,68 @@ +# Copyright 2013 Mike Ragalie +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative "spec_helper" + +describe Rx::Lang::Jruby::Interop do + subject { described_class.instance } + + let(:observable) { Java::Rx::Observable.from([1, 2, 3]) } + + context "with a normal, non-function method signature" do + it "calls straight through to the original Java method" do + observable.should_not_receive(:toBlockingObservable_without_wrapping) + observable.toBlockingObservable.should be_a(Java::RxObservables::BlockingObservable) + end + end + + context "with a method with a function method signature" do + it "wraps function arguments if they're in the right position" do + observable.should_receive(:subscribe_without_wrapping).with(kind_of(Java::RxLangJruby::JRubyActionWrapper)) + observable.subscribe(lambda {}) + end + + it "doesn't wrap function arguments if they're in the wrong position" do + proc = lambda {} + observable.should_receive(:subscribe_without_wrapping).with(1, 1, 1, proc) + observable.subscribe(1, 1, 1, proc) + end + + it "doesn't wrap non-function arguments" do + observable.should_receive(:subscribe_without_wrapping).with(1) + observable.subscribe(1) + end + + it "doesn't wrap OnSubscribeFunc arguments" do + proc = lambda {|observer| observer.onNext("hi")} + Java::Rx::Observable.__persistent__ = true + Java::Rx::Observable.should_not_receive(:create_without_wrapping) + Java::Rx::Observable.create(proc).should be_a(Java::Rx::Observable) + end + + it "works with underscoreized method names" do + observable. + should_receive(:finally_do_without_wrapping). + with(kind_of(Java::RxLangJruby::JRubyActionWrapper)). + and_call_original + + observable.finally_do(lambda {}) + end + + it "passes a block through as the last argument" do + proc = lambda {} + observable.should_receive(:subscribe_without_wrapping).with(1, 1, 1, 1, proc) + observable.subscribe(1, 1, 1, 1, &proc) # intentionally bogus call so it doesn't wrap the proc + end + end +end diff --git a/language-adaptors/rxjava-jruby/src/spec/ruby/rx/lang/jruby/jruby_action_wrapper_spec.rb b/language-adaptors/rxjava-jruby/src/spec/ruby/rx/lang/jruby/jruby_action_wrapper_spec.rb new file mode 100644 index 0000000000..dae1973625 --- /dev/null +++ b/language-adaptors/rxjava-jruby/src/spec/ruby/rx/lang/jruby/jruby_action_wrapper_spec.rb @@ -0,0 +1,46 @@ +# Copyright 2013 Mike Ragalie +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative "spec_helper" + +describe Java::RxLangJruby::JRubyActionWrapper do + let(:spy) { double(:spy, :call => nil) } + subject { described_class.new(JRuby.runtime.get_current_context, lambda {|*args| spy.call(args)}) } + + let(:interfaces) do + [Java::RxUtilFunctions::Action, + Java::RxUtilFunctions::Action0, + Java::RxUtilFunctions::Action1, + Java::RxUtilFunctions::Action2, + Java::RxUtilFunctions::Action3] + end + + it "implements the interfaces" do + interfaces.each do |interface| + subject.is_a?(interface) + end + end + + it "successfully uses the interfaces" do + spy.should_receive(:call).with([]) + spy.should_receive(:call).with([1]) + spy.should_receive(:call).with([1, 2]) + spy.should_receive(:call).with([1, 2, 3]) + + subject.call + subject.call(1) + subject.call(1, 2) + subject.call(1, 2, 3) + end +end diff --git a/language-adaptors/rxjava-jruby/src/spec/ruby/rx/lang/jruby/jruby_function_wrapper_spec.rb b/language-adaptors/rxjava-jruby/src/spec/ruby/rx/lang/jruby/jruby_function_wrapper_spec.rb new file mode 100644 index 0000000000..4c25f6ec1c --- /dev/null +++ b/language-adaptors/rxjava-jruby/src/spec/ruby/rx/lang/jruby/jruby_function_wrapper_spec.rb @@ -0,0 +1,66 @@ +# Copyright 2013 Mike Ragalie +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require_relative "spec_helper" + +describe Java::RxLangJruby::JRubyFunctionWrapper do + let(:spy) { double(:spy, :call => nil) } + subject { described_class.new(JRuby.runtime.get_current_context, lambda {|*args| spy.call(args); args}) } + + let(:interfaces) do + [Java::RxUtilFunctions::Func0, + Java::RxUtilFunctions::Func1, + Java::RxUtilFunctions::Func2, + Java::RxUtilFunctions::Func3, + Java::RxUtilFunctions::Func4, + Java::RxUtilFunctions::Func5, + Java::RxUtilFunctions::Func6, + Java::RxUtilFunctions::Func7, + Java::RxUtilFunctions::Func8, + Java::RxUtilFunctions::Func9, + Java::RxUtilFunctions::FuncN] + end + + it "implements the interfaces" do + interfaces.each do |interface| + subject.is_a?(interface) + end + end + + it "successfully uses the interfaces" do + spy.should_receive(:call).with([]) + spy.should_receive(:call).with([1]) + spy.should_receive(:call).with([1, 2]) + spy.should_receive(:call).with([1, 2, 3]) + spy.should_receive(:call).with([1, 2, 3, 4]) + spy.should_receive(:call).with([1, 2, 3, 4, 5]) + spy.should_receive(:call).with([1, 2, 3, 4, 5, 6]) + spy.should_receive(:call).with([1, 2, 3, 4, 5, 6, 7]) + spy.should_receive(:call).with([1, 2, 3, 4, 5, 6, 7, 8]) + spy.should_receive(:call).with([1, 2, 3, 4, 5, 6, 7, 8, 9]) + spy.should_receive(:call).with([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + + subject.call.should == [] + subject.call(1).should == [1] + subject.call(1, 2).should == [1, 2] + subject.call(1, 2, 3).should == [1, 2, 3] + subject.call(1, 2, 3, 4).should == [1, 2, 3, 4] + subject.call(1, 2, 3, 4, 5).should == [1, 2, 3, 4, 5] + subject.call(1, 2, 3, 4, 5, 6).should == [1, 2, 3, 4, 5, 6] + subject.call(1, 2, 3, 4, 5, 6, 7).should == [1, 2, 3, 4, 5, 6, 7] + subject.call(1, 2, 3, 4, 5, 6, 7, 8).should == [1, 2, 3, 4, 5, 6, 7, 8] + subject.call(1, 2, 3, 4, 5, 6, 7, 8, 9).should == [1, 2, 3, 4, 5, 6, 7, 8, 9] + subject.call(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).should == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + end +end diff --git a/language-adaptors/rxjava-jruby/src/spec/ruby/rx/lang/jruby/performance.rb b/language-adaptors/rxjava-jruby/src/spec/ruby/rx/lang/jruby/performance.rb new file mode 100644 index 0000000000..fc9cde1b0b --- /dev/null +++ b/language-adaptors/rxjava-jruby/src/spec/ruby/rx/lang/jruby/performance.rb @@ -0,0 +1,52 @@ +# Copyright 2013 Mike Ragalie +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Current execution times: +# Without rxjava-jruby: 3.31s +# With rxjava-jruby: 2.18s + +require 'optparse' + +options = {} +OptionParser.new do |opts| + opts.banner = "Usage: jruby --profile.api performance.rb [options]" + + opts.on("-c", "--core CORE-PATH", "Path to the rxjava-core.jar") {|core| options[:core] = core} + opts.on("-j", "--jruby [JRUBY-PATH]", "Path to the rxjava-jruby.jar (optional)") {|jruby| options[:jruby] = jruby} +end.parse! + +require options[:core] + +if options[:jruby] + require options[:jruby] + require 'rx/lang/jruby/interop' +end + +require 'jruby/profiler' + +profile_data = JRuby::Profiler.profile do + 10000.times do + o = Java::Rx::Observable.create do |observer| + observer.onNext("one") + observer.onNext("two") + observer.onNext("three") + observer.onCompleted + Java::RxSubscriptions::Subscription.empty + end + o.map {|n| n * 2}.subscribe {|n| n} + end +end + +profile_printer = JRuby::Profiler::FlatProfilePrinter.new(profile_data) +profile_printer.printProfile(STDOUT) diff --git a/language-adaptors/rxjava-jruby/src/spec/ruby/rx/lang/jruby/spec_helper.rb b/language-adaptors/rxjava-jruby/src/spec/ruby/rx/lang/jruby/spec_helper.rb new file mode 100644 index 0000000000..99b095960f --- /dev/null +++ b/language-adaptors/rxjava-jruby/src/spec/ruby/rx/lang/jruby/spec_helper.rb @@ -0,0 +1,15 @@ +# Copyright 2013 Mike Ragalie +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "rx/lang/jruby/interop" diff --git a/language-adaptors/rxjava-kotlin/README.md b/language-adaptors/rxjava-kotlin/README.md new file mode 100644 index 0000000000..ffd2c05d79 --- /dev/null +++ b/language-adaptors/rxjava-kotlin/README.md @@ -0,0 +1,53 @@ +# Kotlin Adaptor for RxJava + +Kotlin has support for SAM (Single Abstract Method) Interfaces as Functions (i.e. Java 8 Lambdas). So you could use Kotlin in RxJava whitout this adaptor + +```kotlin +Observable.create{ observer -> + observer!!.onNext("Hello") + observer.onCompleted() + Subscriptions.empty() +}!!.subscribe { result -> + a!!.received(result) +} +``` + +This adaptor exposes a set of Extension functions that allow a more idiomatic Kotlin usage + +```kotlin +import rx.lang.kotlin.* + +{(observer: Observer) -> + observer.onNext("Hello") + observer.onCompleted() + Subscriptions.empty()!! +}.asObservable().subscribe { result -> + a!!.received(result) +} +``` + +## Binaries + +Binaries and dependency information for Maven, Ivy, Gradle and others can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22rxjava-kotlin%22). + +Example for Maven: + +```xml + + com.netflix.rxjava + rxjava-kotlin + x.y.z + +``` + +and for Ivy: + +```xml + +``` + +and for Gradle: + +```groovy +compile 'com.netflix.rxjava:rxjava-kotlin:x.y.z' +``` \ No newline at end of file diff --git a/language-adaptors/rxjava-scala-java/build.gradle b/language-adaptors/rxjava-kotlin/build.gradle similarity index 56% rename from language-adaptors/rxjava-scala-java/build.gradle rename to language-adaptors/rxjava-kotlin/build.gradle index d6be5aaeb7..9beef6d08c 100644 --- a/language-adaptors/rxjava-scala-java/build.gradle +++ b/language-adaptors/rxjava-kotlin/build.gradle @@ -1,32 +1,29 @@ +buildscript { + repositories() { + mavenCentral() + } -apply plugin: 'osgi' - - -project(':language-adaptors:rxjava-scala-java') { - //sourceSets.test.java.srcDir 'src/examples/java' - sourceSets.main.java.srcDir 'src/main/java' + dependencies { + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.6.800' + } } -dependencies { - compile 'org.scala-lang:scala-library:2.10.+' +apply plugin: 'kotlin' +apply plugin: 'osgi' +dependencies { compile project(':rxjava-core') - - compile project(':language-adaptors:rxjava-scala') - + compile 'org.jetbrains.kotlin:kotlin-stdlib:0.6.800' provided 'junit:junit-dep:4.10' provided 'org.mockito:mockito-core:1.8.5' - provided 'org.scalatest:scalatest_2.10:1.9.1' } jar { manifest { - name = 'rxjava-scala-java' + name = 'rxjava-kotlin' instruction 'Bundle-Vendor', 'Netflix' instruction 'Bundle-DocURL', 'https://github.com/Netflix/RxJava' instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' instruction 'Fragment-Host', 'com.netflix.rxjava.core' } -} - - +} \ No newline at end of file diff --git a/language-adaptors/rxjava-kotlin/src/main/kotlin/rx/lang/kotlin/namespace.kt b/language-adaptors/rxjava-kotlin/src/main/kotlin/rx/lang/kotlin/namespace.kt new file mode 100644 index 0000000000..62293d5448 --- /dev/null +++ b/language-adaptors/rxjava-kotlin/src/main/kotlin/rx/lang/kotlin/namespace.kt @@ -0,0 +1,65 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.lang.kotlin + +import rx.Subscription +import rx.Observer +import rx.Observable + +public fun Function1, Subscription>.asObservable(): Observable { + return Observable.create { this(it!!) }!! +} + +public fun Function0>.defer(): Observable { + return Observable.defer(this)!! +} + +public fun Iterable.asObservable(): Observable { + return Observable.from(this)!! +} + +public fun T.asObservable(): Observable { + return Observable.from(this)!! +} + +public fun Throwable.asObservable(): Observable { + return Observable.error(this)!! +} + +public fun Pair.asObservable(): Observable { + return Observable.from(this.component1(), this.component2())!! +} + +public fun Triple.asObservable(): Observable { + return Observable.from(this.component1(), this.component2(), this.component3())!! +} + +public fun Pair, Observable>.merge(): Observable { + return Observable.merge(this.component1(), this.component2())!! +} + +public fun Triple, Observable, Observable>.merge(): Observable { + return Observable.merge(this.component1(), this.component2(), this.component3())!! +} + +public fun Pair, Observable>.mergeDelayError(): Observable { + return Observable.mergeDelayError(this.component1(), this.component2())!! +} + +public fun Triple, Observable, Observable>.mergeDelayError(): Observable { + return Observable.mergeDelayError(this.component1(), this.component2(), this.component3())!! +} diff --git a/language-adaptors/rxjava-kotlin/src/test/kotlin/rx/lang/kotlin/BasicKotlinTests.kt b/language-adaptors/rxjava-kotlin/src/test/kotlin/rx/lang/kotlin/BasicKotlinTests.kt new file mode 100644 index 0000000000..d826585ced --- /dev/null +++ b/language-adaptors/rxjava-kotlin/src/test/kotlin/rx/lang/kotlin/BasicKotlinTests.kt @@ -0,0 +1,368 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.lang.kotlin + +import org.mockito.Mock +import rx.Observable +import org.junit.Before +import org.mockito.MockitoAnnotations +import org.junit.Test +import rx.subscriptions.Subscriptions +import org.mockito.Mockito.* +import org.mockito.Matchers.* +import rx.Observer +import org.junit.Assert.* +import rx.Notification +import rx.Subscription +import kotlin.concurrent.thread +import rx.Observable.OnSubscribeFunc +import rx.lang.kotlin.BasicKotlinTests.AsyncObservable + +/** + * This class use plain Kotlin without extensions from the language adaptor + */ +public class BasicKotlinTests { + + [Mock] var a: ScriptAssertion? = null + [Mock] var w: Observable? = null + + [Before] + public fun before() { + MockitoAnnotations.initMocks(this) + } + + fun received(): (T?) -> Unit { + return {(result: T?) -> a!!.received(result) } + } + + [Test] + public fun testCreate() { + Observable.create{ + it!!.onNext("Hello") + it.onCompleted() + Subscriptions.empty() + }!!.subscribe { result -> + a!!.received(result) + } + + verify(a, times(1))!!.received("Hello") + } + + [Test] + public fun testFilter() { + Observable.from(1, 2, 3)!!.filter { it!! >= 2 }!!.subscribe(received()) + verify(a, times(0))!!.received(1); + verify(a, times(1))!!.received(2); + verify(a, times(1))!!.received(3); + } + + [Test] + public fun testLast() { + assertEquals("three", Observable.from("one", "two", "three")!!.toBlockingObservable()!!.last()) + } + + [Test] + public fun testLastWithPredicate() { + assertEquals("two", Observable.from("one", "two", "three")!!.toBlockingObservable()!!.last { x -> x!!.length == 3 }) + } + + [Test] + public fun testMap1() { + Observable.from(1)!!.map { v -> "hello_$v" }!!.subscribe(received()) + verify(a, times(1))!!.received("hello_1") + } + + [Test] + public fun testMap2() { + Observable.from(1, 2, 3)!!.map { v -> "hello_$v" }!!.subscribe((received())) + verify(a, times(1))!!.received("hello_1") + verify(a, times(1))!!.received("hello_2") + verify(a, times(1))!!.received("hello_3") + } + + [Test] + public fun testMaterialize() { + Observable.from(1, 2, 3)!!.materialize()!!.subscribe((received())) + verify(a, times(4))!!.received(any(javaClass>())) + verify(a, times(0))!!.error(any(javaClass())) + } + + [Test] + public fun testMergeDelayError() { + Observable.mergeDelayError( + Observable.from(1, 2, 3), + Observable.merge( + Observable.from(6), + Observable.error(NullPointerException()), + Observable.from(7) + ), + Observable.from(4, 5) + )!!.subscribe(received(), { e -> a!!.error(e) }) + verify(a, times(1))!!.received(1) + verify(a, times(1))!!.received(2) + verify(a, times(1))!!.received(3) + verify(a, times(1))!!.received(4) + verify(a, times(1))!!.received(5) + verify(a, times(1))!!.received(6) + verify(a, times(0))!!.received(7) + verify(a, times(1))!!.error(any(javaClass())) + } + + [Test] + public fun testMerge() { + Observable.merge( + Observable.from(1, 2, 3), + Observable.merge( + Observable.from(6), + Observable.error(NullPointerException()), + Observable.from(7) + ), + Observable.from(4, 5) + )!!.subscribe(received(), { e -> a!!.error(e) }) + verify(a, times(1))!!.received(1) + verify(a, times(1))!!.received(2) + verify(a, times(1))!!.received(3) + verify(a, times(0))!!.received(4) + verify(a, times(0))!!.received(5) + verify(a, times(1))!!.received(6) + verify(a, times(0))!!.received(7) + verify(a, times(1))!!.error(any(javaClass())) + } + + [Test] + public fun testScriptWithMaterialize() { + TestFactory().observable.materialize()!!.subscribe((received())) + verify(a, times(2))!!.received(any(javaClass>())) + } + + [Test] + public fun testScriptWithMerge() { + val factory = TestFactory() + Observable.merge(factory.observable, factory.observable)!!.subscribe((received())) + verify(a, times(1))!!.received("hello_1") + verify(a, times(1))!!.received("hello_2") + } + + [Test] + public fun testFromWithIterable() { + val list = listOf(1, 2, 3, 4, 5) + assertEquals(5, Observable.from(list)!!.count()!!.toBlockingObservable()!!.single()) + } + + [Test] + public fun testFromWithObjects() { + val list = listOf(1, 2, 3, 4, 5) + assertEquals(2, Observable.from(list, 6)!!.count()!!.toBlockingObservable()!!.single()) + } + + [Test] + public fun testStartWith() { + val list = listOf(10, 11, 12, 13, 14) + val startList = listOf(1, 2, 3, 4, 5) + assertEquals(6, Observable.from(list)!!.startWith(0)!!.count()!!.toBlockingObservable()!!.single()) + assertEquals(10, Observable.from(list)!!.startWith(startList)!!.count()!!.toBlockingObservable()!!.single()) + } + + [Test] + public fun testScriptWithOnNext() { + TestFactory().observable.subscribe((received())) + verify(a, times(1))!!.received("hello_1") + } + + [Test] + public fun testSkipTake() { + Observable.from(1, 2, 3)!!.skip(1)!!.take(1)!!.subscribe(received()) + verify(a, times(0))!!.received(1) + verify(a, times(1))!!.received(2) + verify(a, times(0))!!.received(3) + } + + [Test] + public fun testSkip() { + Observable.from(1, 2, 3)!!.skip(2)!!.subscribe(received()) + verify(a, times(0))!!.received(1) + verify(a, times(0))!!.received(2) + verify(a, times(1))!!.received(3) + } + + [Test] + public fun testTake() { + Observable.from(1, 2, 3)!!.take(2)!!.subscribe(received()) + verify(a, times(1))!!.received(1) + verify(a, times(1))!!.received(2) + verify(a, times(0))!!.received(3) + } + + [Test] + public fun testTakeLast() { + TestFactory().observable.takeLast(1)!!.subscribe((received())) + verify(a, times(1))!!.received("hello_1") + } + + [Test] + public fun testTakeWhile() { + Observable.from(1, 2, 3)!!.takeWhile { x -> x!! < 3 }!!.subscribe(received()) + verify(a, times(1))!!.received(1) + verify(a, times(1))!!.received(2) + verify(a, times(0))!!.received(3) + } + + [Test] + public fun testTakeWhileWithIndex() { + Observable.from(1, 2, 3)!!.takeWhileWithIndex { x, i -> i!! < 2 }!!.subscribe(received()) + verify(a, times(1))!!.received(1) + verify(a, times(1))!!.received(2) + verify(a, times(0))!!.received(3) + } + + [Test] + public fun testToSortedList() { + TestFactory().numbers.toSortedList()!!.subscribe(received()) + verify(a, times(1))!!.received(listOf(1, 2, 3, 4, 5)) + } + + [Test] + public fun testForEach() { + Observable.create(AsyncObservable())!!.toBlockingObservable()!!.forEach(received()) + verify(a, times(1))!!.received(1) + verify(a, times(1))!!.received(2) + verify(a, times(1))!!.received(3) + } + + [Test(expected = javaClass())] + public fun testForEachWithError() { + Observable.create(AsyncObservable())!!.toBlockingObservable()!!.forEach { throw RuntimeException("err") } + fail("we expect an exception to be thrown") + } + + [Test] + public fun testLastOrDefault() { + assertEquals("two", Observable.from("one", "two")!!.toBlockingObservable()!!.lastOrDefault("default") { x -> x!!.length == 3 }) + assertEquals("default", Observable.from("one", "two")!!.toBlockingObservable()!!.lastOrDefault("default") { x -> x!!.length > 3 }) + } + + [Test(expected = javaClass())] + public fun testSingle() { + assertEquals("one", Observable.from("one")!!.toBlockingObservable()!!.single { x -> x!!.length == 3 }) + Observable.from("one", "two")!!.toBlockingObservable()!!.single { x -> x!!.length == 3 } + fail() + } + + [Test] + public fun testDefer() { + Observable.defer { Observable.from(1, 2) }!!.subscribe(received()) + verify(a, times(1))!!.received(1) + verify(a, times(1))!!.received(2) + } + + [Test] + public fun testAll() { + Observable.from(1, 2, 3)!!.all { x -> x!! > 0 }!!.subscribe(received()) + verify(a, times(1))!!.received(true) + } + + [Test] + public fun testZip() { + val o1 = Observable.from(1, 2, 3)!! + val o2 = Observable.from(4, 5, 6)!! + val o3 = Observable.from(7, 8, 9)!! + + val values = Observable.zip(o1, o2, o3) { a, b, c -> listOf(a, b, c) }!!.toList()!!.toBlockingObservable()!!.single()!! + assertEquals(listOf(1, 4, 7), values[0]) + assertEquals(listOf(2, 5, 8), values[1]) + assertEquals(listOf(3, 6, 9), values[2]) + } + + [Test] + public fun testZipWithIterable() { + val o1 = Observable.from(1, 2, 3)!! + val o2 = Observable.from(4, 5, 6)!! + val o3 = Observable.from(7, 8, 9)!! + + val values = Observable.zip(listOf(o1, o2, o3)) { args -> listOf(*args) }!!.toList()!!.toBlockingObservable()!!.single()!! + assertEquals(listOf(1, 4, 7), values[0]) + assertEquals(listOf(2, 5, 8), values[1]) + assertEquals(listOf(3, 6, 9), values[2]) + } + + [Test] + public fun testGroupBy() { + var count = 0 + + Observable.from("one", "two", "three", "four", "five", "six")!! + .groupBy { s -> s!!.length }!! + .mapMany { groupObervable -> + groupObervable!!.map { s -> + "Value: $s Group ${groupObervable.getKey()}" + } + }!! + .toBlockingObservable()!!.forEach { s -> + println(s) + count++ + } + + assertEquals(6, count) + } + + public trait ScriptAssertion{ + fun error(e: Throwable?) + + fun received(e: Any?) + } + + public class TestFactory(){ + var counter = 1 + + val numbers: Observable + get(){ + return Observable.from(1, 3, 2, 5, 4)!! + } + + val onSubscribe: TestOnSubscribe + get(){ + return TestOnSubscribe(counter++) + } + + val observable: Observable + get(){ + return Observable.create(onSubscribe)!! + } + + } + + class AsyncObservable : OnSubscribeFunc{ + override fun onSubscribe(t1: Observer?): Subscription? { + thread { + Thread.sleep(50) + t1!!.onNext(1) + t1.onNext(2) + t1.onNext(3) + t1.onCompleted() + } + return Subscriptions.empty() + } + } + + class TestOnSubscribe(val count: Int) : OnSubscribeFunc{ + override fun onSubscribe(t1: Observer?): Subscription? { + t1!!.onNext("hello_$count") + t1.onCompleted() + return Subscription { } + } + + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-kotlin/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt b/language-adaptors/rxjava-kotlin/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt new file mode 100644 index 0000000000..b5ffbcddec --- /dev/null +++ b/language-adaptors/rxjava-kotlin/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt @@ -0,0 +1,321 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.lang.kotlin + +import org.mockito.Mock +import rx.Observable +import org.junit.Before +import org.mockito.MockitoAnnotations +import org.junit.Test +import rx.subscriptions.Subscriptions +import org.mockito.Mockito.* +import org.mockito.Matchers.* +import rx.Observer +import org.junit.Assert.* +import rx.Notification +import rx.Subscription +import kotlin.concurrent.thread + +/** + * This class contains tests using the extension functions provided by the language adaptor. + */ +public class ExtensionTests { + [Mock] var a: ScriptAssertion? = null + [Mock] var w: Observable? = null + + [Before] + public fun before() { + MockitoAnnotations.initMocks(this) + } + + fun received(): (T?) -> Unit { + return {(result: T?) -> a!!.received(result) } + } + + [Test] + public fun testCreate() { + + {(observer: Observer) -> + observer.onNext("Hello") + observer.onCompleted() + Subscriptions.empty()!! + }.asObservable().subscribe { result -> + a!!.received(result) + } + + verify(a, times(1))!!.received("Hello") + } + + [Test] + public fun testFilter() { + listOf(1, 2, 3).asObservable().filter { it!! >= 2 }!!.subscribe(received()) + verify(a, times(0))!!.received(1); + verify(a, times(1))!!.received(2); + verify(a, times(1))!!.received(3); + } + + + [Test] + public fun testLast() { + assertEquals("three", listOf("one", "two", "three").asObservable().toBlockingObservable()!!.last()) + } + + [Test] + public fun testLastWithPredicate() { + assertEquals("two", listOf("one", "two", "three").asObservable().toBlockingObservable()!!.last { x -> x!!.length == 3 }) + } + + [Test] + public fun testMap1() { + 1.asObservable().map { v -> "hello_$v" }!!.subscribe((received())) + verify(a, times(1))!!.received("hello_1") + } + + [Test] + public fun testMap2() { + listOf(1, 2, 3).asObservable().map { v -> "hello_$v" }!!.subscribe((received())) + verify(a, times(1))!!.received("hello_1") + verify(a, times(1))!!.received("hello_2") + verify(a, times(1))!!.received("hello_3") + } + + [Test] + public fun testMaterialize() { + listOf(1, 2, 3).asObservable().materialize()!!.subscribe((received())) + verify(a, times(4))!!.received(any(javaClass>())) + verify(a, times(0))!!.error(any(javaClass())) + } + + [Test] + public fun testMergeDelayError() { + Triple(listOf(1, 2, 3).asObservable(), + Triple(6.asObservable(), + NullPointerException().asObservable(), + 7.asObservable() + ).merge(), + listOf(4, 5).asObservable() + ).mergeDelayError().subscribe(received(), { e -> a!!.error(e) }) + verify(a, times(1))!!.received(1) + verify(a, times(1))!!.received(2) + verify(a, times(1))!!.received(3) + verify(a, times(1))!!.received(4) + verify(a, times(1))!!.received(5) + verify(a, times(1))!!.received(6) + verify(a, times(0))!!.received(7) + verify(a, times(1))!!.error(any(javaClass())) + } + + [Test] + public fun testMerge() { + Triple(listOf(1, 2, 3).asObservable(), + Triple(6.asObservable(), + NullPointerException().asObservable(), + 7.asObservable() + ).merge(), + listOf(4, 5).asObservable() + ).merge().subscribe(received(), { e -> a!!.error(e) }) + verify(a, times(1))!!.received(1) + verify(a, times(1))!!.received(2) + verify(a, times(1))!!.received(3) + verify(a, times(0))!!.received(4) + verify(a, times(0))!!.received(5) + verify(a, times(1))!!.received(6) + verify(a, times(0))!!.received(7) + verify(a, times(1))!!.error(any(javaClass())) + } + + [Test] + public fun testScriptWithMaterialize() { + TestFactory().observable.materialize()!!.subscribe((received())) + verify(a, times(2))!!.received(any(javaClass>())) + } + + [Test] + public fun testScriptWithMerge() { + val factory = TestFactory() + (factory.observable to factory.observable).merge().subscribe((received())) + verify(a, times(1))!!.received("hello_1") + verify(a, times(1))!!.received("hello_2") + } + + + [Test] + public fun testFromWithIterable() { + assertEquals(5, listOf(1, 2, 3, 4, 5).asObservable().count()!!.toBlockingObservable()!!.single()) + } + + [Test] + public fun testStartWith() { + val list = listOf(10, 11, 12, 13, 14) + val startList = listOf(1, 2, 3, 4, 5) + assertEquals(6, list.asObservable().startWith(0)!!.count()!!.toBlockingObservable()!!.single()) + assertEquals(10, list.asObservable().startWith(startList)!!.count()!!.toBlockingObservable()!!.single()) + } + + [Test] + public fun testScriptWithOnNext() { + TestFactory().observable.subscribe((received())) + verify(a, times(1))!!.received("hello_1") + } + + [Test] + public fun testSkipTake() { + Triple(1, 2, 3).asObservable().skip(1)!!.take(1)!!.subscribe(received()) + verify(a, times(0))!!.received(1) + verify(a, times(1))!!.received(2) + verify(a, times(0))!!.received(3) + } + + [Test] + public fun testSkip() { + Triple(1, 2, 3).asObservable().skip(2)!!.subscribe(received()) + verify(a, times(0))!!.received(1) + verify(a, times(0))!!.received(2) + verify(a, times(1))!!.received(3) + } + + [Test] + public fun testTake() { + Triple(1, 2, 3).asObservable().take(2)!!.subscribe(received()) + verify(a, times(1))!!.received(1) + verify(a, times(1))!!.received(2) + verify(a, times(0))!!.received(3) + } + + [Test] + public fun testTakeLast() { + TestFactory().observable.takeLast(1)!!.subscribe((received())) + verify(a, times(1))!!.received("hello_1") + } + + [Test] + public fun testTakeWhile() { + Triple(1, 2, 3).asObservable().takeWhile { x -> x!! < 3 }!!.subscribe(received()) + verify(a, times(1))!!.received(1) + verify(a, times(1))!!.received(2) + verify(a, times(0))!!.received(3) + } + + [Test] + public fun testTakeWhileWithIndex() { + Triple(1, 2, 3).asObservable().takeWhileWithIndex { x, i -> i!! < 2 }!!.subscribe(received()) + verify(a, times(1))!!.received(1) + verify(a, times(1))!!.received(2) + verify(a, times(0))!!.received(3) + } + + [Test] + public fun testToSortedList() { + TestFactory().numbers.toSortedList()!!.subscribe(received()) + verify(a, times(1))!!.received(listOf(1, 2, 3, 4, 5)) + } + + [Test] + public fun testForEach() { + asyncObservable.asObservable().toBlockingObservable()!!.forEach(received()) + verify(a, times(1))!!.received(1) + verify(a, times(1))!!.received(2) + verify(a, times(1))!!.received(3) + } + + [Test(expected = javaClass())] + public fun testForEachWithError() { + asyncObservable.asObservable().toBlockingObservable()!!.forEach { throw RuntimeException("err") } + fail("we expect an exception to be thrown") + } + + [Test] + public fun testLastOrDefault() { + assertEquals("two", ("one" to"two").asObservable().toBlockingObservable()!!.lastOrDefault("default") { x -> x!!.length == 3 }) + assertEquals("default", ("one" to"two").asObservable().toBlockingObservable()!!.lastOrDefault("default") { x -> x!!.length > 3 }) + } + + [Test] + public fun testDefer() { + { (1 to 2).asObservable() }.defer().subscribe(received()) + verify(a, times(1))!!.received(1) + verify(a, times(1))!!.received(2) + } + + [Test] + public fun testAll() { + Triple(1, 2, 3).asObservable().all { x -> x!! > 0 }!!.subscribe(received()) + verify(a, times(1))!!.received(true) + } + + [Test] + public fun testZip() { + val o1 = Observable.from(1, 2, 3)!! + val o2 = Observable.from(4, 5, 6)!! + val o3 = Observable.from(7, 8, 9)!! + + val values = Observable.zip(o1, o2, o3) { a, b, c -> listOf(a, b, c) }!!.toList()!!.toBlockingObservable()!!.single()!! + assertEquals(listOf(1, 4, 7), values[0]) + assertEquals(listOf(2, 5, 8), values[1]) + assertEquals(listOf(3, 6, 9), values[2]) + } + + public trait ScriptAssertion{ + fun error(e: Throwable?) + + fun received(e: Any?) + } + + val funOnSubscribe: (Int, Observer) -> Subscription = { counter, observer -> + observer.onNext("hello_$counter") + observer.onCompleted() + Subscription { } + } + + val asyncObservable: (Observer) -> Subscription = { observer -> + thread { + Thread.sleep(50) + observer.onNext(1) + observer.onNext(2) + observer.onNext(3) + observer.onCompleted() + } + Subscriptions.empty()!! + } + + /** + * Copied from (funKTionale)[https://github.com/MarioAriasC/funKTionale/blob/master/src/main/kotlin/org/funktionale/partials/namespace.kt] + */ + public fun Function2.partially1(p1: P1): (P2) -> R { + return {(p2: P2) -> this(p1, p2) } + } + + inner public class TestFactory(){ + var counter = 1 + + val numbers: Observable + get(){ + return listOf(1, 3, 2, 5, 4).asObservable() + } + + val onSubscribe: (Observer) -> Subscription + get(){ + return funOnSubscribe.partially1(counter++) + } + + val observable: Observable + get(){ + return onSubscribe.asObservable() + } + + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-scala-java/README.md b/language-adaptors/rxjava-scala-java/README.md deleted file mode 100644 index 54d7086366..0000000000 --- a/language-adaptors/rxjava-scala-java/README.md +++ /dev/null @@ -1,5 +0,0 @@ - -rxjava-scala-java ------------------ - -Contains examples illustrating how RxScala code can be used from Java. diff --git a/language-adaptors/rxjava-scala-java/src/main/java/rx/lang/scala/examples/MovieLibUsage.java b/language-adaptors/rxjava-scala-java/src/main/java/rx/lang/scala/examples/MovieLibUsage.java deleted file mode 100644 index cacd48bd79..0000000000 --- a/language-adaptors/rxjava-scala-java/src/main/java/rx/lang/scala/examples/MovieLibUsage.java +++ /dev/null @@ -1,24 +0,0 @@ -package rx.lang.scala.examples; - -import org.junit.Test; - -import rx.Observable; -import rx.util.functions.Action1; - - -public class MovieLibUsage { - - Action1 moviePrinter = new Action1() { - public void call(Movie m) { - System.out.println("A movie of length " + m.lengthInSeconds() + "s"); - } - }; - - @Test - public void test() { - MovieLib lib = new MovieLib(Observable.from(new Movie(3000), new Movie(1000), new Movie(2000))); - - lib.longMovies().subscribe(moviePrinter); - } - -} diff --git a/language-adaptors/rxjava-scala/README.md b/language-adaptors/rxjava-scala/README.md index 470a65744e..30b8d75b63 100644 --- a/language-adaptors/rxjava-scala/README.md +++ b/language-adaptors/rxjava-scala/README.md @@ -1,8 +1,80 @@ # Scala Adaptor for RxJava -There's an old Scala adaptor ( `rx.lang.scala.RxImplicits` with test `rx.lang.scala.RxImplicitsTest` ), which is deprecated. All other classes in `rx.lang.scala` belong to the new adaptor. +This adaptor allows to use RxJava in Scala with anonymous functions, e.g. -# Binaries +```scala +val o = Observable.interval(200 millis).take(5) +o.subscribe(n => println("n = " + n)) +Observable(1, 2, 3, 4).reduce(_ + _) +``` + +For-comprehensions are also supported: + +```scala +val first = Observable(10, 11, 12) +val second = Observable(10, 11, 12) +val booleans = for ((n1, n2) <- (first zip second)) yield (n1 == n2) +``` + +Further, this adaptor attempts to expose an API which is as Scala-idiomatic as possible. This means that certain methods have been renamed, their signature was changed, or static methods were changed to instance methods. Some examples: + +```scala + // instead of concat: +def ++[U >: T](that: Observable[U]): Observable[U] + +// instance method instead of static: +def zip[U](that: Observable[U]): Observable[(T, U)] + +// the implicit evidence argument ensures that dematerialize can only be called on Observables of Notifications: +def dematerialize[U](implicit evidence: T <:< Notification[U]): Observable[U] + +// additional type parameter U with lower bound to get covariance right: +def onErrorResumeNext[U >: T](resumeFunction: Throwable => Observable[U]): Observable[U] + +// curried in Scala collections, so curry fold also here: +def fold[R](initialValue: R)(accumulator: (R, T) => R): Observable[R] + +// using Duration instead of (long timepan, TimeUnit duration): +def sample(duration: Duration): Observable[T] + +// called skip in Java, but drop in Scala +def drop(n: Int): Observable[T] + +// there's only mapWithIndex in Java, because Java doesn't have tuples: +def zipWithIndex: Observable[(T, Int)] + +// corresponds to Java's toList: +def toSeq: Observable[Seq[T]] + +// the implicit evidence argument ensures that switch can only be called on Observables of Observables: +def switch[U](implicit evidence: Observable[T] <:< Observable[Observable[U]]): Observable[U] + +// Java's from becomes apply, and we use Scala Range +def apply(range: Range): Observable[Int] + +// use Bottom type: +def never: Observable[Nothing] +``` + +Also, the Scala Observable is fully covariant in its type parameter, whereas the Java Observable only achieves partial covariance due to limitations of Java's type system (or if you can fix this, your suggestions are very welcome). + +For more examples, see [RxScalaDemo.scala](https://github.com/Netflix/RxJava/blob/master/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/RxScalaDemo.scala). + +Scala code using Rx should only import members from `rx.lang.scala` and below. + + +## Documentation + +The API documentation can be found [here](http://rxscala.github.io/scaladoc/index.html#rx.lang.scala.Observable). + +Note that starting from version 0.15, `rx.lang.scala.Observable` is not a value class any more. [./Rationale.md](https://github.com/Netflix/RxJava/blob/master/language-adaptors/rxjava-scala/Rationale.md) explains why. + +You can build the API documentation yourself by running `./gradlew scaladoc` in the RxJava root directory. + +Then navigate to `RxJava/language-adaptors/rxjava-scala/build/docs/scaladoc/index.html` to display it. + + +## Binaries Binaries and dependency information for Maven, Ivy, Gradle and others can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22rxjava-scala%22). diff --git a/language-adaptors/rxjava-scala/Rationale.md b/language-adaptors/rxjava-scala/Rationale.md new file mode 100644 index 0000000000..5afb2392cc --- /dev/null +++ b/language-adaptors/rxjava-scala/Rationale.md @@ -0,0 +1,144 @@ +Alternative Rx bindings for Scala +================================= + +The current RxScala binding attempt to optimize for seamless interop between Scala and Java. +The intended interop is illustrated by the following example where in Scala a class is defined that takes +an `Observable[Movie]` that is transformed using RxScala operators: +```scala +class MovieLib(val moviesStream: Observable[Movie]) { + val threshold = 1200 + def shortMovies: Observable[Movie] = ??? + def longMovies: Observable[Movie] = ??? +} +``` +which is then called in Java, passing a Java `Observable` to the constructor +```java +public void test() { + MovieLib lib = new MovieLib(Observable.from(...)); + + lib.longMovies().subscribe(moviePrinter); +} +``` +The technique used to obtain this transparency is to use a value class with a private constructor that implements +the Rx operators in an idiomatic Scala way, and a companion object that is used to construct instances in Scala +```scala +object Observable { + def apply[T](asJava: rx.Observable[_ <: T]): Observable[T] = { new Observable[T](asJava) } +} + +class Observable[+T] private[scala] (val asJava: rx.Observable[_ <: T]) extends AnyVal { + // Idiomatic Scala friendly definitions of Rx operators +} +``` +Since `rx.lang.scala.Observable[T] extends AnyVal`, the underlying representation of `rx.lang.scala.Observable[T]` +is the same as `rx.Observable`. Because `rx.lang.scala.Observable[T]` is an opaque type in Scala, +the Scala programmer only sees the Scala-friendly operators. + +However, in the current the illusion of interop is quickly lost when going beyond this simple example. +For example but type `Notification[T]` and `Scheduler[T]` are defined using wrappers, +and hence they are not compatible with `Notification` respectively `Scheduler`. +For instance, when materializing an `Observable[T]` in Scala to an `Observable[Notification[T]]`, +we lost the seamless interop with `Observable>` on the Java side. + +However, the real problems with seamless interop show up when we try to creating bindings for other Rx types. +In particular types that have inheritance or more structure. + +For example, RxScala currently defines a type synonym `type Observer[-T] = rx.Observer[_ >: T]`, +but no further bindings for observers. +Similarly, for subjects RxScala defines `type Subject[-T, +R] = rx.subjects.Subject[_ >: T, _ <: R]`. +The problem with these definitions is that on the Java side, subjects are defined as: +```scala +public abstract class Subject extends Observable implements Observer { …} +``` +without binding any of the Rx subjects. + +The consequence is that `Subject[S,T]` in Scala is unrelated to `rx.lang.scala.Observable[T]` in Scala, +but shows up as a `rx.Observable[T]`. The problem however is that if we want to expose subjects in Scala +such that they derive from both `Observable[S]` and `Observer[T]` we cannot use the `extend AnyVal` trick +we used for `Observable[T]` and immediately lose transparent interop with Java. + +The problem is even worse because `AsyncSubject`, `BehaviorSubject`, … all derive from `Subject`, +so if we want them to derive from a common base `Subject[T,T]` type in Scala we lose transparency for those as well. +And again, if we expose the various subjects by extending `AnyVal`, they are useless in Scala because they do not inherit +from a common base type. To avoid implementing all methods of observable and observer on each specific subject +we might add implicit conversions to `Observable[T]` and `Observer[T]` but that still does not give Scala users +a native `Subject[S,T]` type. +```scala +object AsyncSubject { + def apply[T](): AsyncSubject[T] = + new AsyncSubject[T](rx.subjects.AsyncSubject.create()) +} + +class AsyncSubject[T] private [scala] (val inner: rx.subjects.AsyncSubject[T]) + extends AnyVal +{ … } + +implicit final def asObservable[T](subject: AsyncSubject[T]): Observable[T] = + Observable(subject.inner) + +implicit final def asObserver[T](subject: AsyncSubject[T]): Observer[T] = + subject.inner +``` +The inheritance problem is not just limited to subjects, but also surfaces for subscriptions. +Rx scala currently defines `type Subscription = rx.Subscription` using a type synonym as well, +and we run into exactly the same problems as with subjects when we try to bind the +various Rx subscriptions `BooleanSubscription`, `SerialSubscription`, etc. + +Since we cannot wrap Rx types in Scala such that they are both (a) transparently interoperable with Java, +and (b) feel native and idiomatic to Scala, we should decide in favor of optimizing RxScala for Scala +and consumption of Rx values from Java but not for Scala as a producer. + +If we take that approach, we can make bindings that feels like a completely native Scala library, +without needing any complications of the Scala side. +```scala +object Observer { …} +trait Observable[+T] { + def asJavaObservable: rx.Observable[_ <: T] +} + +object Observer {…} +trait Observer[-T] { + def asJavaObserver: rx.Observer[_ >: T] +} + +object Subject {…} +trait Subject[-T, +R] extends Observable[R] with Observer[T] { + val asJavaSubject: rx.subjects.Subject[_ >: T, _<: R] +} + +object Scheduler {…} +trait Scheduler { + def asJavaScheduler: rx.Scheduler; +} + +object Notification {…} +trait Notification[+T] { + def asJavaNotification: rx.Notification[_ <: T] +} + +object Subscription {…} +trait Subscription { + def asJavaSubscription: rx.Subscription +} +``` +You pay the price when crossing the Scala/Java interop boundary, which is where it should be. +The proper way is to put the burden of interop on the Scala side, in case you want to create +a reusable Rx-based library in Scala, or wrap and unwrap on the Java side. +```java +public static void main(String[] args) { + + Observable movies = Observable.from(new Movie(3000), new Movie(1000), new Movie(2000)); + MovieLib lib = new MovieLib(toScalaObservable(movies)); + lib.longMovies().asJavaObservable().subscribe(m -> + System.out.println("A movie of length " + m.lengthInSeconds() + "s") + ); +} +``` +Delegation versus Inheritance +----------------------------- +The obvious thought is that using delegation instead of inheritance (http://c2.com/cgi/wiki?DelegationIsInheritance) +will lead to excessive wrapping, since all Scala types wrap and delegate to an underlying RxJava implementation. +Note however, that the wrapping happens at query generation time and incurs no overhead when messages are flowing +through the pipeline. Say we have a query `xs.map(f).filter(p).subscribe(o)`. Even though the Scala types are wrappers, +the callback that is registered with xs is something like `x => { val y = f(x); if(p(y)){ o.asJavaObserver.onNext(y) }}` +and hence there is no additional runtime penalty. \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/TODO.md b/language-adaptors/rxjava-scala/TODO.md index 4b8bcab64e..f8965cc498 100644 --- a/language-adaptors/rxjava-scala/TODO.md +++ b/language-adaptors/rxjava-scala/TODO.md @@ -2,19 +2,45 @@ TODOs for Scala Adapter ----------------------- -This is a (probably incomplete) list of what still needs to be done in the Scala adaptor: - -- [ ] ConnectableObservable: Implement adaptor. Note that it cannot extend Scala Observable, since value classes are final. -- [ ] more methods of BlockingObservable -- [ ] multicast, publish, replay once we have ConnectableObservable -- [ ] groupBy and GroupedObservable -- [ ] mirror complete Java package structure in Scala -- [ ] convert Java futures to Scala futures -- [ ] Add methods present in Scala collections library, but not in RxJava, e.g. zipWithIndex, aggregate à la Scala -- [ ] mergeDelayError, combineLatest, merge, concat, zip: decide if instance method or static or both, decide about arities > 2 -- [ ] naming: switch() or switchOnNext()? -- [ ] decide where the MovieLib/MovieLibUsage (use Scala code from Java code) example should live and make sure gradle builds it in the right order -- [ ] Avoid text duplication in scaladoc using templates, add examples, distinction between use case signature and full signature -- [ ] other small TODOs +This is a (probably incomplete) list of what still needs to be done in the Scala adaptor. + +TODOs which came up at the meeting with Erik Meijer on 2013-10-11: + +* Rename the factory methods in `object Observable`, considering that the most important is the one taking an `Observer => Subscription` function (the "king" according to Erik). Thunk to Subscription conversion (?), also consider Jason's [comments](https://github.com/Netflix/RxJava/commit/c1596253fc5567b7cc37d20128374d189471ff79). A useful trick might also be to have `apply(T, T, T*)` instead of just `apply(T*)`. +* Factory methods for observables and instance methods should take implicit scheduler, default is different per method (Isn't this a contradiction? In other words: If I call a method without providing a scheduler, should the default scheduler be used or the implicit that I provided?) Find in .NET source the list of which schedulers goes with which operators by default. If no other default, use NewThreadScheduler. Note that there are methods in Scala Observable which should have an overload taking a Scheduler, but do not yet have it! Also remember Erik saying that he would like to "minimize magically injected concurrency". +* Convert executor to scheduler +* Check if TestScheduler added in 0.14.3 is sufficient +* Infinite Iterables: the java Observable.from version unfolds an iterable, even it is infinite. Should be fixed in java. +* subscribe methods: There are many overloads, but still not all combinations one might need. Make this nicer and complete, maybe using default arguments. Also try to make sure that `subscribe(println)` works, not only `subscribe(println(_))`. `foreach(println)` works on collections, but not on `subscribe(println)`, because subscribe is overloaded. +* Currently all Swing examples use Swing directly, without using the Scala wrappers around Swing. Make sure that everything is nice if the Scala wrappers around Swing are used, eg `Reaction { case clicked: ButtonClicked => … }` -- avoid default case, check out the hooks for partial function applyOrElse to avoid double pattern matching +* There are no examples yet using `async`, but `async` will be used in the course. Write examples and check if everything works as expected when combined with `async`. +* Futures: For the moment, just add a Future->Observable converter method to `object Observable`. Later, think if `Future[T] extends Observable[T]`. +* Operator `delay`: Once Erik has commented on [this](https://github.com/Netflix/RxJava/pull/384), make sure this operator is added accordingly to RxJava and then to RxScala + +Some more TODOs: + +* Integrating Scala Futures: Should there be a common base interface for Futures and Observables? And if all subscribers of an Observable wrapping a Future unsubscribe, the Future should be cancelled, but Futures do not support cancellation. +* Add methods present in Scala collections library, but not in RxJava, e.g. aggregate à la Scala, collect, tails, ... +* combineLatest with arities > 2 +* Avoid text duplication in scaladoc using templates, add examples, distinction between use case signature and full signature +* other small TODOs in source code +* always: keep Scala Observable in sync with Java Observable + + +### Appendix: + +`println` example: + + List(1, 2).foreach(println) + Observable(1, 2).toBlockingObservable.foreach(println) + + Observable(1, 2).subscribe(println) // does not work + + class Ob[+T] { + def subscribe(onNext: T => Unit) = ??? + } + val o2 = new Ob[Int] + o2.subscribe(println) // works + diff --git a/language-adaptors/rxjava-scala/build.gradle b/language-adaptors/rxjava-scala/build.gradle index f0a6579228..db194a6d28 100644 --- a/language-adaptors/rxjava-scala/build.gradle +++ b/language-adaptors/rxjava-scala/build.gradle @@ -13,10 +13,30 @@ tasks.withType(ScalaCompile) { } sourceSets { + main { + scala { + srcDir 'src/main/scala' + } + } test { scala { srcDir 'src/main/scala' + srcDir 'src/test/scala' + srcDir 'src/examples/scala' + srcDir 'src/examples/java' + } + java.srcDirs = [] + } + examples { + // It seems that in Gradle, the dependency "compileScala depends on compileJava" is hardcoded, + // or at least not meant to be removed. + // However, compileScala also runs javac at the very end, so we just add the Java sources to + // the scala source set: + scala { + srcDir 'src/examples/scala' + srcDir 'src/examples/java' } + java.srcDirs = [] } } @@ -34,6 +54,15 @@ tasks.compileScala { classpath = classpath + (configurations.compile + configurations.provided) } +tasks.compileExamplesScala { + classpath = classpath + files(compileScala.destinationDir) + (configurations.compile + configurations.provided) +} + +// Add RxJava core to Scaladoc input: +// tasks.scaladoc.source(project(':rxjava-core').tasks.getByPath(':rxjava-core:compileJava').source) +// println("-------") +// println(tasks.scaladoc.source.asPath) + task test(overwrite: true, dependsOn: testClasses) << { ant.taskdef(name: 'scalatest', classname: 'org.scalatest.tools.ScalaTestAntTask', @@ -49,7 +78,7 @@ jar { name = 'rxjava-scala' instruction 'Bundle-Vendor', 'Netflix' instruction 'Bundle-DocURL', 'https://github.com/Netflix/RxJava' - instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' + instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,!org.scalatest.*,*' instruction 'Fragment-Host', 'com.netflix.rxjava.core' } } diff --git a/language-adaptors/rxjava-scala/src/examples/java/rx/lang/scala/examples/MovieLibUsage.java b/language-adaptors/rxjava-scala/src/examples/java/rx/lang/scala/examples/MovieLibUsage.java new file mode 100644 index 0000000000..fde62e0dd8 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/examples/java/rx/lang/scala/examples/MovieLibUsage.java @@ -0,0 +1,45 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala.examples; + +import rx.Observable; +import rx.lang.scala.examples.Movie; +import rx.lang.scala.examples.MovieLib; +import rx.util.functions.Action1; +import static rx.lang.scala.ImplicitFunctionConversions.toScalaObservable; + +public class MovieLibUsage { + + public static void main(String[] args) { + + Observable movies = Observable.from( + new Movie(3000), + new Movie(1000), + new Movie(2000) + ); + + MovieLib lib = new MovieLib(toScalaObservable(movies)); + + lib.longMovies().asJavaObservable().subscribe(new Action1() { + + @Override + public void call(Movie m) { + System.out.println("A movie of length " + m.lengthInSeconds() + "s"); + } + }); + } + +} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/examples/MovieLib.scala b/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/MovieLib.scala similarity index 97% rename from language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/examples/MovieLib.scala rename to language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/MovieLib.scala index 1eefac79c9..8e2ce5b29b 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/examples/MovieLib.scala +++ b/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/MovieLib.scala @@ -1,12 +1,12 @@ /** * Copyright 2013 Netflix, Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,11 +20,11 @@ import rx.lang.scala.Observable class Movie(val lengthInSeconds: Int) { } class MovieLib(val moviesStream: Observable[Movie]) { - + val threshold = 1200 - + def shortMovies: Observable[Movie] = moviesStream.filter(_.lengthInSeconds <= threshold) - + def longMovies: Observable[Movie] = moviesStream.filter(_.lengthInSeconds > threshold) } diff --git a/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/Olympics.scala b/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/Olympics.scala new file mode 100644 index 0000000000..7a11bdf539 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/Olympics.scala @@ -0,0 +1,86 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala.examples + +import rx.lang.scala.Observable +import scala.concurrent.duration._ + +object Olympics { + case class Medal(val year: Int, val games: String, val discipline: String, val medal: String, val athlete: String, val country: String) + + def mountainBikeMedals: Observable[Medal] = Observable( + Observable( + Medal(1996, "Atlanta 1996", "cross-country men", "Gold", "Bart BRENTJENS", "Netherlands"), + Medal(1996, "Atlanta 1996", "cross-country women", "Gold", "Paola PEZZO", "Italy"), + Medal(1996, "Atlanta 1996", "cross-country men", "Silver", "Thomas FRISCHKNECHT", "Switzerland"), + Medal(1996, "Atlanta 1996", "cross-country women", "Silver", "Alison SYDOR", "Canada"), + Medal(1996, "Atlanta 1996", "cross-country men", "Bronze", "Miguel MARTINEZ", "France"), + Medal(1996, "Atlanta 1996", "cross-country women", "Bronze", "Susan DEMATTEI", "United States of America") + ), + fourYearsEmpty, + Observable( + Medal(2000, "Sydney 2000", "cross-country women", "Gold", "Paola PEZZO", "Italy"), + Medal(2000, "Sydney 2000", "cross-country women", "Silver", "Barbara BLATTER", "Switzerland"), + Medal(2000, "Sydney 2000", "cross-country women", "Bronze", "Marga FULLANA", "Spain"), + Medal(2000, "Sydney 2000", "cross-country men", "Gold", "Miguel MARTINEZ", "France"), + Medal(2000, "Sydney 2000", "cross-country men", "Silver", "Filip MEIRHAEGHE", "Belgium"), + Medal(2000, "Sydney 2000", "cross-country men", "Bronze", "Christoph SAUSER", "Switzerland") + ), + fourYearsEmpty, + Observable( + Medal(2004, "Athens 2004", "cross-country men", "Gold", "Julien ABSALON", "France"), + Medal(2004, "Athens 2004", "cross-country men", "Silver", "Jose Antonio HERMIDA RAMOS", "Spain"), + Medal(2004, "Athens 2004", "cross-country men", "Bronze", "Bart BRENTJENS", "Netherlands"), + Medal(2004, "Athens 2004", "cross-country women", "Gold", "Gunn-Rita DAHLE", "Norway"), + Medal(2004, "Athens 2004", "cross-country women", "Silver", "Marie-Helene PREMONT", "Canada"), + Medal(2004, "Athens 2004", "cross-country women", "Bronze", "Sabine SPITZ", "Germany") + ), + fourYearsEmpty, + Observable( + Medal(2008, "Beijing 2008", "cross-country women", "Gold", "Sabine SPITZ", "Germany"), + Medal(2008, "Beijing 2008", "cross-country women", "Silver", "Maja WLOSZCZOWSKA", "Poland"), + Medal(2008, "Beijing 2008", "cross-country women", "Bronze", "Irina KALENTYEVA", "Russian Federation"), + Medal(2008, "Beijing 2008", "cross-country men", "Gold", "Julien ABSALON", "France"), + Medal(2008, "Beijing 2008", "cross-country men", "Silver", "Jean-Christophe PERAUD", "France"), + Medal(2008, "Beijing 2008", "cross-country men", "Bronze", "Nino SCHURTER", "Switzerland") + ), + fourYearsEmpty, + Observable( + Medal(2012, "London 2012", "cross-country men", "Gold", "Jaroslav KULHAVY", "Czech Republic"), + Medal(2012, "London 2012", "cross-country men", "Silver", "Nino SCHURTER", "Switzerland"), + Medal(2012, "London 2012", "cross-country men", "Bronze", "Marco Aurelio FONTANA", "Italy"), + Medal(2012, "London 2012", "cross-country women", "Gold", "Julie BRESSET", "France"), + Medal(2012, "London 2012", "cross-country women", "Silver", "Sabine SPITZ", "Germany"), + Medal(2012, "London 2012", "cross-country women", "Bronze", "Georgia GOULD", "United States of America") + ) + ).concat + + // speed it up :D + val fourYears = 4000.millis + + val neverUsedDummyMedal = Medal(3333, "?", "?", "?", "?", "?") + + def fourYearsEmpty: Observable[Medal] = { + // TODO this should return an observable which emits nothing during fourYears and then completes + // Because of https://github.com/Netflix/RxJava/issues/388, we get non-terminating tests + // And this https://github.com/Netflix/RxJava/pull/289#issuecomment-24738668 also causes problems + // So we don't use this: + // Observable.interval(fourYears).take(1).map(i => neverUsedDummyMedal).filter(m => false) + // But we just return empty, which completes immediately + Observable() + } + +} \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/RxScalaDemo.scala b/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/RxScalaDemo.scala new file mode 100644 index 0000000000..4a8ee56345 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/RxScalaDemo.scala @@ -0,0 +1,477 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala.examples + +import java.io.IOException + +import scala.concurrent.duration.Duration +import scala.concurrent.duration.DurationInt +import scala.concurrent.duration.DurationLong +import scala.language.postfixOps + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Ignore +import org.junit.Test +import org.scalatest.junit.JUnitSuite + +import rx.lang.scala.Notification +import rx.lang.scala.Observable +import rx.lang.scala.observable +import rx.lang.scala.concurrency.Schedulers + +@Ignore // Since this doesn't do automatic testing, don't increase build time unnecessarily +class RxScalaDemo extends JUnitSuite { + + @Test def intervalExample() { + val o = Observable.interval(200 millis).take(5) + o.subscribe(n => println("n = " + n)) + + // need to wait here because otherwise JUnit kills the thread created by interval() + waitFor(o) + + println("done") + } + + def msTicks(start: Long, step: Long): Observable[Long] = { + // will be easier once we have Observable.generate method + Observable.interval(step millis) map (_ * step + start) + } + + def prefixedTicks(start: Long, step: Long, prefix: String): Observable[String] = { + msTicks(start, step).map(prefix + _) + } + + @Test def testTicks() { + val o = prefixedTicks(5000, 500, "t = ").take(5) + o.subscribe(output(_)) + waitFor(o) + } + + @Test def testSwitch() { + // We do not have ultimate precision: Sometimes, 747 gets through, sometimes not + val o = Observable.interval(1000 millis).map(n => prefixedTicks(0, 249, s"Observable#$n: ")) + .switch.take(16) + o.subscribe(output(_)) + waitFor(o) + } + + @Test def testSwitchOnObservableOfInt() { + // Correctly rejected with error + // "Cannot prove that Observable[Int] <:< Observable[Observable[U]]" + // val o = Observable(1, 2).switch + } + + @Test def testObservableComparison() { + val first = Observable(10, 11, 12) + val second = Observable(10, 11, 12) + + val b1 = (first zip second) map (p => p._1 == p._2) forall (b => b) + + val equality = (a: Any, b: Any) => a == b + val b2 = (first zip second) map (p => equality(p._1, p._2)) forall (b => b) + + assertTrue(b1.toBlockingObservable.single) + assertTrue(b2.toBlockingObservable.single) + } + + @Test def testObservableComparisonWithForComprehension() { + val first = Observable(10, 11, 12) + val second = Observable(10, 11, 12) + + val booleans = for ((n1, n2) <- (first zip second)) yield (n1 == n2) + + val b1 = booleans.forall(_ == true) // without `== true`, b1 is assigned the forall function + + assertTrue(b1.toBlockingObservable.single) + } + + @Test def testStartWithIsUnnecessary() { + val before = Observable(-2, -1, 0) + val source = Observable(1, 2, 3) + println((before ++ source).toBlockingObservable.toList) + } + + @Test def mergeTwoExample() { + val slowNumbers = Observable.interval(400 millis).take(5).map("slow " + _) + val fastNumbers = Observable.interval(200 millis).take(10).map("fast " + _) + val o = (slowNumbers merge fastNumbers) + o.subscribe(output(_)) + waitFor(o) + } + + def myInterval(period: Long): Observable[String] = { + Observable.interval(period.millis).map(n => s"Obs-$period emits $n") + } + + @Test def flattenManyExample() { + val o = Observable.interval(500 millis).map(n => myInterval((n+1)*100)) + val stopper = Observable.interval(5 seconds) + o.flatten.takeUntil(stopper).toBlockingObservable.foreach(println(_)) + } + + @Test def fattenSomeExample() { + // To merge some observables which are all known already: + Observable( + Observable.interval(200 millis), + Observable.interval(400 millis), + Observable.interval(800 millis) + ).flatten.take(12).toBlockingObservable.foreach(println(_)) + } + + @Test def rangeAndBufferExample() { + val o = Observable(1 to 18) + o.buffer(5).subscribe((l: Seq[Int]) => println(l.mkString("[", ", ", "]"))) + } + + @Test def windowExample() { + // this will be nicer once we have zipWithIndex + (for ((o, i) <- Observable(1 to 18).window(5) zip Observable(0 until 4); n <- o) + yield s"Observable#$i emits $n") + .subscribe(output(_)) + } + + @Test def testReduce() { + assertEquals(10, Observable(1, 2, 3, 4).reduce(_ + _).toBlockingObservable.single) + } + + @Test def testForeach() { + val numbers = Observable.interval(200 millis).take(3) + + // foreach is not available on normal Observables: + // for (n <- numbers) println(n+10) + + // but on BlockingObservable, it is: + for (n <- numbers.toBlockingObservable) println(n+10) + } + + @Test def testForComprehension() { + val observables = Observable(Observable(1, 2, 3), Observable(10, 20, 30)) + val squares = (for (o <- observables; i <- o if i % 2 == 0) yield i*i) + assertEquals(squares.toBlockingObservable.toList, List(4, 100, 400, 900)) + } + + @Test def testTwoSubscriptionsToOneInterval() { + val o = Observable.interval(100 millis).take(8) + o.subscribe( + i => println(s"${i}a (on thread #${Thread.currentThread().getId()})") + ) + o.subscribe( + i => println(s"${i}b (on thread #${Thread.currentThread().getId()})") + ) + waitFor(o) + } + + @Test def schedulersExample() { + val o = Observable.interval(100 millis).take(8) + o.observeOn(Schedulers.newThread).subscribe( + i => println(s"${i}a (on thread #${Thread.currentThread().getId()})") + ) + o.observeOn(Schedulers.newThread).subscribe( + i => println(s"${i}b (on thread #${Thread.currentThread().getId()})") + ) + waitFor(o) + } + + @Test def testGroupByThenFlatMap() { + val m = Observable(1, 2, 3, 4) + val g = m.groupBy(i => i % 2) + val t = g.flatMap((p: (Int, Observable[Int])) => p._2) + assertEquals(List(1, 2, 3, 4), t.toBlockingObservable.toList) + } + + @Test def testGroupByThenFlatMapByForComprehension() { + val m = Observable(1, 2, 3, 4) + val g = m.groupBy(i => i % 2) + val t = for ((i, o) <- g; n <- o) yield n + assertEquals(List(1, 2, 3, 4), t.toBlockingObservable.toList) + } + + @Test def testGroupByThenFlatMapByForComprehensionWithTiming() { + val m = Observable.interval(100 millis).take(4) + val g = m.groupBy(i => i % 2) + val t = for ((i, o) <- g; n <- o) yield n + assertEquals(List(0, 1, 2, 3), t.toBlockingObservable.toList) + } + + @Test def timingTest() { + val firstOnly = false + val numbersByModulo3 = Observable.interval(1000 millis).take(9).groupBy(_ % 3) + + (for ((modulo, numbers) <- numbersByModulo3) yield { + println("Observable for modulo" + modulo + " started") + + if (firstOnly) numbers.take(1) else numbers + }).flatten.toBlockingObservable.foreach(println(_)) + } + + @Test def timingTest1() { + val numbersByModulo3 = Observable.interval(1000 millis).take(9).groupBy(_ % 3) + + val t0 = System.currentTimeMillis + + (for ((modulo, numbers) <- numbersByModulo3) yield { + println("Observable for modulo" + modulo + " started at t = " + (System.currentTimeMillis - t0)) + numbers.take(1) // <- TODO very unexpected + //numbers + }).flatten.toBlockingObservable.foreach(println(_)) + } + + @Test def groupByExample() { + val medalsByCountry = Olympics.mountainBikeMedals.groupBy(medal => medal.country) + + val firstMedalOfEachCountry = + for ((country, medals) <- medalsByCountry; firstMedal <- medals.take(1)) yield firstMedal + + firstMedalOfEachCountry.subscribe(medal => { + println(s"${medal.country} wins its first medal in ${medal.year}") + }) + + waitFor(firstMedalOfEachCountry) + } + + @Test def olympicsExample() { + val (go, medals) = Olympics.mountainBikeMedals.publish + medals.subscribe(println(_)) + go() + //waitFor(medals) + } + + @Test def exampleWithoutPublish() { + val unshared = Observable(1 to 4) + unshared.subscribe(n => println(s"subscriber 1 gets $n")) + unshared.subscribe(n => println(s"subscriber 2 gets $n")) + } + + @Test def exampleWithPublish() { + val unshared = Observable(1 to 4) + val (startFunc, shared) = unshared.publish + shared.subscribe(n => println(s"subscriber 1 gets $n")) + shared.subscribe(n => println(s"subscriber 2 gets $n")) + startFunc() + } + + def doLater(waitTime: Duration, action: () => Unit): Unit = { + Observable.interval(waitTime).take(1).subscribe(_ => action()) + } + + @Test def exampleWithoutReplay() { + val numbers = Observable.interval(1000 millis).take(6) + val (startFunc, sharedNumbers) = numbers.publish + sharedNumbers.subscribe(n => println(s"subscriber 1 gets $n")) + startFunc() + // subscriber 2 misses 0, 1, 2! + doLater(3500 millis, () => { sharedNumbers.subscribe(n => println(s"subscriber 2 gets $n")) }) + waitFor(sharedNumbers) + } + + @Test def exampleWithReplay() { + val numbers = Observable.interval(1000 millis).take(6) + val (startFunc, sharedNumbers) = numbers.replay + sharedNumbers.subscribe(n => println(s"subscriber 1 gets $n")) + startFunc() + // subscriber 2 subscribes later but still gets all numbers + doLater(3500 millis, () => { sharedNumbers.subscribe(n => println(s"subscriber 2 gets $n")) }) + waitFor(sharedNumbers) + } + + @Test def testSingleOption() { + assertEquals(None, Observable(1, 2).toBlockingObservable.singleOption) + assertEquals(Some(1), Observable(1) .toBlockingObservable.singleOption) + assertEquals(None, Observable() .toBlockingObservable.singleOption) + } + + // We can't put a general average method into Observable.scala, because Scala's Numeric + // does not have scalar multiplication (we would need to calculate (1.0/numberOfElements)*sum) + def doubleAverage(o: Observable[Double]): Observable[Double] = { + for ((finalSum, finalCount) <- o.foldLeft((0.0, 0))({case ((sum, count), elem) => (sum+elem, count+1)})) + yield finalSum / finalCount + } + + @Test def averageExample() { + println(doubleAverage(Observable()).toBlockingObservable.single) + println(doubleAverage(Observable(0)).toBlockingObservable.single) + println(doubleAverage(Observable(4.44)).toBlockingObservable.single) + println(doubleAverage(Observable(1, 2, 3.5)).toBlockingObservable.single) + } + + @Test def testSum() { + assertEquals(10, Observable(1, 2, 3, 4).sum.toBlockingObservable.single) + assertEquals(6, Observable(4, 2).sum.toBlockingObservable.single) + assertEquals(0, Observable[Int]().sum.toBlockingObservable.single) + } + + @Test def testProduct() { + assertEquals(24, Observable(1, 2, 3, 4).product.toBlockingObservable.single) + assertEquals(8, Observable(4, 2).product.toBlockingObservable.single) + assertEquals(1, Observable[Int]().product.toBlockingObservable.single) + } + + @Test def mapWithIndexExample() { + // We don't need mapWithIndex because we already have zipWithIndex, which we can easily + // combine with map: + Observable("a", "b", "c").zipWithIndex.map(pair => pair._1 + " has index " + pair._2) + .toBlockingObservable.foreach(println(_)) + + // Or even nicer with for-comprehension syntax: + (for ((letter, index) <- Observable("a", "b", "c").zipWithIndex) yield letter + " has index " + index) + .toBlockingObservable.foreach(println(_)) + } + + // source Observables are all known: + @Test def zip3Example() { + val o = Observable.zip(Observable(1, 2), Observable(10, 20), Observable(100, 200)) + (for ((n1, n2, n3) <- o) yield s"$n1, $n2 and $n3") + .toBlockingObservable.foreach(println(_)) + } + + // source Observables are in an Observable: + @Test def zipManyObservableExample() { + val observables = Observable(Observable(1, 2), Observable(10, 20), Observable(100, 200)) + (for (seq <- Observable.zip(observables)) yield seq.mkString("(", ", ", ")")) + .toBlockingObservable.foreach(println(_)) + } + + @Test def takeFirstWithCondition() { + val condition: Int => Boolean = _ >= 3 + assertEquals(3, Observable(1, 2, 3, 4).filter(condition).first.toBlockingObservable.single) + } + + @Test def firstOrDefaultWithCondition() { + val condition: Int => Boolean = _ >= 3 + assertEquals(3, Observable(1, 2, 3, 4).filter(condition).firstOrElse(10).toBlockingObservable.single) + assertEquals(10, Observable(-1, 0, 1).filter(condition).firstOrElse(10).toBlockingObservable.single) + } + + def square(x: Int): Int = { + println(s"$x*$x is being calculated on thread ${Thread.currentThread().getId()}") + Thread.sleep(100) // calculating a square is heavy work :) + x*x + } + + def work(o1: Observable[Int]): Observable[String] = { + println(s"map() is being called on thread ${Thread.currentThread().getId()}") + o1.map(i => s"The square of $i is ${square(i)}") + } + + @Test def parallelExample() { + val t0 = System.currentTimeMillis() + Observable(1 to 10).parallel(work(_)).toBlockingObservable.foreach(println(_)) + println(s"Work took ${System.currentTimeMillis()-t0} ms") + } + + @Test def exampleWithoutParallel() { + val t0 = System.currentTimeMillis() + work(Observable(1 to 10)).toBlockingObservable.foreach(println(_)) + println(s"Work took ${System.currentTimeMillis()-t0} ms") + } + + @Test def toSortedList() { + assertEquals(Seq(7, 8, 9, 10), Observable(10, 7, 8, 9).toSeq.map(_.sorted).toBlockingObservable.single) + val f = (a: Int, b: Int) => b < a + assertEquals(Seq(10, 9, 8, 7), Observable(10, 7, 8, 9).toSeq.map(_.sortWith(f)).toBlockingObservable.single) + } + + @Test def timestampExample() { + val timestamped = Observable.interval(100 millis).take(3).timestamp.toBlockingObservable + for ((millis, value) <- timestamped if value > 0) { + println(value + " at t = " + millis) + } + } + + @Test def materializeExample1() { + def printObservable[T](o: Observable[T]): Unit = { + import Notification._ + o.materialize.subscribe(n => n match { + case OnNext(v) => println("Got value " + v) + case OnCompleted() => println("Completed") + case OnError(err) => println("Error: " + err.getMessage) + }) + } + + val o1 = Observable.interval(100 millis).take(3) + val o2 = Observable(new IOException("Oops")) + printObservable(o1) + //waitFor(o1) + printObservable(o2) + //waitFor(o2) + } + + @Test def materializeExample2() { + import Notification._ + Observable(1, 2, 3).materialize.subscribe(n => n match { + case OnNext(v) => println("Got value " + v) + case OnCompleted() => println("Completed") + case OnError(err) => println("Error: " + err.getMessage) + }) + } + + @Test def elementAtReplacement() { + assertEquals("b", Observable("a", "b", "c").drop(1).first.toBlockingObservable.single) + } + + @Test def elementAtOrDefaultReplacement() { + assertEquals("b", Observable("a", "b", "c").drop(1).firstOrElse("!").toBlockingObservable.single) + assertEquals("!!", Observable("a", "b", "c").drop(10).firstOrElse("!!").toBlockingObservable.single) + } + + @Test def observableLikeFuture1() { + implicit val scheduler = Schedulers.threadPoolForIO + val o1 = observable { + Thread.sleep(1000) + 5 + } + val o2 = observable { + Thread.sleep(500) + 4 + } + Thread.sleep(500) + val t1 = System.currentTimeMillis + println((o1 merge o2).first.toBlockingObservable.single) + println(System.currentTimeMillis - t1) + } + + @Test def observableLikeFuture2() { + class Friend {} + val session = new Object { + def getFriends: List[Friend] = List(new Friend, new Friend) + } + + implicit val scheduler = Schedulers.threadPoolForIO + val o: Observable[List[Friend]] = observable { + session.getFriends + } + o.subscribe( + friendList => println(friendList), + err => println(err.getMessage) + ) + + Thread.sleep(1500) // or convert to BlockingObservable + } + + @Test def takeWhileWithIndexAlternative { + val condition = true + Observable("a", "b").zipWithIndex.takeWhile{case (elem, index) => condition}.map(_._1) + } + + def output(s: String): Unit = println(s) + + // blocks until obs has completed + def waitFor[T](obs: Observable[T]): Unit = { + obs.toBlockingObservable.toIterable.last + } + +} \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/TestSchedulerExample.scala b/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/TestSchedulerExample.scala new file mode 100644 index 0000000000..55df673708 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/TestSchedulerExample.scala @@ -0,0 +1,48 @@ +package rx.lang.scala.examples + +import org.junit.Test +import org.scalatest.junit.JUnitSuite +import scala.concurrent.duration._ +import scala.language.postfixOps +import rx.lang.scala.{ Observable, Observer } +import rx.lang.scala.concurrency.TestScheduler + +class TestSchedulerExample extends JUnitSuite { + + @Test def testInterval() { + import org.mockito.Matchers._ + import org.mockito.Mockito._ + + val scheduler = TestScheduler() + // Use a Java Observer for Mockito + val observer = mock(classOf[rx.Observer[Long]]) + + val o = Observable.interval(1 second, scheduler) + + // Wrap Java Observer in Scala Observer, then subscribe + val sub = o.subscribe(Observer(observer)) + + verify(observer, never).onNext(0L) + verify(observer, never).onCompleted() + verify(observer, never).onError(any(classOf[Throwable])) + + scheduler.advanceTimeTo(2 seconds) + + val inOrdr = inOrder(observer); + inOrdr.verify(observer, times(1)).onNext(0L) + inOrdr.verify(observer, times(1)).onNext(1L) + inOrdr.verify(observer, never).onNext(2L) + verify(observer, never).onCompleted() + verify(observer, never).onError(any(classOf[Throwable])) + + verify(observer, never).onNext(2L) + + sub.unsubscribe(); + + scheduler.advanceTimeTo(4 seconds) + + // after unsubscription we expect no further events + verifyNoMoreInteractions(observer) + } + +} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/ImplicitFunctionConversions.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/ImplicitFunctionConversions.scala new file mode 100644 index 0000000000..ead475bda9 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/ImplicitFunctionConversions.scala @@ -0,0 +1,137 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala + +import java.lang.Exception +import java.{ lang => jlang } +import rx.lang.scala._ +import rx.util.functions._ +import scala.collection.Seq +import java.{lang => jlang} +import scala.language.implicitConversions + +/** + * These function conversions convert between Scala functions and Rx `Func`s and `Action`s. + * Most RxScala users won't need them, but they might be useful if one wants to use + * the `rx.Observable` directly instead of using `rx.lang.scala.Observable` or if one wants + * to use a Java library taking/returning `Func`s and `Action`s. + */ +object ImplicitFunctionConversions { + import language.implicitConversions + + implicit def schedulerActionToFunc2[T](action: (Scheduler, T) => Subscription) = + new Func2[rx.Scheduler, T, rx.Subscription] { + def call(s: rx.Scheduler, t: T): rx.Subscription = { + action(s, t).asJavaSubscription + } + } + + implicit def toJavaNotification[T](s: Notification[T]): rx.Notification[_ <: T] = s.asJava + implicit def toScalaNotification[T](s: rx.Notification[_ <: T]): Notification[T] = Notification(s) + + implicit def toJavaSubscription(s: Subscription): rx.Subscription = s.asJavaSubscription + implicit def toScalaSubscription(s: rx.Subscription): Subscription = Subscription(s) + + implicit def scalaSchedulerToJavaScheduler(s: Scheduler): rx.Scheduler = s.asJavaScheduler + implicit def javaSchedulerToScalaScheduler(s: rx.Scheduler): Scheduler = Scheduler(s) + + implicit def toJavaObserver[T](s: Observer[T]): rx.Observer[_ >: T] = s.asJavaObserver + implicit def toScalaObserver[T](s: rx.Observer[_ >: T]): Observer[T] = Observer(s) + + implicit def toJavaObservable[T](s: Observable[T]): rx.Observable[_ <: T] = s.asJavaObservable + implicit def toScalaObservable[T](s: rx.Observable[_ <: T]): Observable[T] = Observable(s) + + implicit def scalaFunction1ToOnSubscribeFunc[T](f: rx.lang.scala.Observer[T] => Subscription) = + new rx.Observable.OnSubscribeFunc[T] { + def onSubscribe(obs: rx.Observer[_ >: T]): rx.Subscription = { + f(Observer(obs)) + } + } + + implicit def scalaByNameParamToFunc0[B](param: => B): Func0[B] = + new Func0[B] { + def call(): B = param + } + + implicit def scalaFunction0ProducingUnitToAction0(f: (() => Unit)): Action0 = + new Action0 { + def call(): Unit = f() + } + + implicit def Action1toScalaFunction1ProducingUnit[A](f: Action1[A]): (A=>Unit) = { + a => f(a) + } + + implicit def scalaFunction1ProducingUnitToAction1[A](f: (A => Unit)): Action1[A] = + new Action1[A] { + def call(a: A): Unit = f(a) + } + + implicit def scalaBooleanFunction1ToRxBooleanFunc1[A](f: (A => Boolean)): Func1[A, jlang.Boolean] = + new Func1[A, jlang.Boolean] { + def call(a: A): jlang.Boolean = f(a).booleanValue + } + + implicit def scalaBooleanFunction2ToRxBooleanFunc1[A, B](f: ((A, B) => Boolean)): Func2[A, B, jlang.Boolean] = + new Func2[A, B, jlang.Boolean] { + def call(a: A, b: B): jlang.Boolean = f(a, b).booleanValue + } + + implicit def scalaFuncNToRxFuncN[R](f: Seq[java.lang.Object] => R): FuncN[R] = + new FuncN[R] { + def call(args: java.lang.Object*): R = f(args) + } + + implicit def convertTakeWhileFuncToRxFunc2[A](f: (A, Int) => Boolean): Func2[A, jlang.Integer, jlang.Boolean] = + new Func2[A, jlang.Integer, jlang.Boolean] { + def call(a: A, b: jlang.Integer): jlang.Boolean = f(a, b).booleanValue + } + + implicit def convertComparisonFuncToRxFunc2[A](f: (A, A) => Int): Func2[A, A, jlang.Integer] = + new Func2[A, A, jlang.Integer] { + def call(a1: A, a2: A): jlang.Integer = f(a1, a2).intValue + } + + implicit def exceptionFunction1ToRxExceptionFunc1[A <: Exception, B](f: (A => B)): Func1[Exception, B] = + new Func1[Exception, B] { + def call(ex: Exception): B = f(ex.asInstanceOf[A]) + } + + implicit def scalaFunction0ToRxFunc0[A](f: () => A): Func0[A] = + new Func0[A] { + def call(): A = f() + } + + implicit def scalaFunction1ToRxFunc1[A, B](f: (A => B)): Func1[A, B] = + new Func1[A, B] { + def call(a: A): B = f(a) + } + + implicit def scalaFunction2ToRxFunc2[A, B, C](f: (A, B) => C): Func2[A, B, C] = + new Func2[A, B, C] { + def call(a: A, b: B) = f(a, b) + } + + implicit def scalaFunction3ToRxFunc3[A, B, C, D](f: (A, B, C) => D): Func3[A, B, C, D] = + new Func3[A, B, C, D] { + def call(a: A, b: B, c: C) = f(a, b, c) + } + + implicit def scalaFunction4ToRxFunc4[A, B, C, D, E](f: (A, B, C, D) => E): Func4[A, B, C, D, E] = + new Func4[A, B, C, D, E] { + def call(a: A, b: B, c: C, d: D) = f(a, b, c, d) + } +} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Notification.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Notification.scala new file mode 100644 index 0000000000..21491bc96b --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Notification.scala @@ -0,0 +1,96 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala + +/** + * Emitted by Observables returned by [[rx.lang.scala.Observable.materialize]]. + */ +sealed trait Notification[+T] { + def asJava: rx.Notification[_ <: T] +} + +/** + * Provides pattern matching support and constructors for Notifications. + * + * Example: + * {{{ + * import Notification._ + * Observable(1, 2, 3).materialize.subscribe(n => n match { + * case OnNext(v) => println("Got value " + v) + * case OnCompleted() => println("Completed") + * case OnError(err) => println("Error: " + err.getMessage) + * }) + * }}} + */ +object Notification { + + def apply[T](n: rx.Notification[_ <: T]): Notification[T] = n.getKind match { + case rx.Notification.Kind.OnNext => new OnNext(n) + case rx.Notification.Kind.OnCompleted => new OnCompleted(n) + case rx.Notification.Kind.OnError => new OnError(n) + } + + // OnNext, OnError, OnCompleted are not case classes because we don't want pattern matching + // to extract the rx.Notification + + class OnNext[+T](val asJava: rx.Notification[_ <: T]) extends Notification[T] { + def value: T = asJava.getValue + } + + object OnNext { + + def apply[T](value: T): Notification[T] = { + Notification(new rx.Notification[T](value)) + } + + def unapply[U](n: Notification[U]): Option[U] = n match { + case n2: OnNext[U] => Some(n.asJava.getValue) + case _ => None + } + } + + class OnError[+T](val asJava: rx.Notification[_ <: T]) extends Notification[T] { + def error: Throwable = asJava.getThrowable + } + + object OnError { + + def apply[T](error: Throwable): Notification[T] = { + Notification(new rx.Notification[T](error)) + } + + def unapply[U](n: Notification[U]): Option[Throwable] = n match { + case n2: OnError[U] => Some(n2.asJava.getThrowable) + case _ => None + } + } + + class OnCompleted[T](val asJava: rx.Notification[_ <: T]) extends Notification[T] {} + + object OnCompleted { + + def apply[T](): Notification[T] = { + Notification(new rx.Notification()) + } + + def unapply[U](n: Notification[U]): Option[Unit] = n match { + case n2: OnCompleted[U] => Some() + case _ => None + } + } + +} + diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Observable.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Observable.scala index 4c4cccf4a0..68f7530dec 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Observable.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Observable.scala @@ -1,12 +1,12 @@ /** * Copyright 2013 Netflix, Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -14,429 +14,510 @@ * limitations under the License. */ - package rx.lang.scala -import org.scalatest.junit.JUnitSuite -import scala.collection.Seq -import rx.lang.scala.observables.BlockingObservable -import rx.lang.scala.observables.ConnectableObservable - +import rx.util.functions.FuncN +import rx.Observable.OnSubscribeFunc /** * The Observable interface that implements the Reactive Pattern. + * + * @param asJavaObservable the underlying Java observable + * + * @define subscribeObserverMain + * Call this method to subscribe an [[rx.lang.scala.Observer]] for receiving + * items and notifications from the Observable. + * + * A typical implementation of `subscribe` does the following: + * + * It stores a reference to the Observer in a collection object, such as a `List[T]` object. + * + * It returns a reference to the [[rx.lang.scala.Subscription]] interface. This enables Observers to + * unsubscribe, that is, to stop receiving items and notifications before the Observable stops + * sending them, which also invokes the Observer's [[rx.lang.scala.Observer.onCompleted onCompleted]] method. + * + * An `Observable[T]` instance is responsible for accepting all subscriptions + * and notifying all Observers. Unless the documentation for a particular + * `Observable[T]` implementation indicates otherwise, Observers should make no + * assumptions about the order in which multiple Observers will receive their notifications. + * + * @define subscribeObserverParamObserver + * the observer + * @define subscribeObserverParamScheduler + * the [[rx.lang.scala.Scheduler]] on which Observers subscribe to the Observable + * @define subscribeAllReturn + * a [[rx.lang.scala.Subscription]] reference whose `unsubscribe` method can be called to stop receiving items + * before the Observable has finished sending them + * + * @define subscribeCallbacksMainWithNotifications + * Call this method to receive items and notifications from this observable. + * + * @define subscribeCallbacksMainNoNotifications + * Call this method to receive items from this observable. + * + * @define subscribeCallbacksParamOnNext + * this function will be called whenever the Observable emits an item + * @define subscribeCallbacksParamOnError + * this function will be called if an error occurs + * @define subscribeCallbacksParamOnComplete + * this function will be called when this Observable has finished emitting items + * @define subscribeCallbacksParamScheduler + * the scheduler to use + * + * @define debounceVsThrottle + * Information on debounce vs throttle: + * - [[http://drupalmotion.com/article/debounce-and-throttle-visual-explanation]] + * - [[http://unscriptable.com/2009/03/20/debouncing-javascript-methods/]] + * - [[http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/]] + * + * */ -class Observable[+T](val asJava: rx.Observable[_ <: T]) - // Uncommenting this line combined with `new Observable(...)` instead of `new Observable[T](...)` - // makes the compiler crash - extends AnyVal +trait Observable[+T] { - import scala.collection.JavaConverters._ import scala.collection.Seq import scala.concurrent.duration.{Duration, TimeUnit} - import rx.{Observable => JObservable} import rx.util.functions._ - import rx.lang.scala.{Notification, Subscription, Scheduler, Observer} import rx.lang.scala.util._ - import rx.lang.scala.internal.ImplicitFunctionConversions._ - - /** - * An {@link Observer} must call an Observable's {@code subscribe} method in order to - * receive items and notifications from the Observable. - * - *

A typical implementation of {@code subscribe} does the following: - *

- * It stores a reference to the Observer in a collection object, such as a {@code List} object. - *

- * It returns a reference to the {@link Subscription} interface. This enables Observers to - * unsubscribe, that is, to stop receiving items and notifications before the Observable stops - * sending them, which also invokes the Observer's {@link Observer#onCompleted onCompleted} method. - *

- * An Observable<T> instance is responsible for accepting all subscriptions - * and notifying all Observers. Unless the documentation for a particular - * Observable<T> implementation indicates otherwise, Observers should make no - * assumptions about the order in which multiple Observers will receive their notifications. - *

- * - * @param observer - * the observer - * @return a {@link Subscription} reference with which the {@link Observer} can stop receiving items - * before the Observable has finished sending them - * @throws IllegalArgumentException - * if the {@link Observer} provided as the argument to {@code subscribe()} is {@code null} + import rx.lang.scala.observables.BlockingObservable + import rx.lang.scala.ImplicitFunctionConversions._ + + def asJavaObservable: rx.Observable[_ <: T] + + /** + * $subscribeObserverMain + * + * @param observer $subscribeObserverParamObserver + * @param scheduler $subscribeObserverParamScheduler + * @return $subscribeAllReturn */ - def subscribe(observer: Observer[T]): Subscription = { - asJava.subscribe(observer) + def subscribe(observer: Observer[T], scheduler: Scheduler): Subscription = { + asJavaObservable.subscribe(observer.asJavaObserver, scheduler) } - + /** - * An {@link Observer} must call an Observable's {@code subscribe} method in order to - * receive items and notifications from the Observable. - * - *

A typical implementation of {@code subscribe} does the following: - *

- * It stores a reference to the Observer in a collection object, such as a {@code List} object. - *

- * It returns a reference to the {@link Subscription} interface. This enables Observers to - * unsubscribe, that is, to stop receiving items and notifications before the Observable stops - * sending them, which also invokes the Observer's {@link Observer#onCompleted onCompleted} method. - *

- * An {@code Observable} instance is responsible for accepting all subscriptions - * and notifying all Observers. Unless the documentation for a particular {@code Observable} implementation indicates otherwise, Observers should make no - * assumptions about the order in which multiple Observers will receive their notifications. - *

- * - * @param observer - * the observer - * @param scheduler - * the {@link Scheduler} on which Observers subscribe to the Observable - * @return a {@link Subscription} reference with which Observers can stop receiving items and - * notifications before the Observable has finished sending them - * @throws IllegalArgumentException - * if an argument to {@code subscribe()} is {@code null} + * $subscribeObserverMain + * + * @param observer $subscribeObserverParamObserver + * @return $subscribeAllReturn */ - def subscribe(observer: Observer[T], scheduler: Scheduler): Subscription = { - asJava.subscribe(observer, scheduler) + def subscribe(observer: Observer[T]): Subscription = { + asJavaObservable.subscribe(observer.asJavaObserver) } - + + /** + * $subscribeCallbacksMainNoNotifications + * `` + * @param onNext $subscribeCallbacksParamOnNext + * @return $subscribeAllReturn + */ def subscribe(onNext: T => Unit): Subscription = { - asJava.subscribe(onNext) + asJavaObservable.subscribe(scalaFunction1ProducingUnitToAction1(onNext)) } - + + /** + * $subscribeCallbacksMainWithNotifications + * + * @param onNext $subscribeCallbacksParamOnNext + * @param onError $subscribeCallbacksParamOnError + * @return $subscribeAllReturn + */ def subscribe(onNext: T => Unit, onError: Throwable => Unit): Subscription = { - asJava.subscribe(onNext, onError) + asJavaObservable.subscribe( + scalaFunction1ProducingUnitToAction1(onNext), + scalaFunction1ProducingUnitToAction1(onError) + ) } - - def subscribe(onNext: T => Unit, onError: Throwable => Unit, onComplete: () => Unit): Subscription = { - asJava.subscribe(onNext, onError, onComplete) + + /** + * $subscribeCallbacksMainWithNotifications + * + * @param onNext $subscribeCallbacksParamOnNext + * @param onError $subscribeCallbacksParamOnError + * @param onCompleted $subscribeCallbacksParamOnComplete + * @return $subscribeAllReturn + */ + def subscribe(onNext: T => Unit, onError: Throwable => Unit, onCompleted: () => Unit): Subscription = { + asJavaObservable.subscribe( + scalaFunction1ProducingUnitToAction1(onNext), + scalaFunction1ProducingUnitToAction1(onError), + scalaFunction0ProducingUnitToAction0(onCompleted) + ) } - - def subscribe(onNext: T => Unit, onError: Throwable => Unit, onComplete: () => Unit, scheduler: Scheduler): Subscription = { - asJava.subscribe(onNext, onError, onComplete, scheduler) + + /** + * $subscribeCallbacksMainWithNotifications + * + * @param onNext $subscribeCallbacksParamOnNext + * @param onError $subscribeCallbacksParamOnError + * @param onCompleted $subscribeCallbacksParamOnComplete + * @param scheduler $subscribeCallbacksParamScheduler + * @return $subscribeAllReturn + */ + def subscribe(onNext: T => Unit, onError: Throwable => Unit, onCompleted: () => Unit, scheduler: Scheduler): Subscription = { + asJavaObservable.subscribe(scalaFunction1ProducingUnitToAction1(onNext), + scalaFunction1ProducingUnitToAction1(onError), + scalaFunction0ProducingUnitToAction0(onCompleted), + scheduler) } - + + /** + * $subscribeCallbacksMainWithNotifications + * + * @param onNext $subscribeCallbacksParamOnNext + * @param onError $subscribeCallbacksParamOnError + * @param scheduler $subscribeCallbacksParamScheduler + * @return $subscribeAllReturn + */ def subscribe(onNext: T => Unit, onError: Throwable => Unit, scheduler: Scheduler): Subscription = { - asJava.subscribe(onNext, onError, scheduler) + asJavaObservable.subscribe( + scalaFunction1ProducingUnitToAction1(onNext), + scalaFunction1ProducingUnitToAction1(onError), + scheduler) } - + + /** + * $subscribeCallbacksMainNoNotifications + * + * @param onNext $subscribeCallbacksParamOnNext + * @param scheduler $subscribeCallbacksParamScheduler + * @return $subscribeAllReturn + */ def subscribe(onNext: T => Unit, scheduler: Scheduler): Subscription = { - asJava.subscribe(onNext, scheduler) + asJavaObservable.subscribe(scalaFunction1ProducingUnitToAction1(onNext), scheduler) } /** - * Returns a {@link ConnectableObservable} that upon connection causes the source Observable to + * Returns a pair of a start function and an [[rx.lang.scala.Observable]] that upon calling the start function causes the source Observable to * push results into the specified subject. - * + * * @param subject - * the {@link Subject} for the {@link ConnectableObservable} to push source items - * into - * @param + * the `rx.lang.scala.subjects.Subject` to push source items into + * @tparam R * result type - * @return a {@link ConnectableObservable} that upon connection causes the source Observable to - * push results into the specified {@link Subject} + * @return a pair of a start function and an [[rx.lang.scala.Observable]] such that when the start function + * is called, the Observable starts to push results into the specified Subject */ - // public ConnectableObservable multicast(Subject subject) TODO - - + def multicast[R](subject: rx.lang.scala.Subject[T, R]): (() => Subscription, Observable[R]) = { + val javaCO = asJavaObservable.multicast[R](subject.asJavaSubject) + (() => javaCO.connect(), Observable[R](javaCO)) + } + /** - * Returns an Observable that first emits the items emitted by this, and then the items emitted - * by that. - *

+ * Returns an Observable that first emits the items emitted by `this`, and then the items emitted + * by `that`. + * * - * + * * @param that * an Observable to be appended * @return an Observable that emits items that are the result of combining the items emitted by * this and that, one after the other */ def ++[U >: T](that: Observable[U]): Observable[U] = { - val o1: JObservable[_ <: U] = this.asJava - val o2: JObservable[_ <: U] = that.asJava - Observable(JObservable.concat(o1, o2)) + val o1: rx.Observable[_ <: U] = this.asJavaObservable + val o2: rx.Observable[_ <: U] = that.asJavaObservable + Observable(rx.Observable.concat(o1, o2)) + } + + /** + * Returns an Observable that emits the items emitted by several Observables, one after the + * other. + * + * This operation is only available if `this` is of type `Observable[Observable[U]]` for some `U`, + * otherwise you'll get a compilation error. + * + * @usecase def concat[U]: Observable[U] + * @inheritdoc + */ + def concat[U](implicit evidence: Observable[T] <:< Observable[Observable[U]]): Observable[U] = { + val o2: Observable[Observable[U]] = this + val o3: Observable[rx.Observable[_ <: U]] = o2.map(_.asJavaObservable) + val o4: rx.Observable[_ <: rx.Observable[_ <: U]] = o3.asJavaObservable + val o5 = rx.Observable.concat[U](o4) + Observable[U](o5) } - /** * Wraps this Observable in another Observable that ensures that the resulting * Observable is chronologically well-behaved. - *

+ * * - *

- * A well-behaved Observable does not interleave its invocations of the {@link Observer#onNext onNext}, {@link Observer#onCompleted onCompleted}, and {@link Observer#onError onError} methods of - * its {@link Observer}s; it invokes {@code onCompleted} or {@code onError} only once; and it never invokes {@code onNext} after invoking either {@code onCompleted} or {@code onError}. - * {@code synchronize} enforces this, and the Observable it returns invokes {@code onNext} and {@code onCompleted} or {@code onError} synchronously. - * - * @param observable - * the source Observable - * @param - * the type of item emitted by the source Observable + * + * A well-behaved Observable does not interleave its invocations of the [[rx.lang.scala.Observer.onNext onNext]], [[rx.lang.scala.Observer.onCompleted onCompleted]], and [[rx.lang.scala.Observer.onError onError]] methods of + * its [[rx.lang.scala.Observer]]s; it invokes `onCompleted` or `onError` only once; and it never invokes `onNext` after invoking either `onCompleted` or `onError`. + * `synchronize` enforces this, and the Observable it returns invokes `onNext` and `onCompleted` or `onError` synchronously. + * * @return an Observable that is a chronologically well-behaved version of the source - * Observable, and that synchronously notifies its {@link Observer}s + * Observable, and that synchronously notifies its [[rx.lang.scala.Observer]]s */ def synchronize: Observable[T] = { - Observable[T](JObservable.synchronize(asJava)) + Observable[T](asJavaObservable.synchronize) } - + /** - * Wraps each item emitted by a source Observable in a {@link Timestamped} object. - *

+ * Wraps each item emitted by a source Observable in a timestamped tuple. + * * - * + * * @return an Observable that emits timestamped items from the source Observable */ - def timestamp: Observable[Timestamped[T]] = { - Observable[Timestamped[T]](asJava.timestamp()) + def timestamp: Observable[(Long, T)] = { + Observable[rx.util.Timestamped[_ <: T]](asJavaObservable.timestamp()) + .map((t: rx.util.Timestamped[_ <: T]) => (t.getTimestampMillis, t.getValue)) } - + /** * Returns an Observable formed from this Observable and another Observable by combining * corresponding elements in pairs. - * The number of {@code onNext} invocations of the resulting {@code Observable[(T, U)]} - * is the minumum of the number of {@code onNext} invocations of {@code this} and {@code that}. + * The number of `onNext` invocations of the resulting `Observable[(T, U)]` + * is the minumum of the number of `onNext` invocations of `this` and `that`. */ def zip[U](that: Observable[U]): Observable[(T, U)] = { - Observable[(T, U)](JObservable.zip[T, U, (T, U)](this.asJava, that.asJava, (t: T, u: U) => (t, u))) - } - - // There is no method corresponding to - // public static Observable sequenceEqual(Observable first, Observable second) - // because the Scala-idiomatic way of doing this is - // (first zip second) map (p => p._1 == p._2) - - // There is no method corresponding to - // public static Observable sequenceEqual(Observable first, Observable second, Func2 equality) - // because the Scala-idiomatic way of doing this is - // (first zip second) map (p => equality(p._1, p._2)) - + Observable[(T, U)](rx.Observable.zip[T, U, (T, U)](this.asJavaObservable, that.asJavaObservable, (t: T, u: U) => (t, u))) + } + + /** + * Zips this Observable with its indices. + * + * @return An Observable emitting pairs consisting of all elements of this Observable paired with + * their index. Indices start at 0. + */ + def zipWithIndex: Observable[(T, Int)] = { + val fScala: (T, Integer) => (T, Int) = (elem: T, index: Integer) => (elem, index) + val fJava : Func2[_ >: T, Integer, _ <: (T, Int)] = fScala + Observable[(T, Int)](asJavaObservable.mapWithIndex[(T, Int)](fJava)) + } + /** * Creates an Observable which produces buffers of collected values. - * - *

This Observable produces connected non-overlapping buffers. The current buffer is - * emitted and replaced with a new buffer when the Observable produced by the specified {@link Func0} produces a {@link rx.util.Closing} object. The * {@link Func0} will then + * + * This Observable produces connected non-overlapping buffers. The current buffer is + * emitted and replaced with a new buffer when the Observable produced by the specified function produces a [[rx.lang.scala.util.Closing]] object. The function will then * be used to create a new Observable to listen for the end of the next buffer. - * - * @param bufferClosingSelector - * The {@link Func0} which is used to produce an {@link Observable} for every buffer created. - * When this {@link Observable} produces a {@link rx.util.Closing} object, the associated buffer + * + * @param closings + * The function which is used to produce an [[rx.lang.scala.Observable]] for every buffer created. + * When this [[rx.lang.scala.Observable]] produces a [[rx.lang.scala.util.Closing]] object, the associated buffer * is emitted and replaced with a new one. * @return - * An {@link Observable} which produces connected non-overlapping buffers, which are emitted - * when the current {@link Observable} created with the {@link Func0} argument produces a {@link rx.util.Closing} object. + * An [[rx.lang.scala.Observable]] which produces connected non-overlapping buffers, which are emitted + * when the current [[rx.lang.scala.Observable]] created with the function argument produces a [[rx.lang.scala.util.Closing]] object. */ - def buffer(bufferClosingSelector: () => Observable[Closing]) : Observable[Seq[T]] = { - val f: Func0[_ <: rx.Observable[_ <: Closing]] = bufferClosingSelector().asJava - val jObs: rx.Observable[_ <: java.util.List[_]] = asJava.buffer(f) + def buffer(closings: () => Observable[Closing]) : Observable[Seq[T]] = { + val f: Func0[_ <: rx.Observable[_ <: Closing]] = closings().asJavaObservable + val jObs: rx.Observable[_ <: java.util.List[_]] = asJavaObservable.buffer(f) Observable.jObsOfListToScObsOfSeq(jObs.asInstanceOf[rx.Observable[_ <: java.util.List[T]]]) } /** * Creates an Observable which produces buffers of collected values. - * - *

This Observable produces buffers. Buffers are created when the specified "bufferOpenings" - * Observable produces a {@link rx.util.Opening} object. Additionally the {@link Func0} argument - * is used to create an Observable which produces {@link rx.util.Closing} objects. When this + * + * This Observable produces buffers. Buffers are created when the specified `openings` + * Observable produces a [[rx.lang.scala.util.Opening]] object. Additionally the function argument + * is used to create an Observable which produces [[rx.lang.scala.util.Closing]] objects. When this * Observable produces such an object, the associated buffer is emitted. - * - * @param bufferOpenings - * The {@link Observable} which, when it produces a {@link rx.util.Opening} object, will cause + * + * @param openings + * The [[rx.lang.scala.Observable]] which, when it produces a [[rx.lang.scala.util.Opening]] object, will cause * another buffer to be created. - * @param bufferClosingSelector - * The {@link Func0} which is used to produce an {@link Observable} for every buffer created. - * When this {@link Observable} produces a {@link rx.util.Closing} object, the associated buffer + * @param closings + * The function which is used to produce an [[rx.lang.scala.Observable]] for every buffer created. + * When this [[rx.lang.scala.Observable]] produces a [[rx.lang.scala.util.Closing]] object, the associated buffer * is emitted. * @return - * An {@link Observable} which produces buffers which are created and emitted when the specified {@link Observable}s publish certain objects. + * An [[rx.lang.scala.Observable]] which produces buffers which are created and emitted when the specified [[rx.lang.scala.Observable]]s publish certain objects. */ - def buffer(bufferOpenings: Observable[Opening], bufferClosingSelector: Opening => Observable[Closing]): Observable[Seq[T]] = { - val opening: rx.Observable[_ <: Opening] = bufferOpenings.asJava - val closing: Func1[Opening, _ <: rx.Observable[_ <: Closing]] = (o: Opening) => bufferClosingSelector(o).asJava - val jObs: rx.Observable[_ <: java.util.List[_]] = asJava.buffer(opening, closing) + def buffer(openings: Observable[Opening], closings: Opening => Observable[Closing]): Observable[Seq[T]] = { + val opening: rx.Observable[_ <: Opening] = openings.asJavaObservable + val closing: Func1[Opening, _ <: rx.Observable[_ <: Closing]] = (o: Opening) => closings(o).asJavaObservable + val jObs: rx.Observable[_ <: java.util.List[_]] = asJavaObservable.buffer(opening, closing) Observable.jObsOfListToScObsOfSeq(jObs.asInstanceOf[rx.Observable[_ <: java.util.List[T]]]) } /** * Creates an Observable which produces buffers of collected values. - * - *

This Observable produces connected non-overlapping buffers, each containing "count" + * + * This Observable produces connected non-overlapping buffers, each containing `count` * elements. When the source Observable completes or encounters an error, the current * buffer is emitted, and the event is propagated. - * + * * @param count * The maximum size of each buffer before it should be emitted. * @return - * An {@link Observable} which produces connected non-overlapping buffers containing at most - * "count" produced values. + * An [[rx.lang.scala.Observable]] which produces connected non-overlapping buffers containing at most + * `count` produced values. */ def buffer(count: Int): Observable[Seq[T]] = { - val oJava: rx.Observable[_ <: java.util.List[_]] = asJava.buffer(count) + val oJava: rx.Observable[_ <: java.util.List[_]] = asJavaObservable.buffer(count) Observable.jObsOfListToScObsOfSeq(oJava.asInstanceOf[rx.Observable[_ <: java.util.List[T]]]) } /** * Creates an Observable which produces buffers of collected values. - * - *

This Observable produces buffers every "skip" values, each containing "count" + * + * This Observable produces buffers every `skip` values, each containing `count` * elements. When the source Observable completes or encounters an error, the current * buffer is emitted, and the event is propagated. - * + * * @param count * The maximum size of each buffer before it should be emitted. * @param skip - * How many produced values need to be skipped before starting a new buffer. Note that when "skip" and - * "count" are equals that this is the same operation as {@link Observable#buffer(int)}. + * How many produced values need to be skipped before starting a new buffer. Note that when `skip` and + * `count` are equals that this is the same operation as `buffer(int)`. * @return - * An {@link Observable} which produces buffers every "skipped" values containing at most - * "count" produced values. + * An [[rx.lang.scala.Observable]] which produces buffers every `skip` values containing at most + * `count` produced values. */ def buffer(count: Int, skip: Int): Observable[Seq[T]] = { - val oJava: rx.Observable[_ <: java.util.List[_]] = asJava.buffer(count, skip) + val oJava: rx.Observable[_ <: java.util.List[_]] = asJavaObservable.buffer(count, skip) Observable.jObsOfListToScObsOfSeq(oJava.asInstanceOf[rx.Observable[_ <: java.util.List[T]]]) } - + /** * Creates an Observable which produces buffers of collected values. - * - *

This Observable produces connected non-overlapping buffers, each of a fixed duration - * specified by the "timespan" argument. When the source Observable completes or encounters + * + * This Observable produces connected non-overlapping buffers, each of a fixed duration + * specified by the `timespan` argument. When the source Observable completes or encounters * an error, the current buffer is emitted and the event is propagated. - * + * * @param timespan * The period of time each buffer is collecting values before it should be emitted, and * replaced with a new buffer. * @return - * An {@link Observable} which produces connected non-overlapping buffers with a fixed duration. + * An [[rx.lang.scala.Observable]] which produces connected non-overlapping buffers with a fixed duration. */ def buffer(timespan: Duration): Observable[Seq[T]] = { - val oJava: rx.Observable[_ <: java.util.List[_]] = asJava.buffer(timespan.length, timespan.unit) + val oJava: rx.Observable[_ <: java.util.List[_]] = asJavaObservable.buffer(timespan.length, timespan.unit) Observable.jObsOfListToScObsOfSeq(oJava.asInstanceOf[rx.Observable[_ <: java.util.List[T]]]) } /** * Creates an Observable which produces buffers of collected values. - * - *

This Observable produces connected non-overlapping buffers, each of a fixed duration - * specified by the "timespan" argument. When the source Observable completes or encounters + * + * This Observable produces connected non-overlapping buffers, each of a fixed duration + * specified by the `timespan` argument. When the source Observable completes or encounters * an error, the current buffer is emitted and the event is propagated. - * + * * @param timespan * The period of time each buffer is collecting values before it should be emitted, and * replaced with a new buffer. * @param scheduler - * The {@link Scheduler} to use when determining the end and start of a buffer. + * The [[rx.lang.scala.Scheduler]] to use when determining the end and start of a buffer. * @return - * An {@link Observable} which produces connected non-overlapping buffers with a fixed duration. + * An [[rx.lang.scala.Observable]] which produces connected non-overlapping buffers with a fixed duration. */ def buffer(timespan: Duration, scheduler: Scheduler): Observable[Seq[T]] = { - val oJava: rx.Observable[_ <: java.util.List[_]] = asJava.buffer(timespan.length, timespan.unit, scheduler) + val oJava: rx.Observable[_ <: java.util.List[_]] = asJavaObservable.buffer(timespan.length, timespan.unit, scheduler) Observable.jObsOfListToScObsOfSeq(oJava.asInstanceOf[rx.Observable[_ <: java.util.List[T]]]) - } + } /** * Creates an Observable which produces buffers of collected values. This Observable produces connected - * non-overlapping buffers, each of a fixed duration specified by the "timespan" argument or a maximum size - * specified by the "count" argument (which ever is reached first). When the source Observable completes + * non-overlapping buffers, each of a fixed duration specified by the `timespan` argument or a maximum size + * specified by the `count` argument (which ever is reached first). When the source Observable completes * or encounters an error, the current buffer is emitted and the event is propagated. - * + * * @param timespan * The period of time each buffer is collecting values before it should be emitted, and * replaced with a new buffer. * @param count * The maximum size of each buffer before it should be emitted. * @return - * An {@link Observable} which produces connected non-overlapping buffers which are emitted after + * An [[rx.lang.scala.Observable]] which produces connected non-overlapping buffers which are emitted after * a fixed duration or when the buffer has reached maximum capacity (which ever occurs first). */ def buffer(timespan: Duration, count: Int): Observable[Seq[T]] = { - val oJava: rx.Observable[_ <: java.util.List[_]] = asJava.buffer(timespan.length, timespan.unit, count) + val oJava: rx.Observable[_ <: java.util.List[_]] = asJavaObservable.buffer(timespan.length, timespan.unit, count) Observable.jObsOfListToScObsOfSeq(oJava.asInstanceOf[rx.Observable[_ <: java.util.List[T]]]) } /** * Creates an Observable which produces buffers of collected values. This Observable produces connected - * non-overlapping buffers, each of a fixed duration specified by the "timespan" argument or a maximum size - * specified by the "count" argument (which ever is reached first). When the source Observable completes + * non-overlapping buffers, each of a fixed duration specified by the `timespan` argument or a maximum size + * specified by the `count` argument (which ever is reached first). When the source Observable completes * or encounters an error, the current buffer is emitted and the event is propagated. - * + * * @param timespan * The period of time each buffer is collecting values before it should be emitted, and * replaced with a new buffer. * @param count * The maximum size of each buffer before it should be emitted. * @param scheduler - * The {@link Scheduler} to use when determining the end and start of a buffer. + * The [[rx.lang.scala.Scheduler]] to use when determining the end and start of a buffer. * @return - * An {@link Observable} which produces connected non-overlapping buffers which are emitted after + * An [[rx.lang.scala.Observable]] which produces connected non-overlapping buffers which are emitted after * a fixed duration or when the buffer has reached maximum capacity (which ever occurs first). */ def buffer(timespan: Duration, count: Int, scheduler: Scheduler): Observable[Seq[T]] = { - val oJava: rx.Observable[_ <: java.util.List[_]] = asJava.buffer(timespan.length, timespan.unit, count, scheduler) + val oJava: rx.Observable[_ <: java.util.List[_]] = asJavaObservable.buffer(timespan.length, timespan.unit, count, scheduler) Observable.jObsOfListToScObsOfSeq(oJava.asInstanceOf[rx.Observable[_ <: java.util.List[T]]]) } /** * Creates an Observable which produces buffers of collected values. This Observable starts a new buffer - * periodically, which is determined by the "timeshift" argument. Each buffer is emitted after a fixed timespan - * specified by the "timespan" argument. When the source Observable completes or encounters an error, the + * periodically, which is determined by the `timeshift` argument. Each buffer is emitted after a fixed timespan + * specified by the `timespan` argument. When the source Observable completes or encounters an error, the * current buffer is emitted and the event is propagated. - * + * * @param timespan * The period of time each buffer is collecting values before it should be emitted. * @param timeshift * The period of time after which a new buffer will be created. * @return - * An {@link Observable} which produces new buffers periodically, and these are emitted after + * An [[rx.lang.scala.Observable]] which produces new buffers periodically, and these are emitted after * a fixed timespan has elapsed. */ def buffer(timespan: Duration, timeshift: Duration): Observable[Seq[T]] = { val span: Long = timespan.length val shift: Long = timespan.unit.convert(timeshift.length, timeshift.unit) val unit: TimeUnit = timespan.unit - val oJava: rx.Observable[_ <: java.util.List[_]] = asJava.buffer(span, shift, unit) + val oJava: rx.Observable[_ <: java.util.List[_]] = asJavaObservable.buffer(span, shift, unit) Observable.jObsOfListToScObsOfSeq(oJava.asInstanceOf[rx.Observable[_ <: java.util.List[T]]]) - } - + } + /** * Creates an Observable which produces buffers of collected values. This Observable starts a new buffer - * periodically, which is determined by the "timeshift" argument. Each buffer is emitted after a fixed timespan - * specified by the "timespan" argument. When the source Observable completes or encounters an error, the + * periodically, which is determined by the `timeshift` argument. Each buffer is emitted after a fixed timespan + * specified by the `timespan` argument. When the source Observable completes or encounters an error, the * current buffer is emitted and the event is propagated. - * + * * @param timespan * The period of time each buffer is collecting values before it should be emitted. * @param timeshift * The period of time after which a new buffer will be created. * @param scheduler - * The {@link Scheduler} to use when determining the end and start of a buffer. + * The [[rx.lang.scala.Scheduler]] to use when determining the end and start of a buffer. * @return - * An {@link Observable} which produces new buffers periodically, and these are emitted after + * An [[rx.lang.scala.Observable]] which produces new buffers periodically, and these are emitted after * a fixed timespan has elapsed. */ def buffer(timespan: Duration, timeshift: Duration, scheduler: Scheduler): Observable[Seq[T]] = { val span: Long = timespan.length val shift: Long = timespan.unit.convert(timeshift.length, timeshift.unit) val unit: TimeUnit = timespan.unit - val oJava: rx.Observable[_ <: java.util.List[_]] = asJava.buffer(span, shift, unit, scheduler) + val oJava: rx.Observable[_ <: java.util.List[_]] = asJavaObservable.buffer(span, shift, unit, scheduler) Observable.jObsOfListToScObsOfSeq(oJava.asInstanceOf[rx.Observable[_ <: java.util.List[T]]]) - } + } /** * Creates an Observable which produces windows of collected values. This Observable produces connected * non-overlapping windows. The current window is emitted and replaced with a new window when the - * Observable produced by the specified {@link Func0} produces a {@link rx.util.Closing} object. The {@link Func0} will then be used to create a new Observable to listen for the end of the next + * Observable produced by the specified function produces a [[rx.lang.scala.util.Closing]] object. + * The function will then be used to create a new Observable to listen for the end of the next * window. - * - * @param closingSelector - * The {@link Func0} which is used to produce an {@link Observable} for every window created. - * When this {@link Observable} produces a {@link rx.util.Closing} object, the associated window + * + * @param closings + * The function which is used to produce an [[rx.lang.scala.Observable]] for every window created. + * When this [[rx.lang.scala.Observable]] produces a [[rx.lang.scala.util.Closing]] object, the associated window * is emitted and replaced with a new one. * @return - * An {@link Observable} which produces connected non-overlapping windows, which are emitted - * when the current {@link Observable} created with the {@link Func0} argument produces a {@link rx.util.Closing} object. + * An [[rx.lang.scala.Observable]] which produces connected non-overlapping windows, which are emitted + * when the current [[rx.lang.scala.Observable]] created with the function argument produces a [[rx.lang.scala.util.Closing]] object. */ - def window(closingSelector: () => Observable[Closing]): Observable[Observable[T]] = { - val func : Func0[_ <: rx.Observable[_ <: Closing]] = closingSelector().asJava - val o1: rx.Observable[_ <: rx.Observable[_]] = asJava.window(func) - val o2 = new Observable[rx.Observable[_]](o1).map((x: rx.Observable[_]) => { + def window(closings: () => Observable[Closing]): Observable[Observable[T]] = { + val func : Func0[_ <: rx.Observable[_ <: Closing]] = closings().asJavaObservable + val o1: rx.Observable[_ <: rx.Observable[_]] = asJavaObservable.window(func) + val o2 = Observable[rx.Observable[_]](o1).map((x: rx.Observable[_]) => { val x2 = x.asInstanceOf[rx.Observable[_ <: T]] Observable[T](x2) }) @@ -445,421 +526,426 @@ class Observable[+T](val asJava: rx.Observable[_ <: T]) /** * Creates an Observable which produces windows of collected values. This Observable produces windows. - * Chunks are created when the specified "windowOpenings" Observable produces a {@link rx.util.Opening} object. - * Additionally the {@link Func0} argument is used to create an Observable which produces {@link rx.util.Closing} objects. When this Observable produces such an object, the associated window is - * emitted. - * - * @param windowOpenings - * The {@link Observable} which when it produces a {@link rx.util.Opening} object, will cause + * Chunks are created when the specified `openings` Observable produces a [[rx.lang.scala.util.Opening]] object. + * Additionally the `closings` argument is used to create an Observable which produces [[rx.lang.scala.util.Closing]] objects. + * When this Observable produces such an object, the associated window is emitted. + * + * @param openings + * The [[rx.lang.scala.Observable]] which when it produces a [[rx.lang.scala.util.Opening]] object, will cause * another window to be created. - * @param closingSelector - * The {@link Func0} which is used to produce an {@link Observable} for every window created. - * When this {@link Observable} produces a {@link rx.util.Closing} object, the associated window + * @param closings + * The function which is used to produce an [[rx.lang.scala.Observable]] for every window created. + * When this [[rx.lang.scala.Observable]] produces a [[rx.lang.scala.util.Closing]] object, the associated window * is emitted. * @return - * An {@link Observable} which produces windows which are created and emitted when the specified {@link Observable}s publish certain objects. + * An [[rx.lang.scala.Observable]] which produces windows which are created and emitted when the specified [[rx.lang.scala.Observable]]s publish certain objects. */ - def window(windowOpenings: Observable[Opening], closingSelector: Opening => Observable[Closing]) = { + def window(openings: Observable[Opening], closings: Opening => Observable[Closing]) = { Observable.jObsOfJObsToScObsOfScObs( - asJava.window(windowOpenings.asJava, (op: Opening) => closingSelector(op).asJava)) - : Observable[Observable[T]] // SI-7818 - } + asJavaObservable.window(openings.asJavaObservable, (op: Opening) => closings(op).asJavaObservable)) + : Observable[Observable[T]] // SI-7818 + } /** * Creates an Observable which produces windows of collected values. This Observable produces connected - * non-overlapping windows, each containing "count" elements. When the source Observable completes or + * non-overlapping windows, each containing `count` elements. When the source Observable completes or * encounters an error, the current window is emitted, and the event is propagated. - * + * * @param count * The maximum size of each window before it should be emitted. * @return - * An {@link Observable} which produces connected non-overlapping windows containing at most - * "count" produced values. + * An [[rx.lang.scala.Observable]] which produces connected non-overlapping windows containing at most + * `count` produced values. */ def window(count: Int): Observable[Observable[T]] = { // this unnecessary ascription is needed because of this bug (without, compiler crashes): // https://issues.scala-lang.org/browse/SI-7818 - Observable.jObsOfJObsToScObsOfScObs(asJava.window(count)) : Observable[Observable[T]] + Observable.jObsOfJObsToScObsOfScObs(asJavaObservable.window(count)) : Observable[Observable[T]] } /** * Creates an Observable which produces windows of collected values. This Observable produces windows every - * "skip" values, each containing "count" elements. When the source Observable completes or encounters an error, + * `skip` values, each containing `count` elements. When the source Observable completes or encounters an error, * the current window is emitted and the event is propagated. - * + * * @param count * The maximum size of each window before it should be emitted. * @param skip - * How many produced values need to be skipped before starting a new window. Note that when "skip" and - * "count" are equals that this is the same operation as {@link Observable#window(Observable, int)}. + * How many produced values need to be skipped before starting a new window. Note that when `skip` and + * `count` are equal that this is the same operation as `window(int)`. * @return - * An {@link Observable} which produces windows every "skipped" values containing at most - * "count" produced values. + * An [[rx.lang.scala.Observable]] which produces windows every `skip` values containing at most + * `count` produced values. */ def window(count: Int, skip: Int): Observable[Observable[T]] = { - Observable.jObsOfJObsToScObsOfScObs(asJava.window(count, skip)) - : Observable[Observable[T]] // SI-7818 + Observable.jObsOfJObsToScObsOfScObs(asJavaObservable.window(count, skip)) + : Observable[Observable[T]] // SI-7818 } /** * Creates an Observable which produces windows of collected values. This Observable produces connected - * non-overlapping windows, each of a fixed duration specified by the "timespan" argument. When the source + * non-overlapping windows, each of a fixed duration specified by the `timespan` argument. When the source * Observable completes or encounters an error, the current window is emitted and the event is propagated. - * + * * @param timespan * The period of time each window is collecting values before it should be emitted, and * replaced with a new window. * @return - * An {@link Observable} which produces connected non-overlapping windows with a fixed duration. + * An [[rx.lang.scala.Observable]] which produces connected non-overlapping windows with a fixed duration. */ def window(timespan: Duration): Observable[Observable[T]] = { - Observable.jObsOfJObsToScObsOfScObs(asJava.window(timespan.length, timespan.unit)) - : Observable[Observable[T]] // SI-7818 - } + Observable.jObsOfJObsToScObsOfScObs(asJavaObservable.window(timespan.length, timespan.unit)) + : Observable[Observable[T]] // SI-7818 + } /** * Creates an Observable which produces windows of collected values. This Observable produces connected - * non-overlapping windows, each of a fixed duration specified by the "timespan" argument. When the source + * non-overlapping windows, each of a fixed duration specified by the `timespan` argument. When the source * Observable completes or encounters an error, the current window is emitted and the event is propagated. - * + * * @param timespan * The period of time each window is collecting values before it should be emitted, and * replaced with a new window. * @param scheduler - * The {@link Scheduler} to use when determining the end and start of a window. + * The [[rx.lang.scala.Scheduler]] to use when determining the end and start of a window. * @return - * An {@link Observable} which produces connected non-overlapping windows with a fixed duration. + * An [[rx.lang.scala.Observable]] which produces connected non-overlapping windows with a fixed duration. */ def window(timespan: Duration, scheduler: Scheduler): Observable[Observable[T]] = { - Observable.jObsOfJObsToScObsOfScObs(asJava.window(timespan.length, timespan.unit, scheduler)) - : Observable[Observable[T]] // SI-7818 - } + Observable.jObsOfJObsToScObsOfScObs(asJavaObservable.window(timespan.length, timespan.unit, scheduler)) + : Observable[Observable[T]] // SI-7818 + } /** * Creates an Observable which produces windows of collected values. This Observable produces connected - * non-overlapping windows, each of a fixed duration specified by the "timespan" argument or a maximum size - * specified by the "count" argument (which ever is reached first). When the source Observable completes + * non-overlapping windows, each of a fixed duration specified by the `timespan` argument or a maximum size + * specified by the `count` argument (which ever is reached first). When the source Observable completes * or encounters an error, the current window is emitted and the event is propagated. - * + * * @param timespan * The period of time each window is collecting values before it should be emitted, and * replaced with a new window. * @param count * The maximum size of each window before it should be emitted. * @return - * An {@link Observable} which produces connected non-overlapping windows which are emitted after + * An [[rx.lang.scala.Observable]] which produces connected non-overlapping windows which are emitted after * a fixed duration or when the window has reached maximum capacity (which ever occurs first). */ def window(timespan: Duration, count: Int): Observable[Observable[T]] = { - Observable.jObsOfJObsToScObsOfScObs(asJava.window(timespan.length, timespan.unit, count)) - : Observable[Observable[T]] // SI-7818 - } + Observable.jObsOfJObsToScObsOfScObs(asJavaObservable.window(timespan.length, timespan.unit, count)) + : Observable[Observable[T]] // SI-7818 + } /** * Creates an Observable which produces windows of collected values. This Observable produces connected - * non-overlapping windows, each of a fixed duration specified by the "timespan" argument or a maximum size - * specified by the "count" argument (which ever is reached first). When the source Observable completes + * non-overlapping windows, each of a fixed duration specified by the `timespan` argument or a maximum size + * specified by the `count` argument (which ever is reached first). When the source Observable completes * or encounters an error, the current window is emitted and the event is propagated. - * + * * @param timespan * The period of time each window is collecting values before it should be emitted, and * replaced with a new window. * @param count * The maximum size of each window before it should be emitted. * @param scheduler - * The {@link Scheduler} to use when determining the end and start of a window. + * The [[rx.lang.scala.Scheduler]] to use when determining the end and start of a window. * @return - * An {@link Observable} which produces connected non-overlapping windows which are emitted after + * An [[rx.lang.scala.Observable]] which produces connected non-overlapping windows which are emitted after * a fixed duration or when the window has reached maximum capacity (which ever occurs first). */ def window(timespan: Duration, count: Int, scheduler: Scheduler): Observable[Observable[T]] = { - Observable.jObsOfJObsToScObsOfScObs(asJava.window(timespan.length, timespan.unit, count, scheduler)) - : Observable[Observable[T]] // SI-7818 - } + Observable.jObsOfJObsToScObsOfScObs(asJavaObservable.window(timespan.length, timespan.unit, count, scheduler)) + : Observable[Observable[T]] // SI-7818 + } /** * Creates an Observable which produces windows of collected values. This Observable starts a new window - * periodically, which is determined by the "timeshift" argument. Each window is emitted after a fixed timespan - * specified by the "timespan" argument. When the source Observable completes or encounters an error, the + * periodically, which is determined by the `timeshift` argument. Each window is emitted after a fixed timespan + * specified by the `timespan` argument. When the source Observable completes or encounters an error, the * current window is emitted and the event is propagated. - * + * * @param timespan * The period of time each window is collecting values before it should be emitted. * @param timeshift * The period of time after which a new window will be created. * @return - * An {@link Observable} which produces new windows periodically, and these are emitted after + * An [[rx.lang.scala.Observable]] which produces new windows periodically, and these are emitted after * a fixed timespan has elapsed. */ def window(timespan: Duration, timeshift: Duration): Observable[Observable[T]] = { val span: Long = timespan.length val shift: Long = timespan.unit.convert(timeshift.length, timeshift.unit) val unit: TimeUnit = timespan.unit - Observable.jObsOfJObsToScObsOfScObs(asJava.window(span, shift, unit)) - : Observable[Observable[T]] // SI-7818 - } + Observable.jObsOfJObsToScObsOfScObs(asJavaObservable.window(span, shift, unit)) + : Observable[Observable[T]] // SI-7818 + } /** * Creates an Observable which produces windows of collected values. This Observable starts a new window - * periodically, which is determined by the "timeshift" argument. Each window is emitted after a fixed timespan - * specified by the "timespan" argument. When the source Observable completes or encounters an error, the + * periodically, which is determined by the `timeshift` argument. Each window is emitted after a fixed timespan + * specified by the `timespan` argument. When the source Observable completes or encounters an error, the * current window is emitted and the event is propagated. - * + * * @param timespan * The period of time each window is collecting values before it should be emitted. * @param timeshift * The period of time after which a new window will be created. * @param scheduler - * The {@link Scheduler} to use when determining the end and start of a window. + * The [[rx.lang.scala.Scheduler]] to use when determining the end and start of a window. * @return - * An {@link Observable} which produces new windows periodically, and these are emitted after + * An [[rx.lang.scala.Observable]] which produces new windows periodically, and these are emitted after * a fixed timespan has elapsed. */ def window(timespan: Duration, timeshift: Duration, scheduler: Scheduler): Observable[Observable[T]] = { val span: Long = timespan.length val shift: Long = timespan.unit.convert(timeshift.length, timeshift.unit) val unit: TimeUnit = timespan.unit - Observable.jObsOfJObsToScObsOfScObs(asJava.window(span, shift, unit, scheduler)) - : Observable[Observable[T]] // SI-7818 - } - + Observable.jObsOfJObsToScObsOfScObs(asJavaObservable.window(span, shift, unit, scheduler)) + : Observable[Observable[T]] // SI-7818 + } + /** - *

+ * Returns an Observable which only emits those items for which a given predicate holds. + * * - * + * * @param predicate - * a function that evaluates the items emitted by the source Observable, returning {@code true} if they pass the filter + * a function that evaluates the items emitted by the source Observable, returning `true` if they pass the filter * @return an Observable that emits only those items in the original Observable that the filter - * evaluates as {@code true} + * evaluates as `true` */ def filter(predicate: T => Boolean): Observable[T] = { - Observable[T](asJava.filter(predicate)) + Observable[T](asJavaObservable.filter(predicate)) } /** - * Registers an {@link Action0} to be called when this Observable invokes {@link Observer#onCompleted onCompleted} or {@link Observer#onError onError}. - *

+ * Registers an function to be called when this Observable invokes [[rx.lang.scala.Observer.onCompleted onCompleted]] or [[rx.lang.scala.Observer.onError onError]]. + * * - * + * * @param action - * an {@link Action0} to be invoked when the source Observable finishes - * @return an Observable that emits the same items as the source Observable, then invokes the {@link Action0} - * @see MSDN: Observable.Finally Method + * an function to be invoked when the source Observable finishes + * @return an Observable that emits the same items as the source Observable, then invokes the function */ def finallyDo(action: () => Unit): Observable[T] = { - Observable[T](asJava.finallyDo(action)) - } + Observable[T](asJavaObservable.finallyDo(action)) + } /** * Creates a new Observable by applying a function that you supply to each item emitted by * the source Observable, where that function returns an Observable, and then merging those * resulting Observables and emitting the results of this merger. - *

+ * * - *

- * Note: {@code mapMany} and {@code flatMap} are equivalent. - * - * @param func + * + * @param f * a function that, when applied to an item emitted by the source Observable, returns * an Observable * @return an Observable that emits the result of applying the transformation function to each * item emitted by the source Observable and merging the results of the Observables * obtained from this transformation. - * @see #mapMany(Func1) */ def flatMap[R](f: T => Observable[R]): Observable[R] = { - Observable[R](asJava.flatMap[R]((t: T) => f(t).asJava)) + Observable[R](asJavaObservable.flatMap[R](new Func1[T, rx.Observable[_ <: R]]{ + def call(t1: T): rx.Observable[_ <: R] = { f(t1).asJavaObservable } + })) } - - // There is no method like - // public Observable where(Func1 predicate) - // because that's called filter in Scala. /** * Returns an Observable that applies the given function to each item emitted by an * Observable and emits the result. - *

+ * * - * + * * @param func * a function to apply to each item emitted by the Observable * @return an Observable that emits the items from the source Observable, transformed by the * given function */ def map[R](func: T => R): Observable[R] = { - Observable[R](asJava.map[R](func)) + Observable[R](asJavaObservable.map[R](new Func1[T,R] { + def call(t1: T): R = func(t1) + })) } - - // There's no method like - // public Observable mapMany(Func1> func) - // because that's called flatMap in Scala. - + /** - * Turns all of the notifications from a source Observable into {@link Observer#onNext onNext} emissions, and marks them with their original notification types within {@link Notification} objects. - *

+ * Turns all of the notifications from a source Observable into [[rx.lang.scala.Observer.onNext onNext]] emissions, + * and marks them with their original notification types within [[rx.lang.scala.Notification]] objects. + * * - * + * * @return an Observable whose items are the result of materializing the items and * notifications of the source Observable - * @see MSDN: Observable.materialize */ def materialize: Observable[Notification[T]] = { - Observable[Notification[T]](asJava.materialize()) + Observable[rx.Notification[_ <: T]](asJavaObservable.materialize()).map(Notification(_)) } /** - * Asynchronously subscribes and unsubscribes Observers on the specified {@link Scheduler}. - *

+ * Asynchronously subscribes and unsubscribes Observers on the specified [[rx.lang.scala.Scheduler]]. + * * - * + * * @param scheduler - * the {@link Scheduler} to perform subscription and unsubscription actions on + * the [[rx.lang.scala.Scheduler]] to perform subscription and unsubscription actions on * @return the source Observable modified so that its subscriptions and unsubscriptions happen - * on the specified {@link Scheduler} + * on the specified [[rx.lang.scala.Scheduler]] */ def subscribeOn(scheduler: Scheduler): Observable[T] = { - Observable[T](asJava.subscribeOn(scheduler)) - } + Observable[T](asJavaObservable.subscribeOn(scheduler)) + } /** - * Asynchronously notify {@link Observer}s on the specified {@link Scheduler}. - *

+ * Asynchronously notify [[rx.lang.scala.Observer]]s on the specified [[rx.lang.scala.Scheduler]]. + * * - * + * * @param scheduler - * the {@link Scheduler} to notify {@link Observer}s on - * @return the source Observable modified so that its {@link Observer}s are notified on the - * specified {@link Scheduler} + * the [[rx.lang.scala.Scheduler]] to notify [[rx.lang.scala.Observer]]s on + * @return the source Observable modified so that its [[rx.lang.scala.Observer]]s are notified on the + * specified [[rx.lang.scala.Scheduler]] */ def observeOn(scheduler: Scheduler): Observable[T] = { - Observable[T](asJava.observeOn(scheduler)) + Observable[T](asJavaObservable.observeOn(scheduler)) } - + /** - * Returns an Observable that reverses the effect of {@link #materialize materialize} by - * transforming the {@link Notification} objects emitted by the source Observable into the items + * Returns an Observable that reverses the effect of [[rx.lang.scala.Observable.materialize]] by + * transforming the [[rx.lang.scala.Notification]] objects emitted by the source Observable into the items * or notifications they represent. - *

+ * + * This operation is only available if `this` is of type `Observable[Notification[U]]` for some `U`, + * otherwise you will get a compilation error. + * * - * - * @return an Observable that emits the items and notifications embedded in the {@link Notification} objects emitted by the source Observable + * + * @return an Observable that emits the items and notifications embedded in the [[rx.lang.scala.Notification]] objects emitted by the source Observable + * + * @usecase def dematerialize[U]: Observable[U] + * @inheritdoc + * */ // with =:= it does not work, why? - def dematerialize[U](implicit evidence: T <:< Notification[U]): Observable[U] = { - val o = asJava.dematerialize[U]() - Observable[U](o) + def dematerialize[U](implicit evidence: Observable[T] <:< Observable[Notification[U]]): Observable[U] = { + val o1: Observable[Notification[U]] = this + val o2: Observable[rx.Notification[_ <: U]] = o1.map(_.asJava) + val o3 = o2.asJavaObservable.dematerialize[U]() + Observable[U](o3) } - + /** - * Instruct an Observable to pass control to another Observable rather than invoking {@link Observer#onError onError} if it encounters an error. - *

+ * Instruct an Observable to pass control to another Observable rather than invoking [[rx.lang.scala.Observer.onError onError]] if it encounters an error. + * * - *

+ * * By default, when an Observable encounters an error that prevents it from emitting the - * expected item to its {@link Observer}, the Observable invokes its Observer's - * onError method, and then quits without invoking any more of its Observer's - * methods. The onErrorResumeNext method changes this behavior. If you pass a - * function that returns an Observable (resumeFunction) to - * onErrorResumeNext, if the original Observable encounters an error, instead of - * invoking its Observer's onError method, it will instead relinquish control to - * the Observable returned from resumeFunction, which will invoke the Observer's {@link Observer#onNext onNext} method if it is able to do so. In such a case, because no - * Observable necessarily invokes onError, the Observer may never know that an + * expected item to its [[rx.lang.scala.Observer]], the Observable invokes its Observer's + * `onError` method, and then quits without invoking any more of its Observer's + * methods. The `onErrorResumeNext` method changes this behavior. If you pass a + * function that returns an Observable (`resumeFunction`) to + * `onErrorResumeNext`, if the original Observable encounters an error, instead of + * invoking its Observer's `onError` method, it will instead relinquish control to + * the Observable returned from `resumeFunction`, which will invoke the Observer's + * [[rx.lang.scala.Observer.onNext onNext]] method if it is able to do so. In such a case, because no + * Observable necessarily invokes `onError`, the Observer may never know that an * error happened. - *

+ * * You can use this to prevent errors from propagating or to supply fallback data should errors * be encountered. - * + * * @param resumeFunction * a function that returns an Observable that will take over if the source Observable * encounters an error * @return the original Observable, with appropriately modified behavior */ def onErrorResumeNext[U >: T](resumeFunction: Throwable => Observable[U]): Observable[U] = { - val f: Func1[Throwable, rx.Observable[_ <: U]] = (t: Throwable) => resumeFunction(t).asJava + val f: Func1[Throwable, rx.Observable[_ <: U]] = (t: Throwable) => resumeFunction(t).asJavaObservable val f2 = f.asInstanceOf[Func1[Throwable, rx.Observable[Nothing]]] - Observable[U](asJava.onErrorResumeNext(f2)) + Observable[U](asJavaObservable.onErrorResumeNext(f2)) } - + /** - * Instruct an Observable to pass control to another Observable rather than invoking {@link Observer#onError onError} if it encounters an error. - *

+ * Instruct an Observable to pass control to another Observable rather than invoking [[rx.lang.scala.Observer.onError onError]] if it encounters an error. + * * - *

+ * * By default, when an Observable encounters an error that prevents it from emitting the - * expected item to its {@link Observer}, the Observable invokes its Observer's - * onError method, and then quits without invoking any more of its Observer's - * methods. The onErrorResumeNext method changes this behavior. If you pass - * another Observable (resumeSequence) to an Observable's - * onErrorResumeNext method, if the original Observable encounters an error, - * instead of invoking its Observer's onError method, it will instead relinquish - * control to resumeSequence which will invoke the Observer's {@link Observer#onNext onNext} method if it is able to do so. In such a case, because no - * Observable necessarily invokes onError, the Observer may never know that an + * expected item to its [[rx.lang.scala.Observer]], the Observable invokes its Observer's + * `onError` method, and then quits without invoking any more of its Observer's + * methods. The `onErrorResumeNext` method changes this behavior. If you pass + * another Observable (`resumeSequence`) to an Observable's + * `onErrorResumeNext` method, if the original Observable encounters an error, + * instead of invoking its Observer's `onError` method, it will instead relinquish + * control to `resumeSequence` which will invoke the Observer's [[rx.lang.scala.Observer.onNext onNext]] + * method if it is able to do so. In such a case, because no + * Observable necessarily invokes `onError`, the Observer may never know that an * error happened. - *

+ * * You can use this to prevent errors from propagating or to supply fallback data should errors * be encountered. - * + * * @param resumeSequence * a function that returns an Observable that will take over if the source Observable * encounters an error * @return the original Observable, with appropriately modified behavior */ def onErrorResumeNext[U >: T](resumeSequence: Observable[U]): Observable[U] = { - val rSeq1: rx.Observable[_ <: U] = resumeSequence.asJava + val rSeq1: rx.Observable[_ <: U] = resumeSequence.asJavaObservable val rSeq2: rx.Observable[Nothing] = rSeq1.asInstanceOf[rx.Observable[Nothing]] - Observable[U](asJava.onErrorResumeNext(rSeq2)) + Observable[U](asJavaObservable.onErrorResumeNext(rSeq2)) } /** - * Instruct an Observable to pass control to another Observable rather than invoking {@link Observer#onError onError} if it encounters an error of type {@link java.lang.Exception}. - *

- * This differs from {@link #onErrorResumeNext} in that this one does not handle {@link java.lang.Throwable} or {@link java.lang.Error} but lets those continue through. - *

+ * Instruct an Observable to pass control to another Observable rather than invoking [[rx.lang.scala.Observer.onError onError]] if it encounters an error of type `java.lang.Exception`. + * + * This differs from `Observable.onErrorResumeNext` in that this one does not handle `java.lang.Throwable` or `java.lang.Error` but lets those continue through. + * * - *

+ * * By default, when an Observable encounters an error that prevents it from emitting the - * expected item to its {@link Observer}, the Observable invokes its Observer's - * onError method, and then quits without invoking any more of its Observer's - * methods. The onErrorResumeNext method changes this behavior. If you pass - * another Observable (resumeSequence) to an Observable's - * onErrorResumeNext method, if the original Observable encounters an error, - * instead of invoking its Observer's onError method, it will instead relinquish - * control to resumeSequence which will invoke the Observer's {@link Observer#onNext onNext} method if it is able to do so. In such a case, because no - * Observable necessarily invokes onError, the Observer may never know that an + * expected item to its [[rx.lang.scala.Observer]], the Observable invokes its Observer's + * `onError` method, and then quits without invoking any more of its Observer's + * methods. The `onErrorResumeNext` method changes this behavior. If you pass + * another Observable (`resumeSequence`) to an Observable's + * `onErrorResumeNext` method, if the original Observable encounters an error, + * instead of invoking its Observer's `onError` method, it will instead relinquish + * control to `resumeSequence` which will invoke the Observer's [[rx.lang.scala.Observer.onNext onNext]] + * method if it is able to do so. In such a case, because no + * Observable necessarily invokes `onError`, the Observer may never know that an * error happened. - *

+ * * You can use this to prevent errors from propagating or to supply fallback data should errors * be encountered. - * + * * @param resumeSequence * a function that returns an Observable that will take over if the source Observable * encounters an error * @return the original Observable, with appropriately modified behavior - */ + */ def onExceptionResumeNext[U >: T](resumeSequence: Observable[U]): Observable[U] = { - val rSeq1: rx.Observable[_ <: U] = resumeSequence.asJava + val rSeq1: rx.Observable[_ <: U] = resumeSequence.asJavaObservable val rSeq2: rx.Observable[Nothing] = rSeq1.asInstanceOf[rx.Observable[Nothing]] - Observable[U](asJava.onExceptionResumeNext(rSeq2)) + Observable[U](asJavaObservable.onExceptionResumeNext(rSeq2)) } /** * Instruct an Observable to emit an item (returned by a specified function) rather than - * invoking {@link Observer#onError onError} if it encounters an error. - *

+ * invoking [[rx.lang.scala.Observer.onError onError]] if it encounters an error. + * * - *

+ * * By default, when an Observable encounters an error that prevents it from emitting the - * expected item to its {@link Observer}, the Observable invokes its Observer's - * onError method, and then quits without invoking any more of its Observer's - * methods. The onErrorReturn method changes this behavior. If you pass a function - * (resumeFunction) to an Observable's onErrorReturn method, if the + * expected item to its [[rx.lang.scala.Observer]], the Observable invokes its Observer's + * `onError` method, and then quits without invoking any more of its Observer's + * methods. The `onErrorReturn` method changes this behavior. If you pass a function + * (`resumeFunction`) to an Observable's `onErrorReturn` method, if the * original Observable encounters an error, instead of invoking its Observer's - * onError method, it will instead pass the return value of - * resumeFunction to the Observer's {@link Observer#onNext onNext} method. - *

+ * `onError` method, it will instead pass the return value of + * `resumeFunction` to the Observer's [[rx.lang.scala.Observer.onNext onNext]] method. + * * You can use this to prevent errors from propagating or to supply fallback data should errors * be encountered. - * + * * @param resumeFunction * a function that returns an item that the new Observable will emit if the source * Observable encounters an error @@ -868,8 +954,8 @@ class Observable[+T](val asJava: rx.Observable[_ <: T]) def onErrorReturn[U >: T](resumeFunction: Throwable => U): Observable[U] = { val f1: Func1[Throwable, _ <: U] = resumeFunction val f2 = f1.asInstanceOf[Func1[Throwable, Nothing]] - Observable[U](asJava.onErrorReturn(f2)) - } + Observable[U](asJavaObservable.onErrorReturn(f2)) + } /** * Returns an Observable that applies a function of your choosing to the first item emitted by a @@ -877,92 +963,88 @@ class Observable[+T](val asJava: rx.Observable[_ <: T]) * by the source Observable into the same function, and so on until all items have been emitted * by the source Observable, and emits the final result from the final call to your function as * its sole item. - *

+ * * - *

+ * * This technique, which is called "reduce" or "aggregate" here, is sometimes called "fold," * "accumulate," "compress," or "inject" in other programming contexts. Groovy, for instance, - * has an inject method that does a similar operation on lists. - * + * has an `inject` method that does a similar operation on lists. + * * @param accumulator * An accumulator function to be invoked on each item emitted by the source * Observable, whose result will be used in the next accumulator call * @return an Observable that emits a single item that is the result of accumulating the * output from the source Observable - * @see MSDN: Observable.Aggregate - * @see Wikipedia: Fold (higher-order function) */ - def reduce[U >: T](f: (U, U) => U): Observable[U] = { - val func: Func2[_ >: U, _ >: U, _ <: U] = f + def reduce[U >: T](accumulator: (U, U) => U): Observable[U] = { + val func: Func2[_ >: U, _ >: U, _ <: U] = accumulator val func2 = func.asInstanceOf[Func2[T, T, T]] - Observable[U](asJava.asInstanceOf[rx.Observable[T]].reduce(func2)) - } + Observable[U](asJavaObservable.asInstanceOf[rx.Observable[T]].reduce(func2)) + } /** - * Returns a {@link ConnectableObservable} that shares a single subscription to the underlying - * Observable that will replay all of its items and notifications to any future {@link Observer}. - *

+ * Returns a pair of a start function and an [[rx.lang.scala.Observable]] that shares a single subscription to the underlying + * Observable that will replay all of its items and notifications to any future [[rx.lang.scala.Observer]]. + * * - * - * @return a {@link ConnectableObservable} that upon connection causes the source Observable to - * emit items to its {@link Observer}s + * + * @return a pair of a start function and an [[rx.lang.scala.Observable]] such that when the start function + * is called, the Observable starts to emit items to its [[rx.lang.scala.Observer]]s */ - def replay(): ConnectableObservable[T] = { - new ConnectableObservable[T](asJava.replay()) + def replay: (() => Subscription, Observable[T]) = { + val javaCO = asJavaObservable.replay() + (() => javaCO.connect(), Observable[T](javaCO)) } /** - * This method has similar behavior to {@link #replay} except that this auto-subscribes to - * the source Observable rather than returning a {@link ConnectableObservable}. - *

+ * This method has similar behavior to [[rx.lang.scala.Observable.replay]] except that this auto-subscribes to + * the source Observable rather than returning a start function and an Observable. + * * - *

+ * * This is useful when you want an Observable to cache responses and you can't control the - * subscribe/unsubscribe behavior of all the {@link Observer}s. - *

+ * subscribe/unsubscribe behavior of all the [[rx.lang.scala.Observer]]s. + * * NOTE: You sacrifice the ability to unsubscribe from the origin when you use the - * cache() operator so be careful not to use this operator on Observables that + * `cache()` operator so be careful not to use this operator on Observables that * emit an infinite or very large number of items that will use up memory. - * + * * @return an Observable that when first subscribed to, caches all of its notifications for * the benefit of subsequent subscribers. */ def cache: Observable[T] = { - Observable[T](asJava.cache()) + Observable[T](asJavaObservable.cache()) } /** - * Returns a {@link ConnectableObservable}, which waits until its {@link ConnectableObservable#connect connect} method is called before it begins emitting - * items to those {@link Observer}s that have subscribed to it. - *

+ * Returns a a pair of a start function and an [[rx.lang.scala.Observable]], which waits until the start function is called before it begins emitting + * items to those [[rx.lang.scala.Observer]]s that have subscribed to it. + * * - * - * @return a {@link ConnectableObservable} that upon connection causes the source Observable to - * emit items to its {@link Observer}s + * + * @return a pair of a start function and an [[rx.lang.scala.Observable]] such that when the start function + * is called, the Observable starts to emit items to its [[rx.lang.scala.Observer]]s */ - def publish: ConnectableObservable[T] = { - new ConnectableObservable[T](asJava.publish()) + def publish: (() => Subscription, Observable[T]) = { + val javaCO = asJavaObservable.publish() + (() => javaCO.connect(), Observable[T](javaCO)) } - // There is no aggregate function with signature - // public Observable aggregate(Func2 accumulator) - // because that's called reduce in Scala. - // TODO add Scala-like aggregate function - + /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted * by an Observable into the same function, and so on until all items have been emitted by the * source Observable, emitting the final result from the final call to your function as its sole * item. - *

+ * * - *

+ * * This technique, which is called "reduce" or "aggregate" here, is sometimes called "fold," * "accumulate," "compress," or "inject" in other programming contexts. Groovy, for instance, - * has an inject method that does a similar operation on lists. - * + * has an `inject` method that does a similar operation on lists. + * * @param initialValue * the initial (seed) accumulator value * @param accumulator @@ -970,452 +1052,461 @@ class Observable[+T](val asJava: rx.Observable[_ <: T]) * Observable, the result of which will be used in the next accumulator call * @return an Observable that emits a single item that is the result of accumulating the output * from the items emitted by the source Observable - * @see MSDN: Observable.Aggregate - * @see Wikipedia: Fold (higher-order function) */ - def fold[R](initialValue: R)(accumulator: (R, T) => R): Observable[R] = { - Observable[R](asJava.reduce(initialValue, accumulator)) + def foldLeft[R](initialValue: R)(accumulator: (R, T) => R): Observable[R] = { + Observable[R](asJavaObservable.reduce(initialValue, new Func2[R,T,R]{ + def call(t1: R, t2: T): R = accumulator(t1,t2) + })) } - // corresponds to Java's - // public Observable reduce(R initialValue, Func2 accumulator) - // public Observable aggregate(R initialValue, Func2 accumulator) - - // There is no method like - // public Observable scan(Func2 accumulator) - // because scan has a seed in Scala - + /** * Returns an Observable that emits the results of sampling the items emitted by the source * Observable at a specified time interval. - *

+ * * - * - * @param period - * the sampling rate - * @param unit - * the {@link TimeUnit} in which period is defined + * + * @param duration the sampling rate * @return an Observable that emits the results of sampling the items emitted by the source * Observable at the specified time interval */ def sample(duration: Duration): Observable[T] = { - Observable[T](asJava.sample(duration.length, duration.unit)) + Observable[T](asJavaObservable.sample(duration.length, duration.unit)) } /** * Returns an Observable that emits the results of sampling the items emitted by the source * Observable at a specified time interval. - *

+ * * - * - * @param period - * the sampling rate - * @param unit - * the {@link TimeUnit} in which period is defined + * + * @param duration the sampling rate * @param scheduler - * the {@link Scheduler} to use when sampling + * the [[rx.lang.scala.Scheduler]] to use when sampling * @return an Observable that emits the results of sampling the items emitted by the source * Observable at the specified time interval */ def sample(duration: Duration, scheduler: Scheduler): Observable[T] = { - Observable[T](asJava.sample(duration.length, duration.unit, scheduler)) + Observable[T](asJavaObservable.sample(duration.length, duration.unit, scheduler)) } - - /** + + /** * Returns an Observable that applies a function of your choosing to the first item emitted by a * source Observable, then feeds the result of that function along with the second item emitted * by an Observable into the same function, and so on until all items have been emitted by the * source Observable, emitting the result of each of these iterations. - *

+ * * - *

+ * * This sort of function is sometimes called an accumulator. - *

- * Note that when you pass a seed to scan() the resulting Observable will emit + * + * Note that when you pass a seed to `scan()` the resulting Observable will emit * that seed as its first emitted item. - * + * * @param initialValue * the initial (seed) accumulator value * @param accumulator * an accumulator function to be invoked on each item emitted by the source - * Observable, whose result will be emitted to {@link Observer}s via {@link Observer#onNext onNext} and used in the next accumulator call. + * Observable, whose result will be emitted to [[rx.lang.scala.Observer]]s via [[rx.lang.scala.Observer.onNext onNext]] and used in the next accumulator call. * @return an Observable that emits the results of each call to the accumulator function - * @see MSDN: Observable.Scan */ def scan[R](initialValue: R)(accumulator: (R, T) => R): Observable[R] = { - Observable[R](asJava.scan(initialValue, accumulator)) + Observable[R](asJavaObservable.scan(initialValue, new Func2[R,T,R]{ + def call(t1: R, t2: T): R = accumulator(t1,t2) + })) } - // corresponds to Scala's - // public Observable scan(R initialValue, Func2 accumulator) /** * Returns an Observable that emits a Boolean that indicates whether all of the items emitted by * the source Observable satisfy a condition. - *

+ * * - * + * * @param predicate * a function that evaluates an item and returns a Boolean - * @return an Observable that emits true if all items emitted by the source - * Observable satisfy the predicate; otherwise, false + * @return an Observable that emits `true` if all items emitted by the source + * Observable satisfy the predicate; otherwise, `false` */ def forall(predicate: T => Boolean): Observable[Boolean] = { // type mismatch; found : rx.Observable[java.lang.Boolean] required: rx.Observable[_ <: scala.Boolean] // new Observable[Boolean](asJava.all(predicate)) // it's more fun in Scala: - this.map(predicate).fold(true)(_ && _) + this.map(predicate).foldLeft(true)(_ && _) } - // corresponds to Java's - // public Observable all(Func1 predicate) - + /** - * Returns an Observable that skips the first num items emitted by the source + * Returns an Observable that skips the first `num` items emitted by the source * Observable and emits the remainder. - *

+ * * - *

- * You can ignore the first num items emitted by an Observable and attend only to - * those items that come after, by modifying the Observable with the skip method. - * - * @param num + * + * @param n * the number of items to skip * @return an Observable that is identical to the source Observable except that it does not - * emit the first num items that the source emits + * emit the first `num` items that the source emits */ def drop(n: Int): Observable[T] = { - Observable[T](asJava.skip(n)) + Observable[T](asJavaObservable.skip(n)) + } + + /** + * Returns an Observable that bypasses all items from the source Observable as long as the specified + * condition holds true. Emits all further source items as soon as the condition becomes false. + * + * + * + * @param predicate + * A function to test each item emitted from the source Observable for a condition. + * @return an Observable that emits all items from the source Observable as soon as the condition + * becomes false. + */ + def dropWhile(predicate: T => Boolean): Observable[T] = { + Observable(asJavaObservable.skipWhile(predicate)) } - // corresponds to Java's - // public Observable skip(int num) /** - * Returns an Observable that emits only the first num items emitted by the source + * Returns an Observable that emits only the first `num` items emitted by the source * Observable. - *

+ * * - *

- * This method returns an Observable that will invoke a subscribing {@link Observer}'s {@link Observer#onNext onNext} function a maximum of num times before invoking - * {@link Observer#onCompleted onCompleted}. - * - * @param num + * + * This method returns an Observable that will invoke a subscribing [[rx.lang.scala.Observer]]'s + * [[rx.lang.scala.Observer.onNext onNext]] function a maximum of `num` times before invoking + * [[rx.lang.scala.Observer.onCompleted onCompleted]]. + * + * @param n * the number of items to take - * @return an Observable that emits only the first num items from the source + * @return an Observable that emits only the first `num` items from the source * Observable, or all of the items from the source Observable if that Observable emits - * fewer than num items + * fewer than `num` items */ def take(n: Int): Observable[T] = { - Observable[T](asJava.take(n)) + Observable[T](asJavaObservable.take(n)) } /** * Returns an Observable that emits items emitted by the source Observable so long as a * specified condition is true. - *

+ * * - * + * * @param predicate * a function that evaluates an item emitted by the source Observable and returns a * Boolean * @return an Observable that emits the items from the source Observable so long as each item - * satisfies the condition defined by predicate + * satisfies the condition defined by `predicate` */ def takeWhile(predicate: T => Boolean): Observable[T] = { - Observable[T](asJava.takeWhile(predicate)) - } - - /** - * Returns an Observable that emits the items emitted by a source Observable so long as a given - * predicate remains true, where the predicate can operate on both the item and its index - * relative to the complete sequence. - *

- * - * - * @param predicate - * a function to test each item emitted by the source Observable for a condition; - * the second parameter of the function represents the index of the source item - * @return an Observable that emits items from the source Observable so long as the predicate - * continues to return true for each item, then completes - */ - // TODO: if we have zipWithIndex, takeWhileWithIndex is not needed any more - def takeWhileWithIndex(predicate: (T, Integer) => Boolean): Observable[T] = { - Observable[T](asJava.takeWhileWithIndex(predicate)) - } - - /* TODO zipWithIndex once it's in RxJava - def zipWithIndex: Observable[(T, Int)] = { - ??? + Observable[T](asJavaObservable.takeWhile(predicate)) } - */ /** - * Returns an Observable that emits only the last count items emitted by the source + * Returns an Observable that emits only the last `count` items emitted by the source * Observable. - *

+ * * - * + * * @param count * the number of items to emit from the end of the sequence emitted by the source * Observable - * @return an Observable that emits only the last count items emitted by the source + * @return an Observable that emits only the last `count` items emitted by the source * Observable */ - def takeRight(n: Int): Observable[T] = { - Observable[T](asJava.takeLast(n)) + def takeRight(count: Int): Observable[T] = { + Observable[T](asJavaObservable.takeLast(count)) } - // corresponds to Java's - // public Observable takeLast(final int count) - + /** * Returns an Observable that emits the items from the source Observable only until the - * other Observable emits an item. - *

+ * `other` Observable emits an item. + * * - * + * * @param that - * the Observable whose first emitted item will cause takeUntil to stop + * the Observable whose first emitted item will cause `takeUntil` to stop * emitting items from the source Observable - * @param - * the type of items emitted by other + * @tparam E + * the type of items emitted by `other` * @return an Observable that emits the items of the source Observable until such time as - * other emits its first item + * `other` emits its first item */ def takeUntil[E](that: Observable[E]): Observable[T] = { - Observable[T](asJava.takeUntil(that.asJava)) - } - + Observable[T](asJavaObservable.takeUntil(that.asJavaObservable)) + } + /** * Returns an Observable that emits a single item, a list composed of all the items emitted by * the source Observable. - *

+ * * - *

- * Normally, an Observable that returns multiple items will do so by invoking its {@link Observer}'s {@link Observer#onNext onNext} method for each such item. You can change + * + * Normally, an Observable that returns multiple items will do so by invoking its [[rx.lang.scala.Observer]]'s + * [[rx.lang.scala.Observer.onNext onNext]] method for each such item. You can change * this behavior, instructing the Observable to compose a list of all of these items and then to - * invoke the Observer's onNext function once, passing it the entire list, by - * calling the Observable's toList method prior to calling its {@link #subscribe} method. - *

+ * invoke the Observer's `onNext` function once, passing it the entire list, by + * calling the Observable's `toList` method prior to calling its `Observable.subscribe` method. + * * Be careful not to use this operator on Observables that emit infinite or very large numbers * of items, as you do not have the option to unsubscribe. - * + * * @return an Observable that emits a single item: a List containing all of the items emitted by * the source Observable. */ def toSeq: Observable[Seq[T]] = { - Observable.jObsOfListToScObsOfSeq(asJava.toList()) - : Observable[Seq[T]] // SI-7818 + Observable.jObsOfListToScObsOfSeq(asJavaObservable.toList) + : Observable[Seq[T]] // SI-7818 } - // corresponds to Java's method - // public Observable> toList() { - // There are no toSortedList methods because Scala can sort itself - // public Observable> toSortedList() - // public Observable> toSortedList(Func2 sortFunction) - - // There is no method - // def startWith[U >: T](values: U*): Observable[U] - // because we can just use ++ instead - /** - * Groups the items emitted by an Observable according to a specified criterion, and emits these - * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. - *

- * - * - * @param keySelector + * Groups the items emitted by this Observable according to a specified discriminator function. + * + * @param f * a function that extracts the key from an item - * @param elementSelector - * a function to map a source item to an item in a {@link GroupedObservable} - * @param - * the key type - * @param - * the type of items emitted by the resulting {@link GroupedObservable}s - * @return an Observable that emits {@link GroupedObservable}s, each of which corresponds to a - * unique key value and emits items representing items from the source Observable that - * share that key value - */ - /* TODO make a Scala GroupedObservable and groupBy - def groupBy[K,R](keySelector: T => K, elementSelector: T => R ): Observable[GroupedObservable[K,R]] = { - ??? - } - */ - // public Observable> groupBy(final Func1 keySelector, final Func1 elementSelector) - - /** - * Groups the items emitted by an Observable according to a specified criterion, and emits these - * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. - *

- * - * - * @param keySelector - * a function that extracts the key for each item - * @param - * the key type - * @return an Observable that emits {@link GroupedObservable}s, each of which corresponds to a - * unique key value and emits items representing items from the source Observable that - * share that key value + * @tparam K + * the type of keys returned by the discriminator function. + * @return an Observable that emits `(key, observable)` pairs, where `observable` + * contains all items for which `f` returned `key`. */ - /* TODO - def groupBy[K](keySelector: T => K ): Observable[GroupedObservable[K,T]] = { - ??? + def groupBy[K](f: T => K): Observable[(K, Observable[T])] = { + val o1 = asJavaObservable.groupBy[K](f) : rx.Observable[_ <: rx.observables.GroupedObservable[K, _ <: T]] + val func = (o: rx.observables.GroupedObservable[K, _ <: T]) => (o.getKey, Observable[T](o)) + Observable[(K, Observable[T])](o1.map[(K, Observable[T])](func)) } - */ - // public Observable> groupBy(final Func1 keySelector) /** * Given an Observable that emits Observables, creates a single Observable that * emits the items emitted by the most recently published of those Observables. - *

+ * * - * - * @param sequenceOfSequences - * the source Observable that emits Observables + * + * This operation is only available if `this` is of type `Observable[Observable[U]]` for some `U`, + * otherwise you'll get a compilation error. + * * @return an Observable that emits only the items emitted by the most recently published * Observable + * + * @usecase def switch[U]: Observable[U] + * @inheritdoc */ def switch[U](implicit evidence: Observable[T] <:< Observable[Observable[U]]): Observable[U] = { val o2: Observable[Observable[U]] = this - val o3: Observable[rx.Observable[_ <: U]] = o2.map(_.asJava) - val o4: rx.Observable[_ <: rx.Observable[_ <: U]] = o3.asJava + val o3: Observable[rx.Observable[_ <: U]] = o2.map(_.asJavaObservable) + val o4: rx.Observable[_ <: rx.Observable[_ <: U]] = o3.asJavaObservable val o5 = rx.Observable.switchOnNext[U](o4) Observable[U](o5) } - // TODO naming: follow C# (switch) or Java (switchOnNext)? - // public static Observable switchOnNext(Observable> sequenceOfSequences) - - /** + // Naming: We follow C# (switch), not Java (switchOnNext), because Java just had to avoid clash with keyword + + /** * Flattens two Observables into one Observable, without any transformation. - *

+ * * - *

+ * * You can combine items emitted by two Observables so that they act like a single - * Observable by using the {@code merge} method. - * + * Observable by using the `merge` method. + * * @param that * an Observable to be merged - * @return an Observable that emits items from {@code this} and {@code that} until - * {@code this} or {@code that} emits {@code onError} or {@code onComplete}. + * @return an Observable that emits items from `this` and `that` until + * `this` or `that` emits `onError` or `onComplete`. */ def merge[U >: T](that: Observable[U]): Observable[U] = { - val thisJava: rx.Observable[_ <: U] = this.asJava - val thatJava: rx.Observable[_ <: U] = that.asJava + val thisJava: rx.Observable[_ <: U] = this.asJavaObservable + val thatJava: rx.Observable[_ <: U] = that.asJavaObservable Observable[U](rx.Observable.merge(thisJava, thatJava)) } + /** + * This behaves like [[rx.lang.scala.Observable.merge]] except that if any of the merged Observables + * notify of an error via [[rx.lang.scala.Observer.onError onError]], `mergeDelayError` will + * refrain from propagating that error notification until all of the merged Observables have + * finished emitting items. + * + * + * + * Even if multiple merged Observables send `onError` notifications, `mergeDelayError` will only invoke the `onError` method of its + * Observers once. + * + * This method allows an Observer to receive all successfully emitted items from all of the + * source Observables without being interrupted by an error notification from one of them. + * + * @param that + * an Observable to be merged + * @return an Observable that emits items that are the result of flattening the items emitted by + * `this` and `that` + */ + def mergeDelayError[U >: T](that: Observable[U]): Observable[U] = { + Observable[U](rx.Observable.mergeDelayError[U](this.asJavaObservable, that.asJavaObservable)) + } + + /** + * Flattens the sequence of Observables emitted by `this` into one Observable, without any + * transformation. + * + * + * + * You can combine the items emitted by multiple Observables so that they act like a single + * Observable by using this method. + * + * This operation is only available if `this` is of type `Observable[Observable[U]]` for some `U`, + * otherwise you'll get a compilation error. + * + * @return an Observable that emits items that are the result of flattening the items emitted + * by the Observables emitted by `this` + * + * @usecase def flatten[U]: Observable[U] + * @inheritdoc + */ + def flatten[U](implicit evidence: Observable[T] <:< Observable[Observable[U]]): Observable[U] = { + val o2: Observable[Observable[U]] = this + val o3: Observable[rx.Observable[_ <: U]] = o2.map(_.asJavaObservable) + val o4: rx.Observable[_ <: rx.Observable[_ <: U]] = o3.asJavaObservable + val o5 = rx.Observable.merge[U](o4) + Observable[U](o5) + } + + /** + * This behaves like `flatten` except that if any of the merged Observables + * notify of an error via [[rx.lang.scala.Observer.onError onError]], this method will + * refrain from propagating that error notification until all of the merged Observables have + * finished emitting items. + * + * + * + * Even if multiple merged Observables send `onError` notifications, this method will only invoke the `onError` method of its + * Observers once. + * + * This method allows an Observer to receive all successfully emitted items from all of the + * source Observables without being interrupted by an error notification from one of them. + * + * This operation is only available if `this` is of type `Observable[Observable[U]]` for some `U`, + * otherwise you'll get a compilation error. + * + * @return an Observable that emits items that are the result of flattening the items emitted by + * the Observables emitted by the this Observable + * + * @usecase def flattenDelayError[U]: Observable[U] + * @inheritdoc + */ + def flattenDelayError[U](implicit evidence: Observable[T] <:< Observable[Observable[U]]): Observable[U] = { + val o2: Observable[Observable[U]] = this + val o3: Observable[rx.Observable[_ <: U]] = o2.map(_.asJavaObservable) + val o4: rx.Observable[_ <: rx.Observable[_ <: U]] = o3.asJavaObservable + val o5 = rx.Observable.mergeDelayError[U](o4) + Observable[U](o5) + } + + /** + * Combines two observables, emitting a pair of the latest values of each of + * the source observables each time an event is received from one of the source observables, where the + * aggregation is defined by the given function. + * + * @param that + * The second source observable. + * @return An Observable that combines the source Observables + */ + def combineLatest[U](that: Observable[U]): Observable[(T, U)] = { + val f: Func2[_ >: T, _ >: U, _ <: (T, U)] = (t: T, u: U) => (t, u) + Observable[(T, U)](rx.Observable.combineLatest[T, U, (T, U)](this.asJavaObservable, that.asJavaObservable, f)) + } + /** * Debounces by dropping all values that are followed by newer values before the timeout value expires. The timer resets on each `onNext` call. - *

+ * * NOTE: If events keep firing faster than the timeout then no data will be emitted. - *

+ * * - *

- * Information on debounce vs throttle: - *

- *

    - *
  • http://drupalmotion.com/article/debounce-and-throttle-visual-explanation
  • - *
  • http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
  • - *
  • http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/
  • - *
+ * + * $debounceVsThrottle * * @param timeout - * The time each value has to be 'the most recent' of the {@link Observable} to ensure that it's not dropped. + * The time each value has to be 'the most recent' of the [[rx.lang.scala.Observable]] to ensure that it's not dropped. * - * @return An {@link Observable} which filters out values which are too quickly followed up with newer values. - * @see {@link #debounce} + * @return An [[rx.lang.scala.Observable]] which filters out values which are too quickly followed up with newer values. + * @see `Observable.debounce` */ def throttleWithTimeout(timeout: Duration): Observable[T] = { - Observable[T](asJava.throttleWithTimeout(timeout.length, timeout.unit)) + Observable[T](asJavaObservable.throttleWithTimeout(timeout.length, timeout.unit)) } /** * Debounces by dropping all values that are followed by newer values before the timeout value expires. The timer resets on each `onNext` call. - *

+ * * NOTE: If events keep firing faster than the timeout then no data will be emitted. - *

+ * * - *

- * Information on debounce vs throttle: - *

- *

    - *
  • http://drupalmotion.com/article/debounce-and-throttle-visual-explanation
  • - *
  • http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
  • - *
  • http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/
  • - *
+ * + * $debounceVsThrottle * * @param timeout - * The time each value has to be 'the most recent' of the {@link Observable} to ensure that it's not dropped. + * The time each value has to be 'the most recent' of the [[rx.lang.scala.Observable]] to ensure that it's not dropped. * - * @return An {@link Observable} which filters out values which are too quickly followed up with newer values. - * @see {@link #throttleWithTimeout}; + * @return An [[rx.lang.scala.Observable]] which filters out values which are too quickly followed up with newer values. + * @see `Observable.throttleWithTimeout` */ def debounce(timeout: Duration): Observable[T] = { - Observable[T](asJava.debounce(timeout.length, timeout.unit)) + Observable[T](asJavaObservable.debounce(timeout.length, timeout.unit)) } /** * Debounces by dropping all values that are followed by newer values before the timeout value expires. The timer resets on each `onNext` call. - *

+ * * NOTE: If events keep firing faster than the timeout then no data will be emitted. - *

+ * * - *

- * Information on debounce vs throttle: - *

- *

    - *
  • http://drupalmotion.com/article/debounce-and-throttle-visual-explanation
  • - *
  • http://unscriptable.com/2009/03/20/debouncing-javascript-methods/
  • - *
  • http://www.illyriad.co.uk/blog/index.php/2011/09/javascript-dont-spam-your-server-debounce-and-throttle/
  • - *
+ * + * $debounceVsThrottle * * @param timeout - * The time each value has to be 'the most recent' of the {@link Observable} to ensure that it's not dropped. + * The time each value has to be 'the most recent' of the [[rx.lang.scala.Observable]] to ensure that it's not dropped. * @param scheduler - * The {@link Scheduler} to use internally to manage the timers which handle timeout for each event. + * The [[rx.lang.scala.Scheduler]] to use internally to manage the timers which handle timeout for each event. * @return Observable which performs the throttle operation. - * @see {@link #throttleWithTimeout}; + * @see `Observable.throttleWithTimeout` */ def debounce(timeout: Duration, scheduler: Scheduler): Observable[T] = { - Observable[T](asJava.debounce(timeout.length, timeout.unit, scheduler)) + Observable[T](asJavaObservable.debounce(timeout.length, timeout.unit, scheduler)) } /** * Debounces by dropping all values that are followed by newer values before the timeout value expires. The timer resets on each `onNext` call. - *

+ * * NOTE: If events keep firing faster than the timeout then no data will be emitted. - *

+ * * * * @param timeout - * The time each value has to be 'the most recent' of the {@link Observable} to ensure that it's not dropped. + * The time each value has to be 'the most recent' of the [[rx.lang.scala.Observable]] to ensure that it's not dropped. * @param scheduler - * The {@link Scheduler} to use internally to manage the timers which handle timeout for each event. + * The [[rx.lang.scala.Scheduler]] to use internally to manage the timers which handle timeout for each event. * @return Observable which performs the throttle operation. - * @see {@link #debounce} + * @see `Observable.debounce` */ def throttleWithTimeout(timeout: Duration, scheduler: Scheduler): Observable[T] = { - Observable[T](asJava.throttleWithTimeout(timeout.length, timeout.unit, scheduler)) + Observable[T](asJavaObservable.throttleWithTimeout(timeout.length, timeout.unit, scheduler)) } /** * Throttles by skipping value until `skipDuration` passes and then emits the next received value. - *

- * This differs from {@link #throttleLast} in that this only tracks passage of time whereas {@link #throttleLast} ticks at scheduled intervals. - *

+ * + * This differs from `Observable.throttleLast` in that this only tracks passage of time whereas `Observable.throttleLast` ticks at scheduled intervals. + * * * * @param skipDuration * Time to wait before sending another value after emitting last value. * @param scheduler - * The {@link Scheduler} to use internally to manage the timers which handle timeout for each event. + * The [[rx.lang.scala.Scheduler]] to use internally to manage the timers which handle timeout for each event. * @return Observable which performs the throttle operation. */ def throttleFirst(skipDuration: Duration, scheduler: Scheduler): Observable[T] = { - Observable[T](asJava.throttleFirst(skipDuration.length, skipDuration.unit, scheduler)) + Observable[T](asJavaObservable.throttleFirst(skipDuration.length, skipDuration.unit, scheduler)) } /** * Throttles by skipping value until `skipDuration` passes and then emits the next received value. - *

- * This differs from {@link #throttleLast} in that this only tracks passage of time whereas {@link #throttleLast} ticks at scheduled intervals. - *

+ * + * This differs from `Observable.throttleLast` in that this only tracks passage of time whereas `Observable.throttleLast` ticks at scheduled intervals. + * * * * @param skipDuration @@ -1423,436 +1514,541 @@ class Observable[+T](val asJava: rx.Observable[_ <: T]) * @return Observable which performs the throttle operation. */ def throttleFirst(skipDuration: Duration): Observable[T] = { - Observable[T](asJava.throttleFirst(skipDuration.length, skipDuration.unit)) + Observable[T](asJavaObservable.throttleFirst(skipDuration.length, skipDuration.unit)) } /** * Throttles by returning the last value of each interval defined by 'intervalDuration'. - *

- * This differs from {@link #throttleFirst} in that this ticks along at a scheduled interval whereas {@link #throttleFirst} does not tick, it just tracks passage of time. - *

+ * + * This differs from `Observable.throttleFirst` in that this ticks along at a scheduled interval whereas `Observable.throttleFirst` does not tick, it just tracks passage of time. + * * * * @param intervalDuration * Duration of windows within with the last value will be chosen. * @return Observable which performs the throttle operation. - * @see {@link #sample(long, TimeUnit)} */ def throttleLast(intervalDuration: Duration): Observable[T] = { - Observable[T](asJava.throttleLast(intervalDuration.length, intervalDuration.unit)) + Observable[T](asJavaObservable.throttleLast(intervalDuration.length, intervalDuration.unit)) } /** * Throttles by returning the last value of each interval defined by 'intervalDuration'. - *

- * This differs from {@link #throttleFirst} in that this ticks along at a scheduled interval whereas {@link #throttleFirst} does not tick, it just tracks passage of time. - *

+ * + * This differs from `Observable.throttleFirst` in that this ticks along at a scheduled interval whereas `Observable.throttleFirst` does not tick, it just tracks passage of time. + * * * * @param intervalDuration * Duration of windows within with the last value will be chosen. * @return Observable which performs the throttle operation. - * @see {@link #sample(long, TimeUnit, Scheduler)} */ def throttleLast(intervalDuration: Duration, scheduler: Scheduler): Observable[T] = { - Observable[T](asJava.throttleLast(intervalDuration.length, intervalDuration.unit, scheduler)) + Observable[T](asJavaObservable.throttleLast(intervalDuration.length, intervalDuration.unit, scheduler)) + } + + /** + * Returns an Observable that sums up the elements of this Observable. + * + * This operation is only available if the elements of this Observable are numbers, otherwise + * you will get a compilation error. + * + * @return an Observable emitting the sum of all the elements of the source Observable + * as its single item. + * + * @usecase def sum: Observable[T] + * @inheritdoc + */ + def sum[U >: T](implicit num: Numeric[U]): Observable[U] = { + foldLeft(num.zero)(num.plus) + } + + /** + * Returns an Observable that multiplies up the elements of this Observable. + * + * This operation is only available if the elements of this Observable are numbers, otherwise + * you will get a compilation error. + * + * @return an Observable emitting the product of all the elements of the source Observable + * as its single item. + * + * @usecase def product: Observable[T] + * @inheritdoc + */ + def product[U >: T](implicit num: Numeric[U]): Observable[U] = { + foldLeft(num.one)(num.times) + } + + /** + * Returns an Observable that emits only the very first item emitted by the source Observable, or + * a default value if the source Observable is empty. + * + * + * + * @param default + * The default value to emit if the source Observable doesn't emit anything. + * This is a by-name parameter, so it is only evaluated if the source Observable doesn't emit anything. + * @return an Observable that emits only the very first item from the source, or a default value + * if the source Observable completes without emitting any item. + */ + def firstOrElse[U >: T](default: => U): Observable[U] = { + this.take(1).foldLeft[Option[U]](None)((v: Option[U], e: U) => Some(e)).map({ + case Some(element) => element + case None => default + }) + } + + /** + * Returns an Observable that emits only the very first item emitted by the source Observable, or + * a default value if the source Observable is empty. + * + * + * + * @param default + * The default value to emit if the source Observable doesn't emit anything. + * This is a by-name parameter, so it is only evaluated if the source Observable doesn't emit anything. + * @return an Observable that emits only the very first item from the source, or a default value + * if the source Observable completes without emitting any item. + */ + def headOrElse[U >: T](default: => U): Observable[U] = firstOrElse(default) + + /** + * Returns an Observable that emits only the very first item emitted by the source Observable. + * This is just a shorthand for `take(1)`. + * + * + * + * @return an Observable that emits only the very first item from the source, or none if the + * source Observable completes without emitting a single item. + */ + def first: Observable[T] = take(1) + + /* + + TODO once https://github.com/Netflix/RxJava/issues/417 is fixed, we can add head and tail methods + + /** + * emits NoSuchElementException("head of empty Observable") if empty + */ + def head: Observable[T] = { + this.take(1).fold[Option[T]](None)((v: Option[T], e: T) => Some(e)).map({ + case Some(element) => element + case None => throw new NoSuchElementException("head of empty Observable") + }) } /** - * Converts an Observable into a {@link BlockingObservable} (an Observable with blocking + * emits an UnsupportedOperationException("tail of empty list") if empty + */ + def tail: Observable[T] = ??? + + */ + + /** + * Returns an Observable that forwards all sequentially distinct items emitted from the source Observable. + * + * + * + * @return an Observable of sequentially distinct items + */ + def distinctUntilChanged: Observable[T] = { + Observable[T](asJavaObservable.distinctUntilChanged) + } + + /** + * Returns an Observable that forwards all items emitted from the source Observable that are sequentially + * distinct according to a key selector function. + * + * + * + * @param keySelector + * a function that projects an emitted item to a key value which is used for deciding whether an item is sequentially + * distinct from another one or not + * @return an Observable of sequentially distinct items + */ + def distinctUntilChanged[U](keySelector: T => U): Observable[T] = { + Observable[T](asJavaObservable.distinctUntilChanged[U](keySelector)) + } + + /** + * Returns an Observable that forwards all distinct items emitted from the source Observable. + * + * + * + * @return an Observable of distinct items + */ + def distinct: Observable[T] = { + Observable[T](asJavaObservable.distinct()) + } + + /** + * Returns an Observable that forwards all items emitted from the source Observable that are distinct according + * to a key selector function. + * + * + * + * @param keySelector + * a function that projects an emitted item to a key value which is used for deciding whether an item is + * distinct from another one or not + * @return an Observable of distinct items + */ + def distinct[U](keySelector: T => U): Observable[T] = { + Observable[T](asJavaObservable.distinct[U](keySelector)) + } + + /** + * Returns an Observable that counts the total number of elements in the source Observable. + * + * + * + * @return an Observable emitting the number of counted elements of the source Observable + * as its single item. + */ + def length: Observable[Int] = { + Observable[Integer](asJavaObservable.count()).map(_.intValue()) + } + + /** + * Returns an Observable that counts the total number of elements in the source Observable. + * + * + * + * @return an Observable emitting the number of counted elements of the source Observable + * as its single item. + */ + def size: Observable[Int] = length + + /** + * Retry subscription to origin Observable upto given retry count. + * + * + * + * If [[rx.lang.scala.Observer.onError]] is invoked the source Observable will be re-subscribed to as many times as defined by retryCount. + * + * Any [[rx.lang.scala.Observer.onNext]] calls received on each attempt will be emitted and concatenated together. + * + * For example, if an Observable fails on first time but emits [1, 2] then succeeds the second time and + * emits [1, 2, 3, 4, 5] then the complete output would be [1, 2, 1, 2, 3, 4, 5, onCompleted]. + * + * @param retryCount + * Number of retry attempts before failing. + * @return Observable with retry logic. + */ + def retry(retryCount: Int): Observable[T] = { + Observable[T](asJavaObservable.retry(retryCount)) + } + + /** + * Retry subscription to origin Observable whenever onError is called (infinite retry count). + * + * + * + * If [[rx.lang.scala.Observer.onError]] is invoked the source Observable will be re-subscribed to. + * + * Any [[rx.lang.scala.Observer.onNext]] calls received on each attempt will be emitted and concatenated together. + * + * For example, if an Observable fails on first time but emits [1, 2] then succeeds the second time and + * emits [1, 2, 3, 4, 5] then the complete output would be [1, 2, 1, 2, 3, 4, 5, onCompleted]. + * @return Observable with retry logic. + */ + def retry: Observable[T] = { + Observable[T](asJavaObservable.retry()) + } + + /** + * Converts an Observable into a [[rx.lang.scala.observables.BlockingObservable]] (an Observable with blocking * operators). - * + * * @see Blocking Observable Operators */ def toBlockingObservable: BlockingObservable[T] = { - new BlockingObservable[T](asJava.toBlockingObservable()) + new BlockingObservable[T](asJavaObservable.toBlockingObservable) } - + + /** + * Perform work in parallel by sharding an `Observable[T]` on a + * [[rx.lang.scala.concurrency.Schedulers.threadPoolForComputation computation]] + * [[rx.lang.scala.Scheduler]] and return an `Observable[R]` with the output. + * + * @param f + * a function that applies Observable operators to `Observable[T]` in parallel and returns an `Observable[R]` + * @return an Observable with the output of the function executed on a [[rx.lang.scala.Scheduler]] + */ + def parallel[R](f: Observable[T] => Observable[R]): Observable[R] = { + val fJava: Func1[rx.Observable[T], rx.Observable[R]] = + (jo: rx.Observable[T]) => f(Observable[T](jo)).asJavaObservable.asInstanceOf[rx.Observable[R]] + Observable[R](asJavaObservable.asInstanceOf[rx.Observable[T]].parallel[R](fJava)) + } + + /** + * Perform work in parallel by sharding an `Observable[T]` on a [[rx.lang.scala.Scheduler]] and return an `Observable[R]` with the output. + * + * @param f + * a function that applies Observable operators to `Observable[T]` in parallel and returns an `Observable[R]` + * @param scheduler + * a [[rx.lang.scala.Scheduler]] to perform the work on. + * @return an Observable with the output of the function executed on a [[rx.lang.scala.Scheduler]] + */ + def parallel[R](f: Observable[T] => Observable[R], scheduler: Scheduler): Observable[R] = { + val fJava: Func1[rx.Observable[T], rx.Observable[R]] = + (jo: rx.Observable[T]) => f(Observable[T](jo)).asJavaObservable.asInstanceOf[rx.Observable[R]] + Observable[R](asJavaObservable.asInstanceOf[rx.Observable[T]].parallel[R](fJava, scheduler)) + } + + /** Tests whether a predicate holds for some of the elements of this `Observable`. + * + * @param p the predicate used to test elements. + * @return an Observable emitting one single Boolean, which is `true` if the given predicate `p` + * holds for some of the elements of this Observable, and `false` otherwise. + */ + def exists(p: T => Boolean): Observable[Boolean] = { + Observable[java.lang.Boolean](asJavaObservable.exists(p)).map(_.booleanValue()) + } + + /** Tests whether this `Observable` emits no elements. + * + * @return an Observable emitting one single Boolean, which is `true` if this `Observable` + * emits no elements, and `false` otherwise. + */ + def isEmpty: Observable[Boolean] = { + Observable[java.lang.Boolean](asJavaObservable.isEmpty).map(_.booleanValue()) + } + def withFilter(p: T => Boolean): WithFilter[T] = { - new WithFilter[T](p, asJava) + new WithFilter[T](p, asJavaObservable) } - + } +/** + * Provides various ways to construct new Observables. + */ object Observable { import scala.collection.JavaConverters._ import scala.collection.immutable.Range import scala.concurrent.duration.Duration - import scala.concurrent.Future - import rx.{Observable => JObservable} - import rx.lang.scala.{Notification, Subscription, Scheduler, Observer} - import rx.lang.scala.util._ - import rx.lang.scala.internal.ImplicitFunctionConversions._ - - private[scala] + import ImplicitFunctionConversions._ + + private[scala] def jObsOfListToScObsOfSeq[T](jObs: rx.Observable[_ <: java.util.List[T]]): Observable[Seq[T]] = { - val oScala1: Observable[java.util.List[T]] = new Observable[java.util.List[T]](jObs) + val oScala1: Observable[java.util.List[T]] = new Observable[java.util.List[T]]{ def asJavaObservable = jObs } oScala1.map((lJava: java.util.List[T]) => lJava.asScala) } - - private[scala] + + private[scala] def jObsOfJObsToScObsOfScObs[T](jObs: rx.Observable[_ <: rx.Observable[_ <: T]]): Observable[Observable[T]] = { - val oScala1: Observable[rx.Observable[_ <: T]] = new Observable[rx.Observable[_ <: T]](jObs) - oScala1.map((oJava: rx.Observable[_ <: T]) => new Observable[T](oJava)) + val oScala1: Observable[rx.Observable[_ <: T]] = new Observable[rx.Observable[_ <: T]]{ def asJavaObservable = jObs } + oScala1.map((oJava: rx.Observable[_ <: T]) => new Observable[T]{ def asJavaObservable = oJava}) } - - def apply[T](asJava: rx.Observable[_ <: T]): Observable[T] = { - new Observable[T](asJava) + + /** + * Creates a new Scala Observable from a given Java Observable. + */ + def apply[T](observable: rx.Observable[_ <: T]): Observable[T] = { + new Observable[T]{ + def asJavaObservable = observable + } } - + /** - * Creates an Observable that will execute the given function when an {@link Observer} subscribes to it. - *

+ * Creates an Observable that will execute the given function when an [[rx.lang.scala.Observer]] subscribes to it. + * * - *

- * Write the function you pass to create so that it behaves as an Observable: It - * should invoke the Observer's {@link Observer#onNext onNext}, {@link Observer#onError onError}, and {@link Observer#onCompleted onCompleted} methods + * + * Write the function you pass to `create` so that it behaves as an Observable: It + * should invoke the Observer's [[rx.lang.scala.Observer.onNext onNext]], [[rx.lang.scala.Observer.onError onError]], and [[rx.lang.scala.Observer.onCompleted onCompleted]] methods * appropriately. - *

- * A well-formed Observable must invoke either the Observer's onCompleted method - * exactly once or its onError method exactly once. - *

+ * + * A well-formed Observable must invoke either the Observer's `onCompleted` method + * exactly once or its `onError` method exactly once. + * * See Rx Design Guidelines (PDF) * for detailed information. - * - * @param + * + * + * @tparam T * the type of the items that this Observable emits * @param func - * a function that accepts an {@code Observer}, invokes its {@code onNext}, {@code onError}, and {@code onCompleted} methods - * as appropriate, and returns a {@link Subscription} to allow the Observer to + * a function that accepts an `Observer[T]`, invokes its `onNext`, `onError`, and `onCompleted` methods + * as appropriate, and returns a [[rx.lang.scala.Subscription]] to allow the Observer to * canceling the subscription - * @return an Observable that, when an {@link Observer} subscribes to it, will execute the given + * @return an Observable that, when an [[rx.lang.scala.Observer]] subscribes to it, will execute the given * function */ def apply[T](func: Observer[T] => Subscription): Observable[T] = { - Observable[T](JObservable.create(func)) + Observable[T](rx.Observable.create(new OnSubscribeFunc[T] { + def onSubscribe(t1: rx.Observer[_ >: T]): rx.Subscription = { + func(Observer(t1)) + } + })) } - // corresponds to Java's - // public static Observable create(OnSubscribeFunc func) - - // Java's - // public static Observable empty() - // is not needed in Scala because it's a special case of varargs apply - + /** - * Returns an Observable that invokes an {@link Observer}'s {@link Observer#onError onError} method when the Observer subscribes to it - *

+ * Returns an Observable that invokes an [[rx.lang.scala.Observer]]'s [[rx.lang.scala.Observer.onError onError]] method when the Observer subscribes to it + * * - * + * * @param exception * the particular error to report - * @param + * @tparam T * the type of the items (ostensibly) emitted by the Observable - * @return an Observable that invokes the {@link Observer}'s {@link Observer#onError onError} method when the Observer subscribes to it + * @return an Observable that invokes the [[rx.lang.scala.Observer]]'s [[rx.lang.scala.Observer.onError onError]] method when the Observer subscribes to it */ - def apply(exception: Throwable): Observable[Nothing] = { - Observable[Nothing](JObservable.error(exception)) + def apply[T](exception: Throwable): Observable[T] = { + Observable[T](rx.Observable.error(exception)) } - // corresponds to Java's - // public static Observable error(Throwable exception) - - // There is no method corresponding to - // public static Observable from(Iterable iterable) - // because Scala automatically uses the varargs apply for this /** * Converts a sequence of values into an Observable. - *

+ * * - * - *

Implementation note: the entire array will be immediately emitted each time an {@link Observer} subscribes. Since this occurs before the {@link Subscription} is returned, + * + * Implementation note: the entire array will be immediately emitted each time an [[rx.lang.scala.Observer]] subscribes. + * Since this occurs before the [[rx.lang.scala.Subscription]] is returned, * it in not possible to unsubscribe from the sequence before it completes. - * + * * @param items * the source Array - * @param + * @tparam T * the type of items in the Array, and the type of items to be emitted by the * resulting Observable * @return an Observable that emits each item in the source Array */ - def apply[T](args: T*): Observable[T] = { - Observable[T](JObservable.from(args.toIterable.asJava)) + def apply[T](items: T*): Observable[T] = { + Observable[T](rx.Observable.from(items.toIterable.asJava)) } - // corresponds to Java's - // public static Observable from(T... items) - + + /** + * Generates an Observable that emits a sequence of integers within a specified range. + * + * + * + * Implementation note: the entire range will be immediately emitted each time an [[rx.lang.scala.Observer]] subscribes. + * Since this occurs before the [[rx.lang.scala.Subscription]] is returned, + * it in not possible to unsubscribe from the sequence before it completes. + * + * @param range the range + * @return an Observable that emits a range of sequential integers + */ def apply(range: Range): Observable[Int] = { - Observable[Int](JObservable.from(range.toIterable.asJava)) + Observable[Int](rx.Observable.from(range.toIterable.asJava)) } - - // There is no method corresponding to - // public static Observable range(int start, int count) - // because the Scala collection library provides enough methods to create Iterables. - // Examples: Observable(1 to 5), Observable(1 until 10) - + /** * Returns an Observable that calls an Observable factory to create its Observable for each - * new Observer that subscribes. That is, for each subscriber, the actuall Observable is determined + * new Observer that subscribes. That is, for each subscriber, the actual Observable is determined * by the factory function. - * - *

+ * * - *

+ * * The defer operator allows you to defer or delay emitting items from an Observable until such - * time as an Observer subscribes to the Observable. This allows an {@link Observer} to easily + * time as an Observer subscribes to the Observable. This allows an [[rx.lang.scala.Observer]] to easily * obtain updates or a refreshed version of the sequence. - * - * @param observableFactory - * the Observable factory function to invoke for each {@link Observer} that + * + * @param observable + * the Observable factory function to invoke for each [[rx.lang.scala.Observer]] that * subscribes to the resulting Observable - * @param + * @tparam T * the type of the items emitted by the Observable - * @return an Observable whose {@link Observer}s trigger an invocation of the given Observable + * @return an Observable whose [[rx.lang.scala.Observer]]s trigger an invocation of the given Observable * factory function */ def defer[T](observable: => Observable[T]): Observable[T] = { - Observable[T](JObservable.defer(observable.asJava)) - } - // corresponds to Java's - // public static Observable defer(Func0> observableFactory) - - /** - * Returns an Observable that emits a single item and then completes. - *

- * - *

- * To convert any object into an Observable that emits that object, pass that object into the - * just method. - *

- * This is similar to the {@link #apply(Iterable[T])} method, except that - * {@link #apply(Iterable[T])} will convert an {@link Iterable} object into an Observable that emits - * each of the items in the Iterable, one at a time, while the just() method - * converts an Iterable into an Observable that emits the entire Iterable as a single item. - * - * @param value - * the item to pass to the {@link Observer}'s {@link Observer#onNext onNext} method - * @param - * the type of that item - * @return an Observable that emits a single item and then completes - */ - def just[T](value: T): Observable[T] = { - Observable[T](JObservable.just(value)) - } - // corresponds to Java's - // public static Observable just(T value) - - // TODO we have merge and concat (++) as binary instance methods, but do we also need them as - // static methods with arity > 2? - - // There is no method corresponding to - // public static Observable concat(Observable... source) - // because we have the instance method ++ instead - - /** - * This behaves like {@link #merge(java.util.List)} except that if any of the merged Observables - * notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} will - * refrain from propagating that error notification until all of the merged Observables have - * finished emitting items. - *

- * - *

- * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only invoke the {@code onError} method of its - * Observers once. - *

- * This method allows an Observer to receive all successfully emitted items from all of the - * source Observables without being interrupted by an error notification from one of them. - * - * @param source - * a list of Observables - * @return an Observable that emits items that are the result of flattening the items emitted by - * the {@code source} list of Observables - * @see MSDN: Observable.Merge Method - */ - // public static Observable mergeDelayError(List> source) - // TODO decide if instance method mergeWithDelayError (?) + Observable[T](rx.Observable.defer[T](() => observable.asJavaObservable)) + } - /** - * This behaves like {@link #merge(Observable)} except that if any of the merged Observables - * notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} will - * refrain from propagating that error notification until all of the merged Observables have - * finished emitting items. - *

- * - *

- * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only invoke the {@code onError} method of its - * Observers once. - *

- * This method allows an Observer to receive all successfully emitted items from all of the - * source Observables without being interrupted by an error notification from one of them. - * - * @param source - * an Observable that emits Observables - * @return an Observable that emits items that are the result of flattening the items emitted by - * the Observables emitted by the {@code source} Observable - * @see MSDN: Observable.Merge Method - */ - // public static Observable mergeDelayError(Observable> source) - // TODO decide if instance method mergeWithDelayError (?) - - /** - * This behaves like {@link #merge(Observable...)} except that if any of the merged Observables - * notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} will - * refrain from propagating that error notification until all of the merged Observables have - * finished emitting items. - *

- * - *

- * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only invoke the {@code onError} method of its - * Observers once. - *

- * This method allows an Observer to receive all successfully emitted items from all of the - * source Observables without being interrupted by an error notification from one of them. - * - * @param source - * a series of Observables - * @return an Observable that emits items that are the result of flattening the items emitted by - * the {@code source} Observables - * @see MSDN: Observable.Merge Method - */ - // public static Observable mergeDelayError(Observable... source) - // TODO decide if instance method mergeWithDelayError (?) - /** - * Returns an Observable that never sends any items or notifications to an {@link Observer}. - *

+ * Returns an Observable that never sends any items or notifications to an [[rx.lang.scala.Observer]]. + * * - *

+ * * This Observable is useful primarily for testing purposes. - * - * @return an Observable that never sends any items or notifications to an {@link Observer} + * + * @return an Observable that never sends any items or notifications to an [[rx.lang.scala.Observer]] */ def never: Observable[Nothing] = { - Observable[Nothing](JObservable.never()) + Observable[Nothing](rx.Observable.never()) } - - // There is no method corresponding to - // public static Observable switchDo(Observable> sequenceOfSequences) - // because it's deprecated. - - // There's no - // public static Observable switchOnNext(Observable> sequenceOfSequences) - // here because that's an instance method. - - // There is no method here corresponding to - // public static Observable synchronize(Observable observable) - // because that's an instance method. - /* - def apply[T](f: Future[T]): Observable[T] = { - ??? // TODO convert Scala Future to Java Future - } - */ - // corresponds to - // public static Observable from(Future future) - - /* - def apply[T](f: Future[T], scheduler: Scheduler): Observable[T] = { - ??? // TODO convert Scala Future to Java Future + /** + * Given 3 observables, returns an observable that emits Tuples of 3 elements each. + * The first emitted Tuple will contain the first element of each source observable, + * the second Tuple the second element of each source observable, and so on. + * + * @return an Observable that emits the zipped Observables + */ + def zip[A, B, C](obA: Observable[A], obB: Observable[B], obC: Observable[C]): Observable[(A, B, C)] = { + Observable[(A, B, C)](rx.Observable.zip[A, B, C, (A, B, C)](obA.asJavaObservable, obB.asJavaObservable, obC.asJavaObservable, (a: A, b: B, c: C) => (a, b, c))) } - */ - // public static Observable from(Future future, Scheduler scheduler) - - /* - def apply[T](f: Future[T], duration: Duration): Observable[T] = { - ??? // TODO convert Scala Future to Java Future + + /** + * Given 4 observables, returns an observable that emits Tuples of 4 elements each. + * The first emitted Tuple will contain the first element of each source observable, + * the second Tuple the second element of each source observable, and so on. + * + * @return an Observable that emits the zipped Observables + */ + def zip[A, B, C, D](obA: Observable[A], obB: Observable[B], obC: Observable[C], obD: Observable[D]): Observable[(A, B, C, D)] = { + Observable[(A, B, C, D)](rx.Observable.zip[A, B, C, D, (A, B, C, D)](obA.asJavaObservable, obB.asJavaObservable, obC.asJavaObservable, obD.asJavaObservable, (a: A, b: B, c: C, d: D) => (a, b, c, d))) } - */ - // corresponds to - // public static Observable from(Future future, long timeout, TimeUnit unit) - - // There is no method here corresponding to - // public static Observable zip(Observable o1, Observable o2, Func2 zipFunction) - // because it's an instance method - - // There is no method corresponding to - // public static Observable zip(Observable o1, Observable o2, Observable o3, Func3 zipFunction) - // because zip3 is not known in the Scala world - // Also applies to all zipN with N > 3 ;-) - - /** - * Combines the given observables, emitting an event containing an aggregation of the latest values of each of the source observables - * each time an event is received from one of the source observables, where the aggregation is defined by the given function. - *

- * - * - * @param o1 - * The first source observable. - * @param o2 - * The second source observable. - * @param combineFunction - * The aggregation function used to combine the source observable values. - * @return An Observable that combines the source Observables with the given combine function + + /** + * Given an Observable emitting `N` source observables, returns an observable that + * emits Seqs of `N` elements each. + * The first emitted Seq will contain the first element of each source observable, + * the second Seq the second element of each source observable, and so on. + * + * Note that the returned Observable will only start emitting items once the given + * `Observable[Observable[T]]` has completed, because otherwise it cannot know `N`. + * + * @param observables + * An Observable emitting N source Observables + * @return an Observable that emits the zipped Seqs */ - // public static Observable combineLatest(Observable o1, Observable o2, Func2 combineFunction) - // TODO do we want this as an instance method? - // TODO then decide about combineLatest with > 2 Observables - - // TODO what about these two? - // public static Observable zip(Observable> ws, final FuncN zipFunction) - // public static Observable zip(Collection> ws, FuncN zipFunction) + def zip[T](observables: Observable[Observable[T]]): Observable[Seq[T]] = { + val f: FuncN[Seq[T]] = (args: Seq[java.lang.Object]) => { + val asSeq: Seq[Object] = args.toSeq + asSeq.asInstanceOf[Seq[T]] + } + val list = observables.map(_.asJavaObservable).asJavaObservable + val o = rx.Observable.zip(list, f) + Observable[Seq[T]](o) + } + /** + * Emits `0`, `1`, `2`, `...` with a delay of `duration` between consecutive numbers. + * + * + * + * @param duration + * duration between two consecutive numbers + * @return An Observable that emits a number each time interval. + */ def interval(duration: Duration): Observable[Long] = { - (new Observable[java.lang.Long](JObservable.interval(duration.length, duration.unit))).map(_.longValue()) + Observable[java.lang.Long](rx.Observable.interval(duration.length, duration.unit)).map(_.longValue()) + /*XXX*/ } -} -// Cannot yet have inner class because of this error message: -// "implementation restriction: nested class is not allowed in value class. -// This restriction is planned to be removed in subsequent releases." -class WithFilter[+T] private[scala] (p: T => Boolean, asJava: rx.Observable[_ <: T]) { - import rx.lang.scala.internal.ImplicitFunctionConversions._ - - def map[B](f: T => B): Observable[B] = { - Observable[B](asJava.filter(p).map[B](f)) - } - - def flatMap[B](f: T => Observable[B]): Observable[B] = { - Observable[B](asJava.filter(p).flatMap[B]((x: T) => f(x).asJava)) - } - - def withFilter(q: T => Boolean): Observable[T] = { - Observable[T](asJava.filter((x: T) => p(x) && q(x))) + /** + * Emits `0`, `1`, `2`, `...` with a delay of `duration` between consecutive numbers. + * + * + * + * @param duration + * duration between two consecutive numbers + * @param scheduler + * the scheduler to use + * @return An Observable that emits a number each time interval. + */ + def interval(duration: Duration, scheduler: Scheduler): Observable[Long] = { + Observable[java.lang.Long](rx.Observable.interval(duration.length, duration.unit, scheduler)).map(_.longValue()) + /*XXX*/ } - - // there is no foreach here, that's only available on BlockingObservable -} -class UnitTestSuite extends JUnitSuite { - import scala.concurrent.duration._ - import org.junit.{Before, Test, Ignore} - import org.junit.Assert._ - import org.mockito.Matchers.any - import org.mockito.Mockito._ - import org.mockito.{ MockitoAnnotations, Mock } - - // Tests which needn't be run: - - @Ignore def testCovariance = { - println("hey, you shouldn't run this test") - - val o1: Observable[Nothing] = Observable() - val o2: Observable[Int] = o1 - val o3: Observable[App] = o1 - val o4: Observable[Any] = o2 - val o5: Observable[Any] = o3 - } - - // Tests which have to be run: - - @Test def testDematerialize() { - val o = Observable(1, 2, 3) - val mat = o.materialize - val demat = mat.dematerialize - - // correctly rejected: - // val wrongDemat = Observable("hello").dematerialize - - assertEquals(demat.toBlockingObservable.toIterable.toList, List(1, 2, 3)) - } - - @Test def testTest() = { - val a: Observable[Int] = Observable() - assertEquals(4, Observable(1, 2, 3, 4).toBlockingObservable.last) - } - } + + + + + + diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Observer.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Observer.scala new file mode 100644 index 0000000000..73f865ef84 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Observer.scala @@ -0,0 +1,62 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala + +/** + Provides a mechanism for receiving push-based notifications. +* +* After an Observer calls an [[rx.lang.scala.Observable]]'s `subscribe` method, the Observable +* calls the Observer's `onNext` method to provide notifications. A well-behaved Observable will +* call an Observer's `onCompleted` method exactly once or the Observer's `onError` method exactly once. +*/ +trait Observer[-T] { + + def asJavaObserver: rx.Observer[_ >: T] + + /** + * Provides the Observer with new data. + * + * The [[rx.lang.scala.Observable]] calls this closure 0 or more times. + * + * The [[rx.lang.scala.Observable]] will not call this method again after it calls either `onCompleted` or `onError`. + */ + def onNext(value: T): Unit = asJavaObserver.onNext(value) + + /** + * Notifies the Observer that the [[rx.lang.scala.Observable]] has experienced an error condition. + * + * If the [[rx.lang.scala.Observable]] calls this method, it will not thereafter call `onNext` or `onCompleted`. + */ + def onError(error: Throwable): Unit = asJavaObserver.onError(error) + + /** + * Notifies the Observer that the [[rx.lang.scala.Observable]] has finished sending push-based notifications. + * + * The [[rx.lang.scala.Observable]] will not call this method if it calls `onError`. + */ + def onCompleted(): Unit = asJavaObserver.onCompleted() + +} + +object Observer { + def apply[T](observer: rx.Observer[T]) : Observer[T] = { + new Observer[T]() { + def asJavaObserver: rx.Observer[_ >: T] = observer + } + } +} + + diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/RxImplicits.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/RxImplicits.scala deleted file mode 100644 index d0d33954b7..0000000000 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/RxImplicits.scala +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright 2013 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.lang.scala - -/** - * This is the old Scala adaptor. It is kept here for backwards compatibility. - * The new adaptor is {@code rx.lang.scala.Observable}. - */ -@deprecated("use rx.lang.scala.Observable instead", "0.14") -object RxImplicits { - import java.{ lang => jlang } - import language.implicitConversions - - import rx.{ Observable, Observer, Subscription } - import rx.Observable.OnSubscribeFunc - import rx.observables.BlockingObservable - import rx.util.functions._ - - /** - * Converts 0-arg function to Rx Action0 - */ - implicit def scalaFunction0ProducingUnitToAction0(f: (() => Unit)): Action0 = - new Action0 { - def call(): Unit = f() - } - - /** - * Converts 1-arg function to Rx Action1 - */ - implicit def scalaFunction1ProducingUnitToAction1[A](f: (A => Unit)): Action1[A] = - new Action1[A] { - def call(a: A): Unit = f(a) - } - - /** - * Converts 1-arg predicate to Rx Func1[A, java.lang.Boolean] - */ - implicit def scalaBooleanFunction1ToRxBooleanFunc1[A](f: (A => Boolean)): Func1[A, jlang.Boolean] = - new Func1[A, jlang.Boolean] { - def call(a: A): jlang.Boolean = f(a).booleanValue - } - - /** - * Converts a specific function shape (used in takeWhile) to the equivalent Java types with an Rx Func2 - */ - implicit def convertTakeWhileFuncToRxFunc2[A](f: (A, Int) => Boolean): Func2[A, jlang.Integer, jlang.Boolean] = - new Func2[A, jlang.Integer, jlang.Boolean] { - def call(a: A, b: jlang.Integer): jlang.Boolean = f(a, b).booleanValue - } - - /** - * Converts a function shaped like compareTo into the equivalent Rx Func2 - */ - implicit def convertComparisonFuncToRxFunc2[A](f: (A, A) => Int): Func2[A, A, jlang.Integer] = - new Func2[A, A, jlang.Integer] { - def call(a1: A, a2: A): jlang.Integer = f(a1, a2).intValue - } - - /* - * This implicit allows Scala code to use any exception type and still work - * with invariant Func1 interface - */ - implicit def exceptionFunction1ToRxExceptionFunc1[A <: Exception, B](f: (A => B)): Func1[Exception, B] = - new Func1[Exception, B] { - def call(ex: Exception): B = f(ex.asInstanceOf[A]) - } - - /** - * The following implicits convert functions of different arities into the Rx equivalents - */ - implicit def scalaFunction0ToRxFunc0[A](f: () => A): Func0[A] = - new Func0[A] { - def call(): A = f() - } - - implicit def scalaFunction1ToRxFunc1[A, B](f: (A => B)): Func1[A, B] = - new Func1[A, B] { - def call(a: A): B = f(a) - } - - implicit def scalaFunction2ToRxFunc2[A, B, C](f: (A, B) => C): Func2[A, B, C] = - new Func2[A, B, C] { - def call(a: A, b: B) = f(a, b) - } - - implicit def scalaFunction3ToRxFunc3[A, B, C, D](f: (A, B, C) => D): Func3[A, B, C, D] = - new Func3[A, B, C, D] { - def call(a: A, b: B, c: C) = f(a, b, c) - } - - implicit def scalaFunction4ToRxFunc4[A, B, C, D, E](f: (A, B, C, D) => E): Func4[A, B, C, D, E] = - new Func4[A, B, C, D, E] { - def call(a: A, b: B, c: C, d: D) = f(a, b, c, d) - } - - implicit def onSubscribeFunc[A](f: (Observer[_ >: A]) => Subscription): OnSubscribeFunc[A] = - new OnSubscribeFunc[A] { - override def onSubscribe(a: Observer[_ >: A]) = f(a) - } - - /** - * This implicit class implements all of the methods necessary for including Observables in a - * for-comprehension. Note that return type is always Observable, so that the ScalaObservable - * type never escapes the for-comprehension - */ - implicit class ScalaObservable[A](wrapped: Observable[A]) { - def map[B](f: A => B): Observable[B] = wrapped.map[B](f) - def flatMap[B](f: A => Observable[B]): Observable[B] = wrapped.mapMany(f) - def foreach(f: A => Unit): Unit = wrapped.toBlockingObservable.forEach(f) - def withFilter(p: A => Boolean): WithFilter = new WithFilter(p) - - class WithFilter(p: A => Boolean) { - def map[B](f: A => B): Observable[B] = wrapped.filter(p).map(f) - def flatMap[B](f: A => Observable[B]): Observable[B] = wrapped.filter(p).flatMap(f) - def foreach(f: A => Unit): Unit = wrapped.filter(p).toBlockingObservable.forEach(f) - def withFilter(p: A => Boolean): Observable[A] = wrapped.filter(p) - } - } -} \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Scheduler.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Scheduler.scala new file mode 100644 index 0000000000..4c84eed840 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Scheduler.scala @@ -0,0 +1,226 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala + +import java.util.Date +import scala.concurrent.duration.Duration +import ImplicitFunctionConversions.scalaFunction0ProducingUnitToAction0 +import ImplicitFunctionConversions.schedulerActionToFunc2 +import rx.util.functions.{Action0, Action1, Func2} + +/** + * Represents an object thatimport rx.lang.scala.ImplicitFunctionConversions + schedules units of work. + */ +trait Scheduler { + def asJavaScheduler: rx.Scheduler + + /** + * Schedules a cancelable action to be executed. + * + * @param action Action to schedule. + * @return a subscription to be able to unsubscribe from action. + */ + def schedule(action: rx.lang.scala.Scheduler => Subscription): Subscription = { + this.schedule[Integer](0, (s: Scheduler, x: Integer) => action(s): Subscription): Subscription + } + + /** + * Schedules a cancelable action to be executed. + * + * @param state State to pass into the action. + * @param action Action to schedule. + * @return a subscription to be able to unsubscribe from action. + */ + private def schedule[T](state: T, action: (Scheduler, T) => Subscription): Subscription = { + Subscription(asJavaScheduler.schedule(state, new Func2[rx.Scheduler, T, rx.Subscription] { + def call(t1: rx.Scheduler, t2: T): rx.Subscription = { + action(Scheduler(t1), t2).asJavaSubscription + } + })) + } + + /** + * Schedules a cancelable action to be executed in delayTime. + * + * @param action Action to schedule. + * @param delayTime Time the action is to be delayed before executing. + * @return a subscription to be able to unsubscribe from action. + */ + def schedule(delayTime: Duration)(action: Scheduler => Subscription): Subscription = { + this.schedule[Integer](0, (s: Scheduler, x: Integer) => action(s), delayTime: Duration): Subscription + } + + /** + * Schedules a cancelable action to be executed in delayTime. + * + * @param state + * State to pass into the action. + * @param action + * Action to schedule. + * @param delayTime + * Time the action is to be delayed before executing. + * @return a subscription to be able to unsubscribe from action. + */ + private def schedule[T](state: T, action: (Scheduler, T) => Subscription, delayTime: Duration): Subscription = { + Subscription(asJavaScheduler.schedule(state, action, delayTime.length, delayTime.unit)) + } + + /** + * Schedules a cancelable action to be executed periodically. + * This default implementation schedules recursively and waits for actions to complete (instead of potentially executing + * long-running actions concurrently). Each scheduler that can do periodic scheduling in a better way should override this. + * + * @param action The action to execute periodically. + * @param initialDelay Time to wait before executing the action for the first time. + * @param period The time interval to wait each time in between executing the action. + * @return A subscription to be able to unsubscribe from action. + */ + def schedule(initialDelay: Duration, period: Duration)(action: Scheduler => Subscription): Subscription = { + this.schedulePeriodically[Integer](0, (s: Scheduler, x:Integer) => action(s): Subscription, initialDelay: Duration, period: Duration): Subscription + } + + /** + * Schedules a cancelable action to be executed periodically. + * This default implementation schedules recursively and waits for actions to complete (instead of potentially executing + * long-running actions concurrently). Each scheduler that can do periodic scheduling in a better way should override this. + * + * @param state + * State to pass into the action. + * @param action + * The action to execute periodically. + * @param initialDelay + * Time to wait before executing the action for the first time. + * @param period + * The time interval to wait each time in between executing the action. + * @return A subscription to be able to unsubscribe from action. + */ + private def schedulePeriodically[T](state: T, action: (Scheduler, T) => Subscription, initialDelay: Duration, period: Duration): Subscription = { + Subscription(asJavaScheduler.schedulePeriodically(state, action, initialDelay.length, initialDelay.unit.convert(period.length, period.unit), initialDelay.unit)) + } + + /** + * Schedules a cancelable action to be executed at dueTime. + * + * @param action Action to schedule. + * @param dueTime Time the action is to be executed. If in the past it will be executed immediately. + * @return a subscription to be able to unsubscribe from action. + */ + def schedule(dueTime: Date)(action: Scheduler => Subscription): Subscription = { + this.schedule(0: Integer, (s: Scheduler, x: Integer) => action(s): Subscription, dueTime: Date): Subscription + } + + /** + * Schedules a cancelable action to be executed at dueTime. + * + * @param state + * State to pass into the action. + * @param action + * Action to schedule. + * @param dueTime + * Time the action is to be executed. If in the past it will be executed immediately. + * @return a subscription to be able to unsubscribe from action. + */ + private def schedule[T](state: T, action: (Scheduler, T) => Subscription, dueTime: Date): Subscription = { + Subscription(asJavaScheduler.schedule(state, action, dueTime)) + } + + /** + * Schedules an action to be executed. + * + * @param action + * action + * @return a subscription to be able to unsubscribe from action. + */ + def schedule(action: =>Unit): Subscription = { + Subscription(asJavaScheduler.schedule(()=>action)) + } + + /** + * Schedules an action to be executed in delayTime. + * + * @param action action + * @return a subscription to be able to unsubscribe from action. + */ + def schedule(delayTime: Duration)(action: =>Unit): Subscription = { + Subscription(asJavaScheduler.schedule(()=>action, delayTime.length, delayTime.unit)) + } + + /** + * Schedules an action to be executed periodically. + * + * @param action + * The action to execute periodically. + * @param initialDelay + * Time to wait before executing the action for the first time. + * @param period + * The time interval to wait each time in between executing the action. + * @return A subscription to be able to unsubscribe from action. + */ + def schedule(initialDelay: Duration, period: Duration)(action: =>Unit): Subscription = { + Subscription(asJavaScheduler.schedulePeriodically(()=>action, initialDelay.length, initialDelay.unit.convert(period.length, period.unit), initialDelay.unit)) + } + + def scheduleRec(work: (=>Unit)=>Unit): Subscription = { + Subscription(asJavaScheduler.schedule(new Action1[Action0] { + def call(t1: Action0){ + work{ t1.call() } + } + })) + //action1[action0] + +// val subscription = new rx.subscriptions.MultipleAssignmentSubscription() +// +// subscription.setSubscription( +// this.schedule(scheduler => { +// def loop(): Unit = subscription.setSubscription(scheduler.schedule{ work{ loop() }}) +// loop() +// subscription +// })) +// subscription + } + + /** + * Returns the scheduler's notion of current absolute time in milliseconds. + */ + def now: Long = { + asJavaScheduler.now + } + + /** + * Parallelism available to a Scheduler. + * + * This defaults to {@code Runtime.getRuntime().availableProcessors()} but can be overridden for use cases such as scheduling work on a computer cluster. + * + * @return the scheduler's available degree of parallelism. + */ + def degreeOfParallelism: Int = { + asJavaScheduler.degreeOfParallelism + } + +} + +/** + * Provides constructors for Schedulers. + */ +object Scheduler { + private class WrapJavaScheduler(val asJavaScheduler: rx.Scheduler) extends Scheduler + + /** + * Constructs a Scala Scheduler from a Java Scheduler. + */ + def apply(s: rx.Scheduler): Scheduler = new WrapJavaScheduler(s) +} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Subscription.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Subscription.scala new file mode 100644 index 0000000000..bd3c6849a9 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Subscription.scala @@ -0,0 +1,78 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.lang.scala + +/** + * Subscriptions are returned from all `Observable.subscribe` methods to allow unsubscribing. + * + * This interface is the equivalent of `IDisposable` in the .NET Rx implementation. + */ +trait Subscription { + + val asJavaSubscription: rx.Subscription + + /** + * Call this method to stop receiving notifications on the Observer that was registered when + * this Subscription was received. + */ + def unsubscribe(): Unit = asJavaSubscription.unsubscribe() + + /** + * Checks if the subscription is unsubscribed. + */ + def isUnsubscribed: Boolean +} + +object Subscription { + import java.util.concurrent.atomic.AtomicBoolean + import rx.lang.scala.subscriptions._ + + /** + * Creates an [[rx.lang.scala.Subscription]] from an [[rx.Subscription]]. + */ + def apply(subscription: rx.Subscription): Subscription = { + subscription match { + case x: rx.subscriptions.BooleanSubscription => new BooleanSubscription(x) + case x: rx.subscriptions.CompositeSubscription => new CompositeSubscription(x) + case x: rx.subscriptions.MultipleAssignmentSubscription => new MultipleAssignmentSubscription(x) + case x: rx.subscriptions.SerialSubscription => new SerialSubscription(x) + case x: rx.Subscription => Subscription { x.unsubscribe() } + } + } + + /** + * Creates an [[rx.lang.scala.Subscription]] that invokes the specified action when unsubscribed. + */ + def apply(u: => Unit): Subscription = { + new Subscription() { + + private val unsubscribed = new AtomicBoolean(false) + def isUnsubscribed = unsubscribed.get() + + val asJavaSubscription = new rx.Subscription { + def unsubscribe() { if(!unsubscribed.get()) { u ; unsubscribed.set(true) }} + } + } + } + +} + + + + + + diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/WithFilter.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/WithFilter.scala new file mode 100644 index 0000000000..729450d2a5 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/WithFilter.scala @@ -0,0 +1,41 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala + +import ImplicitFunctionConversions.scalaBooleanFunction1ToRxBooleanFunc1 +import ImplicitFunctionConversions.scalaFunction1ToRxFunc1 + +// Cannot yet have inner class because of this error message: +// "implementation restriction: nested class is not allowed in value class. +// This restriction is planned to be removed in subsequent releases." +private[scala] class WithFilter[+T] (p: T => Boolean, asJava: rx.Observable[_ <: T]) { + + import ImplicitFunctionConversions._ + + def map[B](f: T => B): Observable[B] = { + Observable[B](asJava.filter(p).map[B](f)) + } + + def flatMap[B](f: T => Observable[B]): Observable[B] = { + Observable[B](asJava.filter(p).flatMap[B]((x: T) => f(x).asJavaObservable)) + } + + def withFilter(q: T => Boolean): Observable[T] = { + Observable[T](asJava.filter((x: T) => p(x) && q(x))) + } + + // there is no foreach here, that's only available on BlockingObservable +} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/Schedulers.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/Schedulers.scala new file mode 100644 index 0000000000..cbba7fd1db --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/Schedulers.scala @@ -0,0 +1,77 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala.concurrency + +import java.util.concurrent.Executor +import java.util.concurrent.ScheduledExecutorService +import rx.lang.scala.Scheduler +import rx.lang.scala.ImplicitFunctionConversions._ + +/** + * Factory methods for creating Schedulers. + */ +object Schedulers { + + /** + * Returns a [[rx.lang.scala.Scheduler]] that executes work immediately on the current thread. + */ + def immediate: Scheduler = Scheduler(rx.concurrency.Schedulers.immediate()) + + /** + * Returns a [[rx.lang.scala.Scheduler]] that queues work on the current thread to be executed after the current work completes. + */ + def currentThread: Scheduler = Scheduler(rx.concurrency.Schedulers.currentThread()) + + /** + * Returns a [[rx.lang.scala.Scheduler]] that creates a new {@link Thread} for each unit of work. + */ + def newThread: Scheduler = Scheduler(rx.concurrency.Schedulers.newThread) + + /** + * Returns a [[rx.lang.scala.Scheduler]] that queues work on an `java.util.concurrent.Executor`. + * + * Note that this does not support scheduled actions with a delay. + */ + def executor(executor: Executor): Scheduler = Scheduler(rx.concurrency.Schedulers.executor(executor)) + + /** + * Returns a [[rx.lang.scala.Scheduler]] that queues work on an `java.util.concurrent.ScheduledExecutorService`. + */ + def executor(executor: ScheduledExecutorService): Scheduler = Scheduler(rx.concurrency.Schedulers.executor(executor)) + + /** + * Returns a [[rx.lang.scala.Scheduler]] intended for computational work. + * + * The implementation is backed by a `java.util.concurrent.ScheduledExecutorService` thread-pool sized to the number of CPU cores. + * + * This can be used for event-loops, processing callbacks and other computational work. + * + * Do not perform IO-bound work on this scheduler. Use [[rx.lang.scala.concurrency.Schedulers.threadPoolForIO]] instead. + */ + def threadPoolForComputation: Scheduler = Scheduler(rx.concurrency.Schedulers.threadPoolForComputation()) + + /** + * [[rx.lang.scala.Scheduler]] intended for IO-bound work. + * + * The implementation is backed by an `java.util.concurrent.Executor` thread-pool that will grow as needed. + * + * This can be used for asynchronously performing blocking IO. + * + * Do not perform computational work on this scheduler. Use [[rx.lang.scala.concurrency.Schedulers.threadPoolForComputation]] instead. + */ + def threadPoolForIO: Scheduler = Scheduler(rx.concurrency.Schedulers.threadPoolForIO()) + +} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/TestScheduler.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/TestScheduler.scala new file mode 100644 index 0000000000..f7b7e4beba --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/TestScheduler.scala @@ -0,0 +1,82 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala.concurrency + +import scala.concurrent.duration.Duration +import rx.lang.scala.Scheduler + +/** + * Scheduler with artificial time, useful for testing. + * + * For example, you could test the `Observable.interval` operation using a `TestScheduler` as follows: + * + * {{{ + * @Test def testInterval() { + * import org.mockito.Matchers._ + * import org.mockito.Mockito._ + * + * val scheduler = TestScheduler() + * val observer = mock(classOf[rx.Observer[Long]]) + * + * val o = Observable.interval(1 second, scheduler) + * val sub = o.subscribe(observer) + * + * verify(observer, never).onNext(0L) + * verify(observer, never).onCompleted() + * verify(observer, never).onError(any(classOf[Throwable])) + * + * scheduler.advanceTimeTo(2 seconds) + * + * val inOrdr = inOrder(observer); + * inOrdr.verify(observer, times(1)).onNext(0L) + * inOrdr.verify(observer, times(1)).onNext(1L) + * inOrdr.verify(observer, never).onNext(2L) + * verify(observer, never).onCompleted() + * verify(observer, never).onError(any(classOf[Throwable])) + * + * sub.unsubscribe(); + * scheduler.advanceTimeTo(4 seconds) + * verify(observer, never).onNext(2L) + * verify(observer, times(1)).onCompleted() + * verify(observer, never).onError(any(classOf[Throwable])) + * } + * }}} + */ +class TestScheduler extends Scheduler { + val asJavaScheduler = new rx.concurrency.TestScheduler + + def advanceTimeBy(time: Duration) { + asJavaScheduler.advanceTimeBy(time.length, time.unit) + } + + def advanceTimeTo(time: Duration) { + asJavaScheduler.advanceTimeTo(time.length, time.unit) + } + + def triggerActions() { + asJavaScheduler.triggerActions() + } +} + +/** + * Provides constructors for `TestScheduler`. + */ +object TestScheduler { + def apply(): TestScheduler = { + new TestScheduler + } +} + diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/package.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/package.scala index 9dabd3356b..a3e61c0021 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/package.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/package.scala @@ -17,18 +17,15 @@ package rx.lang.scala import rx.concurrency.CurrentThreadScheduler -package object concurrency { - /* - TODO - rx.concurrency.CurrentThreadScheduler - rx.concurrency.ExecutorScheduler - rx.concurrency.ImmediateScheduler - rx.concurrency.NewThreadScheduler - rx.concurrency.Schedulers - rx.concurrency.TestScheduler +/** + * Provides schedulers. */ - - lazy val CurrentThreadScheduler = rx.concurrency.CurrentThreadScheduler.getInstance() - lazy val NewThreadScheduler = rx.concurrency.NewThreadScheduler.getInstance() +package object concurrency { -} \ No newline at end of file + // These classes are not exposed to Scala users, but are accessible through rx.lang.scala.concurrency.Schedulers: + + // rx.concurrency.CurrentThreadScheduler + // rx.concurrency.ExecutorScheduler + // rx.concurrency.ImmediateScheduler + // rx.concurrency.NewThreadScheduler +} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/examples/RxScalaDemo.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/examples/RxScalaDemo.scala deleted file mode 100644 index d46bfe1d3b..0000000000 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/examples/RxScalaDemo.scala +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Copyright 2013 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.lang.scala.examples - -import org.scalatest.junit.JUnitSuite -import scala.language.postfixOps -import rx.lang.scala._ -import scala.concurrent.duration._ -import org.junit.{Before, Test, Ignore} -import org.junit.Assert._ -import rx.lang.scala.concurrency.NewThreadScheduler - -@Ignore // Since this doesn't do automatic testing, don't increase build time unnecessarily -class RxScalaDemo extends JUnitSuite { - - @Test def intervalExample() { - val o = Observable.interval(200 millis).take(5) - o.subscribe(n => println("n = " + n)) - - // need to wait here because otherwise JUnit kills the thread created by interval() - waitFor(o) - - println("done") - } - - def msTicks(start: Long, step: Long): Observable[Long] = { - // will be easier once we have Observable.generate method - Observable.interval(step millis) map (_ * step + start) - } - - def prefixedTicks(start: Long, step: Long, prefix: String): Observable[String] = { - msTicks(start, step).map(prefix + _) - } - - @Test def testTicks() { - val o = prefixedTicks(5000, 500, "t = ").take(5) - o.subscribe(output(_)) - waitFor(o) - } - - @Test def testSwitch() { - // We do not have ultimate precision: Sometimes, 747 gets through, sometimes not - val o = Observable.interval(1000 millis).map(n => prefixedTicks(0, 249, s"Observable#$n: ")) - .switch.take(16) - o.subscribe(output(_)) - waitFor(o) - } - - @Test def testSwitchOnObservableOfInt() { - // Correctly rejected with error - // "Cannot prove that Observable[Int] <:< Observable[Observable[U]]" - // val o = Observable(1, 2).switch - } - - @Test def testObservableComparison() { - val first = Observable(10, 11, 12) - val second = Observable(10, 11, 12) - - val b1 = (first zip second) map (p => p._1 == p._2) forall (b => b) - - val equality = (a: Any, b: Any) => a == b - val b2 = (first zip second) map (p => equality(p._1, p._2)) forall (b => b) - - assertTrue(b1.toBlockingObservable.single) - assertTrue(b2.toBlockingObservable.single) - } - - @Test def testObservableComparisonWithForComprehension() { - val first = Observable(10, 11, 12) - val second = Observable(10, 11, 12) - - val booleans = for ((n1, n2) <- (first zip second)) yield (n1 == n2) - - val b1 = booleans.forall(_ == true) // without `== true`, b1 is assigned the forall function - - assertTrue(b1.toBlockingObservable.single) - } - - @Test def testStartWithIsUnnecessary() { - val before = Observable(-2, -1, 0) - val source = Observable(1, 2, 3) - println((before ++ source).toBlockingObservable.toList) - } - - @Test def mergeExample() { - val slowNumbers = Observable.interval(400 millis).take(5).map("slow " + _) - val fastNumbers = Observable.interval(200 millis).take(10).map("fast " + _) - val o = (slowNumbers merge fastNumbers) - o.subscribe(output(_)) - waitFor(o) - } - - @Test def rangeAndBufferExample() { - val o = Observable(1 to 18) - o.buffer(5).subscribe((l: Seq[Int]) => println(l.mkString("[", ", ", "]"))) - } - - @Test def windowExample() { - // this will be nicer once we have zipWithIndex - (for ((o, i) <- Observable(1 to 18).window(5) zip Observable(0 until 4); n <- o) - yield s"Observable#$i emits $n") - .subscribe(output(_)) - } - - @Test def testReduce() { - assertEquals(10, Observable(1, 2, 3, 4).reduce(_ + _).toBlockingObservable.single) - } - - @Test def testForeach() { - val numbers = Observable.interval(200 millis).take(3) - - // foreach is not available on normal Observables: - // for (n <- numbers) println(n+10) - - // but on BlockingObservable, it is: - for (n <- numbers.toBlockingObservable) println(n+10) - } - - @Test def testForComprehension() { - val observables = Observable(Observable(1, 2, 3), Observable(10, 20, 30)) - val squares = (for (o <- observables; i <- o if i % 2 == 0) yield i*i) - assertEquals(squares.toBlockingObservable.toList, List(4, 100, 400, 900)) - } - - @Test def testTwoSubscriptionsToOneInterval() { - // TODO this does not yet work as expected! - val o = Observable.interval(100 millis).take(8) - o.subscribe( - i => println(s"${i}a (on thread #${Thread.currentThread().getId()})") - ) - o.subscribe( - i => println(s"${i}b (on thread #${Thread.currentThread().getId()})") - ) - waitFor(o) - } - - @Test def schedulersExample() { - val o = Observable.interval(100 millis).take(8) - o.observeOn(NewThreadScheduler).subscribe( - i => println(s"${i}a (on thread #${Thread.currentThread().getId()})") - ) - o.observeOn(NewThreadScheduler).subscribe( - i => println(s"${i}b (on thread #${Thread.currentThread().getId()})") - ) - waitFor(o) - } - - def output(s: String): Unit = println(s) - - // blocks until obs has completed - def waitFor[T](obs: Observable[T]): Unit = { - obs.toBlockingObservable.last - } - -} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/internal/ImplicitFunctionConversions.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/internal/ImplicitFunctionConversions.scala deleted file mode 100644 index e5f6e49fc6..0000000000 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/internal/ImplicitFunctionConversions.scala +++ /dev/null @@ -1,155 +0,0 @@ -/** - * Copyright 2013 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.lang.scala.internal - - -import java.{lang => jlang} -import rx.util.functions.Action0 -import rx.util.functions.Action1 -import rx.util.functions.Func0 -import rx.util.functions.Func1 -import rx.util.functions.Func2 -import rx.util.functions.Func3 -import rx.util.functions.Func4 -import java.{lang => jlang} -import rx.Observer -import rx.Subscription -import java.{lang => jlang} -import scala.language.implicitConversions - -/** - * These function conversions are only used by the ScalaAdapter, users of RxScala don't need them. - */ -object ImplicitFunctionConversions { - // code below is copied from - // https://github.com/Netflix/RxJava/blob/master/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/RxImplicits.scala - - import java.{ lang => jlang } - import language.implicitConversions - - import rx.observables.BlockingObservable - import rx.util.functions._ - import rx.{Observer, Subscription} - - implicit def scalaFunction1ToOnSubscribeFunc[T](f: rx.lang.scala.Observer[T] => Subscription) = - new rx.Observable.OnSubscribeFunc[T] { - def onSubscribe(obs: Observer[_ >: T]): Subscription = { - f(obs) - } - } - - /*implicit def scalaFunction1ToOnSubscribeFunc[T](f: Observer[_ >: T] => Subscription) = - new rx.Observable.OnSubscribeFunc[T] { - def onSubscribe(obs: Observer[_ >: T]): Subscription = { - f(obs) - } - }*/ - - /** - * Converts a by-name parameter to a Rx Func0 - */ - implicit def scalaByNameParamToFunc0[B](param: => B): Func0[B] = - new Func0[B]{ - def call(): B = param - } - - - /** - * Converts 0-arg function to Rx Action0 - */ - implicit def scalaFunction0ProducingUnitToAction0(f: (() => Unit)): Action0 = - new Action0 { - def call(): Unit = f() - } - - /** - * Converts 1-arg function to Rx Action1 - */ - implicit def scalaFunction1ProducingUnitToAction1[A](f: (A => Unit)): Action1[A] = - new Action1[A] { - def call(a: A): Unit = f(a) - } - - /** - * Converts 1-arg predicate to Rx Func1[A, java.lang.Boolean] - */ - implicit def scalaBooleanFunction1ToRxBooleanFunc1[A](f: (A => Boolean)): Func1[A, jlang.Boolean] = - new Func1[A, jlang.Boolean] { - def call(a: A): jlang.Boolean = f(a).booleanValue - } - - /** - * Converts 2-arg predicate to Rx Func2[A, B, java.lang.Boolean] - */ - implicit def scalaBooleanFunction2ToRxBooleanFunc1[A, B](f: ((A, B) => Boolean)): Func2[A, B, jlang.Boolean] = - new Func2[A, B, jlang.Boolean] { - def call(a: A, b: B): jlang.Boolean = f(a, b).booleanValue - } - - /** - * Converts a specific function shape (used in takeWhile) to the equivalent Java types with an Rx Func2 - */ - implicit def convertTakeWhileFuncToRxFunc2[A](f: (A, Int) => Boolean): Func2[A, jlang.Integer, jlang.Boolean] = - new Func2[A, jlang.Integer, jlang.Boolean] { - def call(a: A, b: jlang.Integer): jlang.Boolean = f(a, b).booleanValue - } - - /** - * Converts a function shaped ilke compareTo into the equivalent Rx Func2 - */ - implicit def convertComparisonFuncToRxFunc2[A](f: (A, A) => Int): Func2[A, A, jlang.Integer] = - new Func2[A, A, jlang.Integer] { - def call(a1: A, a2: A): jlang.Integer = f(a1, a2).intValue - } - - /* - * This implicit allows Scala code to use any exception type and still work - * with invariant Func1 interface - */ - implicit def exceptionFunction1ToRxExceptionFunc1[A <: Exception, B](f: (A => B)): Func1[Exception, B] = - new Func1[Exception, B] { - def call(ex: Exception): B = f(ex.asInstanceOf[A]) - } - - /** - * The following implicits convert functions of different arities into the Rx equivalents - */ - implicit def scalaFunction0ToRxFunc0[A](f: () => A): Func0[A] = - new Func0[A] { - def call(): A = f() - } - - implicit def scalaFunction1ToRxFunc1[A, B](f: (A => B)): Func1[A, B] = - new Func1[A, B] { - def call(a: A): B = f(a) - } - - implicit def scalaFunction2ToRxFunc2[A, B, C](f: (A, B) => C): Func2[A, B, C] = - new Func2[A, B, C] { - def call(a: A, b: B) = f(a, b) - } - - implicit def scalaFunction3ToRxFunc3[A, B, C, D](f: (A, B, C) => D): Func3[A, B, C, D] = - new Func3[A, B, C, D] { - def call(a: A, b: B, c: C) = f(a, b, c) - } - - implicit def scalaFunction4ToRxFunc4[A, B, C, D, E](f: (A, B, C, D) => E): Func4[A, B, C, D, E] = - new Func4[A, B, C, D, E] { - def call(a: A, b: B, c: C, d: D) = f(a, b, c, d) - } - -} \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/observables/BlockingObservable.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/observables/BlockingObservable.scala index 3b3562d652..f9e98efa63 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/observables/BlockingObservable.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/observables/BlockingObservable.scala @@ -16,46 +16,135 @@ package rx.lang.scala.observables import scala.collection.JavaConverters._ -import rx.lang.scala.internal.ImplicitFunctionConversions._ +import rx.lang.scala.ImplicitFunctionConversions._ -class BlockingObservable[+T](val asJava: rx.observables.BlockingObservable[_ <: T]) +/** + * An Observable that provides blocking operators. + * + * You can obtain a BlockingObservable from an Observable using [[rx.lang.scala.Observable.toBlockingObservable]] + */ +// constructor is private because users should use Observable.toBlockingObservable +class BlockingObservable[+T] private[scala] (val asJava: rx.observables.BlockingObservable[_ <: T]) extends AnyVal { + /** + * Invoke a method on each item emitted by the {@link Observable}; block until the Observable + * completes. + * + * NOTE: This will block even if the Observable is asynchronous. + * + * This is similar to {@link Observable#subscribe(Observer)}, but it blocks. Because it blocks it does + * not need the {@link Observer#onCompleted()} or {@link Observer#onError(Throwable)} methods. + * + * + * + * @param f + * the {@link Action1} to invoke for every item emitted by the {@link Observable} + * @throws RuntimeException + * if an error occurs + */ def foreach(f: T => Unit): Unit = { asJava.forEach(f) } + + def withFilter(p: T => Boolean): WithFilter[T] = { + new WithFilter[T](p, asJava) + } + + // last -> use toIterable.last + // lastOrDefault -> use toIterable.lastOption + // first -> use toIterable.head + // firstOrDefault -> use toIterable.headOption + // single(predicate) -> use filter and single + // singleOrDefault -> use singleOption - def last: T = { - asJava.last() : T // useless ascription because of compiler bug + /** + * Returns an {@link Iterable} that always returns the item most recently emitted by an {@link Observable}. + *

+ * + * + * @param initialValue + * the initial value that will be yielded by the {@link Iterable} sequence if the {@link Observable} has not yet emitted an item + * @return an {@link Iterable} that on each iteration returns the item that the {@link Observable} has most recently emitted + */ + def mostRecent[U >: T](initialValue: U): Iterable[U] = { + val asJavaU = asJava.asInstanceOf[rx.observables.BlockingObservable[U]] + asJavaU.mostRecent(initialValue).asScala: Iterable[U] // useless ascription because of compiler bug } - // last(Func1) - // lastOrDefault(T) - // lastOrDefault(T, Func1) - // mostRecent(T) - // next() + /** + * Returns an {@link Iterable} that blocks until the {@link Observable} emits another item, + * then returns that item. + *

+ * + * + * @return an {@link Iterable} that blocks upon each iteration until the {@link Observable} emits a new item, whereupon the Iterable returns that item + */ + def next: Iterable[T] = { + asJava.next().asScala: Iterable[T] // useless ascription because of compiler bug + } + /** + * If this {@link Observable} completes after emitting a single item, return that item, + * otherwise throw an exception. + *

+ * + * + * @return the single item emitted by the {@link Observable} + */ def single: T = { - asJava.single() : T // useless ascription because of compiler bug + asJava.single(): T // useless ascription because of compiler bug } + + /** + * If this {@link Observable} completes after emitting a single item, return an Option containing + * this item, otherwise return {@code None}. + */ + def singleOption: Option[T] = { + var size: Int = 0 + var last: Option[T] = None + for (t <- toIterable) { + size += 1 + last = Some(t) + } + if (size == 1) last else None + } + + // TODO toFuture() + + /** + * Returns an {@link Iterator} that iterates over all items emitted by this {@link Observable}. + */ + def toIterable: Iterable[T] = { + asJava.toIterable.asScala: Iterable[T] // useless ascription because of compiler bug + } + + /** + * Returns a {@link List} that contains all items emitted by this {@link Observable}. + */ + def toList: List[T] = { + asJava.toIterable.asScala.toList: List[T] // useless ascription because of compiler bug + } + +} + +// Cannot yet have inner class because of this error message: +// "implementation restriction: nested class is not allowed in value class. +// This restriction is planned to be removed in subsequent releases." +private[observables] class WithFilter[+T] (p: T => Boolean, asJava: rx.observables.BlockingObservable[_ <: T]) { + import rx.lang.scala.ImplicitFunctionConversions._ - // single(Func1) - - // def singleOption: Option[T] = { TODO } - // corresponds to Java's - // singleOrDefault(T) + // there's no map and flatMap here, they're only available on Observable - // singleOrDefault(BlockingObservable, boolean, T) - // singleOrDefault(T, Func1) - // toFuture() + def withFilter(q: T => Boolean) = new WithFilter[T]((x: T) => p(x) && q(x), asJava) - def toIterable: Iterable[T] = { - asJava.toIterable().asScala : Iterable[T] // useless ascription because of compiler bug + def foreach(f: T => Unit): Unit = { + asJava.forEach((e: T) => { + if (p(e)) f(e) + }) } - def toList: List[T] = { - asJava.toIterable().asScala.toList : List[T] // useless ascription because of compiler bug - } +} + -} \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/observables/package.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/observables/package.scala new file mode 100644 index 0000000000..2b43860b53 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/observables/package.scala @@ -0,0 +1,27 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala + +/** + * Contains special Observables. + * + * In Scala, this package only contains [[BlockingObservable]]. + * In the corresponding Java package `rx.observables`, there is also a + * `GroupedObservable` and a `ConnectableObservable`, but these are not needed + * in Scala, because we use a pair `(key, observable)` instead of `GroupedObservable` + * and a pair `(startFunction, observable)` instead of `ConnectableObservable`. + */ +package object observables {} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/package.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/package.scala index 6910599783..0809e1fb2b 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/package.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/package.scala @@ -15,53 +15,34 @@ */ package rx.lang +import java.util.concurrent.TimeUnit +import java.util.Date -/* - * This object contains aliases to all types Scala users need to import. - * Note that: - * - Scala users cannot use Java's type with variance without always using writing - * e.g. rx.Notification[_ <: T], so we create aliases fixing the variance - * - For consistency, we create aliases for all types - * - Type aliases cannot be at top level, they have to be inside an object or class +/** + * This package contains all classes that RxScala users need. + * + * It mirrors the structure of package `rx`, but implementation classes that RxScala users + * will not need are left out. */ package object scala { - type Notification[+T] = rx.Notification[_ <: T] - object Notification { - def apply[T](): Notification[T] = new rx.Notification() - def apply[T](value: T): Notification[T] = new rx.Notification(value) - def apply[T](t: Throwable): Notification[T] = new rx.Notification(t) + /** + * Allows to construct observables in a similar way as futures. + * + * Example: + * + * {{{ + * implicit val scheduler = Schedulers.threadPoolForIO + * val o: Observable[List[Friend]] = observable { + * session.getFriends + * } + * o.subscribe( + * friendList => println(friendList), + * err => println(err.getMessage) + * ) + * }}} + */ + def observable[T](body: => T)(implicit scheduler: Scheduler): Observable[T] = { + Observable(1).observeOn(scheduler).map(_ => body) } - - type Observer[-T] = rx.Observer[_ >: T] - type Scheduler = rx.Scheduler - type Subscription = rx.Subscription - } - -/* - -TODO make aliases for these types because: -* those which are covariant or contravariant do need an alias to get variance correct -* the others for consistency - -rx.observables.BlockingObservable -rx.observables.ConnectableObservable -rx.observables.GroupedObservable - -rx.plugins.RxJavaErrorHandler -rx.plugins.RxJavaObservableExecutionHook -rx.plugins.RxJavaPlugins - -rx.subjects.AsyncSubject -rx.subjects.BehaviorSubject -rx.subjects.PublishSubject -rx.subjects.ReplaySubject -rx.subjects.Subject - -rx.subscriptions.BooleanSubscription -rx.subscriptions.CompositeSubscription -rx.subscriptions.Subscriptions - -*/ - diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/AsyncSubject.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/AsyncSubject.scala new file mode 100644 index 0000000000..80892b9ac1 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/AsyncSubject.scala @@ -0,0 +1,26 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala.subjects + +import rx.lang.scala.Subject + +object AsyncSubject { + def apply[T](): AsyncSubject[T] = { + new AsyncSubject[T](rx.subjects.AsyncSubject.create()) + } +} + +class AsyncSubject[T] private[scala] (val asJavaSubject: rx.subjects.AsyncSubject[T]) extends Subject[T,T] {} \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/BehaviorSubject.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/BehaviorSubject.scala new file mode 100644 index 0000000000..5b358aba9a --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/BehaviorSubject.scala @@ -0,0 +1,30 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala.subjects + +import rx.lang.scala.Subject + +object BehaviorSubject { + def apply[T](value: T): BehaviorSubject[T] = { + new BehaviorSubject[T](rx.subjects.BehaviorSubject.createWithDefaultValue(value)) + } +} + +class BehaviorSubject[T] private[scala] (val asJavaSubject: rx.subjects.BehaviorSubject[T]) extends Subject[T,T] {} + + + + diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/PublishSubject.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/PublishSubject.scala new file mode 100644 index 0000000000..7c06101460 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/PublishSubject.scala @@ -0,0 +1,27 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala.subjects + +import rx.lang.scala.Subject + +object PublishSubject { + def apply[T](): PublishSubject[T] = { + new PublishSubject[T](rx.subjects.PublishSubject.create()) + } +} + +class PublishSubject[T] private[scala] (val asJavaSubject: rx.subjects.PublishSubject[T]) extends Subject[T,T] { + } diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/ReplaySubject.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/ReplaySubject.scala new file mode 100644 index 0000000000..f88fb65280 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/ReplaySubject.scala @@ -0,0 +1,30 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala.subjects + +import rx.lang.scala.Subject + +object ReplaySubject { + def apply[T](): ReplaySubject[T] = { + new ReplaySubject[T](rx.subjects.ReplaySubject.create()) + } +} + +class ReplaySubject[T] private[scala] (val asJavaSubject: rx.subjects.ReplaySubject[T]) extends Subject[T,T] { +} + + + diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/Subject.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/Subject.scala new file mode 100644 index 0000000000..08ba9e404c --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/Subject.scala @@ -0,0 +1,26 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala + +/** +* A Subject is an Observable and an Observer at the same time. +*/ +trait Subject[-T, +R] extends Observable[R] with Observer[T] { + val asJavaSubject: rx.subjects.Subject[_ >: T, _<: R] + def asJavaObservable: rx.Observable[_ <: R] = asJavaSubject + def asJavaObserver: rx.Observer[_ >: T] = asJavaSubject +} + diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/package.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/package.scala new file mode 100644 index 0000000000..7566d5fad8 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subjects/package.scala @@ -0,0 +1,21 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala + +/** + * Subjects are Observers and Observables at the same time. + */ +package object subjects {} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subscriptions/BooleanSubscription.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subscriptions/BooleanSubscription.scala new file mode 100644 index 0000000000..be2cb5f392 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subscriptions/BooleanSubscription.scala @@ -0,0 +1,55 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala.subscriptions + +import rx.lang.scala._ + +object BooleanSubscription { + + /** + * Creates a [[rx.lang.scala.subscriptions.BooleanSubscription]]. + */ + def apply(): BooleanSubscription = { + new BooleanSubscription(new rx.subscriptions.BooleanSubscription()) + } + + /** + * Creates a [[rx.lang.scala.subscriptions.BooleanSubscription]] that invokes the specified action when unsubscribed. + */ + def apply(u: => Unit): BooleanSubscription = { + new BooleanSubscription(new rx.subscriptions.BooleanSubscription { + override def unsubscribe(): Unit = { + if(!super.isUnsubscribed()) { + u + super.unsubscribe() + } + } + }) + } +} + +/** + * Represents a [[rx.lang.scala.Subscription]] that can be checked for status. + */ +class BooleanSubscription private[scala] (val asJavaSubscription: rx.subscriptions.BooleanSubscription) + extends Subscription { + + /** + * Checks whether the subscription has been unsubscribed. + */ + def isUnsubscribed: Boolean = asJavaSubscription.isUnsubscribed + +} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subscriptions/CompositeSubscription.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subscriptions/CompositeSubscription.scala new file mode 100644 index 0000000000..1fe4a4afa5 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subscriptions/CompositeSubscription.scala @@ -0,0 +1,76 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala.subscriptions + +import rx.lang.scala._ + +object CompositeSubscription { + + /** + * Creates a [[rx.lang.scala.subscriptions.CompositeSubscription]] from a group of [[rx.lang.scala.Subscription]]. + */ + def apply(subscriptions: Subscription*): CompositeSubscription = { + new CompositeSubscription(new rx.subscriptions.CompositeSubscription(subscriptions.map(_.asJavaSubscription).toArray : _*)) + } + + /** + * Creates a [[rx.lang.scala.subscriptions.CompositeSubscription]]. + */ + def apply(): CompositeSubscription = { + new CompositeSubscription(new rx.subscriptions.CompositeSubscription()) + } + + /** + * Creates a [[rx.lang.scala.subscriptions.CompositeSubscription]]. + */ + def apply(subscription: rx.subscriptions.CompositeSubscription): CompositeSubscription = { + new CompositeSubscription(subscription) + } +} + +/** + * Represents a group of [[rx.lang.scala.Subscription]] that are disposed together. + */ +class CompositeSubscription private[scala] (val asJavaSubscription: rx.subscriptions.CompositeSubscription) + extends Subscription +{ + /** + * Adds a subscription to the group, + * or unsubscribes immediately is the [[rx.subscriptions.CompositeSubscription]] is unsubscribed. + * @param subscription the subscription to be added. + * @return the [[rx.subscriptions.CompositeSubscription]] itself. + */ + def +=(subscription: Subscription): this.type = { + asJavaSubscription.add(subscription.asJavaSubscription) + this + } + + /** + * Removes and unsubscribes a subscription to the group, + * @param subscription the subscription be removed. + * @return the [[rx.subscriptions.CompositeSubscription]] itself. + */ + def -=(subscription: Subscription): this.type = { + asJavaSubscription.remove(subscription.asJavaSubscription) + this + } + + /** + * Checks whether the subscription has been unsubscribed. + */ + def isUnsubscribed: Boolean = asJavaSubscription.isUnsubscribed + +} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subscriptions/MultiAssignmentSubscription.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subscriptions/MultiAssignmentSubscription.scala new file mode 100644 index 0000000000..84740870c7 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subscriptions/MultiAssignmentSubscription.scala @@ -0,0 +1,69 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala.subscriptions + +import rx.lang.scala._ + +object MultipleAssignmentSubscription { + + /** + * Creates a [[rx.lang.scala.subscriptions.MultipleAssignmentSubscription]] that invokes the specified action when unsubscribed. + */ + def apply(subscription: => Unit): MultipleAssignmentSubscription = { + val m = MultipleAssignmentSubscription() + m.subscription = Subscription{ subscription } + m + } + + /** + * Creates a [[rx.lang.scala.subscriptions.MultipleAssignmentSubscription]]. + */ + def apply(): MultipleAssignmentSubscription = { + new MultipleAssignmentSubscription(new rx.subscriptions.MultipleAssignmentSubscription()) + } +} + + + +/** + * Represents a [[rx.lang.scala.Subscription]] whose underlying subscription can be swapped for another subscription. + */ +class MultipleAssignmentSubscription private[scala] (val asJavaSubscription: rx.subscriptions.MultipleAssignmentSubscription) + extends Subscription { + + /** + * Gets the underlying subscription. + */ + def subscription: Subscription = Subscription(asJavaSubscription.getSubscription) + + /** + * Gets the underlying subscription + * @param that the new subscription + * @return the [[rx.lang.scala.subscriptions.MultipleAssignmentSubscription]] itself. + */ + def subscription_=(that: Subscription): this.type = { + asJavaSubscription.setSubscription(that.asJavaSubscription) + this + } + + /** + * Checks whether the subscription has been unsubscribed. + */ + def isUnsubscribed: Boolean = asJavaSubscription.isUnsubscribed + +} + + diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subscriptions/SerialSubscription.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subscriptions/SerialSubscription.scala new file mode 100644 index 0000000000..94b50f93e8 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subscriptions/SerialSubscription.scala @@ -0,0 +1,63 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala.subscriptions + +import rx.lang.scala.Subscription +import java.util.concurrent.atomic.AtomicBoolean + + +object SerialSubscription { + + /** + * Creates a [[rx.lang.scala.subscriptions.SerialSubscription]]. + */ + def apply(): SerialSubscription = { + new SerialSubscription(new rx.subscriptions.SerialSubscription()) + } + + /** + * Creates a [[rx.lang.scala.subscriptions.SerialSubscription]] that invokes the specified action when unsubscribed. + */ + def apply(unsubscribe: => Unit): SerialSubscription = { + val s= SerialSubscription() + s.subscription = Subscription{ unsubscribe } + s + } +} + +/** + * Represents a [[rx.lang.scala.Subscription]] that can be checked for status. + */ +class SerialSubscription private[scala] (val asJavaSubscription: rx.subscriptions.SerialSubscription) + extends Subscription { + + private val unsubscribed = new AtomicBoolean(false) + + /** + * Checks whether the subscription has been unsubscribed. + */ + def isUnsubscribed: Boolean = unsubscribed.get() + + /** + * Unsubscribes this subscription, setting isUnsubscribed to true. + */ + override def unsubscribe(): Unit = { super.unsubscribe(); unsubscribed.set(true) } + + def subscription_=(value: Subscription): Unit = asJavaSubscription.setSubscription(value.asJavaSubscription) + def subscription: Subscription = Subscription(asJavaSubscription.getSubscription) + +} + diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subscriptions/package.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subscriptions/package.scala new file mode 100644 index 0000000000..c54f83c982 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/subscriptions/package.scala @@ -0,0 +1,21 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.lang.scala + +/** + * Provides `Subscription`, and specialized versions of it. + */ +package object subscriptions {} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/util/package.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/util/package.scala index 6dced72f8e..ed19d849ab 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/util/package.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/util/package.scala @@ -15,34 +15,35 @@ */ package rx.lang.scala +/** + * Provides [[Opening]]s, [[Closing]]s, and [[Timestamped]]. + */ package object util { - type Closing = rx.util.Closing - - object Closings { - def create(): Closing = rx.util.Closings.create() - } - - type CompositeException = rx.util.CompositeException - - // TODO not sure if we need this in Scala - object Exceptions { - def propageate(ex: Throwable) = rx.util.Exceptions.propagate(ex) - } - - // rx.util.OnErrorNotImplementedException TODO what's this? + /** + * Tagging interface for objects which can open buffers. + * @see [[Observable `Observable.buffer(Observable[Opening], Opening => Observable[Closing])`]] + */ type Opening = rx.util.Opening - object Openings { - def create(): Opening = rx.util.Openings.create() - } + /** + * Creates an object which can open buffers. + * @see [[Observable `Observable.buffer(Observable[Opening], Opening => Observable[Closing])`]] + */ + def Opening() = rx.util.Openings.create() + + /** + * Tagging interface for objects which can close buffers. + * @see [[Observable `Observable.buffer(Observable[Opening], Opening => Observable[Closing])`]] + */ + type Closing = rx.util.Closing + /** + * Creates an object which can close buffers. + * @see [[Observable `Observable.buffer(Observable[Opening], Opening => Observable[Closing])`]] + */ + def Closing() = rx.util.Closings.create() + // rx.util.Range not needed because there's a standard Scala Range - - type Timestamped[+T] = rx.util.Timestamped[_ <: T] - object Timestamped { - def apply[T](timestampMillis: Long, value: T): Timestamped[T] = { - new rx.util.Timestamped(timestampMillis, value) - } - } -} \ No newline at end of file + +} diff --git a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/CompletenessTest.scala b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/CompletenessTest.scala new file mode 100644 index 0000000000..f38ac0d521 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/CompletenessTest.scala @@ -0,0 +1,354 @@ +package rx.lang.scala + +import java.util.Calendar + +import scala.collection.SortedMap +import scala.reflect.runtime.universe +import scala.reflect.runtime.universe.Symbol +import scala.reflect.runtime.universe.Type +import scala.reflect.runtime.universe.typeOf + +import org.junit.Ignore +import org.junit.Test +import org.scalatest.junit.JUnitSuite + +/** + * These tests can be used to check if all methods of the Java Observable have a corresponding + * method in the Scala Observable. + * + * These tests don't contain any assertions, so they will always succeed, but they print their + * results to stdout. + */ +class CompletenessTest extends JUnitSuite { + + // some frequently used comments: + val unnecessary = "[considered unnecessary in Scala land]" + val deprecated = "[deprecated in RxJava]" + val averageProblem = "[We can't have a general average method because Scala's `Numeric` does not have " + + "scalar multiplication (we would need to calculate `(1.0/numberOfElements)*sum`). " + + "You can use `fold` instead to accumulate `sum` and `numberOfElements` and divide at the end.]" + val commentForFirstWithPredicate = "[use `.filter(condition).first`]" + val fromFuture = "[TODO: Decide how Scala Futures should relate to Observables. Should there be a " + + "common base interface for Future and Observable? And should Futures also have an unsubscribe method?]" + + /** + * Maps each method from the Java Observable to its corresponding method in the Scala Observable + */ + val correspondence = defaultMethodCorrespondence ++ correspondenceChanges // ++ overrides LHS with RHS + + /** + * Creates default method correspondence mappings, assuming that Scala methods have the same + * name and the same argument types as in Java + */ + def defaultMethodCorrespondence: Map[String, String] = { + val allMethods = getPublicInstanceAndCompanionMethods(typeOf[rx.Observable[_]]) + val tuples = for (javaM <- allMethods) yield (javaM, javaMethodSignatureToScala(javaM)) + tuples.toMap + } + + /** + * Manually added mappings from Java Observable methods to Scala Observable methods + */ + def correspondenceChanges = Map( + // manually added entries for Java instance methods + "aggregate(Func2[T, T, T])" -> "reduce((U, U) => U)", + "aggregate(R, Func2[R, _ >: T, R])" -> "foldLeft(R)((R, T) => R)", + "all(Func1[_ >: T, Boolean])" -> "forall(T => Boolean)", + "buffer(Long, Long, TimeUnit)" -> "buffer(Duration, Duration)", + "buffer(Long, Long, TimeUnit, Scheduler)" -> "buffer(Duration, Duration, Scheduler)", + "count()" -> "length", + "dematerialize()" -> "dematerialize(<:<[Observable[T], Observable[Notification[U]]])", + "elementAt(Int)" -> "[use `.drop(index).first`]", + "elementAtOrDefault(Int, T)" -> "[use `.drop(index).firstOrElse(default)`]", + "first(Func1[_ >: T, Boolean])" -> commentForFirstWithPredicate, + "firstOrDefault(T)" -> "firstOrElse(=> U)", + "firstOrDefault(Func1[_ >: T, Boolean], T)" -> "[use `.filter(condition).firstOrElse(default)`]", + "groupBy(Func1[_ >: T, _ <: K], Func1[_ >: T, _ <: R])" -> "[use `groupBy` and `map`]", + "mapMany(Func1[_ >: T, _ <: Observable[_ <: R]])" -> "flatMap(T => Observable[R])", + "mapWithIndex(Func2[_ >: T, Integer, _ <: R])" -> "[combine `zipWithIndex` with `map` or with a for comprehension]", + "onErrorResumeNext(Func1[Throwable, _ <: Observable[_ <: T]])" -> "onErrorResumeNext(Throwable => Observable[U])", + "onErrorResumeNext(Observable[_ <: T])" -> "onErrorResumeNext(Observable[U])", + "onErrorReturn(Func1[Throwable, _ <: T])" -> "onErrorReturn(Throwable => U)", + "onExceptionResumeNext(Observable[_ <: T])" -> "onExceptionResumeNext(Observable[U])", + "parallel(Func1[Observable[T], Observable[R]])" -> "parallel(Observable[T] => Observable[R])", + "parallel(Func1[Observable[T], Observable[R]], Scheduler)" -> "parallel(Observable[T] => Observable[R], Scheduler)", + "reduce(Func2[T, T, T])" -> "reduce((U, U) => U)", + "reduce(R, Func2[R, _ >: T, R])" -> "foldLeft(R)((R, T) => R)", + "scan(Func2[T, T, T])" -> unnecessary, + "scan(R, Func2[R, _ >: T, R])" -> "scan(R)((R, T) => R)", + "skip(Int)" -> "drop(Int)", + "skipWhile(Func1[_ >: T, Boolean])" -> "dropWhile(T => Boolean)", + "skipWhileWithIndex(Func2[_ >: T, Integer, Boolean])" -> unnecessary, + "startWith(Iterable[T])" -> "[unnecessary because we can just use `++` instead]", + "takeFirst()" -> "first", + "takeFirst(Func1[_ >: T, Boolean])" -> commentForFirstWithPredicate, + "takeLast(Int)" -> "takeRight(Int)", + "takeWhileWithIndex(Func2[_ >: T, _ >: Integer, Boolean])" -> "[use `.zipWithIndex.takeWhile{case (elem, index) => condition}.map(_._1)`]", + "toList()" -> "toSeq", + "toSortedList()" -> "[Sorting is already done in Scala's collection library, use `.toSeq.map(_.sorted)`]", + "toSortedList(Func2[_ >: T, _ >: T, Integer])" -> "[Sorting is already done in Scala's collection library, use `.toSeq.map(_.sortWith(f))`]", + "where(Func1[_ >: T, Boolean])" -> "filter(T => Boolean)", + "window(Long, Long, TimeUnit)" -> "window(Duration, Duration)", + "window(Long, Long, TimeUnit, Scheduler)" -> "window(Duration, Duration, Scheduler)", + + // manually added entries for Java static methods + "average(Observable[Integer])" -> averageProblem, + "averageDoubles(Observable[Double])" -> averageProblem, + "averageFloats(Observable[Float])" -> averageProblem, + "averageLongs(Observable[Long])" -> averageProblem, + "create(OnSubscribeFunc[T])" -> "apply(Observer[T] => Subscription)", + "combineLatest(Observable[_ <: T1], Observable[_ <: T2], Func2[_ >: T1, _ >: T2, _ <: R])" -> "combineLatest(Observable[U])", + "concat(Observable[_ <: Observable[_ <: T]])" -> "concat(<:<[Observable[T], Observable[Observable[U]]])", + "defer(Func0[_ <: Observable[_ <: T]])" -> "defer(=> Observable[T])", + "empty()" -> "apply(T*)", + "error(Throwable)" -> "apply(Throwable)", + "from(Array[T])" -> "apply(T*)", + "from(Iterable[_ <: T])" -> "apply(T*)", + "from(Future[_ <: T])" -> fromFuture, + "from(Future[_ <: T], Long, TimeUnit)" -> fromFuture, + "from(Future[_ <: T], Scheduler)" -> fromFuture, + "just(T)" -> "apply(T*)", + "merge(Observable[_ <: T], Observable[_ <: T])" -> "merge(Observable[U])", + "merge(Observable[_ <: Observable[_ <: T]])" -> "flatten(<:<[Observable[T], Observable[Observable[U]]])", + "mergeDelayError(Observable[_ <: T], Observable[_ <: T])" -> "mergeDelayError(Observable[U])", + "mergeDelayError(Observable[_ <: Observable[_ <: T]])" -> "flattenDelayError(<:<[Observable[T], Observable[Observable[U]]])", + "range(Int, Int)" -> "apply(Range)", + "sequenceEqual(Observable[_ <: T], Observable[_ <: T])" -> "[use `(first zip second) map (p => p._1 == p._2)`]", + "sequenceEqual(Observable[_ <: T], Observable[_ <: T], Func2[_ >: T, _ >: T, Boolean])" -> "[use `(first zip second) map (p => equality(p._1, p._2))`]", + "sum(Observable[Integer])" -> "sum(Numeric[U])", + "sumDoubles(Observable[Double])" -> "sum(Numeric[U])", + "sumFloats(Observable[Float])" -> "sum(Numeric[U])", + "sumLongs(Observable[Long])" -> "sum(Numeric[U])", + "synchronize(Observable[T])" -> "synchronize", + "switchDo(Observable[_ <: Observable[_ <: T]])" -> deprecated, + "switchOnNext(Observable[_ <: Observable[_ <: T]])" -> "switch(<:<[Observable[T], Observable[Observable[U]]])", + "zip(Observable[_ <: T1], Observable[_ <: T2], Func2[_ >: T1, _ >: T2, _ <: R])" -> "[use instance method `zip` and `map`]", + "zip(Observable[_ <: Observable[_]], FuncN[_ <: R])" -> "[use `zip` in companion object and `map`]", + "zip(Iterable[_ <: Observable[_]], FuncN[_ <: R])" -> "[use `zip` in companion object and `map`]" + ) ++ List.iterate("T", 9)(s => s + ", T").map( + // all 9 overloads of startWith: + "startWith(" + _ + ")" -> "[unnecessary because we can just use `++` instead]" + ).toMap ++ List.iterate("Observable[_ <: T]", 9)(s => s + ", Observable[_ <: T]").map( + // concat 2-9 + "concat(" + _ + ")" -> "[unnecessary because we can use `++` instead or `Observable(o1, o2, ...).concat`]" + ).drop(1).toMap ++ List.iterate("T", 10)(s => s + ", T").map( + // all 10 overloads of from: + "from(" + _ + ")" -> "apply(T*)" + ).toMap ++ (3 to 9).map(i => { + // zip3-9: + val obsArgs = (1 to i).map(j => s"Observable[_ <: T$j], ").mkString("") + val funcParams = (1 to i).map(j => s"_ >: T$j, ").mkString("") + ("zip(" + obsArgs + "Func" + i + "[" + funcParams + "_ <: R])", unnecessary) + }).toMap ++ List.iterate("Observable[_ <: T]", 9)(s => s + ", Observable[_ <: T]").map( + // merge 3-9: + "merge(" + _ + ")" -> "[unnecessary because we can use `Observable(o1, o2, ...).flatten` instead]" + ).drop(2).toMap ++ List.iterate("Observable[_ <: T]", 9)(s => s + ", Observable[_ <: T]").map( + // mergeDelayError 3-9: + "mergeDelayError(" + _ + ")" -> "[unnecessary because we can use `Observable(o1, o2, ...).flattenDelayError` instead]" + ).drop(2).toMap ++ (3 to 9).map(i => { + // combineLatest 3-9: + val obsArgs = (1 to i).map(j => s"Observable[_ <: T$j], ").mkString("") + val funcParams = (1 to i).map(j => s"_ >: T$j, ").mkString("") + ("combineLatest(" + obsArgs + "Func" + i + "[" + funcParams + "_ <: R])", "[If C# doesn't need it, Scala doesn't need it either ;-)]") + }).toMap + + def removePackage(s: String) = s.replaceAll("(\\w+\\.)+(\\w+)", "$2") + + def methodMembersToMethodStrings(members: Iterable[Symbol]): Iterable[String] = { + for (member <- members; alt <- member.asTerm.alternatives) yield { + val m = alt.asMethod + // multiple parameter lists in case of curried functions + val paramListStrs = for (paramList <- m.paramss) yield { + paramList.map( + symb => removePackage(symb.typeSignature.toString.replaceAll(",(\\S)", ", $1")) + ).mkString("(", ", ", ")") + } + val name = alt.asMethod.name.decoded + name + paramListStrs.mkString("") + } + } + + def getPublicInstanceMethods(tp: Type): Iterable[String] = { + // declarations: => only those declared in Observable + // members => also those of superclasses + methodMembersToMethodStrings(tp.declarations.filter(m => m.isMethod && m.isPublic)) + // TODO how can we filter out instance methods which were put into companion because + // of extends AnyVal in a way which does not depend on implementation-chosen name '$extension'? + .filter(! _.contains("$extension")) + } + + // also applicable for Java types + def getPublicInstanceAndCompanionMethods(tp: Type): Iterable[String] = + getPublicInstanceMethods(tp) ++ + getPublicInstanceMethods(tp.typeSymbol.companionSymbol.typeSignature) + + def printMethodSet(title: String, tp: Type) { + println("\n" + title) + println(title.map(_ => '-') + "\n") + getPublicInstanceMethods(tp).toList.sorted.foreach(println(_)) + } + + @Ignore // because spams output + @Test def printJavaInstanceMethods: Unit = { + printMethodSet("Instance methods of rx.Observable", + typeOf[rx.Observable[_]]) + } + + @Ignore // because spams output + @Test def printScalaInstanceMethods: Unit = { + printMethodSet("Instance methods of rx.lang.scala.Observable", + typeOf[rx.lang.scala.Observable[_]]) + } + + @Ignore // because spams output + @Test def printJavaStaticMethods: Unit = { + printMethodSet("Static methods of rx.Observable", + typeOf[rx.Observable[_]].typeSymbol.companionSymbol.typeSignature) + } + + @Ignore // because spams output + @Test def printScalaCompanionMethods: Unit = { + printMethodSet("Companion methods of rx.lang.scala.Observable", + typeOf[rx.lang.scala.Observable.type]) + } + + def javaMethodSignatureToScala(s: String): String = { + s.replaceAllLiterally("Long, TimeUnit", "Duration") + .replaceAll("Action0", "() => Unit") + // nested [] can't be parsed with regex, so these will have to be added manually + .replaceAll("Action1\\[([^]]*)\\]", "$1 => Unit") + .replaceAll("Action2\\[([^]]*), ([^]]*)\\]", "($1, $2) => Unit") + .replaceAll("Func0\\[([^]]*)\\]", "() => $1") + .replaceAll("Func1\\[([^]]*), ([^]]*)\\]", "$1 => $2") + .replaceAll("Func2\\[([^]]*), ([^]]*), ([^]]*)\\]", "($1, $2) => $3") + .replaceAllLiterally("_ <: ", "") + .replaceAllLiterally("_ >: ", "") + .replaceAll("(\\w+)\\(\\)", "$1") + } + + @Ignore // because spams output + @Test def printDefaultMethodCorrespondence: Unit = { + println("\nDefault Method Correspondence") + println( "-----------------------------\n") + val c = SortedMap(defaultMethodCorrespondence.toSeq : _*) + val len = c.keys.map(_.length).max + 2 + for ((javaM, scalaM) <- c) { + println(s""" %-${len}s -> %s,""".format("\"" + javaM + "\"", "\"" + scalaM + "\"")) + } + } + + @Ignore // because spams output + @Test def printCorrectedMethodCorrespondence: Unit = { + println("\nCorrected Method Correspondence") + println( "-------------------------------\n") + val c = SortedMap(correspondence.toSeq : _*) + for ((javaM, scalaM) <- c) { + println("%s -> %s,".format("\"" + javaM + "\"", "\"" + scalaM + "\"")) + } + } + + def checkMethodPresence(expectedMethods: Iterable[String], tp: Type): Unit = { + val actualMethods = getPublicInstanceAndCompanionMethods(tp).toSet + val expMethodsSorted = expectedMethods.toList.sorted + var good = 0 + var bad = 0 + for (m <- expMethodsSorted) if (actualMethods.contains(m) || m.charAt(0) == '[') { + good += 1 + } else { + bad += 1 + println(s"Warning: $m is NOT present in $tp") + } + val status = if (bad == 0) "SUCCESS" else "BAD" + println(s"$status: $bad out of ${bad+good} methods were not found in $tp") + } + + @Test def checkScalaMethodPresenceVerbose: Unit = { + println("\nTesting that all mentioned Scala methods exist") + println( "----------------------------------------------\n") + + val actualMethods = getPublicInstanceAndCompanionMethods(typeOf[rx.lang.scala.Observable[_]]).toSet + var good = 0 + var bad = 0 + for ((javaM, scalaM) <- SortedMap(correspondence.toSeq :_*)) { + if (actualMethods.contains(scalaM) || scalaM.charAt(0) == '[') { + good += 1 + } else { + bad += 1 + println(s"Warning:") + println(s"$scalaM is NOT present in Scala Observable") + println(s"$javaM is the method in Java Observable generating this warning") + } + } + val status = if (bad == 0) "SUCCESS" else "BAD" + println(s"\n$status: $bad out of ${bad+good} methods were not found in Scala Observable") + } + + def setTodoForMissingMethods(corresp: Map[String, String]): Map[String, String] = { + val actualMethods = getPublicInstanceAndCompanionMethods(typeOf[rx.lang.scala.Observable[_]]).toSet + for ((javaM, scalaM) <- corresp) yield + (javaM, if (actualMethods.contains(scalaM) || scalaM.charAt(0) == '[') scalaM else "[**TODO: missing**]") + } + + @Test def checkJavaMethodPresence: Unit = { + println("\nTesting that all mentioned Java methods exist") + println( "---------------------------------------------\n") + checkMethodPresence(correspondence.keys, typeOf[rx.Observable[_]]) + } + + @Ignore // because we prefer the verbose version + @Test def checkScalaMethodPresence: Unit = { + checkMethodPresence(correspondence.values, typeOf[rx.lang.scala.Observable[_]]) + } + + def scalaToJavaSignature(s: String) = + s.replaceAllLiterally("_ <:", "? extends") + .replaceAllLiterally("_ >:", "? super") + .replaceAllLiterally("[", "<") + .replaceAllLiterally("]", ">") + .replaceAllLiterally("Array", "T[]") + + def escapeJava(s: String) = + s.replaceAllLiterally("<", "<") + .replaceAllLiterally(">", ">") + + @Ignore // because spams output + @Test def printMarkdownCorrespondenceTable() { + def isInteresting(p: (String, String)): Boolean = + p._1.replaceAllLiterally("()", "") != p._2 + def groupingKey(p: (String, String)): (String, String) = + (if (p._1.startsWith("average")) "average" else p._1.takeWhile(_ != '('), p._2) + def formatJavaCol(name: String, alternatives: Iterable[String]): String = { + alternatives.toList.sorted.map(scalaToJavaSignature(_)).map(s => { + if (s.length > 64) { + val toolTip = escapeJava(s) + "" + name + "(...)" + } else { + "`" + s + "`" + } + }).mkString("
") + } + def formatScalaCol(s: String): String = + if (s.startsWith("[") && s.endsWith("]")) s.drop(1).dropRight(1) else "`" + s + "`" + def escape(s: String) = s.replaceAllLiterally("[", "<").replaceAllLiterally("]", ">") + + println(""" +## Comparison of Scala Observable and Java Observable + +Note: +* This table contains both static methods and instance methods. +* If a signature is too long, move your mouse over it to get the full signature. + + +| Java Method | Scala Method | +|-------------|--------------|""") + + val ps = setTodoForMissingMethods(correspondence) + + (for (((javaName, scalaCol), pairs) <- ps.groupBy(groupingKey(_)).toList.sortBy(_._1._1)) yield { + "| " + formatJavaCol(javaName, pairs.map(_._1)) + " | " + formatScalaCol(scalaCol) + " |" + }).foreach(println(_)) + println(s"\nThis table was generated on ${Calendar.getInstance().getTime()}.") + println(s"**Do not edit**. Instead, edit `${getClass().getCanonicalName()}`.") + } + +} diff --git a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/ObservableTest.scala b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/ObservableTest.scala new file mode 100644 index 0000000000..d96b23fe43 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/ObservableTest.scala @@ -0,0 +1,88 @@ +package rx.lang.scala + +import org.junit.Assert._ +import org.junit.{ Ignore, Test } +import org.scalatest.junit.JUnitSuite + +class ObservableTests extends JUnitSuite { + + // Tests which needn't be run: + + @Ignore + def testCovariance = { + //println("hey, you shouldn't run this test") + + val o1: Observable[Nothing] = Observable() + val o2: Observable[Int] = o1 + val o3: Observable[App] = o1 + val o4: Observable[Any] = o2 + val o5: Observable[Any] = o3 + } + + // Tests which have to be run: + + @Test + def testDematerialize() { + val o = Observable(1, 2, 3) + val mat = o.materialize + val demat = mat.dematerialize + + // correctly rejected: + //val wrongDemat = Observable("hello").dematerialize + + assertEquals(demat.toBlockingObservable.toIterable.toList, List(1, 2, 3)) + } + + // Test that Java's firstOrDefault propagates errors. + // If this changes (i.e. it suppresses errors and returns default) then Scala's firstOrElse + // should be changed accordingly. + @Test def testJavaFirstOrDefault() { + assertEquals(1, rx.Observable.from(1, 2).firstOrDefault(10).toBlockingObservable().single) + assertEquals(10, rx.Observable.empty().firstOrDefault(10).toBlockingObservable().single) + val msg = "msg6251" + var receivedMsg = "none" + try { + rx.Observable.error(new Exception(msg)).firstOrDefault(10).toBlockingObservable().single + } catch { + case e: Exception => receivedMsg = e.getCause().getMessage() + } + assertEquals(receivedMsg, msg) + } + + @Test def testFirstOrElse() { + def mustNotBeCalled: String = sys.error("this method should not be called") + def mustBeCalled: String = "this is the default value" + assertEquals("hello", Observable("hello").firstOrElse(mustNotBeCalled).toBlockingObservable.single) + assertEquals("this is the default value", Observable().firstOrElse(mustBeCalled).toBlockingObservable.single) + } + + @Test def testFirstOrElseWithError() { + val msg = "msg6251" + var receivedMsg = "none" + try { + Observable[Int](new Exception(msg)).firstOrElse(10).toBlockingObservable.single + } catch { + case e: Exception => receivedMsg = e.getCause().getMessage() + } + assertEquals(receivedMsg, msg) + } + + /* + @Test def testHead() { + val observer = mock(classOf[Observer[Int]]) + val o = Observable().head + val sub = o.subscribe(observer) + + verify(observer, never).onNext(any(classOf[Int])) + verify(observer, never).onCompleted() + verify(observer, times(1)).onError(any(classOf[NoSuchElementException])) + } + */ + + @Test def testTest() = { + val a: Observable[Int] = Observable() + assertEquals(4, Observable(1, 2, 3, 4).toBlockingObservable.toIterable.last) + //println("This UnitTestSuite.testTest() for rx.lang.scala.Observable") + } + +} diff --git a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/RxImplicitsTests.scala b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/RxImplicitsTests.scala deleted file mode 100644 index c2252d2ee6..0000000000 --- a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/RxImplicitsTests.scala +++ /dev/null @@ -1,474 +0,0 @@ -/** - * Copyright 2013 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.lang.scala - -import org.scalatest.junit.JUnitSuite - -/** - * This is the test suite for the old Scala adaptor. - */ -class OldUnitTestSuite extends JUnitSuite { - import rx.lang.scala.RxImplicits._ - - import org.junit.{ Before, Test } - import org.junit.Assert._ - import org.mockito.Matchers.any - import org.mockito.Mockito._ - import org.mockito.{ MockitoAnnotations, Mock } - import rx.{ Notification, Observer, Observable, Subscription } - import rx.Observable.OnSubscribeFunc - import rx.observables.GroupedObservable - import rx.subscriptions.Subscriptions - import collection.mutable.ArrayBuffer - import collection.JavaConverters._ - - @Mock private[this] - val observer: Observer[Any] = null - - @Mock private[this] - val subscription: Subscription = null - - val isOdd = (i: Int) => i % 2 == 1 - val isEven = (i: Int) => i % 2 == 0 - - class OnSubscribeWithException(s: Subscription, values: String*) extends OnSubscribeFunc[String] { - var t: Thread = null - - override def onSubscribe(observer: Observer[_ >: String]): Subscription = { - println("ObservableWithException subscribed to ...") - try { - println("running ObservableWithException thread") - values.toList.foreach(v => { - println("ObservableWithException onNext: " + v) - observer.onNext(v) - }) - throw new RuntimeException("Forced Failure") - } catch { - case ex: Exception => observer.onError(ex) - } - s - } - } - - @Before def before { - MockitoAnnotations.initMocks(this) - } - - // tests of static methods - @Test def testSingle { - assertEquals(1, Observable.from(1).toBlockingObservable.single) - } - - @Test def testSinglePredicate { - val found = Observable.from(1, 2, 3).toBlockingObservable.single(isEven) - assertEquals(2, found) - } - - @Test def testSingleOrDefault { - assertEquals(0, Observable.empty[Int]().toBlockingObservable.singleOrDefault(0)) - assertEquals(1, Observable.from(1).toBlockingObservable.singleOrDefault(0)) - try { - Observable.from(1, 2, 3).toBlockingObservable.singleOrDefault(0) - fail("Did not catch any exception, expected IllegalStateException") - } catch { - case ex: IllegalStateException => println("Caught expected IllegalStateException") - case ex: Throwable => fail("Caught unexpected exception " + ex.getCause + ", expected IllegalStateException") - } - } - - @Test def testSingleOrDefaultPredicate { - assertEquals(2, Observable.from(1, 2, 3).toBlockingObservable.singleOrDefault(0, isEven)) - assertEquals(0, Observable.from(1, 3).toBlockingObservable.singleOrDefault(0, isEven)) - try { - Observable.from(1, 2, 3).toBlockingObservable.singleOrDefault(0, isOdd) - fail("Did not catch any exception, expected IllegalStateException") - } catch { - case ex: IllegalStateException => println("Caught expected IllegalStateException") - case ex: Throwable => fail("Caught unexpected exception " + ex.getCause + ", expected IllegalStateException") - } - } - - @Test def testCreateFromOnSubscribeFunc { - val created = Observable.create((o: Observer[_ >: Integer]) => Subscriptions.empty) - //no assertions on subscription, just testing the implicit - } - - @Test def testFromJavaInterop { - val observable = Observable.from(List(1, 2, 3).asJava) - assertSubscribeReceives(observable)(1, 2, 3) - } - - @Test def testSubscribe { - val observable = Observable.from("1", "2", "3") - assertSubscribeReceives(observable)("1", "2", "3") - } - - //should not compile - adapted from https://gist.github.com/jmhofer/5195589 - /*@Test def testSubscribeOnInt() { - val observable = Observable.from("1", "2", "3") - observable.subscribe((arg: Int) => { - println("testSubscribe: arg = " + arg) - }) - }*/ - - @Test def testDefer { - val lazyObservableFactory = () => Observable.from(1, 2) - val observable = Observable.defer(lazyObservableFactory) - assertSubscribeReceives(observable)(1, 2) - } - - @Test def testJust { - val observable = Observable.just("foo") - assertSubscribeReceives(observable)("foo") - } - - @Test def testMerge { - val observable1 = Observable.from(1, 2, 3) - val observable2 = Observable.from(4, 5, 6) - val merged = Observable.merge(observable1, observable2) - assertSubscribeReceives(merged)(1, 2, 3, 4, 5, 6) - } - - @Test def testFlattenMerge { - val observable = Observable.from(Observable.from(1, 2, 3)) - val merged = Observable.merge[Int](observable) - assertSubscribeReceives(merged)(1, 2, 3) - } - - @Test def testSequenceMerge { - val observable1 = Observable.from(1, 2, 3) - val observable2 = Observable.from(4, 5, 6) - val merged = Observable.merge(observable1, observable2) - assertSubscribeReceives(merged)(1, 2, 3, 4, 5, 6) - } - - @Test def testConcat { - val observable1 = Observable.from(1, 2, 3) - val observable2 = Observable.from(4, 5, 6) - val concatenated = Observable.concat(observable1, observable2) - assertSubscribeReceives(concatenated)(1, 2, 3, 4, 5, 6) - } - - @Test def testSynchronize { - val observable = Observable.from(1, 2, 3) - val synchronized = Observable.synchronize(observable) - assertSubscribeReceives(synchronized)(1, 2, 3) - } - - @Test def testZip2() { - val colors: Observable[String] = Observable.from("red", "green", "blue") - val names: Observable[String] = Observable.from("lion-o", "cheetara", "panthro") - - case class Character(color: String, name: String) - - val cheetara = Character("green", "cheetara") - val panthro = Character("blue", "panthro") - val characters = Observable.zip[String, String, Character](colors, names, Character.apply _) - assertSubscribeReceives(characters)(cheetara, panthro) - } - - @Test def testZip3() { - val numbers = Observable.from(1, 2, 3) - val colors = Observable.from("red", "green", "blue") - val names = Observable.from("lion-o", "cheetara", "panthro") - - case class Character(id: Int, color: String, name: String) - - val liono = Character(1, "red", "lion-o") - val cheetara = Character(2, "green", "cheetara") - val panthro = Character(3, "blue", "panthro") - - val characters = Observable.zip[Int, String, String, Character](numbers, colors, names, Character.apply _) - assertSubscribeReceives(characters)(liono, cheetara, panthro) - } - - @Test def testZip4() { - val numbers = Observable.from(1, 2, 3) - val colors = Observable.from("red", "green", "blue") - val names = Observable.from("lion-o", "cheetara", "panthro") - val isLeader = Observable.from(true, false, false) - - case class Character(id: Int, color: String, name: String, isLeader: Boolean) - - val liono = Character(1, "red", "lion-o", true) - val cheetara = Character(2, "green", "cheetara", false) - val panthro = Character(3, "blue", "panthro", false) - - val characters = Observable.zip[Int, String, String, Boolean, Character](numbers, colors, names, isLeader, Character.apply _) - assertSubscribeReceives(characters)(liono, cheetara, panthro) - } - - //tests of instance methods - - // missing tests for : takeUntil, groupBy, next, mostRecent - - @Test def testFilter { - val numbers = Observable.from(1, 2, 3, 4, 5, 6, 7, 8, 9) - val observable = numbers.filter(isEven) - assertSubscribeReceives(observable)(2, 4, 6, 8) - } - - @Test def testLast { - val observable = Observable.from(1, 2, 3, 4) - assertEquals(4, observable.toBlockingObservable.last) - } - - @Test def testLastPredicate { - val observable = Observable.from(1, 2, 3, 4) - assertEquals(3, observable.toBlockingObservable.last(isOdd)) - } - - @Test def testLastOrDefault { - val observable = Observable.from(1, 2, 3, 4) - assertEquals(4, observable.toBlockingObservable.lastOrDefault(5)) - assertEquals(5, Observable.empty[Int]().toBlockingObservable.lastOrDefault(5)) - } - - @Test def testLastOrDefaultPredicate { - val observable = Observable.from(1, 2, 3, 4) - assertEquals(3, observable.toBlockingObservable.lastOrDefault(5, isOdd)) - assertEquals(5, Observable.empty[Int]().toBlockingObservable.lastOrDefault(5, isOdd)) - } - - @Test def testMap { - val numbers = Observable.from(1, 2, 3, 4, 5, 6, 7, 8, 9) - val mappedNumbers = ArrayBuffer.empty[Int] - val mapped: Observable[Int] = numbers map ((x: Int) => x * x) - mapped.subscribe((squareVal: Int) => { - mappedNumbers.append(squareVal) - }) - assertEquals(List(1, 4, 9, 16, 25, 36, 49, 64, 81), mappedNumbers.toList) - } - - @Test def testMapMany { - val numbers = Observable.from(1, 2, 3, 4) - val f = (i: Int) => Observable.from(List(i, -i).asJava) - val mappedNumbers = ArrayBuffer.empty[Int] - numbers.mapMany(f).subscribe((i: Int) => { - mappedNumbers.append(i) - }) - assertEquals(List(1, -1, 2, -2, 3, -3, 4, -4), mappedNumbers.toList) - } - - @Test def testMaterialize { - val observable = Observable.from(1, 2, 3, 4) - val expectedNotifications: List[Notification[Int]] = - ((1.to(4).map(i => new Notification(i))) :+ new Notification()).toList - val actualNotifications: ArrayBuffer[Notification[Int]] = ArrayBuffer.empty - observable.materialize.subscribe((n: Notification[Int]) => { - actualNotifications.append(n) - }) - assertEquals(expectedNotifications, actualNotifications.toList) - } - - @Test def testDematerialize { - val notifications: List[Notification[Int]] = - ((1.to(4).map(i => new Notification(i))) :+ new Notification()).toList - val observableNotifications: Observable[Notification[Int]] = - Observable.from(notifications.asJava) - val observable: Observable[Int] = - observableNotifications.dematerialize() - assertSubscribeReceives(observable)(1, 2, 3, 4) - } - - @Test def testOnErrorResumeNextObservableNoError { - val observable = Observable.from(1, 2, 3, 4) - val resumeObservable = Observable.from(5, 6, 7, 8) - val observableWithErrorHandler = observable.onErrorResumeNext(resumeObservable) - assertSubscribeReceives(observableWithErrorHandler)(1, 2, 3, 4) - } - - @Test def testOnErrorResumeNextObservableErrorOccurs { - val observable = Observable.create(new OnSubscribeWithException(subscription, "foo", "bar")) - val resumeObservable = Observable.from("a", "b", "c", "d") - val observableWithErrorHandler = observable.onErrorResumeNext(resumeObservable) - observableWithErrorHandler.subscribe(observer.asInstanceOf[Observer[String]]) - - List("foo", "bar", "a", "b", "c", "d").foreach(t => verify(observer, times(1)).onNext(t)) - verify(observer, never()).onError(any(classOf[Exception])) - verify(observer, times(1)).onCompleted() - } - - @Test def testOnErrorResumeNextFuncNoError { - val observable = Observable.from(1, 2, 3, 4) - val resumeFunc = (ex: Throwable) => Observable.from(5, 6, 7, 8) - val observableWithErrorHandler = observable.onErrorResumeNext(resumeFunc) - assertSubscribeReceives(observableWithErrorHandler)(1, 2, 3, 4) - } - - @Test def testOnErrorResumeNextFuncErrorOccurs { - val observable = Observable.create(new OnSubscribeWithException(subscription, "foo", "bar")) - val resumeFunc = (ex: Throwable) => Observable.from("a", "b", "c", "d") - val observableWithErrorHandler = observable.onErrorResumeNext(resumeFunc) - observableWithErrorHandler.subscribe(observer.asInstanceOf[Observer[String]]) - - List("foo", "bar", "a", "b", "c", "d").foreach(t => verify(observer, times(1)).onNext(t)) - verify(observer, never()).onError(any(classOf[Exception])) - verify(observer, times(1)).onCompleted() - } - - @Test def testOnErrorReturnFuncNoError { - val observable = Observable.from(1, 2, 3, 4) - val returnFunc = (ex: Throwable) => 87 - val observableWithErrorHandler = observable.onErrorReturn(returnFunc) - assertSubscribeReceives(observableWithErrorHandler)(1, 2, 3, 4) - } - - @Test def testOnErrorReturnFuncErrorOccurs { - val observable = Observable.create(new OnSubscribeWithException(subscription, "foo", "bar")) - val returnFunc = (ex: Throwable) => "baz" - val observableWithErrorHandler = observable.onErrorReturn(returnFunc) - observableWithErrorHandler.subscribe(observer.asInstanceOf[Observer[String]]) - - List("foo", "bar", "baz").foreach(t => verify(observer, times(1)).onNext(t)) - verify(observer, never()).onError(any(classOf[Exception])) - verify(observer, times(1)).onCompleted() - } - - @Test def testReduce { - val observable = Observable.from(1, 2, 3, 4) - assertEquals(10, observable.reduce((a: Int, b: Int) => a + b).toBlockingObservable.single) - } - - @Test def testSkip { - val observable = Observable.from(1, 2, 3, 4) - val skipped = observable.skip(2) - assertSubscribeReceives(skipped)(3, 4) - } - - @Test def testTake { - val observable = Observable.from(1, 2, 3, 4, 5) - val took = observable.take(2) - assertSubscribeReceives(took)(1, 2) - } - - @Test def testTakeWhile { - val observable = Observable.from(1, 3, 5, 6, 7, 9, 11) - val took = observable.takeWhile(isOdd) - assertSubscribeReceives(took)(1, 3, 5) - } - - @Test def testTakeWhileWithIndex { - val observable = Observable.from(1, 3, 5, 7, 9, 11, 12, 13, 15, 17) - val took = observable.takeWhileWithIndex((i: Int, idx: Int) => isOdd(i) && idx < 8) - assertSubscribeReceives(took)(1, 3, 5, 7, 9, 11) - } - - @Test def testTakeLast { - val observable = Observable.from(1, 2, 3, 4, 5, 6, 7, 8, 9) - val tookLast = observable.takeLast(3) - assertSubscribeReceives(tookLast)(7, 8, 9) - } - - @Test def testToList { - val observable = Observable.from(1, 2, 3, 4) - val toList = observable.toList - assertSubscribeReceives(toList)(List(1, 2, 3, 4).asJava) - } - - @Test def testToSortedList { - val observable = Observable.from(1, 3, 4, 2) - val toSortedList = observable.toSortedList - assertSubscribeReceives(toSortedList)(List(1, 2, 3, 4).asJava) - } - - @Test def testToArbitrarySortedList { - val observable = Observable.from("a", "aaa", "aaaa", "aa") - val sortByLength = (s1: String, s2: String) => s1.length.compareTo(s2.length) - val toSortedList = observable.toSortedList(sortByLength) - assertSubscribeReceives(toSortedList)(List("a", "aa", "aaa", "aaaa").asJava) - } - - @Test def testToIterable { - val observable = Observable.from(1, 2) - val it = observable.toBlockingObservable.toIterable.iterator - assertTrue(it.hasNext) - assertEquals(1, it.next) - assertTrue(it.hasNext) - assertEquals(2, it.next) - assertFalse(it.hasNext) - } - - @Test def testStartWith { - val observable = Observable.from(1, 2, 3, 4) - val newStart = observable.startWith(-1, 0) - assertSubscribeReceives(newStart)(-1, 0, 1, 2, 3, 4) - } - - @Test def testOneLineForComprehension { - val mappedObservable = for { - i: Int <- Observable.from(1, 2, 3, 4) - } yield i + 1 - assertSubscribeReceives(mappedObservable)(2, 3, 4, 5) - assertFalse(mappedObservable.isInstanceOf[ScalaObservable[_]]) - } - - @Test def testSimpleMultiLineForComprehension { - val flatMappedObservable = for { - i: Int <- Observable.from(1, 2, 3, 4) - j: Int <- Observable.from(1, 10, 100, 1000) - } yield i + j - assertSubscribeReceives(flatMappedObservable)(2, 12, 103, 1004) - assertFalse(flatMappedObservable.isInstanceOf[ScalaObservable[_]]) - } - - @Test def testMultiLineForComprehension { - val doubler = (i: Int) => Observable.from(i, i) - val flatMappedObservable = for { - i: Int <- Observable.from(1, 2, 3, 4) - j: Int <- doubler(i) - } yield j - //can't use assertSubscribeReceives since each number comes in 2x - flatMappedObservable.subscribe(observer.asInstanceOf[Observer[Int]]) - List(1, 2, 3, 4).foreach(i => verify(observer, times(2)).onNext(i)) - verify(observer, never()).onError(any(classOf[Exception])) - verify(observer, times(1)).onCompleted() - assertFalse(flatMappedObservable.isInstanceOf[ScalaObservable[_]]) - } - - @Test def testFilterInForComprehension { - val doubler = (i: Int) => Observable.from(i, i) - val filteredObservable: Observable[Int] = for { - i: Int <- Observable.from(1, 2, 3, 4) - j: Int <- doubler(i) if isOdd(i) - } yield j - //can't use assertSubscribeReceives since each number comes in 2x - filteredObservable.subscribe(observer.asInstanceOf[Observer[Int]]) - List(1, 3).foreach(i => verify(observer, times(2)).onNext(i)) - verify(observer, never()).onError(any(classOf[Exception])) - verify(observer, times(1)).onCompleted() - assertFalse(filteredObservable.isInstanceOf[ScalaObservable[_]]) - } - - @Test def testForEachForComprehension { - val doubler = (i: Int) => Observable.from(i, i) - val intBuffer = ArrayBuffer.empty[Int] - val forEachComprehension = for { - i: Int <- Observable.from(1, 2, 3, 4) - j: Int <- doubler(i) if isEven(i) - } { - intBuffer.append(j) - } - assertEquals(List(2, 2, 4, 4), intBuffer.toList) - } - - private def assertSubscribeReceives[T](o: Observable[T])(values: T*) = { - o.subscribe(observer.asInstanceOf[Observer[T]]) - values.toList.foreach(t => verify(observer, times(1)).onNext(t)) - verify(observer, never()).onError(any(classOf[Exception])) - verify(observer, times(1)).onCompleted() - } -} diff --git a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/subscriptions/SubscriptionTests.scala b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/subscriptions/SubscriptionTests.scala new file mode 100644 index 0000000000..4309967c0a --- /dev/null +++ b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/subscriptions/SubscriptionTests.scala @@ -0,0 +1,118 @@ +package rx.lang.scala.subscriptions + +import org.junit.Assert._ +import org.junit.Test +import org.scalatest.junit.JUnitSuite + +import rx.lang.scala.Subscription + +class SubscriptionTests extends JUnitSuite { + + @Test + def anonymousSubscriptionCreate() { + val subscription = Subscription{} + assertNotNull(subscription) + } + + @Test + def anonymousSubscriptionDispose() { + var unsubscribed = false + val subscription = Subscription{ unsubscribed = true } + assertFalse(unsubscribed) + subscription.unsubscribe() + assertTrue(unsubscribed) + } + + @Test + def emptySubscription() { + val subscription = Subscription() + subscription.unsubscribe() + } + + @Test + def booleanSubscription() { + val subscription = BooleanSubscription() + assertFalse(subscription.isUnsubscribed) + subscription.unsubscribe() + assertTrue(subscription.isUnsubscribed) + subscription.unsubscribe() + assertTrue(subscription.isUnsubscribed) + } + + @Test + def compositeSubscriptionAdd() { + + var u0 = false + val s0 = BooleanSubscription{ u0 = true } + + var u1 = false + val s1 = Subscription{ u1 = true } + + val composite = CompositeSubscription() + + assertFalse(composite.isUnsubscribed) + + composite += s0 + composite += s1 + + composite.unsubscribe() + + assertTrue(composite.isUnsubscribed) + assertTrue(s0.isUnsubscribed) + assertTrue(u0) + assertTrue(u1) + + val s2 = BooleanSubscription() + assertFalse(s2.isUnsubscribed) + composite += s2 + assertTrue(s2.isUnsubscribed) + + } + + @Test + def compositeSubscriptionRemove() { + + val s0 = BooleanSubscription() + val composite = CompositeSubscription() + + composite += s0 + assertFalse(s0.isUnsubscribed) + composite -= s0 + assertTrue(s0.isUnsubscribed) + + composite.unsubscribe() + + assertTrue(composite.isUnsubscribed) + } + + @Test + def multiAssignmentSubscriptionAdd() { + + val s0 = BooleanSubscription() + val s1 = BooleanSubscription() + val multiple = MultipleAssignmentSubscription() + + assertFalse(multiple.isUnsubscribed) + + multiple.subscription = s0 + assertEquals(s0.asJavaSubscription, multiple.subscription.asJavaSubscription) + + multiple.subscription = s1 + assertEquals(s1.asJavaSubscription, multiple.subscription.asJavaSubscription) + + assertFalse(s0.isUnsubscribed) + assertFalse(s1.isUnsubscribed) + + multiple.unsubscribe() + + assertTrue(multiple.isUnsubscribed) + assertFalse(s0.isUnsubscribed) + assertTrue(s1.isUnsubscribed) + + val s2 = BooleanSubscription() + assertFalse(s2.isUnsubscribed) + multiple.subscription = s2 + assertTrue(s2.isUnsubscribed) + } + +} diff --git a/rxjava-contrib/rxjava-android/build.gradle b/rxjava-contrib/rxjava-android/build.gradle index f3b7745c81..144d3cd68a 100644 --- a/rxjava-contrib/rxjava-android/build.gradle +++ b/rxjava-contrib/rxjava-android/build.gradle @@ -2,10 +2,13 @@ apply plugin: 'osgi' dependencies { compile project(':rxjava-core') + provided 'com.google.android:android:4.0.1.2' + provided 'com.google.android:support-v4:r7' + + // testing provided 'junit:junit-dep:4.10' provided 'org.mockito:mockito-core:1.8.5' provided 'org.robolectric:robolectric:2.1.1' - provided 'com.google.android:android:4.0.1.2' } javadoc { diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/android/concurrency/AndroidSchedulers.java b/rxjava-contrib/rxjava-android/src/main/java/rx/android/concurrency/AndroidSchedulers.java index 36a8154d16..0b238b1644 100644 --- a/rxjava-contrib/rxjava-android/src/main/java/rx/android/concurrency/AndroidSchedulers.java +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/android/concurrency/AndroidSchedulers.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx.android.concurrency; import android.os.Handler; diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/android/concurrency/HandlerThreadScheduler.java b/rxjava-contrib/rxjava-android/src/main/java/rx/android/concurrency/HandlerThreadScheduler.java index cd1af987ae..ae01d17c1a 100644 --- a/rxjava-contrib/rxjava-android/src/main/java/rx/android/concurrency/HandlerThreadScheduler.java +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/android/concurrency/HandlerThreadScheduler.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx.android.concurrency; import android.os.Handler; diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/android/observables/AndroidObservable.java b/rxjava-contrib/rxjava-android/src/main/java/rx/android/observables/AndroidObservable.java new file mode 100644 index 0000000000..c70ba970c3 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/android/observables/AndroidObservable.java @@ -0,0 +1,40 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.android.observables; + +import rx.Observable; +import rx.operators.OperationObserveFromAndroidComponent; + +import android.app.Activity; +import android.app.Fragment; + +public final class AndroidObservable { + + private AndroidObservable() {} + + public static Observable fromActivity(Activity activity, Observable sourceObservable) { + return OperationObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, activity); + } + + public static Observable fromFragment(Fragment fragment, Observable sourceObservable) { + return OperationObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, fragment); + } + + public static Observable fromFragment(android.support.v4.app.Fragment fragment, Observable sourceObservable) { + return OperationObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, fragment); + } + +} diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperationObserveFromAndroidComponent.java b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperationObserveFromAndroidComponent.java new file mode 100644 index 0000000000..cfce38f8bb --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperationObserveFromAndroidComponent.java @@ -0,0 +1,313 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.android.concurrency.AndroidSchedulers; +import rx.subjects.PublishSubject; + +import android.app.Activity; +import android.app.Fragment; +import android.os.Looper; +import android.util.Log; + +import java.lang.reflect.Field; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +public class OperationObserveFromAndroidComponent { + + public static Observable observeFromAndroidComponent(Observable source, android.app.Fragment fragment) { + return Observable.create(new OnSubscribeFragment(source, fragment)); + } + + public static Observable observeFromAndroidComponent(Observable source, android.support.v4.app.Fragment fragment) { + return Observable.create(new OnSubscribeSupportFragment(source, fragment)); + } + + public static Observable observeFromAndroidComponent(Observable source, Activity activity) { + return Observable.create(new OnSubscribeBase(source, activity)); + } + + private static class OnSubscribeBase implements Observable.OnSubscribeFunc { + + private static final String LOG_TAG = "AndroidObserver"; + + private final Observable source; + private AndroidComponent componentRef; + private Observer observerRef; + + private OnSubscribeBase(Observable source, AndroidComponent component) { + this.source = source; + this.componentRef = component; + } + + private void log(String message) { + if (Log.isLoggable(LOG_TAG, Log.DEBUG)) { + Log.d(LOG_TAG, "componentRef = " + componentRef); + Log.d(LOG_TAG, "observerRef = " + observerRef); + Log.d(LOG_TAG, message); + } + } + + protected boolean isComponentValid(AndroidComponent component) { + return true; + } + + @Override + public Subscription onSubscribe(Observer observer) { + assertUiThread(); + observerRef = observer; + final Subscription sourceSub = source.observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer() { + @Override + public void onCompleted() { + if (componentRef != null && isComponentValid(componentRef)) { + observerRef.onCompleted(); + } else { + log("onComplete: target component released or detached; dropping message"); + } + } + + @Override + public void onError(Throwable e) { + if (componentRef != null && isComponentValid(componentRef)) { + observerRef.onError(e); + } else { + log("onError: target component released or detached; dropping message"); + } + } + + @Override + public void onNext(T args) { + if (componentRef != null && isComponentValid(componentRef)) { + observerRef.onNext(args); + } else { + log("onNext: target component released or detached; dropping message"); + } + } + }); + return new Subscription() { + @Override + public void unsubscribe() { + log("unsubscribing from source sequence"); + releaseReferences(); + sourceSub.unsubscribe(); + } + }; + } + + private void releaseReferences() { + observerRef = null; + componentRef = null; + } + + private void assertUiThread() { + if (Looper.getMainLooper() != Looper.myLooper()) { + throw new IllegalStateException("Observers must subscribe from the main UI thread, but was " + Thread.currentThread()); + } + } + } + + private static final class OnSubscribeFragment extends OnSubscribeBase { + + private OnSubscribeFragment(Observable source, android.app.Fragment fragment) { + super(source, fragment); + } + + @Override + protected boolean isComponentValid(android.app.Fragment fragment) { + return fragment.isAdded(); + } + } + + private static final class OnSubscribeSupportFragment extends OnSubscribeBase { + + private OnSubscribeSupportFragment(Observable source, android.support.v4.app.Fragment fragment) { + super(source, fragment); + } + + @Override + protected boolean isComponentValid(android.support.v4.app.Fragment fragment) { + return fragment.isAdded(); + } + } + + @RunWith(RobolectricTestRunner.class) + @Config(manifest = Config.NONE) + public static final class UnitTest { + + @Mock + private Observer mockObserver; + + @Mock + private Fragment mockFragment; + + @Mock + private Activity mockActivity; + + @Mock + private Observable mockObservable; + + @Before + public void setupMocks() { + MockitoAnnotations.initMocks(this); + when(mockFragment.isAdded()).thenReturn(true); + } + + @Test + public void itThrowsIfObserverSubscribesFromBackgroundThread() throws Exception { + final Future future = Executors.newSingleThreadExecutor().submit(new Callable() { + @Override + public Object call() throws Exception { + OperationObserveFromAndroidComponent.observeFromAndroidComponent( + mockObservable, mockFragment).subscribe(mockObserver); + return null; + } + }); + future.get(1, TimeUnit.SECONDS); + verify(mockObserver).onError(any(IllegalStateException.class)); + verifyNoMoreInteractions(mockObserver); + } + + @Test + public void itObservesTheSourceSequenceOnTheMainUIThread() { + OperationObserveFromAndroidComponent.observeFromAndroidComponent(mockObservable, mockFragment).subscribe(mockObserver); + verify(mockObservable).observeOn(AndroidSchedulers.mainThread()); + } + + @Test + public void itForwardsOnNextOnCompletedSequenceToTargetObserver() { + Observable source = Observable.from(1, 2, 3); + OperationObserveFromAndroidComponent.observeFromAndroidComponent(source, mockFragment).subscribe(mockObserver); + verify(mockObserver, times(3)).onNext(anyInt()); + verify(mockObserver).onCompleted(); + verify(mockObserver, never()).onError(any(Exception.class)); + } + + @Test + public void itForwardsOnErrorToTargetObserver() { + final Exception exception = new Exception(); + Observable source = Observable.error(exception); + OperationObserveFromAndroidComponent.observeFromAndroidComponent(source, mockFragment).subscribe(mockObserver); + verify(mockObserver).onError(exception); + verify(mockObserver, never()).onNext(anyInt()); + verify(mockObserver, never()).onCompleted(); + } + + @Test + public void itDropsOnNextOnCompletedSequenceIfTargetComponentIsGone() throws Throwable { + PublishSubject source = PublishSubject.create(); + + final OnSubscribeFragment operator = new OnSubscribeFragment(source, mockFragment); + operator.onSubscribe(mockObserver); + + source.onNext(1); + releaseComponentRef(operator); + + source.onNext(2); + source.onNext(3); + source.onCompleted(); + + verify(mockObserver).onNext(1); + verifyNoMoreInteractions(mockObserver); + } + + @Test + public void itDropsOnErrorIfTargetComponentIsGone() throws Throwable { + PublishSubject source = PublishSubject.create(); + + final OnSubscribeFragment operator = new OnSubscribeFragment(source, mockFragment); + operator.onSubscribe(mockObserver); + + source.onNext(1); + releaseComponentRef(operator); + + source.onError(new Exception()); + + verify(mockObserver).onNext(1); + verifyNoMoreInteractions(mockObserver); + } + + private void releaseComponentRef(OnSubscribeFragment operator) throws NoSuchFieldException, IllegalAccessException { + final Field componentRef = operator.getClass().getSuperclass().getDeclaredField("componentRef"); + componentRef.setAccessible(true); + componentRef.set(operator, null); + } + + @Test + public void itDoesNotForwardOnNextOnCompletedSequenceIfFragmentIsDetached() { + PublishSubject source = PublishSubject.create(); + OperationObserveFromAndroidComponent.observeFromAndroidComponent(source, mockFragment).subscribe(mockObserver); + + source.onNext(1); + + when(mockFragment.isAdded()).thenReturn(false); + source.onNext(2); + source.onNext(3); + source.onCompleted(); + + verify(mockObserver).onNext(1); + verify(mockObserver, never()).onCompleted(); + } + + @Test + public void itDoesNotForwardOnErrorIfFragmentIsDetached() { + PublishSubject source = PublishSubject.create(); + OperationObserveFromAndroidComponent.observeFromAndroidComponent(source, mockFragment).subscribe(mockObserver); + + source.onNext(1); + + when(mockFragment.isAdded()).thenReturn(false); + source.onError(new Exception()); + + verify(mockObserver).onNext(1); + verify(mockObserver, never()).onError(any(Exception.class)); + } + + @Test + public void itUnsubscribesFromTheSourceSequence() { + Subscription underlying = mock(Subscription.class); + when(mockObservable.observeOn(AndroidSchedulers.mainThread())).thenReturn(mockObservable); + when(mockObservable.subscribe(any(Observer.class))).thenReturn(underlying); + + Subscription sub = OperationObserveFromAndroidComponent.observeFromAndroidComponent( + mockObservable, mockActivity).subscribe(mockObserver); + sub.unsubscribe(); + + verify(underlying).unsubscribe(); + } + } +} diff --git a/rxjava-contrib/rxjava-apache-http/README.md b/rxjava-contrib/rxjava-apache-http/README.md new file mode 100644 index 0000000000..d921b3047e --- /dev/null +++ b/rxjava-contrib/rxjava-apache-http/README.md @@ -0,0 +1,111 @@ +# rxjava-apache-http + +Observable API for Apache [HttpAsyncClient](http://hc.apache.org/httpcomponents-asyncclient-dev/) + +It is aware of Content-Type `text/event-stream` and will stream each event via `Observer.onNext`. + +Other Content-Types will be returned as a single call to `Observer.onNext`. + +Main Classes: + +- [ObservableHttp](https://github.com/Netflix/RxJava/blob/master/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/ObservableHttp.java) +- [ObservableHttpResponse](https://github.com/Netflix/RxJava/blob/master/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/ObservableHttpResponse.java) + + +# Binaries + +Binaries and dependency information for Maven, Ivy, Gradle and others can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Ccom.netflix.rxjava). + +Example for [Maven](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22rxjava-apache-http%22): + +```xml + + com.netflix.rxjava + rxjava-apache-http + x.y.z + +``` + +and for Ivy: + +```xml + +``` + +# Sample Usage + +### Create a Request + +```java +ObservableHttp.createGet("http://www.wikipedia.com", httpClient).toObservable(); +ObservableHttp.createRequest(HttpAsyncMethods.createGet("http://www.wikipedia.com"), httpClient).toObservable(); +``` + +### Http Client + +A basic default client: + +```java +CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault(); +``` + +or a custom client with configuration options: + +```java +final RequestConfig requestConfig = RequestConfig.custom() + .setSocketTimeout(3000) + .setConnectTimeout(500).build(); +final CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom() + .setDefaultRequestConfig(requestConfig) + .setMaxConnPerRoute(20) + .setMaxConnTotal(50) + .build(); +``` + +### Normal Http GET + +Execute a request and transform the `byte[]` reponse to a `String`: + +```groovy + ObservableHttp.createRequest(HttpAsyncMethods.createGet("http://www.wikipedia.com"), client) + .toObservable() + .flatMap({ ObservableHttpResponse response -> + return response.getContent().map({ byte[] bb -> + return new String(bb); + }); + }) + .toBlockingObservable() + .forEach({ String resp -> + // this will be invoked once with the response + println(resp); + }); +``` + +### Streaming Http GET with [Server-Sent Events (text/event-stream)](http://www.w3.org/TR/eventsource/) Response + +Execute a request and transform the `byte[]` response of each event to a `String`: + +```groovy + ObservableHttp.createRequest(HttpAsyncMethods.createGet("http://hostname/event.stream"), client) + .toObservable() + .flatMap({ ObservableHttpResponse response -> + return response.getContent().map({ byte[] bb -> + return new String(bb); + }); + }) + .toBlockingObservable() + .forEach({ String resp -> + // this will be invoked for each event + println(resp); + }); +``` + +An example event-stream is from [Hystrix](https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-metrics-event-stream) used for streaming metrics. An [example webapp](https://github.com/Netflix/Hystrix/tree/master/hystrix-examples-webapp) can be used to test. + +Output looks like: + +``` +data: {"type":"HystrixCommand","name":"CreditCardCommand","group":"CreditCard","currentTime":1379823924934,"isCircuitBreakerOpen":false,"errorPercentage":0,"errorCount":0,"requestCount":0,"rollingCountCollapsedRequests":0,"rollingCountExceptionsThrown":0,"rollingCountFailure":0,"rollingCountFallbackFailure":0,"rollingCountFallbackRejection":0,"rollingCountFallbackSuccess":0,"rollingCountResponsesFromCache":0,"rollingCountSemaphoreRejected":0,"rollingCountShortCircuited":0,"rollingCountSuccess":0,"rollingCountThreadPoolRejected":0,"rollingCountTimeout":0,"currentConcurrentExecutionCount":0,"latencyExecute_mean":0,"latencyExecute":{"0":0,"25":0,"50":0,"75":0,"90":0,"95":0,"99":0,"99.5":0,"100":0},"latencyTotal_mean":0,"latencyTotal":{"0":0,"25":0,"50":0,"75":0,"90":0,"95":0,"99":0,"99.5":0,"100":0},"propertyValue_circuitBreakerRequestVolumeThreshold":20,"propertyValue_circuitBreakerSleepWindowInMilliseconds":5000,"propertyValue_circuitBreakerErrorThresholdPercentage":50,"propertyValue_circuitBreakerForceOpen":false,"propertyValue_circuitBreakerForceClosed":false,"propertyValue_circuitBreakerEnabled":true,"propertyValue_executionIsolationStrategy":"THREAD","propertyValue_executionIsolationThreadTimeoutInMilliseconds":3000,"propertyValue_executionIsolationThreadInterruptOnTimeout":true,"propertyValue_executionIsolationThreadPoolKeyOverride":null,"propertyValue_executionIsolationSemaphoreMaxConcurrentRequests":10,"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests":10,"propertyValue_metricsRollingStatisticalWindowInMilliseconds":10000,"propertyValue_requestCacheEnabled":true,"propertyValue_requestLogEnabled":true,"reportingHosts":1} +data: {"type":"HystrixCommand","name":"GetPaymentInformationCommand","group":"PaymentInformation","currentTime":1379823924934,"isCircuitBreakerOpen":false,"errorPercentage":0,"errorCount":0,"requestCount":0,"rollingCountCollapsedRequests":0,"rollingCountExceptionsThrown":0,"rollingCountFailure":0,"rollingCountFallbackFailure":0,"rollingCountFallbackRejection":0,"rollingCountFallbackSuccess":0,"rollingCountResponsesFromCache":0,"rollingCountSemaphoreRejected":0,"rollingCountShortCircuited":0,"rollingCountSuccess":0,"rollingCountThreadPoolRejected":0,"rollingCountTimeout":0,"currentConcurrentExecutionCount":0,"latencyExecute_mean":0,"latencyExecute":{"0":0,"25":0,"50":0,"75":0,"90":0,"95":0,"99":0,"99.5":0,"100":0},"latencyTotal_mean":0,"latencyTotal":{"0":0,"25":0,"50":0,"75":0,"90":0,"95":0,"99":0,"99.5":0,"100":0},"propertyValue_circuitBreakerRequestVolumeThreshold":20,"propertyValue_circuitBreakerSleepWindowInMilliseconds":5000,"propertyValue_circuitBreakerErrorThresholdPercentage":50,"propertyValue_circuitBreakerForceOpen":false,"propertyValue_circuitBreakerForceClosed":false,"propertyValue_circuitBreakerEnabled":true,"propertyValue_executionIsolationStrategy":"THREAD","propertyValue_executionIsolationThreadTimeoutInMilliseconds":1000,"propertyValue_executionIsolationThreadInterruptOnTimeout":true,"propertyValue_executionIsolationThreadPoolKeyOverride":null,"propertyValue_executionIsolationSemaphoreMaxConcurrentRequests":10,"propertyValue_fallbackIsolationSemaphoreMaxConcurrentRequests":10,"propertyValue_metricsRollingStatisticalWindowInMilliseconds":10000,"propertyValue_requestCacheEnabled":true,"propertyValue_requestLogEnabled":true,"reportingHosts":1} +``` + diff --git a/rxjava-contrib/rxjava-apache-http/build.gradle b/rxjava-contrib/rxjava-apache-http/build.gradle new file mode 100644 index 0000000000..81d150ccc3 --- /dev/null +++ b/rxjava-contrib/rxjava-apache-http/build.gradle @@ -0,0 +1,20 @@ +apply plugin: 'osgi' + +sourceCompatibility = JavaVersion.VERSION_1_6 +targetCompatibility = JavaVersion.VERSION_1_6 + +dependencies { + compile project(':rxjava-core') + compile 'org.apache.httpcomponents:httpclient:4.3' + compile 'org.apache.httpcomponents:httpcore-nio:4.3' + compile 'org.apache.httpcomponents:httpasyncclient:4.0' +} + +jar { + manifest { + name = 'rxjava-apache-http' + instruction 'Bundle-Vendor', 'Netflix' + instruction 'Bundle-DocURL', 'https://github.com/Netflix/RxJava' + instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*' + } +} diff --git a/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/ObservableHttp.java b/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/ObservableHttp.java new file mode 100644 index 0000000000..7f34365eb4 --- /dev/null +++ b/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/ObservableHttp.java @@ -0,0 +1,171 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.apache.http; + +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.concurrent.FutureCallback; +import org.apache.http.nio.client.HttpAsyncClient; +import org.apache.http.nio.client.methods.HttpAsyncMethods; +import org.apache.http.nio.protocol.HttpAsyncRequestProducer; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.apache.http.consumers.ResponseConsumerDelegate; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.Subscriptions; + +/** + * An {@link Observable} interface to Apache {@link HttpAsyncClient}. + *

+ * The initial {@link HttpResponse} is returned via {@link Observer#onNext} wrapped in a {@link ObservableHttpResponse}. + *

+ * The content stream is retrieved from {@link ObservableHttpResponse#getContent()}. + *

+ * It is aware of Content-Type text/event-stream and will stream each event via {@link Observer#onNext}. + *

+ * Other Content-Types will be returned as a single call to {@link Observer#onNext}. + *

+ * Examples: + *

+ *

 {@code
+ * ObservableHttp.createGet("http://www.wikipedia.com", httpClient).toObservable();
+ * } 
+ *

+ *

 {@code
+ * ObservableHttp.createRequest(HttpAsyncMethods.createGet("http://www.wikipedia.com"), httpClient).toObservable();
+ * } 
+ * + * An {@link HttpClient} can be created like this: + * + *
 {@code
+ * CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault();
+ * httpClient.start(); // start it
+ * httpClient.stop(); // stop it
+ * } 
+ *

+ * A client with custom configurations can be created like this: + *

+ *
 {@code
+ * final RequestConfig requestConfig = RequestConfig.custom()
+ *     .setSocketTimeout(1000)
+ *     .setConnectTimeout(200).build();
+ * final CloseableHttpAsyncClient httpClient = HttpAsyncClients.custom()
+ *     .setDefaultRequestConfig(requestConfig)
+ *     .setMaxConnPerRoute(20)
+ *     .setMaxConnTotal(50)
+ *     .build();
+ * httpClient.start();
+ * }
+ *

+ * + * @param + */ +public class ObservableHttp { + + private final OnSubscribeFunc onSubscribe; + + private ObservableHttp(OnSubscribeFunc onSubscribe) { + this.onSubscribe = onSubscribe; + } + + private static ObservableHttp create(OnSubscribeFunc onSubscribe) { + return new ObservableHttp(onSubscribe); + } + + public Observable toObservable() { + return Observable.create(new OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(Observer observer) { + return onSubscribe.onSubscribe(observer); + } + }); + } + + public static ObservableHttp createGet(String uri, final HttpAsyncClient client) { + return createRequest(HttpAsyncMethods.createGet(uri), client); + } + + /** + * Execute request using {@link HttpAsyncRequestProducer} to define HTTP Method, URI and payload (if applicable). + *

+ * If the response is chunked (or flushed progressively such as with text/event-stream Server-Sent Events) this will call + * {@link Observer#onNext} multiple times. + *

+ * Use {@code HttpAsyncMethods.create* } factory methods to create {@link HttpAsyncRequestProducer} instances. + *

+ * A client can be retrieved like this: + *

+ *

 {@code      CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault(); } 

+ *

+ * A client with custom configurations can be created like this: + *

+ *
 {@code
+     * final RequestConfig requestConfig = RequestConfig.custom()
+     *     .setSocketTimeout(3000)
+     *     .setConnectTimeout(3000).build();
+     * final CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom()
+     *     .setDefaultRequestConfig(requestConfig)
+     *     .setMaxConnPerRoute(20)
+     *     .setMaxConnTotal(50)
+     *     .build();
+     * httpclient.start();
+     * }
+ * + * + * @param requestProducer + * @param client + * @return + */ + public static ObservableHttp createRequest(final HttpAsyncRequestProducer requestProducer, final HttpAsyncClient client) { + + return ObservableHttp.create(new OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(final Observer observer) { + + final CompositeSubscription parentSubscription = new CompositeSubscription(); + + // return a Subscription that wraps the Future so it can be cancelled + parentSubscription.add(Subscriptions.from(client.execute(requestProducer, new ResponseConsumerDelegate(observer, parentSubscription), + new FutureCallback() { + + @Override + public void completed(HttpResponse result) { + observer.onCompleted(); + } + + @Override + public void failed(Exception ex) { + observer.onError(ex); + } + + @Override + public void cancelled() { + observer.onCompleted(); + } + + }))); + + return parentSubscription; + } + }); + } + +} diff --git a/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/ObservableHttpResponse.java b/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/ObservableHttpResponse.java new file mode 100644 index 0000000000..8077152ccc --- /dev/null +++ b/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/ObservableHttpResponse.java @@ -0,0 +1,51 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.apache.http; + +import org.apache.http.HttpResponse; + +import rx.Observable; + +/** + * The {@link HttpResponse} for the entire request and accessor to {@link Observable} of the content stream. + */ +public class ObservableHttpResponse { + + private final HttpResponse response; + private final Observable contentSubscription; + + public ObservableHttpResponse(HttpResponse response, Observable contentSubscription) { + this.response = response; + this.contentSubscription = contentSubscription; + } + + /** + * The {@link HttpResponse} returned by the Apache client at the beginning of the response. + * + * @return {@link HttpResponse} with HTTP status codes, headers, etc + */ + public HttpResponse getResponse() { + return response; + } + + /** + * If the response is not chunked then only a single array will be returned. If chunked then multiple arrays. + */ + public Observable getContent() { + return contentSubscription; + } + +} diff --git a/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/consumers/ExpandableByteBuffer.java b/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/consumers/ExpandableByteBuffer.java new file mode 100644 index 0000000000..da6c81e7f2 --- /dev/null +++ b/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/consumers/ExpandableByteBuffer.java @@ -0,0 +1,65 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.apache.http.consumers; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.http.nio.util.ExpandableBuffer; +import org.apache.http.nio.util.HeapByteBufferAllocator; + +class ExpandableByteBuffer extends ExpandableBuffer { + public ExpandableByteBuffer(int size) { + super(size, HeapByteBufferAllocator.INSTANCE); + } + + public ExpandableByteBuffer() { + super(4 * 1024, HeapByteBufferAllocator.INSTANCE); + } + + public void addByte(byte b) { + if (this.buffer.remaining() == 0) { + expand(); + } + this.buffer.put(b); + } + + public boolean hasContent() { + return this.buffer.position() > 0; + } + + public byte[] getBytes() { + byte[] data = new byte[this.buffer.position()]; + this.buffer.position(0); + this.buffer.get(data); + return data; + } + + public void reset() { + clear(); + } + + public void consumeInputStream(InputStream content) throws IOException { + try { + int b = -1; + while ((b = content.read()) != -1) { + addByte((byte) b); + } + } finally { + content.close(); + } + } +} \ No newline at end of file diff --git a/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/consumers/ResponseConsumerBasic.java b/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/consumers/ResponseConsumerBasic.java new file mode 100644 index 0000000000..8101c9ad8d --- /dev/null +++ b/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/consumers/ResponseConsumerBasic.java @@ -0,0 +1,102 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.apache.http.consumers; + +import java.io.IOException; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpResponse; +import org.apache.http.entity.ContentType; +import org.apache.http.nio.ContentDecoder; +import org.apache.http.nio.IOControl; +import org.apache.http.nio.protocol.BasicAsyncResponseConsumer; +import org.apache.http.protocol.HttpContext; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.apache.http.ObservableHttpResponse; +import rx.subscriptions.CompositeSubscription; + +/** + * Delegate wrapper around {@link BasicAsyncResponseConsumer} so it works with {@link ResponseConsumerDelegate} + */ +class ResponseConsumerBasic extends BasicAsyncResponseConsumer implements ResponseDelegate { + + private final Observer observer; + private final CompositeSubscription parentSubscription; + + public ResponseConsumerBasic(final Observer observer, CompositeSubscription parentSubscription) { + this.observer = observer; + this.parentSubscription = parentSubscription; + } + + @Override + public void _onResponseReceived(HttpResponse response) throws HttpException, IOException { + onResponseReceived(response); + } + + @Override + public void _onContentReceived(ContentDecoder decoder, IOControl ioctrl) throws IOException { + if (parentSubscription.isUnsubscribed()) { + ioctrl.shutdown(); + } + onContentReceived(decoder, ioctrl); + } + + @Override + public void _onEntityEnclosed(HttpEntity entity, ContentType contentType) throws IOException { + onEntityEnclosed(entity, contentType); + } + + @Override + public HttpResponse _buildResult(HttpContext context) throws Exception { + final HttpResponse response = buildResult(context); + + Observable contentObservable = Observable.create(new OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(Observer o) { + long length = response.getEntity().getContentLength(); + if (length > Integer.MAX_VALUE) { + o.onError(new IllegalStateException("Content Length too large for a byte[] => " + length)); + } else { + ExpandableByteBuffer buf = new ExpandableByteBuffer((int) length); + try { + buf.consumeInputStream(response.getEntity().getContent()); + o.onNext(buf.getBytes()); + o.onCompleted(); + } catch (Throwable e) { + o.onError(e); + } + } + + return parentSubscription; + } + }); + + observer.onNext(new ObservableHttpResponse(response, contentObservable)); + return response; + } + + @Override + public void _releaseResources() { + releaseResources(); + } + +} diff --git a/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/consumers/ResponseConsumerDelegate.java b/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/consumers/ResponseConsumerDelegate.java new file mode 100644 index 0000000000..b352ecb61a --- /dev/null +++ b/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/consumers/ResponseConsumerDelegate.java @@ -0,0 +1,99 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.apache.http.consumers; + +import java.io.IOException; + +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpResponse; +import org.apache.http.entity.ContentType; +import org.apache.http.nio.ContentDecoder; +import org.apache.http.nio.IOControl; +import org.apache.http.nio.protocol.AbstractAsyncResponseConsumer; +import org.apache.http.protocol.HttpContext; + +import rx.Observer; +import rx.apache.http.ObservableHttpResponse; +import rx.subscriptions.CompositeSubscription; + +/** + * AbstractAsyncResponseConsumer that chooses different implementations based on return headers. + *

+ *

    + *
  • Content-Type:text/event-stream == {@link ResponseConsumerEventStream}
  • + *
  • All others == {@link ResponseConsumerBasic}
  • + *
+ */ +public class ResponseConsumerDelegate extends AbstractAsyncResponseConsumer { + + private volatile ResponseDelegate consumer = null; + final Observer observer; + final CompositeSubscription subscription; + + public ResponseConsumerDelegate(final Observer observer, CompositeSubscription subscription) { + this.observer = observer; + this.subscription = subscription; + } + + @Override + protected void onResponseReceived(HttpResponse response) throws HttpException, IOException { + // when we receive the response with headers we evaluate what type of consumer we want + if (responseIsStreamLike(response)) { + consumer = new ResponseConsumerEventStream(observer, subscription); + } else { + consumer = new ResponseConsumerBasic(observer, subscription); + } + // forward 'response' to actual consumer + consumer._onResponseReceived(response); + } + + private boolean responseIsStreamLike(HttpResponse response) { + final Header contentType = response.getFirstHeader("Content-Type"); + // use 'contains' instead of equals since Content-Type can contain additional information + // such as charset ... see here: http://www.w3.org/International/O-HTTP-charset + if (contentType != null && contentType.getValue().contains("text/event-stream")) { + return true; + } + final Header transferEncoding = response.getFirstHeader("Transfer-Encoding"); + if (transferEncoding != null && transferEncoding.getValue().equals("chunked")) { + return true; + } + return false; + } + + @Override + protected void onContentReceived(ContentDecoder decoder, IOControl ioctrl) throws IOException { + consumer._onContentReceived(decoder, ioctrl); + } + + @Override + protected void onEntityEnclosed(HttpEntity entity, ContentType contentType) throws IOException { + consumer._onEntityEnclosed(entity, contentType); + } + + @Override + protected HttpResponse buildResult(HttpContext context) throws Exception { + return consumer._buildResult(context); + } + + @Override + protected void releaseResources() { + consumer._releaseResources(); + } + +} diff --git a/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/consumers/ResponseConsumerEventStream.java b/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/consumers/ResponseConsumerEventStream.java new file mode 100644 index 0000000000..be8407aecf --- /dev/null +++ b/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/consumers/ResponseConsumerEventStream.java @@ -0,0 +1,121 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.apache.http.consumers; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpResponse; +import org.apache.http.entity.ContentType; +import org.apache.http.nio.ContentDecoder; +import org.apache.http.nio.IOControl; +import org.apache.http.nio.client.methods.AsyncByteConsumer; +import org.apache.http.nio.protocol.HttpAsyncResponseConsumer; +import org.apache.http.protocol.HttpContext; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.apache.http.ObservableHttpResponse; +import rx.subjects.PublishSubject; +import rx.subscriptions.CompositeSubscription; + +/** + * {@link HttpAsyncResponseConsumer} for Content-Type:text/event-stream + *

+ * It will emit a byte[] via {@link Observer#onNext} for each non-empty line. + */ +class ResponseConsumerEventStream extends AsyncByteConsumer implements ResponseDelegate { + + private final Observer observer; + private final PublishSubject contentSubject = PublishSubject. create(); + private final CompositeSubscription parentSubscription; + + public ResponseConsumerEventStream(final Observer observer, CompositeSubscription parentSubscription) { + this.observer = observer; + this.parentSubscription = parentSubscription; + } + + final ExpandableByteBuffer dataBuffer = new ExpandableByteBuffer(); + + @Override + public void _onResponseReceived(HttpResponse response) throws HttpException, IOException { + onResponseReceived(response); + } + + @Override + protected void onByteReceived(ByteBuffer buf, IOControl ioctrl) throws IOException { + if (parentSubscription.isUnsubscribed()) { + ioctrl.shutdown(); + } + while (buf.position() < buf.limit()) { + byte b = buf.get(); + if (b == 10 || b == 13) { + if (dataBuffer.hasContent()) { + contentSubject.onNext(dataBuffer.getBytes()); + } + dataBuffer.reset(); + } else { + dataBuffer.addByte(b); + } + } + } + + @Override + protected void onResponseReceived(HttpResponse response) throws HttpException, IOException { + + // wrap the contentSubject so we can chain the Subscription between parent and child + Observable contentObservable = Observable.create(new OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(Observer observer) { + parentSubscription.add(contentSubject.subscribe(observer)); + return parentSubscription; + } + }); + observer.onNext(new ObservableHttpResponse(response, contentObservable)); + } + + @Override + protected HttpResponse buildResult(HttpContext context) throws Exception { + // streaming results, so not returning anything here + return null; + } + + @Override + public void _onContentReceived(ContentDecoder decoder, IOControl ioctrl) throws IOException { + onContentReceived(decoder, ioctrl); + } + + @Override + public void _onEntityEnclosed(HttpEntity entity, ContentType contentType) throws IOException { + onEntityEnclosed(entity, contentType); + } + + @Override + public HttpResponse _buildResult(HttpContext context) throws Exception { + return buildResult(context); + } + + @Override + public void _releaseResources() { + releaseResources(); + } + +} \ No newline at end of file diff --git a/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/consumers/ResponseDelegate.java b/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/consumers/ResponseDelegate.java new file mode 100644 index 0000000000..566237a9d1 --- /dev/null +++ b/rxjava-contrib/rxjava-apache-http/src/main/java/rx/apache/http/consumers/ResponseDelegate.java @@ -0,0 +1,43 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.apache.http.consumers; + +import java.io.IOException; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpResponse; +import org.apache.http.entity.ContentType; +import org.apache.http.nio.ContentDecoder; +import org.apache.http.nio.IOControl; +import org.apache.http.nio.protocol.HttpAsyncResponseConsumer; +import org.apache.http.protocol.HttpContext; + +/** + * Delegate methods for getting access to protected methods. + */ +abstract interface ResponseDelegate extends HttpAsyncResponseConsumer { + + public void _onResponseReceived(HttpResponse response) throws HttpException, IOException; + + public void _onContentReceived(ContentDecoder decoder, IOControl ioctrl) throws IOException; + + public void _onEntityEnclosed(HttpEntity entity, ContentType contentType) throws IOException; + + public HttpResponse _buildResult(HttpContext context) throws Exception; + + public void _releaseResources(); +} diff --git a/rxjava-contrib/rxjava-apache-http/src/test/java/rx/apache/http/examples/ExampleObservableHttp.java b/rxjava-contrib/rxjava-apache-http/src/test/java/rx/apache/http/examples/ExampleObservableHttp.java new file mode 100644 index 0000000000..4fe7e0549a --- /dev/null +++ b/rxjava-contrib/rxjava-apache-http/src/test/java/rx/apache/http/examples/ExampleObservableHttp.java @@ -0,0 +1,131 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.apache.http.examples; + +import java.io.IOException; +import java.net.URISyntaxException; + +import org.apache.http.impl.nio.client.CloseableHttpAsyncClient; +import org.apache.http.impl.nio.client.HttpAsyncClients; +import org.apache.http.nio.client.HttpAsyncClient; +import org.apache.http.nio.client.methods.HttpAsyncMethods; + +import rx.Observable; +import rx.apache.http.ObservableHttp; +import rx.apache.http.ObservableHttpResponse; +import rx.util.functions.Action1; +import rx.util.functions.Func1; + +public class ExampleObservableHttp { + + public static void main(String args[]) { + CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault(); + + // final RequestConfig requestConfig = RequestConfig.custom() + // .setSocketTimeout(3000) + // .setConnectTimeout(3000).build(); + // final CloseableHttpAsyncClient httpclient = HttpAsyncClients.custom() + // .setDefaultRequestConfig(requestConfig) + // .setMaxConnPerRoute(20) + // .setMaxConnTotal(50) + // .build(); + + try { + httpclient.start(); + executeViaObservableHttpWithForEach(httpclient); + executeStreamingViaObservableHttpWithForEach(httpclient); + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + httpclient.close(); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + + CloseableHttpAsyncClient httpClient = HttpAsyncClients.createDefault(); + ObservableHttp.createGet("http://www.wikipedia.com", httpClient).toObservable(); + ObservableHttp.createRequest(HttpAsyncMethods.createGet("http://www.wikipedia.com"), httpClient).toObservable(); + } + + protected static void executeViaObservableHttpWithForEach(final HttpAsyncClient client) throws URISyntaxException, IOException, InterruptedException { + System.out.println("---- executeViaObservableHttpWithForEach"); + ObservableHttp.createRequest(HttpAsyncMethods.createGet("http://www.wikipedia.com"), client) + .toObservable() + .flatMap(new Func1>() { + + @Override + public Observable call(ObservableHttpResponse response) { + return response.getContent().map(new Func1() { + + @Override + public String call(byte[] bb) { + return new String(bb); + } + + }); + } + }) + .toBlockingObservable() + .forEach(new Action1() { + + @Override + public void call(String resp) { + System.out.println(resp); + } + }); + } + + protected static void executeStreamingViaObservableHttpWithForEach(final HttpAsyncClient client) throws URISyntaxException, IOException, InterruptedException { + System.out.println("---- executeStreamingViaObservableHttpWithForEach"); + // URL against https://github.com/Netflix/Hystrix/tree/master/hystrix-examples-webapp + // More information at https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-metrics-event-stream + ObservableHttp.createRequest(HttpAsyncMethods.createGet("http://localhost:8989/hystrix-examples-webapp/hystrix.stream"), client) + .toObservable() + .flatMap(new Func1>() { + + @Override + public Observable call(ObservableHttpResponse response) { + return response.getContent().map(new Func1() { + + @Override + public String call(byte[] bb) { + return new String(bb); + } + + }); + } + }) + .filter(new Func1() { + + @Override + public Boolean call(String t1) { + return !t1.startsWith(": ping"); + } + }) + .take(3) + .toBlockingObservable() + .forEach(new Action1() { + + @Override + public void call(String resp) { + System.out.println(resp); + } + }); + } + +} diff --git a/rxjava-core/src/main/java/rx/Notification.java b/rxjava-core/src/main/java/rx/Notification.java index 866ed06450..996e217d50 100644 --- a/rxjava-core/src/main/java/rx/Notification.java +++ b/rxjava-core/src/main/java/rx/Notification.java @@ -116,6 +116,16 @@ public boolean isOnNext() { return getKind() == Kind.OnNext; } + public void accept(Observer observer) { + if (isOnNext()) { + observer.onNext(getValue()); + } else if (isOnCompleted()) { + observer.onCompleted(); + } else if (isOnError()) { + observer.onError(getThrowable()); + } + } + public static enum Kind { OnNext, OnError, OnCompleted } diff --git a/rxjava-core/src/main/java/rx/Observable.java b/rxjava-core/src/main/java/rx/Observable.java index 85c0359b3f..18dbbf25e4 100644 --- a/rxjava-core/src/main/java/rx/Observable.java +++ b/rxjava-core/src/main/java/rx/Observable.java @@ -1,17 +1,17 @@ /** * Copyright 2013 Netflix, Inc. * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. */ package rx; @@ -19,44 +19,65 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Comparator; +import java.util.Date; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import rx.concurrency.Schedulers; +import rx.joins.Pattern2; +import rx.joins.Plan0; import rx.observables.BlockingObservable; import rx.observables.ConnectableObservable; import rx.observables.GroupedObservable; import rx.operators.OperationAll; +import rx.operators.OperationAmb; +import rx.operators.OperationAny; import rx.operators.OperationAverage; import rx.operators.OperationBuffer; import rx.operators.OperationCache; +import rx.operators.OperationCast; import rx.operators.OperationCombineLatest; import rx.operators.OperationConcat; +import rx.operators.OperationDebounce; +import rx.operators.OperationDefaultIfEmpty; import rx.operators.OperationDefer; +import rx.operators.OperationDelay; import rx.operators.OperationDematerialize; -import rx.operators.OperationDistinctUntilChanged; import rx.operators.OperationDistinct; +import rx.operators.OperationDistinctUntilChanged; +import rx.operators.OperationDoOnEach; +import rx.operators.OperationElementAt; import rx.operators.OperationFilter; import rx.operators.OperationFinally; import rx.operators.OperationFirstOrDefault; import rx.operators.OperationGroupBy; import rx.operators.OperationInterval; +import rx.operators.OperationJoin; +import rx.operators.OperationJoinPatterns; +import rx.operators.OperationLast; import rx.operators.OperationMap; import rx.operators.OperationMaterialize; import rx.operators.OperationMerge; import rx.operators.OperationMergeDelayError; +import rx.operators.OperationMinMax; import rx.operators.OperationMulticast; import rx.operators.OperationObserveOn; import rx.operators.OperationOnErrorResumeNextViaFunction; import rx.operators.OperationOnErrorResumeNextViaObservable; import rx.operators.OperationOnErrorReturn; import rx.operators.OperationOnExceptionResumeNextViaObservable; +import rx.operators.OperationParallel; +import rx.operators.OperationParallelMerge; import rx.operators.OperationRetry; import rx.operators.OperationSample; import rx.operators.OperationScan; import rx.operators.OperationSkip; +import rx.operators.OperationSkipLast; import rx.operators.OperationSkipWhile; import rx.operators.OperationSubscribeOn; import rx.operators.OperationSum; @@ -67,12 +88,16 @@ import rx.operators.OperationTakeUntil; import rx.operators.OperationTakeWhile; import rx.operators.OperationThrottleFirst; -import rx.operators.OperationDebounce; +import rx.operators.OperationTimeInterval; +import rx.operators.OperationTimeout; import rx.operators.OperationTimestamp; +import rx.operators.OperationToMap; +import rx.operators.OperationToMultimap; import rx.operators.OperationToObservableFuture; import rx.operators.OperationToObservableIterable; import rx.operators.OperationToObservableList; import rx.operators.OperationToObservableSortedList; +import rx.operators.OperationUsing; import rx.operators.OperationWindow; import rx.operators.OperationZip; import rx.operators.SafeObservableSubscription; @@ -80,6 +105,7 @@ import rx.plugins.RxJavaErrorHandler; import rx.plugins.RxJavaObservableExecutionHook; import rx.plugins.RxJavaPlugins; +import rx.subjects.AsyncSubject; import rx.subjects.PublishSubject; import rx.subjects.ReplaySubject; import rx.subjects.Subject; @@ -88,6 +114,7 @@ import rx.util.OnErrorNotImplementedException; import rx.util.Opening; import rx.util.Range; +import rx.util.TimeInterval; import rx.util.Timestamped; import rx.util.functions.Action0; import rx.util.functions.Action1; @@ -107,27 +134,31 @@ /** * The Observable interface that implements the Reactive Pattern. *

- * This interface provides overloaded methods for subscribing as well as delegate methods to the - * various operators. + * This interface provides overloaded methods for subscribing as well as + * delegate methods to the various operators. *

- * The documentation for this interface makes use of marble diagrams. The following legend explains - * these diagrams: + * The documentation for this interface makes use of marble diagrams. The + * following legend explains these diagrams: *

- * + * *

- * For more information see the RxJava Wiki + * For more information see the + * RxJava Wiki * - * @param + * @param the type of the item emitted by the Observable */ public class Observable { + private final static ConcurrentHashMap internalClassMap = new ConcurrentHashMap(); + /** * Executed when 'subscribe' is invoked. */ private final OnSubscribeFunc onSubscribe; /** - * Function interface for work to be performed when an {@link Observable} is subscribed to via {@link Observable#subscribe(Observer)} + * Function interface for work to be performed when an {@link Observable} + * is subscribed to via {@link Observable#subscribe(Observer)} * * @param */ @@ -140,11 +171,12 @@ public static interface OnSubscribeFunc extends Function { /** * Observable with Function to execute when subscribed to. *

- * NOTE: Use {@link #create(OnSubscribeFunc)} to create an Observable instead of this constructor unless you - * specifically have a need for inheritance. + * NOTE: Use {@link #create(OnSubscribeFunc)} to create an Observable + * instead of this constructor unless you specifically have a need for + * inheritance. * - * @param onSubscribe - * {@link OnSubscribeFunc} to be executed when {@link #subscribe(Observer)} is called. + * @param onSubscribe {@link OnSubscribeFunc} to be executed when + * {@link #subscribe(Observer)} is called */ protected Observable(OnSubscribeFunc onSubscribe) { this.onSubscribe = onSubscribe; @@ -153,31 +185,35 @@ protected Observable(OnSubscribeFunc onSubscribe) { private final static RxJavaObservableExecutionHook hook = RxJavaPlugins.getInstance().getObservableExecutionHook(); /** - * An {@link Observer} must call an Observable's {@code subscribe} method in order to - * receive items and notifications from the Observable. - * - *

A typical implementation of {@code subscribe} does the following: - *

- * It stores a reference to the Observer in a collection object, such as a {@code List} object. - *

- * It returns a reference to the {@link Subscription} interface. This enables Observers to - * unsubscribe, that is, to stop receiving items and notifications before the Observable stops - * sending them, which also invokes the Observer's {@link Observer#onCompleted onCompleted} method. - *

- * An Observable<T> instance is responsible for accepting all subscriptions - * and notifying all Observers. Unless the documentation for a particular - * Observable<T> implementation indicates otherwise, Observers should make no - * assumptions about the order in which multiple Observers will receive their notifications. + * An {@link Observer} must call an Observable's {@code subscribe} method in + * order to receive items and notifications from the Observable. + *

+ * A typical implementation of {@code subscribe} does the following: + *

    + *
  1. It stores a reference to the Observer in a collection object, such as + * a {@code List} object.
  2. + *
  3. It returns a reference to the {@link Subscription} interface. This + * enables Observers to unsubscribe, that is, to stop receiving items + * and notifications before the Observable stops sending them, which + * also invokes the Observer's {@link Observer#onCompleted onCompleted} + * method.
  4. + *

+ * An Observable<T> instance is responsible for accepting + * all subscriptions and notifying all Observers. Unless the documentation + * for a particular Observable<T> implementation + * indicates otherwise, Observers should make no assumptions about the order + * in which multiple Observers will receive their notifications. *

* For more information see the * RxJava Wiki * - * @param observer - * the observer - * @return a {@link Subscription} reference with which the {@link Observer} can stop receiving items - * before the Observable has finished sending them - * @throws IllegalArgumentException - * if the {@link Observer} provided as the argument to {@code subscribe()} is {@code null} + * @param observer the Observer + * @return a {@link Subscription} reference with which the {@link Observer} + * can stop receiving items before the Observable has finished + * sending them + * @throws IllegalArgumentException if the {@link Observer} provided as the + * argument to {@code subscribe()} is + * {@code null} */ public Subscription subscribe(Observer observer) { // allow the hook to intercept and/or decorate @@ -230,47 +266,87 @@ public Subscription subscribe(Observer observer) { } /** - * An {@link Observer} must call an Observable's {@code subscribe} method in order to - * receive items and notifications from the Observable. - * - *

A typical implementation of {@code subscribe} does the following: - *

- * It stores a reference to the Observer in a collection object, such as a {@code List} object. - *

- * It returns a reference to the {@link Subscription} interface. This enables Observers to - * unsubscribe, that is, to stop receiving items and notifications before the Observable stops - * sending them, which also invokes the Observer's {@link Observer#onCompleted onCompleted} method. + * An {@link Observer} must call an Observable's {@code subscribe} method in + * order to receive items and notifications from the Observable. *

- * An {@code Observable} instance is responsible for accepting all subscriptions - * and notifying all Observers. Unless the documentation for a particular {@code Observable} implementation indicates otherwise, Observers should make no - * assumptions about the order in which multiple Observers will receive their notifications. + * A typical implementation of {@code subscribe} does the following: + *

    + *
  1. It stores a reference to the Observer in a collection object, such as + * a {@code List} object.
  2. + *
  3. It returns a reference to the {@link Subscription} interface. This + * enables Observers to unsubscribe, that is, to stop receiving items + * and notifications before the Observable stops sending them, which + * also invokes the Observer's {@link Observer#onCompleted onCompleted} + * method.
  4. + *

+ * An {@code Observable} instance is responsible for accepting all + * subscriptions and notifying all Observers. Unless the documentation for a + * particular {@code Observable} implementation indicates otherwise, + * Observers should make no assumptions about the order in which multiple + * Observers will receive their notifications. *

* For more information see the * RxJava Wiki * - * @param observer - * the observer - * @param scheduler - * the {@link Scheduler} on which Observers subscribe to the Observable - * @return a {@link Subscription} reference with which Observers can stop receiving items and - * notifications before the Observable has finished sending them - * @throws IllegalArgumentException - * if an argument to {@code subscribe()} is {@code null} + * @param observer the Observer + * @param scheduler the {@link Scheduler} on which Observers subscribe to + * the Observable + * @return a {@link Subscription} reference with which Observers can stop + * receiving items and notifications before the Observable has + * finished sending them + * @throws IllegalArgumentException if an argument to {@code subscribe()} + * is {@code null} */ public Subscription subscribe(Observer observer, Scheduler scheduler) { return subscribeOn(scheduler).subscribe(observer); } /** - * Used for protecting against errors being thrown from Observer implementations and ensuring onNext/onError/onCompleted contract compliance. + * Protects against errors being thrown from Observer implementations and + * ensures onNext/onError/onCompleted contract compliance. *

- * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator" + * See https://github.com/Netflix/RxJava/issues/216 for a discussion on + * "Guideline 6.4: Protect calls to user code from within an operator" */ private Subscription protectivelyWrapAndSubscribe(Observer o) { SafeObservableSubscription subscription = new SafeObservableSubscription(); return subscription.wrap(subscribe(new SafeObserver(subscription, o))); } + /** + * Subscribe and ignore all events. + * + * @return + */ + public Subscription subscribe() { + return protectivelyWrapAndSubscribe(new Observer() { + + @Override + public void onCompleted() { + // do nothing + } + + @Override + public void onError(Throwable e) { + handleError(e); + throw new OnErrorNotImplementedException(e); + } + + @Override + public void onNext(T args) { + // do nothing + } + + }); + } + + /** + * An {@link Observer} must call an Observable's {@code subscribe} method + * in order to receive items and notifications from the Observable. + * + * @param onNext + * @return + */ public Subscription subscribe(final Action1 onNext) { if (onNext == null) { throw new IllegalArgumentException("onNext can not be null"); @@ -302,10 +378,26 @@ public void onNext(T args) { }); } + /** + * An {@link Observer} must call an Observable's {@code subscribe} method in + * order to receive items and notifications from the Observable. + * + * @param onNext + * @param scheduler + * @return + */ public Subscription subscribe(final Action1 onNext, Scheduler scheduler) { return subscribeOn(scheduler).subscribe(onNext); } + /** + * An {@link Observer} must call an Observable's {@code subscribe} method in + * order to receive items and notifications from the Observable. + * + * @param onNext + * @param onError + * @return + */ public Subscription subscribe(final Action1 onNext, final Action1 onError) { if (onNext == null) { throw new IllegalArgumentException("onNext can not be null"); @@ -340,10 +432,28 @@ public void onNext(T args) { }); } + /** + * An {@link Observer} must call an Observable's {@code subscribe} method in + * order to receive items and notifications from the Observable. + * + * @param onNext + * @param onError + * @param scheduler + * @return + */ public Subscription subscribe(final Action1 onNext, final Action1 onError, Scheduler scheduler) { return subscribeOn(scheduler).subscribe(onNext, onError); } + /** + * An {@link Observer} must call an Observable's {@code subscribe} method in + * order to receive items and notifications from the Observable. + * + * @param onNext + * @param onError + * @param onComplete + * @return + */ public Subscription subscribe(final Action1 onNext, final Action1 onError, final Action0 onComplete) { if (onNext == null) { throw new IllegalArgumentException("onNext can not be null"); @@ -381,28 +491,39 @@ public void onNext(T args) { }); } + /** + * An {@link Observer} must call an Observable's {@code subscribe} method in + * order to receive items and notifications from the Observable. + * + * @param onNext + * @param onError + * @param onComplete + * @param scheduler + * @return + */ public Subscription subscribe(final Action1 onNext, final Action1 onError, final Action0 onComplete, Scheduler scheduler) { return subscribeOn(scheduler).subscribe(onNext, onError, onComplete); } /** - * Returns a {@link ConnectableObservable} that upon connection causes the source Observable to - * push results into the specified subject. + * Returns a {@link ConnectableObservable} that upon connection causes the + * source Observable to push results into the specified subject. * - * @param subject - * the {@link Subject} for the {@link ConnectableObservable} to push source items - * into - * @param - * result type - * @return a {@link ConnectableObservable} that upon connection causes the source Observable to - * push results into the specified {@link Subject} + * @param subject the {@link Subject} for the {@link ConnectableObservable} + * to push source items into + * @param result type + * @return a {@link ConnectableObservable} that upon connection causes the + * source Observable to push results into the specified + * {@link Subject} + * @see RxJava Wiki: Observable.publish() and Observable.multicast() */ - public ConnectableObservable multicast(Subject subject) { + public ConnectableObservable multicast(Subject subject) { return OperationMulticast.multicast(this, subject); } /** - * Allow the {@link RxJavaErrorHandler} to receive the exception from onError. + * Allow the {@link RxJavaErrorHandler} to receive the exception from + * onError. * * @param e */ @@ -416,8 +537,7 @@ private void handleError(Throwable e) { * * This Observable is useful primarily for testing purposes. * - * @param - * the type of item emitted by the Observable + * @param the type of item emitted by the Observable */ private static class NeverObservable extends Observable { public NeverObservable() { @@ -433,10 +553,10 @@ public Subscription onSubscribe(Observer t1) { } /** - * an Observable that invokes {@link Observer#onError onError} when the {@link Observer} subscribes to it. + * An Observable that invokes {@link Observer#onError onError} when the + * {@link Observer} subscribes to it. * - * @param - * the type of item emitted by the Observable + * @param the type of item emitted by the Observable */ private static class ThrowObservable extends Observable { @@ -444,10 +564,10 @@ public ThrowObservable(final Throwable exception) { super(new OnSubscribeFunc() { /** - * Accepts an {@link Observer} and calls its {@link Observer#onError onError} method. + * Accepts an {@link Observer} and calls its + * {@link Observer#onError onError} method. * - * @param observer - * an {@link Observer} of this Observable + * @param observer an {@link Observer} of this Observable * @return a reference to the subscription */ @Override @@ -462,115 +582,187 @@ public Subscription onSubscribe(Observer observer) { } /** - * Creates an Observable that will execute the given function when an {@link Observer} subscribes to it. + * Creates an Observable that will execute the given function when an + * {@link Observer} subscribes to it. *

- * + * *

- * Write the function you pass to create so that it behaves as an Observable: It - * should invoke the Observer's {@link Observer#onNext onNext}, {@link Observer#onError onError}, and {@link Observer#onCompleted onCompleted} methods - * appropriately. + * Write the function you pass to create so that it behaves as + * an Observable: It should invoke the Observer's + * {@link Observer#onNext onNext}, {@link Observer#onError onError}, and + * {@link Observer#onCompleted onCompleted} methods appropriately. *

- * A well-formed Observable must invoke either the Observer's onCompleted method - * exactly once or its onError method exactly once. + * A well-formed Observable must invoke either the Observer's + * onCompleted method exactly once or its onError + * method exactly once. *

- * See Rx Design Guidelines (PDF) - * for detailed information. + * See Rx Design + * Guidelines (PDF) for detailed information. * - * @param - * the type of the items that this Observable emits - * @param func - * a function that accepts an {@code Observer}, invokes its {@code onNext}, {@code onError}, and {@code onCompleted} methods - * as appropriate, and returns a {@link Subscription} to allow the Observer to - * canceling the subscription - * @return an Observable that, when an {@link Observer} subscribes to it, will execute the given - * function + * @param the type of the items that this Observable emits + * @param func a function that accepts an {@code Observer}, invokes its + * {@code onNext}, {@code onError}, and {@code onCompleted} + * methods as appropriate, and returns a {@link Subscription} to + * allow the Observer to cancel the subscription + * @return an Observable that, when an {@link Observer} subscribes to it, + * will execute the given function + * @see RxJava Wiki: create() */ public static Observable create(OnSubscribeFunc func) { return new Observable(func); } /** - * Returns an Observable that emits no data to the {@link Observer} and immediately invokes - * its {@link Observer#onCompleted onCompleted} method. + * Returns an Observable that emits no data to the {@link Observer} and + * immediately invokes its {@link Observer#onCompleted onCompleted} method. *

* * - * @param - * the type of the items (ostensibly) emitted by the Observable - * @return an Observable that returns no data to the {@link Observer} and immediately invokes - * the {@link Observer}'s {@link Observer#onCompleted() onCompleted} method + * @param the type of the items (ostensibly) emitted by the Observable + * @return an Observable that returns no data to the {@link Observer} and + * immediately invokes the {@link Observer}'s + * {@link Observer#onCompleted() onCompleted} method + * @see RxJava Wiki: empty() + * @see MSDN: Observable.Empty Method */ public static Observable empty() { return from(new ArrayList()); } /** - * Returns an Observable that invokes an {@link Observer}'s {@link Observer#onError onError} method when the Observer subscribes to it + * Returns an Observable that emits no data to the {@link Observer} and + * immediately invokes its {@link Observer#onCompleted onCompleted} method + * with the specified scheduler. + *

+ * + * + * @param scheduler the scheduler to call the + {@link Observer#onCompleted onCompleted} method + * @param the type of the items (ostensibly) emitted by the Observable + * @return an Observable that returns no data to the {@link Observer} and + * immediately invokes the {@link Observer}'s + * {@link Observer#onCompleted() onCompleted} method with the + * specified scheduler + * @see RxJava Wiki: empty() + * @see MSDN: Observable.Empty Method (IScheduler) + */ + public static Observable empty(Scheduler scheduler) { + return Observable. empty().subscribeOn(scheduler); + } + + /** + * Returns an Observable that invokes an {@link Observer}'s + * {@link Observer#onError onError} method when the Observer subscribes to + * it. *

* * - * @param exception - * the particular error to report - * @param - * the type of the items (ostensibly) emitted by the Observable - * @return an Observable that invokes the {@link Observer}'s {@link Observer#onError onError} method when the Observer subscribes to it + * @param exception the particular error to report + * @param the type of the items (ostensibly) emitted by the Observable + * @return an Observable that invokes the {@link Observer}'s + * {@link Observer#onError onError} method when the Observer + * subscribes to it + * @see RxJava Wiki: error() + * @see MSDN: Observable.Throw Method */ public static Observable error(Throwable exception) { return new ThrowObservable(exception); } /** - * Converts an {@link Iterable} sequence into an Observable. + * Returns an Observable that invokes an {@link Observer}'s + * {@link Observer#onError onError} method with the specified scheduler. *

- * + * * - *

Implementation note: the entire iterable sequence will be immediately emitted each time an {@link Observer} subscribes. Since this occurs before the {@link Subscription} is returned, - * it in not possible to unsubscribe from the sequence before it completes. + * @param exception the particular error to report + * @param scheduler the scheduler to call the + * {@link Observer#onError onError} method + * @param the type of the items (ostensibly) emitted by the Observable + * @return an Observable that invokes the {@link Observer}'s + * {@link Observer#onError onError} method with the specified + * scheduler + * @see RxJava Wiki: error() + * @see MSDN: Observable.Throw Method + */ + public static Observable error(Throwable exception, Scheduler scheduler) { + return Observable. error(exception).subscribeOn(scheduler); + } + + /** + * Converts an {@link Iterable} sequence into an Observable. + *

+ * + *

+ * Note: the entire iterable sequence is immediately emitted each time an + * {@link Observer} subscribes. Since this occurs before the + * {@link Subscription} is returned, it is not possible to unsubscribe from + * the sequence before it completes. * - * @param iterable - * the source {@link Iterable} sequence - * @param - * the type of items in the {@link Iterable} sequence and the type of items to be - * emitted by the resulting Observable - * @return an Observable that emits each item in the source {@link Iterable} sequence + * @param iterable the source {@link Iterable} sequence + * @param the type of items in the {@link Iterable} sequence and the + * type of items to be emitted by the resulting Observable + * @return an Observable that emits each item in the source {@link Iterable} + * sequence + * @see RxJava Wiki: from() */ public static Observable from(Iterable iterable) { return create(OperationToObservableIterable.toObservableIterable(iterable)); } - + /** - * Converts an Array into an Observable. + * Converts an {@link Iterable} sequence into an Observable with the specified scheduler. *

- * + * * - *

Implementation note: the entire iterable sequence will be immediately emitted each time an {@link Observer} subscribes. Since this occurs before the {@link Subscription} is returned, - * it in not possible to unsubscribe from the sequence before it completes. + * @param iterable the source {@link Iterable} sequence + * @param scheduler the scheduler to emit the items of the iterable + * @param the type of items in the {@link Iterable} sequence and the + * type of items to be emitted by the resulting Observable + * @return an Observable that emits each item in the source {@link Iterable} + * sequence with the specified scheduler + * @see RxJava Wiki: from() + * @see MSDN: Observable.ToObservable + */ + public static Observable from(Iterable iterable, Scheduler scheduler) { + return from(iterable).observeOn(scheduler); + } + + /** + * Converts an Array into an Observable. + *

+ * + *

+ * Note: the entire array is immediately emitted each time an + * {@link Observer} subscribes. Since this occurs before the + * {@link Subscription} is returned, it is not possible to unsubscribe from + * the sequence before it completes. * - * @param items - * the source sequence - * @param - * the type of items in the {@link Iterable} sequence and the type of items to be + * @param items the source sequence + * @param the type of items in the Array and the type of items to be * emitted by the resulting Observable - * @return an Observable that emits each item in the source {@link Iterable} sequence + * @return an Observable that emits each item in the source Array + * @see RxJava Wiki: from() */ public static Observable from(T[] items) { return create(OperationToObservableIterable.toObservableIterable(Arrays.asList(items))); } /** - * Converts a series of items into an Observable. + * Converts an item into an Observable that emits that item. *

- * - * - *

Implementation note: the entire array will be immediately emitted each time an {@link Observer} subscribes. Since this occurs before the {@link Subscription} is returned, - * it in not possible to unsubscribe from the sequence before it completes. + * + *

+ * Note: the item is immediately emitted each time an {@link Observer} + * subscribes. Since this occurs before the {@link Subscription} is + * returned, it is not possible to unsubscribe from the sequence before it + * completes. * - * @param t1 - * item - * @param - * the type of items in the Array, and the type of items to be emitted by the - * resulting Observable - * @return an Observable that emits each item in the source Array + * @param t1 the item + * @param the type of the item, and the type of the item to be + * emitted by the resulting Observable + * @return an Observable that emits the item + * @see RxJava Wiki: from() */ @SuppressWarnings("unchecked") // suppress unchecked because we are using varargs inside the method @@ -581,19 +773,19 @@ public static Observable from(T t1) { /** * Converts a series of items into an Observable. *

- * - * - *

Implementation note: the entire array will be immediately emitted each time an {@link Observer} subscribes. Since this occurs before the {@link Subscription} is returned, - * it in not possible to unsubscribe from the sequence before it completes. + * + *

+ * Note: the items will be immediately emitted each time an {@link Observer} + * subscribes. Since this occurs before the {@link Subscription} is + * returned, it is not possible to unsubscribe from the sequence before it + * completes. * - * @param t1 - * item - * @param t2 - * item - * @param - * the type of items in the Array, and the type of items to be emitted by the + * @param t1 first item + * @param t2 second item + * @param the type of items, and the type of items to be emitted by the * resulting Observable - * @return an Observable that emits each item in the source Array + * @return an Observable that emits each item + * @see RxJava Wiki: from() */ @SuppressWarnings("unchecked") // suppress unchecked because we are using varargs inside the method @@ -604,21 +796,20 @@ public static Observable from(T t1, T t2) { /** * Converts a series of items into an Observable. *

- * - * - *

Implementation note: the entire array will be immediately emitted each time an {@link Observer} subscribes. Since this occurs before the {@link Subscription} is returned, - * it in not possible to unsubscribe from the sequence before it completes. + * + *

+ * Note: the items will be immediately emitted each time an {@link Observer} + * subscribes. Since this occurs before the {@link Subscription} is + * returned, it is not possible to unsubscribe from the sequence before it + * completes. * - * @param t1 - * item - * @param t2 - * item - * @param t3 - * item - * @param - * the type of items in the Array, and the type of items to be emitted by the + * @param t1 first item + * @param t2 second item + * @param t3 third item + * @param the type of items, and the type of items to be emitted by the * resulting Observable - * @return an Observable that emits each item in the source Array + * @return an Observable that emits each item + * @see RxJava Wiki: from() */ @SuppressWarnings("unchecked") // suppress unchecked because we are using varargs inside the method @@ -629,23 +820,21 @@ public static Observable from(T t1, T t2, T t3) { /** * Converts a series of items into an Observable. *

- * - * - *

Implementation note: the entire array will be immediately emitted each time an {@link Observer} subscribes. Since this occurs before the {@link Subscription} is returned, - * it in not possible to unsubscribe from the sequence before it completes. + * + *

+ * Note: the items will be immediately emitted each time an {@link Observer} + * subscribes. Since this occurs before the {@link Subscription} is + * returned, it is not possible to unsubscribe from the sequence before it + * completes. * - * @param t1 - * item - * @param t2 - * item - * @param t3 - * item - * @param t4 - * item - * @param - * the type of items in the Array, and the type of items to be emitted by the + * @param t1 first item + * @param t2 second item + * @param t3 third item + * @param t4 fourth item + * @param the type of items, and the type of items to be emitted by the * resulting Observable - * @return an Observable that emits each item in the source Array + * @return an Observable that emits each item + * @see RxJava Wiki: from() */ @SuppressWarnings("unchecked") // suppress unchecked because we are using varargs inside the method @@ -656,25 +845,22 @@ public static Observable from(T t1, T t2, T t3, T t4) { /** * Converts a series of items into an Observable. *

- * - * - *

Implementation note: the entire array will be immediately emitted each time an {@link Observer} subscribes. Since this occurs before the {@link Subscription} is returned, - * it in not possible to unsubscribe from the sequence before it completes. - * - * @param t1 - * item - * @param t2 - * item - * @param t3 - * item - * @param t4 - * item - * @param t5 - * item - * @param - * the type of items in the Array, and the type of items to be emitted by the + * + *

+ * Note: the items will be immediately emitted each time an {@link Observer} + * subscribes. Since this occurs before the {@link Subscription} is + * returned, it is not possible to unsubscribe from the sequence before it + * completes. + * + * @param t1 first item + * @param t2 second item + * @param t3 third item + * @param t4 fourth item + * @param t5 fifth item + * @param the type of items, and the type of items to be emitted by the * resulting Observable - * @return an Observable that emits each item in the source Array + * @return an Observable that emits each item + * @see RxJava Wiki: from() */ @SuppressWarnings("unchecked") // suppress unchecked because we are using varargs inside the method @@ -685,27 +871,23 @@ public static Observable from(T t1, T t2, T t3, T t4, T t5) { /** * Converts a series of items into an Observable. *

- * - * - *

Implementation note: the entire array will be immediately emitted each time an {@link Observer} subscribes. Since this occurs before the {@link Subscription} is returned, - * it in not possible to unsubscribe from the sequence before it completes. - * - * @param t1 - * item - * @param t2 - * item - * @param t3 - * item - * @param t4 - * item - * @param t5 - * item - * @param t6 - * item - * @param - * the type of items in the Array, and the type of items to be emitted by the + * + *

+ * Note: the items will be immediately emitted each time an {@link Observer} + * subscribes. Since this occurs before the {@link Subscription} is + * returned, it is not possible to unsubscribe from the sequence before it + * completes. + * + * @param t1 first item + * @param t2 second item + * @param t3 third item + * @param t4 fourth item + * @param t5 fifth item + * @param t6 sixth item + * @param the type of items, and the type of items to be emitted by the * resulting Observable - * @return an Observable that emits each item in the source Array + * @return an Observable that emits each item + * @see RxJava Wiki: from() */ @SuppressWarnings("unchecked") // suppress unchecked because we are using varargs inside the method @@ -716,29 +898,24 @@ public static Observable from(T t1, T t2, T t3, T t4, T t5, T t6) { /** * Converts a series of items into an Observable. *

- * - * - *

Implementation note: the entire array will be immediately emitted each time an {@link Observer} subscribes. Since this occurs before the {@link Subscription} is returned, - * it in not possible to unsubscribe from the sequence before it completes. - * - * @param t1 - * item - * @param t2 - * item - * @param t3 - * item - * @param t4 - * item - * @param t5 - * item - * @param t6 - * item - * @param t7 - * item - * @param - * the type of items in the Array, and the type of items to be emitted by the + * + *

+ * Note: the items will be immediately emitted each time an {@link Observer} + * subscribes. Since this occurs before the {@link Subscription} is + * returned, it is not possible to unsubscribe from the sequence before it + * completes. + * + * @param t1 first item + * @param t2 second item + * @param t3 third item + * @param t4 fourth item + * @param t5 fifth item + * @param t6 sixth item + * @param t7 seventh item + * @param the type of items, and the type of items to be emitted by the * resulting Observable - * @return an Observable that emits each item in the source Array + * @return an Observable that emits each item + * @see RxJava Wiki: from() */ @SuppressWarnings("unchecked") // suppress unchecked because we are using varargs inside the method @@ -749,31 +926,25 @@ public static Observable from(T t1, T t2, T t3, T t4, T t5, T t6, T t7) { /** * Converts a series of items into an Observable. *

- * - * - *

Implementation note: the entire array will be immediately emitted each time an {@link Observer} subscribes. Since this occurs before the {@link Subscription} is returned, - * it in not possible to unsubscribe from the sequence before it completes. - * - * @param t1 - * item - * @param t2 - * item - * @param t3 - * item - * @param t4 - * item - * @param t5 - * item - * @param t6 - * item - * @param t7 - * item - * @param t8 - * item - * @param - * the type of items in the Array, and the type of items to be emitted by the + * + *

+ * Note: the items will be immediately emitted each time an {@link Observer} + * subscribes. Since this occurs before the {@link Subscription} is + * returned, it is not possible to unsubscribe from the sequence before it + * completes. + * + * @param t1 first item + * @param t2 second item + * @param t3 third item + * @param t4 fourth item + * @param t5 fifth item + * @param t6 sixth item + * @param t7 seventh item + * @param t8 eighth item + * @param the type of items, and the type of items to be emitted by the * resulting Observable - * @return an Observable that emits each item in the source Array + * @return an Observable that emits each item + * @see RxJava Wiki: from() */ @SuppressWarnings("unchecked") // suppress unchecked because we are using varargs inside the method @@ -784,70 +955,57 @@ public static Observable from(T t1, T t2, T t3, T t4, T t5, T t6, T t7, T /** * Converts a series of items into an Observable. *

- * - * - *

Implementation note: the entire array will be immediately emitted each time an {@link Observer} subscribes. Since this occurs before the {@link Subscription} is returned, - * it in not possible to unsubscribe from the sequence before it completes. - * - * @param t1 - * item - * @param t2 - * item - * @param t3 - * item - * @param t4 - * item - * @param t5 - * item - * @param t6 - * item - * @param t7 - * item - * @param t8 - * item - * @param t9 - * item - * @param - * the type of items in the Array, and the type of items to be emitted by the + * + *

+ * Note: the items will be immediately emitted each time an {@link Observer} + * subscribes. Since this occurs before the {@link Subscription} is + * returned, it is not possible to unsubscribe from the sequence before it + * completes. + * + * @param t1 first item + * @param t2 second item + * @param t3 third item + * @param t4 fourth item + * @param t5 fifth item + * @param t6 sixth item + * @param t7 seventh item + * @param t8 eighth item + * @param t9 ninth item + * @param the type of items, and the type of items to be emitted by the * resulting Observable - * @return an Observable that emits each item in the source Array + * @return an Observable that emits each item + * @see RxJava Wiki: from() */ @SuppressWarnings("unchecked") // suppress unchecked because we are using varargs inside the method public static Observable from(T t1, T t2, T t3, T t4, T t5, T t6, T t7, T t8, T t9) { return from(Arrays.asList(t1, t2, t3, t4, t5, t6, t7, t8, t9)); } - + /** * Converts a series of items into an Observable. *

- * - * - *

Implementation note: the entire array will be immediately emitted each time an {@link Observer} subscribes. Since this occurs before the {@link Subscription} is returned, - * it in not possible to unsubscribe from the sequence before it completes. - * - * @param t1 - * item - * @param t2 - * item - * @param t3 - * item - * @param t4 - * item - * @param t5 - * item - * @param t6 - * item - * @param t7 - * item - * @param t8 - * item - * @param t10 - * item - * @param - * the type of items in the Array, and the type of items to be emitted by the + * + *

+ * Note: the items will be immediately emitted each time an {@link Observer} + * subscribes. Since this occurs before the {@link Subscription} is + * returned, it is not possible to unsubscribe from the sequence before it + * completes. + * + * @param t1 first item + * @param t2 second item + * @param t3 third item + * @param t4 fourth item + * @param t5 fifth item + * @param t6 sixth item + * @param t7 seventh item + * @param t8 eighth item + * @param t9 ninth item + * @param t10 tenth item + * @param the type of items, and the type of items to be emitted by the * resulting Observable - * @return an Observable that emits each item in the source Array + * @return an Observable that emits each item + * @see RxJava Wiki: from() */ @SuppressWarnings("unchecked") // suppress unchecked because we are using varargs inside the method @@ -856,45 +1014,61 @@ public static Observable from(T t1, T t2, T t3, T t4, T t5, T t6, T t7, T } /** - * Generates an Observable that emits a sequence of integers within a specified range. + * Generates an Observable that emits a sequence of integers within a + * specified range. *

- * - * - *

Implementation note: the entire range will be immediately emitted each time an {@link Observer} subscribes. Since this occurs before the {@link Subscription} is returned, - * it in not possible to unsubscribe from the sequence before it completes. - * - * @param start - * the value of the first integer in the sequence - * @param count - * the number of sequential integers to generate + * + *

+ * Note: the entire range is immediately emitted each time an + * {@link Observer} subscribes. Since this occurs before the + * {@link Subscription} is returned, it is not possible to unsubscribe from + * the sequence before it completes. * + * @param start the value of the first integer in the sequence + * @param count the number of sequential integers to generate * @return an Observable that emits a range of sequential integers - * - * @see Observable.Range Method (Int32, Int32) + * @see RxJava Wiki: range() + * @see Observable.Range Method (Int32, Int32) */ public static Observable range(int start, int count) { return from(Range.createWithCount(start, count)); } /** - * Returns an Observable that calls an Observable factory to create its Observable for each - * new Observer that subscribes. That is, for each subscriber, the actuall Observable is determined - * by the factory function. - * + * Generates an Observable that emits a sequence of integers within a + * specified range with the specified scheduler. + *

+ * + * @param start the value of the first integer in the sequence + * @param count the number of sequential integers to generate + * @param scheduler the scheduler to run the generator loop on + * @return an Observable that emits a range of sequential integers + * @see RxJava Wiki: range() + * @see Observable.Range Method (Int32, Int32, IScheduler) + */ + public static Observable range(int start, int count, Scheduler scheduler) { + return range(start, count).observeOn(scheduler); + } + + /** + * Returns an Observable that calls an Observable factory to create its + * Observable for each new Observer that subscribes. That is, for each + * subscriber, the actuall Observable is determined by the factory function. *

- * + * *

- * The defer operator allows you to defer or delay emitting items from an Observable until such - * time as an Observer subscribes to the Observable. This allows an {@link Observer} to easily - * obtain updates or a refreshed version of the sequence. + * The defer operator allows you to defer or delay emitting items from an + * Observable until such time as an Observer subscribes to the Observable. + * This allows an {@link Observer} to easily obtain updates or a refreshed + * version of the sequence. * - * @param observableFactory - * the Observable factory function to invoke for each {@link Observer} that - * subscribes to the resulting Observable - * @param - * the type of the items emitted by the Observable - * @return an Observable whose {@link Observer}s trigger an invocation of the given Observable - * factory function + * @param observableFactory the Observable factory function to invoke for + * each {@link Observer} that subscribes to the + * resulting Observable + * @param the type of the items emitted by the Observable + * @return an Observable whose {@link Observer}s trigger an invocation of + * the given Observable factory function + * @see RxJava Wiki: defer() */ public static Observable defer(Func0> observableFactory) { return create(OperationDefer.defer(observableFactory)); @@ -905,19 +1079,20 @@ public static Observable defer(Func0> o *

* *

- * To convert any object into an Observable that emits that object, pass that object into the - * just method. + * To convert any object into an Observable that emits that object, pass + * that object into the just method. *

- * This is similar to the {@link #from(java.lang.Object[])} method, except that - * from() will convert an {@link Iterable} object into an Observable that emits - * each of the items in the Iterable, one at a time, while the just() method - * converts an Iterable into an Observable that emits the entire Iterable as a single item. + * This is similar to the {@link #from(java.lang.Object[])} method, except + * that from() will convert an {@link Iterable} object into an + * Observable that emits each of the items in the Iterable, one at a time, + * while the just() method converts an Iterable into an + * Observable that emits the entire Iterable as a single item. * - * @param value - * the item to pass to the {@link Observer}'s {@link Observer#onNext onNext} method - * @param - * the type of that item + * @param value the item to pass to the {@link Observer}'s + * {@link Observer#onNext onNext} method + * @param the type of that item * @return an Observable that emits a single item and then completes + * @see RxJava Wiki: just() */ public static Observable just(T value) { List list = new ArrayList(); @@ -927,39 +1102,58 @@ public static Observable just(T value) { } /** - * Flattens a sequence of Observables emitted by an Observable into one Observable, without any - * transformation. + * Returns an Observable that emits a single item and then completes on a + * specified scheduler. + *

+ * This is a scheduler version of {@link Observable#just(Object)}. + * + * @param value the item to pass to the {@link Observer}'s + * {@link Observer#onNext onNext} method + * @param the type of that item + * @param scheduler the scheduler to send the single element on + * @return an Observable that emits a single item and then completes on a + * specified scheduler + * @see RxJava Wiki: just() + */ + public static Observable just(T value, Scheduler scheduler) { + return just(value).observeOn(scheduler); + } + + /** + * Flattens a sequence of Observables emitted by an Observable into one + * Observable, without any transformation. *

* *

- * You can combine the items emitted by multiple Observables so that they act like a single - * Observable, by using the {@code merge} method. + * You can combine the items emitted by multiple Observables so that they + * act like a single Observable, by using the {@code merge} method. * - * @param source - * an Observable that emits Observables - * @return an Observable that emits items that are the result of flattening the items emitted - * by the Observables emitted by the {@code source} Observable - * @see MSDN: Observable.Merge Method + * @param source an Observable that emits Observables + * @return an Observable that emits items that are the result of flattening + * the items emitted by the Observables emitted by the + * {@code source} Observable + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge Method */ public static Observable merge(Observable> source) { return create(OperationMerge.merge(source)); } - + /** - * Flattens a series of Observables into one Observable, without any transformation. + * Flattens a series of Observables into one Observable, without any + * transformation. *

* *

- * You can combine items emitted by multiple Observables so that they act like a single - * Observable, by using the {@code merge} method. + * You can combine items emitted by multiple Observables so that they act + * like a single Observable, by using the {@code merge} method. * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted - * by the {@code source} Observables - * @see MSDN: Observable.Merge Method + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg @@ -968,208 +1162,180 @@ public static Observable merge(Observable t1, Observable * *

- * You can combine items emitted by multiple Observables so that they act like a single - * Observable, by using the {@code merge} method. + * You can combine items emitted by multiple Observables so that they act + * like a single Observable, by using the {@code merge} method. * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @param t3 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted - * by the {@code source} Observables - * @see MSDN: Observable.Merge Method + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @param t3 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg public static Observable merge(Observable t1, Observable t2, Observable t3) { return create(OperationMerge.merge(t1, t2, t3)); } - + /** - * Flattens a series of Observables into one Observable, without any transformation. + * Flattens a series of Observables into one Observable, without any + * transformation. *

* *

- * You can combine items emitted by multiple Observables so that they act like a single - * Observable, by using the {@code merge} method. - * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @param t3 - * an Observable to be merged - * @param t4 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted - * by the {@code source} Observables - * @see MSDN: Observable.Merge Method + * You can combine items emitted by multiple Observables so that they act + * like a single Observable, by using the {@code merge} method. + * + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @param t3 an Observable to be merged + * @param t4 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg public static Observable merge(Observable t1, Observable t2, Observable t3, Observable t4) { return create(OperationMerge.merge(t1, t2, t3, t4)); } - + /** - * Flattens a series of Observables into one Observable, without any transformation. + * Flattens a series of Observables into one Observable, without any + * transformation. *

* *

- * You can combine items emitted by multiple Observables so that they act like a single - * Observable, by using the {@code merge} method. - * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @param t3 - * an Observable to be merged - * @param t4 - * an Observable to be merged - * @param t5 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted - * by the {@code source} Observables - * @see MSDN: Observable.Merge Method + * You can combine items emitted by multiple Observables so that they act + * like a single Observable, by using the {@code merge} method. + * + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @param t3 an Observable to be merged + * @param t4 an Observable to be merged + * @param t5 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg public static Observable merge(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5) { return create(OperationMerge.merge(t1, t2, t3, t4, t5)); } - + /** - * Flattens a series of Observables into one Observable, without any transformation. + * Flattens a series of Observables into one Observable, without any + * transformation. *

* *

- * You can combine items emitted by multiple Observables so that they act like a single - * Observable, by using the {@code merge} method. - * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @param t3 - * an Observable to be merged - * @param t4 - * an Observable to be merged - * @param t5 - * an Observable to be merged - * @param t6 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted - * by the {@code source} Observables - * @see MSDN: Observable.Merge Method + * You can combine items emitted by multiple Observables so that they act + * like a single Observable, by using the {@code merge} method. + * + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @param t3 an Observable to be merged + * @param t4 an Observable to be merged + * @param t5 an Observable to be merged + * @param t6 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg public static Observable merge(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6) { return create(OperationMerge.merge(t1, t2, t3, t4, t5, t6)); } - + /** - * Flattens a series of Observables into one Observable, without any transformation. + * Flattens a series of Observables into one Observable, without any + * transformation. *

* *

- * You can combine items emitted by multiple Observables so that they act like a single - * Observable, by using the {@code merge} method. - * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @param t3 - * an Observable to be merged - * @param t4 - * an Observable to be merged - * @param t5 - * an Observable to be merged - * @param t6 - * an Observable to be merged - * @param t7 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted - * by the {@code source} Observables - * @see MSDN: Observable.Merge Method + * You can combine items emitted by multiple Observables so that they act + * like a single Observable, by using the {@code merge} method. + * + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @param t3 an Observable to be merged + * @param t4 an Observable to be merged + * @param t5 an Observable to be merged + * @param t6 an Observable to be merged + * @param t7 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg public static Observable merge(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7) { return create(OperationMerge.merge(t1, t2, t3, t4, t5, t6, t7)); } - + /** - * Flattens a series of Observables into one Observable, without any transformation. + * Flattens a series of Observables into one Observable, without any + * transformation. *

* *

- * You can combine items emitted by multiple Observables so that they act like a single - * Observable, by using the {@code merge} method. - * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @param t3 - * an Observable to be merged - * @param t4 - * an Observable to be merged - * @param t5 - * an Observable to be merged - * @param t6 - * an Observable to be merged - * @param t7 - * an Observable to be merged - * @param t8 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted - * by the {@code source} Observables - * @see MSDN: Observable.Merge Method + * You can combine items emitted by multiple Observables so that they act + * like a single Observable, by using the {@code merge} method. + * + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @param t3 an Observable to be merged + * @param t4 an Observable to be merged + * @param t5 an Observable to be merged + * @param t6 an Observable to be merged + * @param t7 an Observable to be merged + * @param t8 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg public static Observable merge(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7, Observable t8) { return create(OperationMerge.merge(t1, t2, t3, t4, t5, t6, t7, t8)); } - + /** - * Flattens a series of Observables into one Observable, without any transformation. + * Flattens a series of Observables into one Observable, without any + * transformation. *

* *

- * You can combine items emitted by multiple Observables so that they act like a single - * Observable, by using the {@code merge} method. - * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @param t3 - * an Observable to be merged - * @param t4 - * an Observable to be merged - * @param t5 - * an Observable to be merged - * @param t6 - * an Observable to be merged - * @param t7 - * an Observable to be merged - * @param t8 - * an Observable to be merged - * @param t9 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted - * by the {@code source} Observables - * @see MSDN: Observable.Merge Method + * You can combine items emitted by multiple Observables so that they act + * like a single Observable, by using the {@code merge} method. + * + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @param t3 an Observable to be merged + * @param t4 an Observable to be merged + * @param t5 an Observable to be merged + * @param t6 an Observable to be merged + * @param t7 an Observable to be merged + * @param t8 an Observable to be merged + * @param t9 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg @@ -1178,35 +1344,35 @@ public static Observable merge(Observable t1, Observable * * - * @param observables - * an Observable of Observables - * @return an Observable that emits items that are the result of combining the items emitted by - * the {@code source} Observables, one after the other - * @see MSDN: Observable.Concat Method + * @param observables an Observable that emits Observables + * @return an Observable that emits items that are the result of combining + * the items emitted by the {@code source} Observables, one after + * the other + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat Method */ public static Observable concat(Observable> observables) { return create(OperationConcat.concat(observables)); } - + /** - * Returns an Observable that emits the items emitted by two or more Observables, one after the - * other. + * Returns an Observable that emits the items emitted by two Observables, + * one after the other. *

* * - * @param t1 - * an Observable to be concatenated - * @param t2 - * an Observable to be concatenated - * an Observable to be concatenated - * @return an Observable that emits items that are the result of combining the items emitted by - * the {@code source} Observables, one after the other - * @see MSDN: Observable.Concat Method + * @param t1 an Observable to be concatenated + * @param t2 an Observable to be concatenated + * @return an Observable that emits items that are the result of combining + * the items emitted by the {@code source} Observables, one after + * the other + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg @@ -1215,21 +1381,20 @@ public static Observable concat(Observable t1, Observable * * - * @param t1 - * an Observable to be concatenated - * @param t2 - * an Observable to be concatenated - * @param t3 - * an Observable to be concatenated - * an Observable to be concatenated - * @return an Observable that emits items that are the result of combining the items emitted by - * the {@code source} Observables, one after the other - * @see MSDN: Observable.Concat Method + * @param t1 an Observable to be concatenated + * @param t2 an Observable to be concatenated + * @param t3 an Observable to be concatenated + * + * @return an Observable that emits items that are the result of combining + * the items emitted by the {@code source} Observables, one after + * the other + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg @@ -1238,22 +1403,20 @@ public static Observable concat(Observable t1, Observable * * - * @param t1 - * an Observable to be concatenated - * @param t2 - * an Observable to be concatenated - * @param t3 - * an Observable to be concatenated - * @param t4 - * an Observable to be concatenated - * @return an Observable that emits items that are the result of combining the items emitted by - * the {@code source} Observables, one after the other - * @see MSDN: Observable.Concat Method + * @param t1 an Observable to be concatenated + * @param t2 an Observable to be concatenated + * @param t3 an Observable to be concatenated + * @param t4 an Observable to be concatenated + * @return an Observable that emits items that are the result of combining + * the items emitted by the {@code source} Observables, one after + * the other + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg @@ -1262,24 +1425,21 @@ public static Observable concat(Observable t1, Observable * * - * @param t1 - * an Observable to be concatenated - * @param t2 - * an Observable to be concatenated - * @param t3 - * an Observable to be concatenated - * @param t4 - * an Observable to be concatenated - * @param t5 - * an Observable to be concatenated - * @return an Observable that emits items that are the result of combining the items emitted by - * the {@code source} Observables, one after the other - * @see MSDN: Observable.Concat Method + * @param t1 an Observable to be concatenated + * @param t2 an Observable to be concatenated + * @param t3 an Observable to be concatenated + * @param t4 an Observable to be concatenated + * @param t5 an Observable to be concatenated + * @return an Observable that emits items that are the result of combining + * the items emitted by the {@code source} Observables, one after + * the other + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg @@ -1288,26 +1448,22 @@ public static Observable concat(Observable t1, Observable * * - * @param t1 - * an Observable to be concatenated - * @param t2 - * an Observable to be concatenated - * @param t3 - * an Observable to be concatenated - * @param t4 - * an Observable to be concatenated - * @param t5 - * an Observable to be concatenated - * @param t6 - * an Observable to be concatenated - * @return an Observable that emits items that are the result of combining the items emitted by - * the {@code source} Observables, one after the other - * @see MSDN: Observable.Concat Method + * @param t1 an Observable to be concatenated + * @param t2 an Observable to be concatenated + * @param t3 an Observable to be concatenated + * @param t4 an Observable to be concatenated + * @param t5 an Observable to be concatenated + * @param t6 an Observable to be concatenated + * @return an Observable that emits items that are the result of combining + * the items emitted by the {@code source} Observables, one after + * the other + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg @@ -1316,28 +1472,23 @@ public static Observable concat(Observable t1, Observable * * - * @param t1 - * an Observable to be concatenated - * @param t2 - * an Observable to be concatenated - * @param t3 - * an Observable to be concatenated - * @param t4 - * an Observable to be concatenated - * @param t5 - * an Observable to be concatenated - * @param t6 - * an Observable to be concatenated - * @param t7 - * an Observable to be concatenated - * @return an Observable that emits items that are the result of combining the items emitted by - * the {@code source} Observables, one after the other - * @see MSDN: Observable.Concat Method + * @param t1 an Observable to be concatenated + * @param t2 an Observable to be concatenated + * @param t3 an Observable to be concatenated + * @param t4 an Observable to be concatenated + * @param t5 an Observable to be concatenated + * @param t6 an Observable to be concatenated + * @param t7 an Observable to be concatenated + * @return an Observable that emits items that are the result of combining + * the items emitted by the {@code source} Observables, one after + * the other + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg @@ -1346,30 +1497,24 @@ public static Observable concat(Observable t1, Observable * * - * @param t1 - * an Observable to be concatenated - * @param t2 - * an Observable to be concatenated - * @param t3 - * an Observable to be concatenated - * @param t4 - * an Observable to be concatenated - * @param t5 - * an Observable to be concatenated - * @param t6 - * an Observable to be concatenated - * @param t7 - * an Observable to be concatenated - * @param t8 - * an Observable to be concatenated - * @return an Observable that emits items that are the result of combining the items emitted by - * the {@code source} Observables, one after the other - * @see MSDN: Observable.Concat Method + * @param t1 an Observable to be concatenated + * @param t2 an Observable to be concatenated + * @param t3 an Observable to be concatenated + * @param t4 an Observable to be concatenated + * @param t5 an Observable to be concatenated + * @param t6 an Observable to be concatenated + * @param t7 an Observable to be concatenated + * @param t8 an Observable to be concatenated + * @return an Observable that emits items that are the result of combining + * the items emitted by the {@code source} Observables, one after + * the other + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg @@ -1378,32 +1523,25 @@ public static Observable concat(Observable t1, Observable * * - * @param t1 - * an Observable to be concatenated - * @param t2 - * an Observable to be concatenated - * @param t3 - * an Observable to be concatenated - * @param t4 - * an Observable to be concatenated - * @param t5 - * an Observable to be concatenated - * @param t6 - * an Observable to be concatenated - * @param t7 - * an Observable to be concatenated - * @param t8 - * an Observable to be concatenated - * @param t9 - * an Observable to be concatenated - * @return an Observable that emits items that are the result of combining the items emitted by - * the {@code source} Observables, one after the other - * @see MSDN: Observable.Concat Method + * @param t1 an Observable to be concatenated + * @param t2 an Observable to be concatenated + * @param t3 an Observable to be concatenated + * @param t4 an Observable to be concatenated + * @param t5 an Observable to be concatenated + * @param t6 an Observable to be concatenated + * @param t7 an Observable to be concatenated + * @param t8 an Observable to be concatenated + * @param t9 an Observable to be concatenated + * @return an Observable that emits items that are the result of combining + * the items emitted by the {@code source} Observables, one after + * the other + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg @@ -1412,302 +1550,295 @@ public static Observable concat(Observable t1, Observable * *

- * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only invoke the {@code onError} method of its - * Observers once. + * Even if multiple merged Observables send {@code onError} notifications, + * {@code mergeDelayError} will only invoke the {@code onError} method of + * its Observers once. *

- * This method allows an Observer to receive all successfully emitted items from all of the - * source Observables without being interrupted by an error notification from one of them. + * This method allows an Observer to receive all successfully emitted items + * from all of the source Observables without being interrupted by an error + * notification from one of them. * - * @param source - * an Observable that emits Observables - * @return an Observable that emits items that are the result of flattening the items emitted by - * the Observables emitted by the {@code source} Observable - * @see MSDN: Observable.Merge Method + * @param source an Observable that emits Observables + * @return an Observable that emits items that are the result of flattening + * the items emitted by the Observables emitted by the + * {@code source} Observable + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge Method */ public static Observable mergeDelayError(Observable> source) { return create(OperationMergeDelayError.mergeDelayError(source)); } /** - * This behaves like {@link #merge(Observable, Observable)} except that if any of the merged Observables - * notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} will - * refrain from propagating that error notification until all of the merged Observables have - * finished emitting items. + * This behaves like {@link #merge(Observable, Observable)} except that if + * any of the merged Observables notify of an error via + * {@link Observer#onError onError}, {@code mergeDelayError} will refrain + * from propagating that error notification until all of the merged + * Observables have finished emitting items. *

* *

- * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only invoke the {@code onError} method of its - * Observers once. + * Even if multiple merged Observables send {@code onError} notifications, + * {@code mergeDelayError} will only invoke the {@code onError} method of + * its Observers once. *

- * This method allows an Observer to receive all successfully emitted items from all of the - * source Observables without being interrupted by an error notification from one of them. + * This method allows an Observer to receive all successfully emitted items + * from all of the source Observables without being interrupted by an error + * notification from one of them. * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted by - * the {@code source} Observables - * @see MSDN: Observable.Merge Method + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg public static Observable mergeDelayError(Observable t1, Observable t2) { return create(OperationMergeDelayError.mergeDelayError(t1, t2)); } - + /** - * This behaves like {@link #merge(Observable, Observable, Observable)} except that if any of the merged Observables - * notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} will - * refrain from propagating that error notification until all of the merged Observables have - * finished emitting items. + * This behaves like {@link #merge(Observable, Observable, Observable)} + * except that if any of the merged Observables notify of an error via + * {@link Observer#onError onError}, {@code mergeDelayError} will refrain + * from propagating that error notification until all of the merged + * Observables have finished emitting items. *

* *

- * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only invoke the {@code onError} method of its - * Observers once. + * Even if multiple merged Observables send {@code onError} notifications, + * {@code mergeDelayError} will only invoke the {@code onError} method of + * its Observers once. *

- * This method allows an Observer to receive all successfully emitted items from all of the - * source Observables without being interrupted by an error notification from one of them. + * This method allows an Observer to receive all successfully emitted items + * from all of the source Observables without being interrupted by an error + * notification from one of them. * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @param t3 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted by - * the {@code source} Observables - * @see MSDN: Observable.Merge Method + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @param t3 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg public static Observable mergeDelayError(Observable t1, Observable t2, Observable t3) { return create(OperationMergeDelayError.mergeDelayError(t1, t2, t3)); } - + /** - * This behaves like {@link #merge(Observable, Observable, Observable, Observable)} except that if any of the merged Observables - * notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} will - * refrain from propagating that error notification until all of the merged Observables have - * finished emitting items. + * This behaves like + * {@link #merge(Observable, Observable, Observable, Observable)} except + * that if any of the merged Observables notify of an error via + * {@link Observer#onError onError}, {@code mergeDelayError} will refrain + * from propagating that error notification until all of the merged + * Observables have finished emitting items. *

* *

- * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only invoke the {@code onError} method of its - * Observers once. + * Even if multiple merged Observables send {@code onError} notifications, + * {@code mergeDelayError} will only invoke the {@code onError} method of + * its Observers once. *

- * This method allows an Observer to receive all successfully emitted items from all of the - * source Observables without being interrupted by an error notification from one of them. + * This method allows an Observer to receive all successfully emitted items + * from all of the source Observables without being interrupted by an error + * notification from one of them. * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @param t3 - * an Observable to be merged - * @param t4 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted by - * the {@code source} Observables - * @see MSDN: Observable.Merge Method + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @param t3 an Observable to be merged + * @param t4 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg public static Observable mergeDelayError(Observable t1, Observable t2, Observable t3, Observable t4) { return create(OperationMergeDelayError.mergeDelayError(t1, t2, t3, t4)); } - + /** - * This behaves like {@link #merge(Observable, Observable, Observable, Observable, Observable)} except that if any of the merged Observables - * notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} will - * refrain from propagating that error notification until all of the merged Observables have - * finished emitting items. + * This behaves like {@link #merge(Observable, Observable, Observable, Observable, Observable)} + * except that if any of the merged Observables notify of an error via + * {@link Observer#onError onError}, {@code mergeDelayError} will refrain + * from propagating that error notification until all of the merged + * Observables have finished emitting items. *

* *

- * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only invoke the {@code onError} method of its - * Observers once. - *

- * This method allows an Observer to receive all successfully emitted items from all of the - * source Observables without being interrupted by an error notification from one of them. - * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @param t3 - * an Observable to be merged - * @param t4 - * an Observable to be merged - * @param t5 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted by - * the {@code source} Observables - * @see MSDN: Observable.Merge Method + * Even if multiple merged Observables send {@code onError} notifications, + * {@code mergeDelayError} will only invoke the {@code onError} method of + * its Observers once. + *

+ * This method allows an Observer to receive all successfully emitted items + * from all of the source Observables without being interrupted by an error + * notification from one of them. + * + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @param t3 an Observable to be merged + * @param t4 an Observable to be merged + * @param t5 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg public static Observable mergeDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5) { return create(OperationMergeDelayError.mergeDelayError(t1, t2, t3, t4, t5)); } - + /** - * This behaves like {@link #merge(Observable, Observable, Observable, Observable, Observable, Observable)} except that if any of the merged Observables - * notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} will - * refrain from propagating that error notification until all of the merged Observables have - * finished emitting items. + * This behaves like {@link #merge(Observable, Observable, Observable, Observable, Observable, Observable)} + * except that if any of the merged Observables notify of an error via + * {@link Observer#onError onError}, {@code mergeDelayError} will refrain + * from propagating that error notification until all of the merged + * Observables have finished emitting items. *

* *

- * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only invoke the {@code onError} method of its - * Observers once. - *

- * This method allows an Observer to receive all successfully emitted items from all of the - * source Observables without being interrupted by an error notification from one of them. - * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @param t3 - * an Observable to be merged - * @param t4 - * an Observable to be merged - * @param t5 - * an Observable to be merged - * @param t6 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted by - * the {@code source} Observables - * @see MSDN: Observable.Merge Method + * Even if multiple merged Observables send {@code onError} notifications, + * {@code mergeDelayError} will only invoke the {@code onError} method of + * its Observers once. + *

+ * This method allows an Observer to receive all successfully emitted items + * from all of the source Observables without being interrupted by an error + * notification from one of them. + * + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @param t3 an Observable to be merged + * @param t4 an Observable to be merged + * @param t5 an Observable to be merged + * @param t6 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg public static Observable mergeDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6) { return create(OperationMergeDelayError.mergeDelayError(t1, t2, t3, t4, t5, t6)); } - + /** - * This behaves like {@link #merge(Observable, Observable, Observable, Observable, Observable, Observable, Observable)} except that if any of the merged Observables - * notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} will - * refrain from propagating that error notification until all of the merged Observables have - * finished emitting items. + * This behaves like {@link #merge(Observable, Observable, Observable, Observable, Observable, Observable, Observable)} + * except that if any of the merged Observables notify of an error via + * {@link Observer#onError onError}, {@code mergeDelayError} will refrain + * from propagating that error notification until all of the merged + * Observables have finished emitting items. *

* *

- * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only invoke the {@code onError} method of its - * Observers once. - *

- * This method allows an Observer to receive all successfully emitted items from all of the - * source Observables without being interrupted by an error notification from one of them. - * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @param t3 - * an Observable to be merged - * @param t4 - * an Observable to be merged - * @param t5 - * an Observable to be merged - * @param t6 - * an Observable to be merged - * @param t7 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted by - * the {@code source} Observables - * @see MSDN: Observable.Merge Method + * Even if multiple merged Observables send {@code onError} notifications, + * {@code mergeDelayError} will only invoke the {@code onError} method of + * its Observers once. + *

+ * This method allows an Observer to receive all successfully emitted items + * from all of the source Observables without being interrupted by an error + * notification from one of them. + * + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @param t3 an Observable to be merged + * @param t4 an Observable to be merged + * @param t5 an Observable to be merged + * @param t6 an Observable to be merged + * @param t7 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg public static Observable mergeDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7) { return create(OperationMergeDelayError.mergeDelayError(t1, t2, t3, t4, t5, t6, t7)); } - + /** - * This behaves like {@link #merge(Observable, Observable, Observable, Observable, Observable, Observable, Observable, Observable)} except that if any of the merged Observables - * notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} will - * refrain from propagating that error notification until all of the merged Observables have - * finished emitting items. + * This behaves like {@link #merge(Observable, Observable, Observable, Observable, Observable, Observable, Observable, Observable)} + * except that if any of the merged Observables notify of an error via + * {@link Observer#onError onError}, {@code mergeDelayError} will refrain + * from propagating that error notification until all of the merged + * Observables have finished emitting items. *

* *

- * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only invoke the {@code onError} method of its - * Observers once. - *

- * This method allows an Observer to receive all successfully emitted items from all of the - * source Observables without being interrupted by an error notification from one of them. - * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @param t3 - * an Observable to be merged - * @param t4 - * an Observable to be merged - * @param t5 - * an Observable to be merged - * @param t6 - * an Observable to be merged - * @param t7 - * an Observable to be merged - * @param t8 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted by - * the {@code source} Observables - * @see MSDN: Observable.Merge Method + * Even if multiple merged Observables send {@code onError} notifications, + * {@code mergeDelayError} will only invoke the {@code onError} method of + * its Observers once. + *

+ * This method allows an Observer to receive all successfully emitted items + * from all of the source Observables without being interrupted by an error + * notification from one of them. + * + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @param t3 an Observable to be merged + * @param t4 an Observable to be merged + * @param t5 an Observable to be merged + * @param t6 an Observable to be merged + * @param t7 an Observable to be merged + * @param t8 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg public static Observable mergeDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7, Observable t8) { return create(OperationMergeDelayError.mergeDelayError(t1, t2, t3, t4, t5, t6, t7, t8)); } - + /** - * This behaves like {@link #merge(Observable, Observable, Observable, Observable, Observable, Observable, Observable, Observable, Observable)} except that if any of the merged Observables - * notify of an error via {@link Observer#onError onError}, {@code mergeDelayError} will - * refrain from propagating that error notification until all of the merged Observables have - * finished emitting items. + * This behaves like {@link #merge(Observable, Observable, Observable, Observable, Observable, Observable, Observable, Observable, Observable)} + * except that if any of the merged Observables notify of an error via + * {@link Observer#onError onError}, {@code mergeDelayError} will refrain + * from propagating that error notification until all of the merged + * Observables have finished emitting items. *

* *

- * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only invoke the {@code onError} method of its - * Observers once. - *

- * This method allows an Observer to receive all successfully emitted items from all of the - * source Observables without being interrupted by an error notification from one of them. - * - * @param t1 - * an Observable to be merged - * @param t2 - * an Observable to be merged - * @param t3 - * an Observable to be merged - * @param t4 - * an Observable to be merged - * @param t5 - * an Observable to be merged - * @param t6 - * an Observable to be merged - * @param t7 - * an Observable to be merged - * @param t8 - * an Observable to be merged - * @param t9 - * an Observable to be merged - * @return an Observable that emits items that are the result of flattening the items emitted by - * the {@code source} Observables - * @see MSDN: Observable.Merge Method + * Even if multiple merged Observables send {@code onError} notifications, + * {@code mergeDelayError} will only invoke the {@code onError} method of + * its Observers once. + *

+ * This method allows an Observer to receive all successfully emitted items + * from all of the source Observables without being interrupted by an error + * notification from one of them. + * + * @param t1 an Observable to be merged + * @param t2 an Observable to be merged + * @param t3 an Observable to be merged + * @param t4 an Observable to be merged + * @param t5 an Observable to be merged + * @param t6 an Observable to be merged + * @param t7 an Observable to be merged + * @param t8 an Observable to be merged + * @param t9 an Observable to be merged + * @return an Observable that emits items that are the result of flattening + * the items emitted by the {@code source} Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge Method */ @SuppressWarnings("unchecked") // suppress because the types are checked by the method signature before using a vararg @@ -1716,31 +1847,34 @@ public static Observable mergeDelayError(Observable t1, Obse } /** - * Returns an Observable that never sends any items or notifications to an {@link Observer}. + * Returns an Observable that never sends any items or notifications to an + * {@link Observer}. *

- * + * *

* This Observable is useful primarily for testing purposes. * - * @param - * the type of items (not) emitted by the Observable - * @return an Observable that never sends any items or notifications to an {@link Observer} + * @param the type of items (not) emitted by the Observable + * @return an Observable that never sends any items or notifications to an + * {@link Observer} + * @see RxJava Wiki: never() */ public static Observable never() { return new NeverObservable(); } /** - * Given an Observable that emits Observables, creates a single Observable that - * emits the items emitted by the most recently published of those Observables. + * Given an Observable that emits Observables, creates a single Observable + * that emits the items emitted by the most recently published of those + * Observables. *

- * + * * - * @param sequenceOfSequences - * the source Observable that emits Observables - * @return an Observable that emits only the items emitted by the most recently published - * Observable - * @deprecated Being renamed to {@link #switchOnNext} + * @param sequenceOfSequences the source Observable that emits Observables + * @return an Observable that emits only the items emitted by the most + * recently published Observable + * @see RxJava Wiki: switchOnNext() + * @deprecated use {@link #switchOnNext} */ @Deprecated public static Observable switchDo(Observable> sequenceOfSequences) { @@ -1748,92 +1882,134 @@ public static Observable switchDo(Observable - * + * * - * @param sequenceOfSequences - * the source Observable that emits Observables - * @return an Observable that emits only the items emitted by the most recently published - * Observable + * @param sequenceOfSequences the source Observable that emits Observables + * @return an Observable that emits only the items emitted by the most + * recently published Observable + * @see RxJava Wiki: switchOnNext() */ public static Observable switchOnNext(Observable> sequenceOfSequences) { return create(OperationSwitch.switchDo(sequenceOfSequences)); } - /** - * Accepts an Observable and wraps it in another Observable that ensures that the resulting - * Observable is chronologically well-behaved. + * Accepts an Observable and wraps it in another Observable that ensures + * that the resulting Observable is chronologically well-behaved. *

- * + * *

- * A well-behaved Observable does not interleave its invocations of the {@link Observer#onNext onNext}, {@link Observer#onCompleted onCompleted}, and {@link Observer#onError onError} methods of - * its {@link Observer}s; it invokes {@code onCompleted} or {@code onError} only once; and it never invokes {@code onNext} after invoking either {@code onCompleted} or {@code onError}. - * {@code synchronize} enforces this, and the Observable it returns invokes {@code onNext} and {@code onCompleted} or {@code onError} synchronously. + * A well-behaved Observable does not interleave its invocations of the + * {@link Observer#onNext onNext}, {@link Observer#onCompleted onCompleted}, + * and {@link Observer#onError onError} methods of its {@link Observer}s; it + * invokes {@code onCompleted} or {@code onError} only once; and it never + * invokes {@code onNext} after invoking either {@code onCompleted} or + * {@code onError}. {@code synchronize} enforces this, and the Observable it + * returns invokes {@code onNext} and {@code onCompleted} or {@code onError} + * synchronously. * - * @param observable - * the source Observable - * @param - * the type of item emitted by the source Observable - * @return an Observable that is a chronologically well-behaved version of the source - * Observable, and that synchronously notifies its {@link Observer}s + * @return an Observable that is a chronologically well-behaved version of + * the source Observable, and that synchronously notifies its + * {@link Observer}s + * @see RxJava Wiki: synchronize() */ - public static Observable synchronize(Observable observable) { - return create(OperationSynchronize.synchronize(observable)); + public Observable synchronize() { + return create(OperationSynchronize.synchronize(this)); + } + + /** + * Accepts an Observable and wraps it in another Observable that ensures + * that the resulting Observable is chronologically well-behaved. This is + * accomplished by acquiring a mutual-exclusion lock for the object + * provided as the lock parameter. + *

+ * + *

+ * A well-behaved Observable does not interleave its invocations of the + * {@link Observer#onNext onNext}, {@link Observer#onCompleted onCompleted}, + * and {@link Observer#onError onError} methods of its {@link Observer}s; it + * invokes {@code onCompleted} or {@code onError} only once; and it never + * invokes {@code onNext} after invoking either {@code onCompleted} or + * {@code onError}. {@code synchronize} enforces this, and the Observable it + * returns invokes {@code onNext} and {@code onCompleted} or {@code onError} + * synchronously. + * + * @param lock the lock object to synchronize each observer call on + * @return an Observable that is a chronologically well-behaved version of + * the source Observable, and that synchronously notifies its + * {@link Observer}s + * @see RxJava Wiki: synchronize() + */ + public Observable synchronize(Object lock) { + return create(OperationSynchronize.synchronize(this, lock)); + } + + /** + * @deprecated use {@link #synchronize()} or {@link #synchronize(Object)} + */ + @Deprecated + public static Observable synchronize(Observable source) { + return create(OperationSynchronize.synchronize(source)); } - /** * Emits an item each time interval (containing a sequential number). - * @param interval - * Interval size in time units (see below). - * @param unit - * Time units to use for the interval size. - * @return An Observable that emits an item each time interval. - * @see MSDN: Observable.Interval + *

+ * + * + * @param interval interval size in time units (see below) + * @param unit time units to use for the interval size + * @return an Observable that emits an item each time interval + * @see RxJava Wiki: interval() + * @see MSDN: Observable.Interval */ public static Observable interval(long interval, TimeUnit unit) { return create(OperationInterval.interval(interval, unit)); } - + /** * Emits an item each time interval (containing a sequential number). - * @param interval - * Interval size in time units (see below). - * @param unit - * Time units to use for the interval size. - * @param scheduler - * The scheduler to use for scheduling the items. - * @return An Observable that emits an item each time interval. - * @see MSDN: Observable.Interval + *

+ * + * + * @param interval interval size in time units (see below) + * @param unit time units to use for the interval size + * @param scheduler the scheduler to use for scheduling the items + * @return an Observable that emits an item each time interval + * @see RxJava Wiki: interval() + * @see MSDN: Observable.Interval */ public static Observable interval(long interval, TimeUnit unit, Scheduler scheduler) { return create(OperationInterval.interval(interval, unit, scheduler)); } - + /** - * Debounces by dropping all values that are followed by newer values before the timeout value expires. The timer resets on each `onNext` call. + * Drops items emitted by an Observable that are followed by newer items + * before a timeout value expires. The timer resets on each emission. *

- * NOTE: If events keep firing faster than the timeout then no data will be emitted. + * Note: If events keep firing faster than the timeout then no data will be + * emitted. *

- * + * *

* Information on debounce vs throttle: *

*

* - * @param timeout - * The time each value has to be 'the most recent' of the {@link Observable} to ensure that it's not dropped. - * @param unit - * The {@link TimeUnit} for the timeout. - * - * @return An {@link Observable} which filters out values which are too quickly followed up with newer values. + * @param timeout the time each value has to be "the most recent" of the + * {@link Observable} to ensure that it's not dropped + * @param unit the {@link TimeUnit} for the timeout + * @return an {@link Observable} that filters out items that are too + * quickly followed by newer items + * @see RxJava Wiki: debounce() * @see #throttleWithTimeout(long, TimeUnit) */ public Observable debounce(long timeout, TimeUnit unit) { @@ -1841,27 +2017,30 @@ public Observable debounce(long timeout, TimeUnit unit) { } /** - * Debounces by dropping all values that are followed by newer values before the timeout value expires. The timer resets on each `onNext` call. + * Drops items emitted by an Observable that are followed by newer items + * before a timeout value expires. The timer resets on each emission. *

- * NOTE: If events keep firing faster than the timeout then no data will be emitted. + * Note: If events keep firing faster than the timeout then no data will be + * emitted. *

- * + * *

* Information on debounce vs throttle: *

*

* - * @param timeout - * The time each value has to be 'the most recent' of the {@link Observable} to ensure that it's not dropped. - * @param unit - * The unit of time for the specified timeout. - * @param scheduler - * The {@link Scheduler} to use internally to manage the timers which handle timeout for each event. - * @return Observable which performs the throttle operation. + * @param timeout the time each value has to be "the most recent" of the + * {@link Observable} to ensure that it's not dropped + * @param unit the unit of time for the specified timeout + * @param scheduler the {@link Scheduler} to use internally to manage the + * timers that handle the timeout for each event + * @return an {@link Observable} that filters out items that are too + * quickly followed by newer items + * @see RxJava Wiki: debounce() * @see #throttleWithTimeout(long, TimeUnit, Scheduler) */ public Observable debounce(long timeout, TimeUnit unit, Scheduler scheduler) { @@ -1869,26 +2048,28 @@ public Observable debounce(long timeout, TimeUnit unit, Scheduler scheduler) } /** - * Debounces by dropping all values that are followed by newer values before the timeout value expires. The timer resets on each `onNext` call. + * Drops items emitted by an Observable that are followed by newer items + * before a timeout value expires. The timer resets on each emission. *

- * NOTE: If events keep firing faster than the timeout then no data will be emitted. + * Note: If events keep firing faster than the timeout then no data will be + * emitted. *

- * + * *

* Information on debounce vs throttle: *

*

* - * @param timeout - * The time each value has to be 'the most recent' of the {@link Observable} to ensure that it's not dropped. - * @param unit - * The {@link TimeUnit} for the timeout. - * - * @return An {@link Observable} which filters out values which are too quickly followed up with newer values. + * @param timeout the time each value has to be "the most recent" of the + * {@link Observable} to ensure that it's not dropped + * @param unit the {@link TimeUnit} for the timeout + * @return an {@link Observable} that filters out items that are too + * quickly followed by newer items + * @see RxJava Wiki: throttleWithTimeout() * @see #debounce(long, TimeUnit) */ public Observable throttleWithTimeout(long timeout, TimeUnit unit) { @@ -1896,19 +2077,30 @@ public Observable throttleWithTimeout(long timeout, TimeUnit unit) { } /** - * Debounces by dropping all values that are followed by newer values before the timeout value expires. The timer resets on each `onNext` call. + * Drops items emitted by an Observable that are followed by newer items + * before a timeout value expires. The timer resets on each emission. + *

+ * Note: If events keep firing faster than the timeout then no data will be + * emitted. *

- * NOTE: If events keep firing faster than the timeout then no data will be emitted. + * *

- * + * Information on debounce vs throttle: + *

+ *

* - * @param timeout - * The time each value has to be 'the most recent' of the {@link Observable} to ensure that it's not dropped. - * @param unit - * The unit of time for the specified timeout. - * @param scheduler - * The {@link Scheduler} to use internally to manage the timers which handle timeout for each event. - * @return Observable which performs the throttle operation. + * @param timeout the time each value has to be "the most recent" of the + * {@link Observable} to ensure that it's not dropped + * @param unit the {@link TimeUnit} for the timeout + * @param scheduler the {@link Scheduler} to use internally to manage the + * timers that handle the timeout for each event + * @return an {@link Observable} that filters out items that are too + * quickly followed by newer items + * @see RxJava Wiki: throttleWithTimeout() * @see #debounce(long, TimeUnit, Scheduler) */ public Observable throttleWithTimeout(long timeout, TimeUnit unit, Scheduler scheduler) { @@ -1916,53 +2108,60 @@ public Observable throttleWithTimeout(long timeout, TimeUnit unit, Scheduler } /** - * Throttles by skipping value until `skipDuration` passes and then emits the next received value. + * Throttles by skipping items until "skipDuration" passes and then emits + * the next received item. *

- * This differs from {@link #throttleLast} in that this only tracks passage of time whereas {@link #throttleLast} ticks at scheduled intervals. + * This differs from {@link #throttleLast} in that this only tracks passage + * of time whereas {@link #throttleLast} ticks at scheduled intervals. *

- * + * * - * @param windowDuration - * Time to wait before sending another value after emitting last value. - * @param unit - * The unit of time for the specified timeout. - * @return Observable which performs the throttle operation. + * @param windowDuration time to wait before sending another item after + * emitting the last item + * @param unit the unit of time for the specified timeout + * @return an Observable that performs the throttle operation + * @see RxJava Wiki: throttleFirst() */ public Observable throttleFirst(long windowDuration, TimeUnit unit) { return create(OperationThrottleFirst.throttleFirst(this, windowDuration, unit)); } /** - * Throttles by skipping value until `skipDuration` passes and then emits the next received value. + * Throttles by skipping items until "skipDuration" passes and then emits + * the next received item. *

- * This differs from {@link #throttleLast} in that this only tracks passage of time whereas {@link #throttleLast} ticks at scheduled intervals. + * This differs from {@link #throttleLast} in that this only tracks passage + * of time whereas {@link #throttleLast} ticks at scheduled intervals. *

- * + * * - * @param skipDuration - * Time to wait before sending another value after emitting last value. - * @param unit - * The unit of time for the specified timeout. - * @param scheduler - * The {@link Scheduler} to use internally to manage the timers which handle timeout for each event. - * @return Observable which performs the throttle operation. + * @param skipDuration time to wait before sending another item after + * emitting the last item + * @param unit the unit of time for the specified timeout + * @param scheduler the {@link Scheduler} to use internally to manage the + * timers that handle timeout for each event + * @return an Observable that performs the throttle operation + * @see RxJava Wiki: throttleFirst() */ public Observable throttleFirst(long skipDuration, TimeUnit unit, Scheduler scheduler) { return create(OperationThrottleFirst.throttleFirst(this, skipDuration, unit, scheduler)); } /** - * Throttles by returning the last value of each interval defined by 'intervalDuration'. + * Throttles by emitting the last item in each interval defined by + * intervalDuration. *

- * This differs from {@link #throttleFirst} in that this ticks along at a scheduled interval whereas {@link #throttleFirst} does not tick, it just tracks passage of time. + * This differs from {@link #throttleFirst} in that this ticks along at a + * scheduled interval whereas {@link #throttleFirst} does not tick, it just + * tracks passage of time. *

- * + * * - * @param intervalDuration - * Duration of windows within with the last value will be chosen. - * @param unit - * The unit of time for the specified interval. - * @return Observable which performs the throttle operation. + * @param intervalDuration duration of windows within which the last item + * will be emitted + * @param unit the unit of time for the specified interval + * @return an Observable that performs the throttle operation + * @see RxJava Wiki: throttleLast() * @see #sample(long, TimeUnit) */ public Observable throttleLast(long intervalDuration, TimeUnit unit) { @@ -1970,17 +2169,20 @@ public Observable throttleLast(long intervalDuration, TimeUnit unit) { } /** - * Throttles by returning the last value of each interval defined by 'intervalDuration'. + * Throttles by emitting the last item in each interval defined by + * intervalDuration. *

- * This differs from {@link #throttleFirst} in that this ticks along at a scheduled interval whereas {@link #throttleFirst} does not tick, it just tracks passage of time. + * This differs from {@link #throttleFirst} in that this ticks along at a + * scheduled interval whereas {@link #throttleFirst} does not tick, it just + * tracks passage of time. *

- * + * * - * @param intervalDuration - * Duration of windows within with the last value will be chosen. - * @param unit - * The unit of time for the specified interval. - * @return Observable which performs the throttle operation. + * @param intervalDuration duration of windows within which the last item + * will be emitted + * @param unit the unit of time for the specified interval + * @return an Observable that performs the throttle operation + * @see RxJava Wiki: throttleLast() * @see #sample(long, TimeUnit, Scheduler) */ public Observable throttleLast(long intervalDuration, TimeUnit unit, Scheduler scheduler) { @@ -1988,11 +2190,14 @@ public Observable throttleLast(long intervalDuration, TimeUnit unit, Schedule } /** - * Wraps each item emitted by a source Observable in a {@link Timestamped} object. + * Wraps each item emitted by a source Observable in a {@link Timestamped} + * object. *

- * + * * - * @return an Observable that emits timestamped items from the source Observable + * @return an Observable that emits timestamped items from the source + * Observable + * @see RxJava Wiki: timestamp() */ public Observable> timestamp() { return create(OperationTimestamp.timestamp(this)); @@ -2001,20 +2206,21 @@ public Observable> timestamp() { /** * Converts a {@link Future} into an Observable. *

- * + * *

- * You can convert any object that supports the {@link Future} interface into an Observable that - * emits the return value of the {@link Future#get} method of that object, by passing the - * object into the {@code from} method. + * You can convert any object that supports the {@link Future} interface + * into an Observable that emits the return value of the {@link Future#get} + * method of that object, by passing the object into the {@code from} + * method. *

- * Important note: This Observable is blocking; you cannot unsubscribe from it. + * Important note: This Observable is blocking; you cannot + * unsubscribe from it. * - * @param future - * the source {@link Future} - * @param - * the type of object that the {@link Future} returns, and also the type of item to - * be emitted by the resulting Observable + * @param future the source {@link Future} + * @param the type of object that the {@link Future} returns, and also + * the type of item to be emitted by the resulting Observable * @return an Observable that emits the item from the source Future + * @see RxJava Wiki: from() */ public static Observable from(Future future) { return create(OperationToObservableFuture.toObservableFuture(future)); @@ -2023,21 +2229,22 @@ public static Observable from(Future future) { /** * Converts a {@link Future} into an Observable. *

- * + * *

- * You can convert any object that supports the {@link Future} interface into an Observable that - * emits the return value of the {@link Future#get} method of that object, by passing the - * object into the {@code from} method. + * You can convert any object that supports the {@link Future} interface + * into an Observable that emits the return value of the {@link Future#get} + * method of that object, by passing the object into the {@code from} + * method. *

* - * @param future - * the source {@link Future} - * @param scheduler - * the {@link Scheduler} to wait for the Future on. Use a Scheduler such as {@link Schedulers#threadPoolForIO()} that can block and wait on the future. - * @param - * the type of object that the {@link Future} returns, and also the type of item to - * be emitted by the resulting Observable + * @param future the source {@link Future} + * @param scheduler the {@link Scheduler} to wait for the Future on. Use a + * Scheduler such as {@link Schedulers#threadPoolForIO()} + * that can block and wait on the future. + * @param the type of object that the {@link Future} returns, and also + * the type of item to be emitted by the resulting Observable * @return an Observable that emits the item from the source Future + * @see RxJava Wiki: from() */ public static Observable from(Future future, Scheduler scheduler) { return create(OperationToObservableFuture.toObservableFuture(future)).subscribeOn(scheduler); @@ -2046,43 +2253,40 @@ public static Observable from(Future future, Scheduler sched /** * Converts a {@link Future} into an Observable with timeout. *

- * + * *

- * You can convert any object that supports the {@link Future} interface into an Observable that - * emits the return value of the {link Future#get} method of that object, by passing the - * object into the {@code from} method. + * You can convert any object that supports the {@link Future} interface + * into an Observable that emits the return value of the {link Future#get} + * method of that object, by passing the object into the {@code from} + * method. *

- * Important note: This Observable is blocking; you cannot unsubscribe from it. + * Important note: This Observable is blocking; you cannot + * unsubscribe from it. * - * @param future - * the source {@link Future} - * @param timeout - * the maximum time to wait before calling get() - * @param unit - * the {@link TimeUnit} of the time argument - * @param - * the type of object that the {@link Future} returns, and also the type of item to - * be emitted by the resulting Observable + * @param future the source {@link Future} + * @param timeout the maximum time to wait before calling get() + * @param unit the {@link TimeUnit} of the timeout argument + * @param the type of object that the {@link Future} returns, and also + * the type of item to be emitted by the resulting Observable * @return an Observable that emits the item from the source {@link Future} + * @see RxJava Wiki: from() */ public static Observable from(Future future, long timeout, TimeUnit unit) { return create(OperationToObservableFuture.toObservableFuture(future, timeout, unit)); } /** - * Returns an Observable that emits Boolean values that indicate whether the pairs of items - * emitted by two source Observables are equal. + * Returns an Observable that emits Boolean values that indicate whether the + * pairs of items emitted by two source Observables are equal. *

- * + * * - * @param first - * one Observable to compare - * @param second - * the second Observable to compare - * @param - * the type of items emitted by each Observable - * @return an Observable that emits Booleans that indicate whether the corresponding items - * emitted by the source Observables are equal + * @param first the first Observable to compare + * @param second the second Observable to compare + * @param the type of items emitted by each Observable + * @return an Observable that emits Booleans that indicate whether the + * corresponding items emitted by the source Observables are equal + * @see RxJava Wiki: sequenceEqual() */ public static Observable sequenceEqual(Observable first, Observable second) { return sequenceEqual(first, second, new Func2() { @@ -2094,174 +2298,187 @@ public Boolean call(T first, T second) { } /** - * Returns an Observable that emits Boolean values that indicate whether the pairs of items - * emitted by two source Observables are equal based on the results of a specified equality - * function. + * Returns an Observable that emits Boolean values that indicate whether the + * pairs of items emitted by two source Observables are equal based on the + * results of a specified equality function. *

- * + * * - * @param first - * one Observable to compare - * @param second - * the second Observable to compare - * @param equality - * a function used to compare items emitted by both Observables - * @param - * the type of items emitted by each Observable - * @return an Observable that emits Booleans that indicate whether the corresponding items - * emitted by the source Observables are equal + * @param first the first Observable to compare + * @param second the second Observable to compare + * @param equality a function used to compare items emitted by both + * Observables + * @param the type of items emitted by each Observable + * @return an Observable that emits Booleans that indicate whether the + * corresponding items emitted by the source Observables are equal + * @see RxJava Wiki: sequenceEqual() */ public static Observable sequenceEqual(Observable first, Observable second, Func2 equality) { return zip(first, second, equality); } /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of two items emitted, in sequence, by two other Observables. + * Returns an Observable that emits the results of a function of your + * choosing applied to combinations of two items emitted, in sequence, by + * two other Observables. *

* - *

{@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by {@code w0} and the first item emitted by {@code w1}; the second item emitted by - * the new Observable will be the result of the function applied to the second item emitted by {@code w0} and the second item emitted by {@code w1}; and so forth. *

- * The resulting {@code Observable} returned from {@code zip} will invoke {@link Observer#onNext onNext} as many times as the number of {@code onNext} invocations - * of the source Observable that emits the fewest items. - * - * @param o1 - * one source Observable - * @param o2 - * another source Observable - * @param zipFunction - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable + * {@code zip} applies this function in strict sequence, so the first item + * emitted by the new Observable will be the result of the function applied + * to the first item emitted by {@code o1} and the first item emitted by + * {@code o2}; the second item emitted by the new Observable will be the + * result of the function applied to the second item emitted by {@code o1} + * and the second item emitted by {@code o2}; and so forth. + *

+ * The resulting {@code Observable} returned from {@code zip} will + * invoke {@link Observer#onNext onNext} as many times as the number of + * {@code onNext} invocations of the source Observable that emits the fewest + * items. + * + * @param o1 the first source Observable + * @param o2 another source Observable + * @param zipFunction a function that, when applied to an item emitted by + * each of the source Observables, results in an item that will + * be emitted by the resulting Observable * @return an Observable that emits the zipped results + * @see RxJava Wiki: zip() */ public static Observable zip(Observable o1, Observable o2, Func2 zipFunction) { return create(OperationZip.zip(o1, o2, zipFunction)); } /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of three items emitted, in sequence, by three other Observables. + * Returns an Observable that emits the results of a function of your + * choosing applied to combinations of three items emitted, in sequence, by + * three other Observables. *

* - *

{@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by {@code w0}, the first item emitted by {@code w1}, and the first item emitted by {@code w2}; the second - * item emitted by the new Observable will be the result of the - * function applied to the second item emitted by {@code w0}, the second item emitted by {@code w1}, and the second item emitted by {@code w2}; and so forth. *

- * The resulting {@code Observable} returned from {@code zip} will invoke {@link Observer#onNext onNext} as many times as the number of {@code onNext} invocations - * of the source Observable that emits the fewest items. - * - * @param o1 - * one source Observable - * @param o2 - * a second source Observable - * @param o3 - * a third source Observable - * @param zipFunction - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable + * {@code zip} applies this function in strict sequence, so the first item + * emitted by the new Observable will be the result of the function applied + * to the first item emitted by {@code o1}, the first item emitted by + * {@code o2}, and the first item emitted by {@code o3}; the second item + * emitted by the new Observable will be the result of the function applied + * to the second item emitted by {@code o1}, the second item emitted by + * {@code o2}, and the second item emitted by {@code o3}; and so forth. + *

+ * The resulting {@code Observable} returned from {@code zip} will + * invoke {@link Observer#onNext onNext} as many times as the number of + * {@code onNext} invocations of the source Observable that emits the fewest + * items. + * + * @param o1 the first source Observable + * @param o2 a second source Observable + * @param o3 a third source Observable + * @param zipFunction a function that, when applied to an item emitted by + * each of the source Observables, results in an item + * that will be emitted by the resulting Observable * @return an Observable that emits the zipped results + * @see RxJava Wiki: zip() */ public static Observable zip(Observable o1, Observable o2, Observable o3, Func3 zipFunction) { return create(OperationZip.zip(o1, o2, o3, zipFunction)); } /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of four items emitted, in sequence, by four other Observables. + * Returns an Observable that emits the results of a function of your + * choosing applied to combinations of four items emitted, in sequence, by + * four other Observables. *

* - *

{@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by {@code w0}, the first item emitted by {@code w1}, the first item emitted by {@code w2}, and the first item - * emitted by {@code w3}; the second item emitted by - * the new Observable will be the result of the function applied to the second item emitted by - * each of those Observables; and so forth. *

- * The resulting {@code Observable} returned from {@code zip} will invoke {@link Observer#onNext onNext} as many times as the number of {@code onNext} invocations - * of the source Observable that emits the fewest items. - * - * @param o1 - * one source Observable - * @param o2 - * a second source Observable - * @param o3 - * a third source Observable - * @param o4 - * a fourth source Observable - * @param zipFunction - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable + * {@code zip} applies this function in strict sequence, so the first item + * emitted by the new Observable will be the result of the function applied + * to the first item emitted by {@code o1}, the first item emitted by + * {@code o2}, the first item emitted by {@code o3}, and the first item + * emitted by {@code 04}; the second item emitted by the new Observable will + * be the result of the function applied to the second item emitted by each + * of those Observables; and so forth. + *

+ * The resulting {@code Observable} returned from {@code zip} will + * invoke {@link Observer#onNext onNext} as many times as the number of + * {@code onNext} invocations of the source Observable that emits the fewest + * items. + * + * @param o1 one source Observable + * @param o2 a second source Observable + * @param o3 a third source Observable + * @param o4 a fourth source Observable + * @param zipFunction a function that, when applied to an item emitted by + * each of the source Observables, results in an item that will + * be emitted by the resulting Observable * @return an Observable that emits the zipped results + * @see RxJava Wiki: zip() */ public static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Func4 zipFunction) { return create(OperationZip.zip(o1, o2, o3, o4, zipFunction)); } /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of five items emitted, in sequence, by five other Observables. + * Returns an Observable that emits the results of a function of your + * choosing applied to combinations of five items emitted, in sequence, by + * five other Observables. *

* - *

{@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by {@code w0}, the first item emitted by {@code w1}, the first item emitted by {@code w2}, and the first item - * emitted by {@code w3}; the second item emitted by - * the new Observable will be the result of the function applied to the second item emitted by - * each of those Observables; and so forth. *

- * The resulting {@code Observable} returned from {@code zip} will invoke {@link Observer#onNext onNext} as many times as the number of {@code onNext} invocations - * of the source Observable that emits the fewest items. - * - * @param o1 - * one source Observable - * @param o2 - * a second source Observable - * @param o3 - * a third source Observable - * @param o4 - * a fourth source Observable - * @param o5 - * a fifth source Observable - * @param zipFunction - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable + * {@code zip} applies this function in strict sequence, so the first item + * emitted by the new Observable will be the result of the function applied + * to the first item emitted by {@code o1}, the first item emitted by + * {@code o2}, the first item emitted by {@code o3}, the first item emitted + * by {@code o4}, and the first item emitted by {@code o5}; the second item + * emitted by the new Observable will be the result of the function applied + * to the second item emitted by each of those Observables; and so forth. + *

+ * The resulting {@code Observable} returned from {@code zip} will + * invoke {@link Observer#onNext onNext} as many times as the number of + * {@code onNext} invocations of the source Observable that emits the fewest + * items. + * + * @param o1 the first source Observable + * @param o2 a second source Observable + * @param o3 a third source Observable + * @param o4 a fourth source Observable + * @param o5 a fifth source Observable + * @param zipFunction a function that, when applied to an item emitted by + * each of the source Observables, results in an item + * that will be emitted by the resulting Observable * @return an Observable that emits the zipped results + * @see RxJava Wiki: zip() */ public static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Func5 zipFunction) { return create(OperationZip.zip(o1, o2, o3, o4, o5, zipFunction)); } /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of six items emitted, in sequence, by six other Observables. + * Returns an Observable that emits the results of a function of your + * choosing applied to combinations of six items emitted, in sequence, by + * six other Observables. *

* - *

{@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by {@code w0}, the first item emitted by {@code w1}, the first item emitted by {@code w2}, and the first item - * emitted by {@code w3}; the second item emitted by - * the new Observable will be the result of the function applied to the second item emitted by - * each of those Observables; and so forth. *

- * The resulting {@code Observable} returned from {@code zip} will invoke {@link Observer#onNext onNext} as many times as the number of {@code onNext} invocations - * of the source Observable that emits the fewest items. - * - * @param o1 - * one source Observable - * @param o2 - * a second source Observable - * @param o3 - * a third source Observable - * @param o4 - * a fourth source Observable - * @param o5 - * a fifth source Observable - * @param o6 - * a sixth source Observable - * @param zipFunction - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable + * {@code zip} applies this function in strict sequence, so the first item + * emitted by the new Observable will be the result of the function applied + * to the first item emitted each source Observable, the second item emitted + * by the new Observable will be the result of the function applied to the + * second item emitted by each of those Observables, and so forth. + *

+ * The resulting {@code Observable} returned from {@code zip} will + * invoke {@link Observer#onNext onNext} as many times as the number of + * {@code onNext} invocations of the source Observable that emits the fewest + * items. + * + * @param o1 the first source Observable + * @param o2 a second source Observable + * @param o3 a third source Observable + * @param o4 a fourth source Observable + * @param o5 a fifth source Observable + * @param o6 a sixth source Observable + * @param zipFunction a function that, when applied to an item emitted by + * each of the source Observables, results in an item + * that will be emitted by the resulting Observable * @return an Observable that emits the zipped results + * @see RxJava Wiki: zip() */ public static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Func6 zipFunction) { @@ -2269,37 +2486,35 @@ public static Observable zip(Observable * - *

{@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by {@code w0}, the first item emitted by {@code w1}, the first item emitted by {@code w2}, and the first item - * emitted by {@code w3}; the second item emitted by - * the new Observable will be the result of the function applied to the second item emitted by - * each of those Observables; and so forth. *

- * The resulting {@code Observable} returned from {@code zip} will invoke {@link Observer#onNext onNext} as many times as the number of {@code onNext} invocations - * of the source Observable that emits the fewest items. - * - * @param o1 - * one source Observable - * @param o2 - * a second source Observable - * @param o3 - * a third source Observable - * @param o4 - * a fourth source Observable - * @param o5 - * a fifth source Observable - * @param o6 - * a sixth source Observable - * @param o7 - * a seventh source Observable - * @param zipFunction - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable + * {@code zip} applies this function in strict sequence, so the first item + * emitted by the new Observable will be the result of the function applied + * to the first item emitted each source Observable, the second item emitted + * by the new Observable will be the result of the function applied to the + * second item emitted by each of those Observables, and so forth. + *

+ * The resulting {@code Observable} returned from {@code zip} will + * invoke {@link Observer#onNext onNext} as many times as the number of + * {@code onNext} invocations of the source Observable that emits the fewest + * items. + * + * @param o1 the first source Observable + * @param o2 a second source Observable + * @param o3 a third source Observable + * @param o4 a fourth source Observable + * @param o5 a fifth source Observable + * @param o6 a sixth source Observable + * @param o7 a seventh source Observable + * @param zipFunction a function that, when applied to an item emitted by + * each of the source Observables, results in an item + * that will be emitted by the resulting Observable * @return an Observable that emits the zipped results + * @see RxJava Wiki: zip() */ public static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Func7 zipFunction) { @@ -2307,39 +2522,36 @@ public static Observable zip(Observable * - *

{@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by {@code w0}, the first item emitted by {@code w1}, the first item emitted by {@code w2}, and the first item - * emitted by {@code w3}; the second item emitted by - * the new Observable will be the result of the function applied to the second item emitted by - * each of those Observables; and so forth. *

- * The resulting {@code Observable} returned from {@code zip} will invoke {@link Observer#onNext onNext} as many times as the number of {@code onNext} invocations - * of the source Observable that emits the fewest items. - * - * @param o1 - * one source Observable - * @param o2 - * a second source Observable - * @param o3 - * a third source Observable - * @param o4 - * a fourth source Observable - * @param o5 - * a fifth source Observable - * @param o6 - * a sixth source Observable - * @param o7 - * a seventh source Observable - * @param o8 - * an eighth source Observable - * @param zipFunction - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable + * {@code zip} applies this function in strict sequence, so the first item + * emitted by the new Observable will be the result of the function applied + * to the first item emitted each source Observable, the second item emitted + * by the new Observable will be the result of the function applied to the + * second item emitted by each of those Observables, and so forth. + *

+ * The resulting {@code Observable} returned from {@code zip} will + * invoke {@link Observer#onNext onNext} as many times as the number of + * {@code onNext} invocations of the source Observable that emits the fewest + * items. + * + * @param o1 the first source Observable + * @param o2 a second source Observable + * @param o3 a third source Observable + * @param o4 a fourth source Observable + * @param o5 a fifth source Observable + * @param o6 a sixth source Observable + * @param o7 a seventh source Observable + * @param o8 an eighth source Observable + * @param zipFunction a function that, when applied to an item emitted by + * each of the source Observables, results in an item + * that will be emitted by the resulting Observable * @return an Observable that emits the zipped results + * @see RxJava Wiki: zip() */ public static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, Func8 zipFunction) { @@ -2347,41 +2559,37 @@ public static Observable zip(Observable * - *

{@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by {@code w0}, the first item emitted by {@code w1}, the first item emitted by {@code w2}, and the first item - * emitted by {@code w3}; the second item emitted by - * the new Observable will be the result of the function applied to the second item emitted by - * each of those Observables; and so forth. *

- * The resulting {@code Observable} returned from {@code zip} will invoke {@link Observer#onNext onNext} as many times as the number of {@code onNext} invocations - * of the source Observable that emits the fewest items. - * - * @param o1 - * one source Observable - * @param o2 - * a second source Observable - * @param o3 - * a third source Observable - * @param o4 - * a fourth source Observable - * @param o5 - * a fifth source Observable - * @param o6 - * a sixth source Observable - * @param o7 - * a seventh source Observable - * @param o8 - * an eighth source Observable - * @param o9 - * a ninth source Observable - * @param zipFunction - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable + * {@code zip} applies this function in strict sequence, so the first item + * emitted by the new Observable will be the result of the function applied + * to the first item emitted each source Observable, the second item emitted + * by the new Observable will be the result of the function applied to the + * second item emitted by each of those Observables, and so forth. + *

+ * The resulting {@code Observable} returned from {@code zip} will + * invoke {@link Observer#onNext onNext} as many times as the number of + * {@code onNext} invocations of the source Observable that emits the fewest + * items. + * + * @param o1 the first source Observable + * @param o2 a second source Observable + * @param o3 a third source Observable + * @param o4 a fourth source Observable + * @param o5 a fifth source Observable + * @param o6 a sixth source Observable + * @param o7 a seventh source Observable + * @param o8 an eighth source Observable + * @param o9 a ninth source Observable + * @param zipFunction a function that, when applied to an item emitted by + * each of the source Observables, results in an item + * that will be emitted by the resulting Observable * @return an Observable that emits the zipped results + * @see RxJava Wiki: zip() */ public static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, Observable o9, Func9 zipFunction) { @@ -2389,32 +2597,63 @@ public static Observable zip(Observab } /** - * Combines the given observables, emitting an event containing an aggregation of the latest values of each of the source observables - * each time an event is received from one of the source observables, where the aggregation is defined by the given function. + * Combines the given Observables, emitting an event containing an + * aggregation of the latest values of each of the source observables each + * time an event is received from one of the source observables, where the + * aggregation is defined by the given function. *

- * + * * - * @param o1 - * The first source observable. - * @param o2 - * The second source observable. - * @param combineFunction - * The aggregation function used to combine the source observable values. - * @return An Observable that combines the source Observables with the given combine function + * @param o1 the first source Observable + * @param o2 the second source Observable + * @param combineFunction the aggregation function used to combine the + * source observable values + * @return an Observable that combines the source Observables with the + * given combine function + * @see RxJava Wiki: combineLatest() */ public static Observable combineLatest(Observable o1, Observable o2, Func2 combineFunction) { return create(OperationCombineLatest.combineLatest(o1, o2, combineFunction)); } /** - * @see #combineLatest(Observable, Observable, Func2) + * Combines the given Observables, emitting an event containing an + * aggregation of the latest values of each of the source observables each + * time an event is received from one of the source observables, where the + * aggregation is defined by the given function. + *

+ * + * + * @param o1 the first source Observable + * @param o2 the second source Observable + * @param o3 the third source Observable + * @param combineFunction the aggregation function used to combine the + * source observable values + * @return an Observable that combines the source Observables with the + * given combine function + * @see RxJava Wiki: combineLatest() */ public static Observable combineLatest(Observable o1, Observable o2, Observable o3, Func3 combineFunction) { return create(OperationCombineLatest.combineLatest(o1, o2, o3, combineFunction)); } /** - * @see #combineLatest(Observable, Observable, Func2) + * Combines the given Observables, emitting an event containing an + * aggregation of the latest values of each of the source observables each + * time an event is received from one of the source observables, where the + * aggregation is defined by the given function. + *

+ * + * + * @param o1 the first source Observable + * @param o2 the second source Observable + * @param o3 the third source Observable + * @param o4 the fourth source Observable + * @param combineFunction the aggregation function used to combine the + * source observable values + * @return an Observable that combines the source Observables with the + * given combine function + * @see RxJava Wiki: combineLatest() */ public static Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Func4 combineFunction) { @@ -2422,7 +2661,23 @@ public static Observable combineLatest(Observable + * + * + * @param o1 the first source Observable + * @param o2 the second source Observable + * @param o3 the third source Observable + * @param o4 the fourth source Observable + * @param o5 the fifth source Observable + * @param combineFunction the aggregation function used to combine the + * source observable values + * @return an Observable that combines the source Observables with the + * given combine function + * @see RxJava Wiki: combineLatest() */ public static Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Func5 combineFunction) { @@ -2430,7 +2685,24 @@ public static Observable combineLatest(Observable + * + * + * @param o1 the first source Observable + * @param o2 the second source Observable + * @param o3 the third source Observable + * @param o4 the fourth source Observable + * @param o5 the fifth source Observable + * @param o6 the sixth source Observable + * @param combineFunction the aggregation function used to combine the + * source observable values + * @return an Observable that combines the source Observables with the + * given combine function + * @see RxJava Wiki: combineLatest() */ public static Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Func6 combineFunction) { @@ -2438,7 +2710,25 @@ public static Observable combineLatest(Observable } /** - * @see #combineLatest(Observable, Observable, Func2) + * Combines the given Observables, emitting an event containing an + * aggregation of the latest values of each of the source observables each + * time an event is received from one of the source observables, where the + * aggregation is defined by the given function. + *

+ * + * + * @param o1 the first source Observable + * @param o2 the second source Observable + * @param o3 the third source Observable + * @param o4 the fourth source Observable + * @param o5 the fifth source Observable + * @param o6 the sixth source Observable + * @param o7 the seventh source Observable + * @param combineFunction the aggregation function used to combine the + * source observable values + * @return an Observable that combines the source Observables with the + * given combine function + * @see RxJava Wiki: combineLatest() */ public static Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Func7 combineFunction) { @@ -2446,7 +2736,26 @@ public static Observable combineLatest(Observ } /** - * @see #combineLatest(Observable, Observable, Func2) + * Combines the given Observables, emitting an event containing an + * aggregation of the latest values of each of the source observables each + * time an event is received from one of the source observables, where the + * aggregation is defined by the given function. + *

+ * + * + * @param o1 the first source Observable + * @param o2 the second source Observable + * @param o3 the third source Observable + * @param o4 the fourth source Observable + * @param o5 the fifth source Observable + * @param o6 the sixth source Observable + * @param o7 the seventh source Observable + * @param o8 the eighth source Observable + * @param combineFunction the aggregation function used to combine the + * source observable values + * @return an Observable that combines the source Observables with the + * given combine function + * @see RxJava Wiki: combineLatest() */ public static Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, Func8 combineFunction) { @@ -2454,7 +2763,27 @@ public static Observable combineLatest(Ob } /** - * @see #combineLatest(Observable, Observable, Func2) + * Combines the given Observables, emitting an event containing an + * aggregation of the latest values of each of the source observables each + * time an event is received from one of the source observables, where the + * aggregation is defined by the given function. + *

+ * + * + * @param o1 the first source Observable + * @param o2 the second source Observable + * @param o3 the third source Observable + * @param o4 the fourth source Observable + * @param o5 the fifth source Observable + * @param o6 the sixth source Observable + * @param o7 the seventh source Observable + * @param o8 the eighth source Observable + * @param o9 the ninth source Observable + * @param combineFunction the aggregation function used to combine the + * source observable values + * @return an Observable that combines the source Observables with the + * given combine function + * @see RxJava Wiki: combineLatest() */ public static Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, Observable o9, Func9 combineFunction) { @@ -2462,19 +2791,28 @@ public static Observable combineLates } /** - * Creates an Observable which produces buffers of collected values. - * - *

This Observable produces connected non-overlapping buffers. The current buffer is - * emitted and replaced with a new buffer when the Observable produced by the specified {@link Func0} produces a {@link rx.util.Closing} object. The * {@link Func0} will then - * be used to create a new Observable to listen for the end of the next buffer. + * Creates an Observable that produces buffers of collected items. + *

+ * + *

+ * This Observable produces connected, non-overlapping buffers. The current + * buffer is emitted and replaced with a new buffer when the Observable + * produced by the specified bufferClosingSelector produces a + * {@link rx.util.Closing} object. The bufferClosingSelector + * will then be used to create a new Observable to listen for the end of + * the next buffer. * - * @param bufferClosingSelector - * The {@link Func0} which is used to produce an {@link Observable} for every buffer created. - * When this {@link Observable} produces a {@link rx.util.Closing} object, the associated buffer - * is emitted and replaced with a new one. - * @return - * An {@link Observable} which produces connected non-overlapping buffers, which are emitted - * when the current {@link Observable} created with the {@link Func0} argument produces a {@link rx.util.Closing} object. + * @param bufferClosingSelector the {@link Func0} which is used to produce + * an {@link Observable} for every buffer + * created. When this {@link Observable} + * produces a {@link rx.util.Closing} object, + * the associated buffer is emitted and + * replaced with a new one. + * @return an {@link Observable} which produces connected, non-overlapping + * buffers, which are emitted when the current {@link Observable} + * created with the {@link Func0} argument produces a + * {@link rx.util.Closing} object + * @see RxJava Wiki: buffer() */ public Observable> buffer(Func0> bufferClosingSelector) { return create(OperationBuffer.buffer(this, bufferClosingSelector)); @@ -2482,402 +2820,481 @@ public Observable> buffer(Func0> /** * Creates an Observable which produces buffers of collected values. - * - *

This Observable produces buffers. Buffers are created when the specified "bufferOpenings" - * Observable produces a {@link rx.util.Opening} object. Additionally the {@link Func0} argument - * is used to create an Observable which produces {@link rx.util.Closing} objects. When this - * Observable produces such an object, the associated buffer is emitted. - * - * @param bufferOpenings - * The {@link Observable} which, when it produces a {@link rx.util.Opening} object, will cause - * another buffer to be created. - * @param bufferClosingSelector - * The {@link Func0} which is used to produce an {@link Observable} for every buffer created. - * When this {@link Observable} produces a {@link rx.util.Closing} object, the associated buffer - * is emitted. - * @return - * An {@link Observable} which produces buffers which are created and emitted when the specified {@link Observable}s publish certain objects. + *

+ * + *

+ * This Observable produces buffers. Buffers are created when the specified + * bufferOpenings Observable produces a {@link rx.util.Opening} + * object. Additionally the bufferClosingSelector argument is + * used to create an Observable which produces {@link rx.util.Closing} + * objects. When this Observable produces such an object, the associated + * buffer is emitted. + * + * @param bufferOpenings the {@link Observable} that, when it produces a + * {@link rx.util.Opening} object, will cause another + * buffer to be created + * @param bufferClosingSelector the {@link Func1} that is used to produce + * an {@link Observable} for every buffer + * created. When this {@link Observable} + * produces a {@link rx.util.Closing} object, + * the associated buffer is emitted. + * @return an {@link Observable} that produces buffers that are created and + * emitted when the specified {@link Observable}s publish certain + * objects + * @see RxJava Wiki: buffer() */ public Observable> buffer(Observable bufferOpenings, Func1> bufferClosingSelector) { return create(OperationBuffer.buffer(this, bufferOpenings, bufferClosingSelector)); } /** - * Creates an Observable which produces buffers of collected values. - * - *

This Observable produces connected non-overlapping buffers, each containing "count" - * elements. When the source Observable completes or encounters an error, the current - * buffer is emitted, and the event is propagated. + * Creates an Observable that produces buffers of collected items. + *

+ * + *

+ * This Observable produces connected, non-overlapping buffers, each + * containing count items. When the source Observable completes + * or encounters an error, the current buffer is emitted, and the event is + * propagated. * - * @param count - * The maximum size of each buffer before it should be emitted. - * @return - * An {@link Observable} which produces connected non-overlapping buffers containing at most - * "count" produced values. + * @param count the maximum size of each buffer before it should be emitted + * @return an {@link Observable} that produces connected, non-overlapping + * buffers containing at most "count" items + * @see RxJava Wiki: buffer() */ public Observable> buffer(int count) { return create(OperationBuffer.buffer(this, count)); } /** - * Creates an Observable which produces buffers of collected values. - * - *

This Observable produces buffers every "skip" values, each containing "count" - * elements. When the source Observable completes or encounters an error, the current - * buffer is emitted, and the event is propagated. + * Creates an Observable which produces buffers of collected items. + *

+ * + *

+ * This Observable produces buffers every skip items, each + * containing count items. When the source Observable + * completes or encounters an error, the current buffer is emitted, and the + * event is propagated. * - * @param count - * The maximum size of each buffer before it should be emitted. - * @param skip - * How many produced values need to be skipped before starting a new buffer. Note that when "skip" and - * "count" are equals that this is the same operation as {@link Observable#buffer(int)}. - * @return - * An {@link Observable} which produces buffers every "skipped" values containing at most - * "count" produced values. + * @param count the maximum size of each buffer before it should be emitted + * @param skip how many produced items need to be skipped before starting a + * new buffer. Note that when skip and + * count are equal, this is the same operation as + * {@link Observable#buffer(int)}. + * @return an {@link Observable} that produces buffers every + * skip item containing at most count + * items + * @see RxJava Wiki: buffer() */ public Observable> buffer(int count, int skip) { return create(OperationBuffer.buffer(this, count, skip)); } /** - * Creates an Observable which produces buffers of collected values. - * - *

This Observable produces connected non-overlapping buffers, each of a fixed duration - * specified by the "timespan" argument. When the source Observable completes or encounters - * an error, the current buffer is emitted and the event is propagated. + * Creates an Observable that produces buffers of collected values. + *

+ * + *

+ * This Observable produces connected, non-overlapping buffers, each of a + * fixed duration specified by the timespan argument. When the + * source Observable completes or encounters an error, the current buffer is + * emitted and the event is propagated. * - * @param timespan - * The period of time each buffer is collecting values before it should be emitted, and - * replaced with a new buffer. - * @param unit - * The unit of time which applies to the "timespan" argument. - * @return - * An {@link Observable} which produces connected non-overlapping buffers with a fixed duration. + * @param timespan the period of time each buffer collects values before it + * should be emitted and replaced with a new buffer + * @param unit the unit of time which applies to the timespan + * argument + * @return an {@link Observable} that produces connected, non-overlapping + * buffers with a fixed duration + * @see RxJava Wiki: buffer() */ public Observable> buffer(long timespan, TimeUnit unit) { return create(OperationBuffer.buffer(this, timespan, unit)); } /** - * Creates an Observable which produces buffers of collected values. - * - *

This Observable produces connected non-overlapping buffers, each of a fixed duration - * specified by the "timespan" argument. When the source Observable completes or encounters - * an error, the current buffer is emitted and the event is propagated. + * Creates an Observable that produces buffers of collected values. + *

+ * + *

+ * This Observable produces connected, non-overlapping buffers, each of a + * fixed duration specified by the timespan argument. When the + * source Observable completes or encounters an error, the current buffer is + * emitted and the event is propagated. * - * @param timespan - * The period of time each buffer is collecting values before it should be emitted, and - * replaced with a new buffer. - * @param unit - * The unit of time which applies to the "timespan" argument. - * @param scheduler - * The {@link Scheduler} to use when determining the end and start of a buffer. - * @return - * An {@link Observable} which produces connected non-overlapping buffers with a fixed duration. + * @param timespan the period of time each buffer collects values before it + * should be emitted and replaced with a new buffer + * @param unit the unit of time which applies to the timespan + * argument + * @param scheduler the {@link Scheduler} to use when determining the end + * and start of a buffer + * @return an {@link Observable} that produces connected, non-overlapping + * buffers with a fixed duration + * @see RxJava Wiki: buffer() */ public Observable> buffer(long timespan, TimeUnit unit, Scheduler scheduler) { return create(OperationBuffer.buffer(this, timespan, unit, scheduler)); } /** - * Creates an Observable which produces buffers of collected values. This Observable produces connected - * non-overlapping buffers, each of a fixed duration specified by the "timespan" argument or a maximum size - * specified by the "count" argument (which ever is reached first). When the source Observable completes - * or encounters an error, the current buffer is emitted and the event is propagated. + * Creates an Observable that produces buffers of collected items. This + * Observable produces connected, non-overlapping buffers, each of a fixed + * duration specified by the timespan argument or a maximum + * size specified by the count argument (whichever is reached + * first). When the source Observable completes or encounters an error, the + * current buffer is emitted and the event is propagated. + *

+ * * - * @param timespan - * The period of time each buffer is collecting values before it should be emitted, and - * replaced with a new buffer. - * @param unit - * The unit of time which applies to the "timespan" argument. - * @param count - * The maximum size of each buffer before it should be emitted. - * @return - * An {@link Observable} which produces connected non-overlapping buffers which are emitted after - * a fixed duration or when the buffer has reached maximum capacity (which ever occurs first). + * @param timespan the period of time each buffer collects values before it + * should be emitted and replaced with a new buffer + * @param unit the unit of time which applies to the timespan + * argument + * @param count the maximum size of each buffer before it should be emitted + * @return an {@link Observable} that produces connected, non-overlapping + * buffers that are emitted after a fixed duration or when the + * buffer reaches maximum capacity (whichever occurs first) + * @see RxJava Wiki: buffer() */ public Observable> buffer(long timespan, TimeUnit unit, int count) { return create(OperationBuffer.buffer(this, timespan, unit, count)); } /** - * Creates an Observable which produces buffers of collected values. This Observable produces connected - * non-overlapping buffers, each of a fixed duration specified by the "timespan" argument or a maximum size - * specified by the "count" argument (which ever is reached first). When the source Observable completes - * or encounters an error, the current buffer is emitted and the event is propagated. + * Creates an Observable that produces buffers of collected items. This + * Observable produces connected, non-overlapping buffers, each of a fixed + * duration specified by the timespan argument or a maximum + * size specified by the count argument (whichever is reached + * first). When the source Observable completes or encounters an error, the + * current buffer is emitted and the event is propagated. + *

+ * * - * @param timespan - * The period of time each buffer is collecting values before it should be emitted, and - * replaced with a new buffer. - * @param unit - * The unit of time which applies to the "timespan" argument. - * @param count - * The maximum size of each buffer before it should be emitted. - * @param scheduler - * The {@link Scheduler} to use when determining the end and start of a buffer. - * @return - * An {@link Observable} which produces connected non-overlapping buffers which are emitted after - * a fixed duration or when the buffer has reached maximum capacity (which ever occurs first). + * @param timespan the period of time each buffer collects values before it + * should be emitted and replaced with a new buffer + * @param unit the unit of time which applies to the timespan + * argument + * @param count the maximum size of each buffer before it should be emitted + * @param scheduler the {@link Scheduler} to use when determining the end + and start of a buffer + * @return an {@link Observable} that produces connected, non-overlapping + * buffers that are emitted after a fixed duration or when the + * buffer has reached maximum capacity (whichever occurs first) + * @see RxJava Wiki: buffer() */ public Observable> buffer(long timespan, TimeUnit unit, int count, Scheduler scheduler) { return create(OperationBuffer.buffer(this, timespan, unit, count, scheduler)); } /** - * Creates an Observable which produces buffers of collected values. This Observable starts a new buffer - * periodically, which is determined by the "timeshift" argument. Each buffer is emitted after a fixed timespan - * specified by the "timespan" argument. When the source Observable completes or encounters an error, the - * current buffer is emitted and the event is propagated. + * Creates an Observable that produces buffers of collected items. This + * Observable starts a new buffer periodically, as determined by the + * timeshift argument. Each buffer is emitted after a fixed + * timespan, specified by the timespan argument. When the + * source Observable completes or encounters an error, the current buffer is + * emitted and the event is propagated. + *

+ * * - * @param timespan - * The period of time each buffer is collecting values before it should be emitted. - * @param timeshift - * The period of time after which a new buffer will be created. - * @param unit - * The unit of time which applies to the "timespan" and "timeshift" argument. - * @return - * An {@link Observable} which produces new buffers periodically, and these are emitted after - * a fixed timespan has elapsed. + * @param timespan the period of time each buffer collects values before it + * should be emitted + * @param timeshift the period of time after which a new buffer will be + * created + * @param unit the unit of time that applies to the timespan + * and timeshift arguments + * @return an {@link Observable} that produces new buffers periodically and + * emits these after a fixed timespan has elapsed. + * @see RxJava Wiki: buffer() */ public Observable> buffer(long timespan, long timeshift, TimeUnit unit) { return create(OperationBuffer.buffer(this, timespan, timeshift, unit)); } /** - * Creates an Observable which produces buffers of collected values. This Observable starts a new buffer - * periodically, which is determined by the "timeshift" argument. Each buffer is emitted after a fixed timespan - * specified by the "timespan" argument. When the source Observable completes or encounters an error, the - * current buffer is emitted and the event is propagated. + * Creates an Observable that produces buffers of collected items. This + * Observable starts a new buffer periodically, as determined by the + * timeshift argument. Each buffer is emitted after a fixed + * timespan, specified by the timespan argument. When the + * source Observable completes or encounters an error, the current buffer is + * emitted and the event is propagated. + *

+ * * - * @param timespan - * The period of time each buffer is collecting values before it should be emitted. - * @param timeshift - * The period of time after which a new buffer will be created. - * @param unit - * The unit of time which applies to the "timespan" and "timeshift" argument. - * @param scheduler - * The {@link Scheduler} to use when determining the end and start of a buffer. - * @return - * An {@link Observable} which produces new buffers periodically, and these are emitted after - * a fixed timespan has elapsed. + * @param timespan the period of time each buffer collects values before it + * should be emitted + * @param timeshift the period of time after which a new buffer will be + * created + * @param unit the unit of time that applies to the timespan + * and timeshift arguments + * @param scheduler the {@link Scheduler} to use when determining the end + * and start of a buffer + * @return an {@link Observable} that produces new buffers periodically and + * emits these after a fixed timespan has elapsed + * @see RxJava Wiki: buffer() */ public Observable> buffer(long timespan, long timeshift, TimeUnit unit, Scheduler scheduler) { return create(OperationBuffer.buffer(this, timespan, timeshift, unit, scheduler)); } /** - * Creates an Observable which produces windows of collected values. This Observable produces connected - * non-overlapping windows. The current window is emitted and replaced with a new window when the - * Observable produced by the specified {@link Func0} produces a {@link rx.util.Closing} object. The {@link Func0} will then be used to create a new Observable to listen for the end of the next + * Creates an Observable that produces windows of collected items. This + * Observable produces connected, non-overlapping windows. The current + * window is emitted and replaced with a new window when the Observable + * produced by the specified closingSelector produces a + * {@link rx.util.Closing} object. The closingSelector will + * then be used to create a new Observable to listen for the end of the next * window. + *

+ * * - * @param closingSelector - * The {@link Func0} which is used to produce an {@link Observable} for every window created. - * When this {@link Observable} produces a {@link rx.util.Closing} object, the associated window - * is emitted and replaced with a new one. - * @return - * An {@link Observable} which produces connected non-overlapping windows, which are emitted - * when the current {@link Observable} created with the {@link Func0} argument produces a {@link rx.util.Closing} object. + * @param closingSelector the {@link Func0} used to produce an + * {@link Observable} for every window created. When this + * {@link Observable} emits a {@link rx.util.Closing} object, the + * associated window is emitted and replaced with a new one. + * @return an {@link Observable} that produces connected, non-overlapping + * windows, which are emitted when the current {@link Observable} + * created with the closingSelector argument emits a + * {@link rx.util.Closing} object. + * @see RxJava Wiki: window() */ public Observable> window(Func0> closingSelector) { return create(OperationWindow.window(this, closingSelector)); } /** - * Creates an Observable which produces windows of collected values. This Observable produces windows. - * Chunks are created when the specified "windowOpenings" Observable produces a {@link rx.util.Opening} object. - * Additionally the {@link Func0} argument is used to create an Observable which produces {@link rx.util.Closing} objects. When this Observable produces such an object, the associated window is - * emitted. + * Creates an Observable that produces windows of collected items. This + * Observable produces windows. Chunks are created when the + * windowOpenings Observable produces a {@link rx.util.Opening} + * object. Additionally the closingSelector argument creates an + * Observable that produces {@link rx.util.Closing} objects. When this + * Observable produces such an object, the associated window is emitted. + *

+ * * - * @param windowOpenings - * The {@link Observable} which when it produces a {@link rx.util.Opening} object, will cause - * another window to be created. - * @param closingSelector - * The {@link Func0} which is used to produce an {@link Observable} for every window created. - * When this {@link Observable} produces a {@link rx.util.Closing} object, the associated window - * is emitted. - * @return - * An {@link Observable} which produces windows which are created and emitted when the specified {@link Observable}s publish certain objects. + * @param windowOpenings the {@link Observable} that, when it produces a + * {@link rx.util.Opening} object, causes another + * window to be created + * @param closingSelector the {@link Func1} that produces an + * {@link Observable} for every window created. When + * this {@link Observable} produces a + * {@link rx.util.Closing} object, the associated + * window is emitted. + * @return an {@link Observable} that produces windows that are created and + * emitted when the specified {@link Observable}s publish certain + * objects + * @see RxJava Wiki: window() */ public Observable> window(Observable windowOpenings, Func1> closingSelector) { return create(OperationWindow.window(this, windowOpenings, closingSelector)); } /** - * Creates an Observable which produces windows of collected values. This Observable produces connected - * non-overlapping windows, each containing "count" elements. When the source Observable completes or - * encounters an error, the current window is emitted, and the event is propagated. + * Creates an Observable that produces windows of collected items. This + * Observable produces connected, non-overlapping windows, each containing + * count elements. When the source Observable completes or + * encounters an error, the current window is emitted, and the event is + * propagated. + *

+ * * - * @param count - * The maximum size of each window before it should be emitted. - * @return - * An {@link Observable} which produces connected non-overlapping windows containing at most - * "count" produced values. + * @param count the maximum size of each window before it should be emitted + * @return an {@link Observable} that produces connected, non-overlapping + * windows containing at most count items + * @see RxJava Wiki: window() */ public Observable> window(int count) { return create(OperationWindow.window(this, count)); } /** - * Creates an Observable which produces windows of collected values. This Observable produces windows every - * "skip" values, each containing "count" elements. When the source Observable completes or encounters an error, - * the current window is emitted and the event is propagated. + * Creates an Observable that produces windows of collected items. This + * Observable produces windows every skip items, each + * containing count elements. When the source Observable + * completes or encounters an error, the current window is emitted and the + * event is propagated. + *

+ * * - * @param count - * The maximum size of each window before it should be emitted. - * @param skip - * How many produced values need to be skipped before starting a new window. Note that when "skip" and - * "count" are equals that this is the same operation as {@link #window(int)}. - * @return - * An {@link Observable} which produces windows every "skipped" values containing at most - * "count" produced values. + * @param count the maximum size of each window before it should be emitted + * @param skip how many items need to be skipped before starting a new + * window. Note that if skip and count + * are equal this is the same operation as {@link #window(int)}. + * @return an {@link Observable} that produces windows every "skipped" + * items containing at most count items + * @see RxJava Wiki: window() */ public Observable> window(int count, int skip) { return create(OperationWindow.window(this, count, skip)); } /** - * Creates an Observable which produces windows of collected values. This Observable produces connected - * non-overlapping windows, each of a fixed duration specified by the "timespan" argument. When the source - * Observable completes or encounters an error, the current window is emitted and the event is propagated. + * Creates an Observable that produces windows of collected items. This + * Observable produces connected, non-overlapping windows, each of a fixed + * duration specified by the timespan argument. When the source + * Observable completes or encounters an error, the current window is + * emitted and the event is propagated. + *

+ * * - * @param timespan - * The period of time each window is collecting values before it should be emitted, and - * replaced with a new window. - * @param unit - * The unit of time which applies to the "timespan" argument. - * @return - * An {@link Observable} which produces connected non-overlapping windows with a fixed duration. + * @param timespan the period of time each window collects items before it + * should be emitted and replaced with a new window + * @param unit the unit of time that applies to the timespan + * argument + * @return an {@link Observable} that produces connected, non-overlapping + * windows with a fixed duration + * @see RxJava Wiki: window() */ public Observable> window(long timespan, TimeUnit unit) { return create(OperationWindow.window(this, timespan, unit)); } /** - * Creates an Observable which produces windows of collected values. This Observable produces connected - * non-overlapping windows, each of a fixed duration specified by the "timespan" argument. When the source - * Observable completes or encounters an error, the current window is emitted and the event is propagated. + * Creates an Observable that produces windows of collected items. This + * Observable produces connected, non-overlapping windows, each of a fixed + * duration as specified by the timespan argument. When the + * source Observable completes or encounters an error, the current window is + * emitted and the event is propagated. + *

+ * * - * @param timespan - * The period of time each window is collecting values before it should be emitted, and - * replaced with a new window. - * @param unit - * The unit of time which applies to the "timespan" argument. - * @param scheduler - * The {@link Scheduler} to use when determining the end and start of a window. - * @return - * An {@link Observable} which produces connected non-overlapping windows with a fixed duration. + * @param timespan the period of time each window collects items before it + * should be emitted and replaced with a new window + * @param unit the unit of time which applies to the timespan + * argument + * @param scheduler the {@link Scheduler} to use when determining the end + * and start of a window + * @return an {@link Observable} that produces connected, non-overlapping + * windows with a fixed duration + * @see RxJava Wiki: window() */ public Observable> window(long timespan, TimeUnit unit, Scheduler scheduler) { return create(OperationWindow.window(this, timespan, unit, scheduler)); } /** - * Creates an Observable which produces windows of collected values. This Observable produces connected - * non-overlapping windows, each of a fixed duration specified by the "timespan" argument or a maximum size - * specified by the "count" argument (which ever is reached first). When the source Observable completes - * or encounters an error, the current window is emitted and the event is propagated. + * Creates an Observable that produces windows of collected items. This + * Observable produces connected non-overlapping windows, each of a fixed + * duration as specified by the timespan argument or a maximum + * size as specified by the count argument (whichever is + * reached first). When the source Observable completes or encounters an + * error, the current window is emitted and the event is propagated. + *

+ * * - * @param timespan - * The period of time each window is collecting values before it should be emitted, and - * replaced with a new window. - * @param unit - * The unit of time which applies to the "timespan" argument. - * @param count - * The maximum size of each window before it should be emitted. - * @return - * An {@link Observable} which produces connected non-overlapping windows which are emitted after - * a fixed duration or when the window has reached maximum capacity (which ever occurs first). + * @param timespan the period of time each window collects values before it + * should be emitted and replaced with a new window + * @param unit the unit of time that applies to the timespan + * argument + * @param count the maximum size of each window before it should be emitted + * @return an {@link Observable} that produces connected, non-overlapping + * windows that are emitted after a fixed duration or when the + * window has reached maximum capacity (whichever occurs first) + * @see RxJava Wiki: window() */ public Observable> window(long timespan, TimeUnit unit, int count) { return create(OperationWindow.window(this, timespan, unit, count)); } /** - * Creates an Observable which produces windows of collected values. This Observable produces connected - * non-overlapping windows, each of a fixed duration specified by the "timespan" argument or a maximum size - * specified by the "count" argument (which ever is reached first). When the source Observable completes - * or encounters an error, the current window is emitted and the event is propagated. + * Creates an Observable that produces windows of collected items. This + * Observable produces connected, non-overlapping windows, each of a fixed + * duration specified by the timespan argument or a maximum + * size specified by the count argument (whichever is reached + * first). When the source Observable completes or encounters an error, the + * current window is emitted and the event is propagated. + *

+ * * - * @param timespan - * The period of time each window is collecting values before it should be emitted, and - * replaced with a new window. - * @param unit - * The unit of time which applies to the "timespan" argument. - * @param count - * The maximum size of each window before it should be emitted. - * @param scheduler - * The {@link Scheduler} to use when determining the end and start of a window. - * @return - * An {@link Observable} which produces connected non-overlapping windows which are emitted after - * a fixed duration or when the window has reached maximum capacity (which ever occurs first). + * @param timespan the period of time each window collects values before it + * should be emitted and replaced with a new window + * @param unit the unit of time which applies to the timespan + * argument + * @param count the maximum size of each window before it should be emitted + * @param scheduler the {@link Scheduler} to use when determining the end + * and start of a window. + * @return an {@link Observable} that produces connected non-overlapping + * windows that are emitted after a fixed duration or when the + * window has reached maximum capacity (whichever occurs first). + * @see RxJava Wiki: window() */ public Observable> window(long timespan, TimeUnit unit, int count, Scheduler scheduler) { return create(OperationWindow.window(this, timespan, unit, count, scheduler)); } /** - * Creates an Observable which produces windows of collected values. This Observable starts a new window - * periodically, which is determined by the "timeshift" argument. Each window is emitted after a fixed timespan - * specified by the "timespan" argument. When the source Observable completes or encounters an error, the - * current window is emitted and the event is propagated. + * Creates an Observable that produces windows of collected items. This + * Observable starts a new window periodically, as determined by the + * timeshift argument. Each window is emitted after a fixed + * timespan, specified by the timespan argument. When the + * source Observable completes or encounters an error, the current window is + * emitted and the event is propagated. + *

+ * * - * @param timespan - * The period of time each window is collecting values before it should be emitted. - * @param timeshift - * The period of time after which a new window will be created. - * @param unit - * The unit of time which applies to the "timespan" and "timeshift" argument. - * @return - * An {@link Observable} which produces new windows periodically, and these are emitted after - * a fixed timespan has elapsed. + * @param timespan the period of time each window collects values before it + * should be emitted + * @param timeshift the period of time after which a new window will be + * created + * @param unit the unit of time that applies to the timespan + * and timeshift arguments + * @return an {@link Observable} that produces new windows periodically and + * emits these after a fixed timespan has elapsed + * @see RxJava Wiki: window() */ public Observable> window(long timespan, long timeshift, TimeUnit unit) { return create(OperationWindow.window(this, timespan, timeshift, unit)); } /** - * Creates an Observable which produces windows of collected values. This Observable starts a new window - * periodically, which is determined by the "timeshift" argument. Each window is emitted after a fixed timespan - * specified by the "timespan" argument. When the source Observable completes or encounters an error, the - * current window is emitted and the event is propagated. + * Creates an Observable that produces windows of collected items. This + * Observable starts a new window periodically, as determined by the + * timeshift argument. Each window is emitted after a fixed + * timespan, specified by the timespan argument. When the + * source Observable completes or encounters an error, the current window is + * emitted and the event is propagated. + *

+ * * - * @param timespan - * The period of time each window is collecting values before it should be emitted. - * @param timeshift - * The period of time after which a new window will be created. - * @param unit - * The unit of time which applies to the "timespan" and "timeshift" argument. - * @param scheduler - * The {@link Scheduler} to use when determining the end and start of a window. - * @return - * An {@link Observable} which produces new windows periodically, and these are emitted after - * a fixed timespan has elapsed. + * @param timespan the period of time each window collects values before it + * should be emitted + * @param timeshift the period of time after which a new window will be + * created + * @param unit the unit of time that applies to the timespan + * and timeshift arguments + * @param scheduler the {@link Scheduler} to use when determining the end + * and start of a window + * @return an {@link Observable} that produces new windows periodically and + * emits these after a fixed timespan has elapsed + * @see RxJava Wiki: window() */ public Observable> window(long timespan, long timeshift, TimeUnit unit, Scheduler scheduler) { return create(OperationWindow.window(this, timespan, timeshift, unit, scheduler)); } /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of N items emitted, in sequence, by N other Observables as provided by an Iterable. - * - *

{@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * all of the Observalbes; the second item emitted by the new Observable will be the result of - * the function applied to the second item emitted by each of those Observables; and so forth. + * Returns an Observable that emits the results of a function of your + * choosing applied to combinations of n items emitted, in sequence, + * by n other Observables as provided by an Iterable. *

- * The resulting {@code Observable} returned from {@code zip} will invoke {@code onNext} as many times as the number of {@code onNext} invokations of the - * source Observable that emits the fewest items. + * {@code zip} applies this function in strict sequence, so the first item + * emitted by the new Observable will be the result of the function applied + * to the first item emitted by all of the source Observables; the second + * item emitted by the new Observable will be the result of the function + * applied to the second item emitted by each of those Observables; and so + * forth. + *

+ * The resulting {@code Observable} returned from {@code zip} will + * invoke {@code onNext} as many times as the number of {@code onNext} + * invokations of the source Observable that emits the fewest items. *

* * - * @param ws - * An Observable of source Observables - * @param zipFunction - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable + * @param ws an Observable of source Observables + * @param zipFunction a function that, when applied to an item emitted by + * each of the source Observables, results in an item + * that will be emitted by the resulting Observable * @return an Observable that emits the zipped results + * @see RxJava Wiki: zip() */ public static Observable zip(Observable> ws, final FuncN zipFunction) { return ws.toList().mapMany(new Func1>, Observable>() { @@ -2889,177 +3306,223 @@ public Observable call(List> wsList) { } /** - * Returns an Observable that emits the results of a function of your choosing applied to - * combinations of four items emitted, in sequence, by four other Observables. - *

{@code zip} applies this function in strict sequence, so the first item emitted by the - * new Observable will be the result of the function applied to the first item emitted by - * all of the Observalbes; the second item emitted by the new Observable will be the result of - * the function applied to the second item emitted by each of those Observables; and so forth. + * Returns an Observable that emits the results of a function of your + * choosing applied to combinations items emitted, in sequence, by a + * collection of other Observables. + *

+ * {@code zip} applies this function in strict sequence, so the first item + * emitted by the new Observable will be the result of the function applied + * to the first item emitted by all of the source Observables; the second + * item emitted by the new Observable will be the result of the function + * applied to the second item emitted by each of those Observables; and so + * forth. *

- * The resulting {@code Observable} returned from {@code zip} will invoke {@code onNext} as many times as the number of {@code onNext} invokations of the - * source Observable that emits the fewest items. + * The resulting {@code Observable} returned from {@code zip} will invoke + * {@code onNext} as many times as the number of {@code onNext} invokations + * of the source Observable that emits the fewest items. *

* * - * @param ws - * A collection of source Observables - * @param zipFunction - * a function that, when applied to an item emitted by each of the source - * Observables, results in an item that will be emitted by the resulting Observable + * @param ws a collection of source Observables + * @param zipFunction a function that, when applied to an item emitted by + * each of the source Observables, results in an item + * that will be emitted by the resulting Observable * @return an Observable that emits the zipped results + * @see RxJava Wiki: zip() */ public static Observable zip(Iterable> ws, FuncN zipFunction) { return create(OperationZip.zip(ws, zipFunction)); } /** + * Filter items emitted by an Observable. *

* * - * @param predicate - * a function that evaluates the items emitted by the source Observable, returning {@code true} if they pass the filter - * @return an Observable that emits only those items in the original Observable that the filter - * evaluates as {@code true} + * @param predicate a function that evaluates the items emitted by the + * source Observable, returning {@code true} if they pass + * the filter + * @return an Observable that emits only those items in the original + * Observable that the filter evaluates as {@code true} + * @see RxJava Wiki: filter() */ public Observable filter(Func1 predicate) { return create(OperationFilter.filter(this, predicate)); } /** - * Returns an Observable that forwards all sequentially distinct items emitted from the source Observable. + * Returns an Observable that forwards all sequentially distinct items + * emitted from the source Observable. + *

+ * * * @return an Observable of sequentially distinct items - * @see MSDN: Observable.distinctUntilChanged + * @see RxJava Wiki: distinctUntilChanged() + * @see MSDN: Observable.distinctUntilChanged */ public Observable distinctUntilChanged() { return create(OperationDistinctUntilChanged.distinctUntilChanged(this)); } /** - * Returns an Observable that forwards all items emitted from the source Observable that are sequentially distinct according to - * a key selector function. + * Returns an Observable that forwards all items emitted from the source + * Observable that are sequentially distinct according to a key selector + * function. + *

+ * * - * @param keySelector - * a function that projects an emitted item to a key value which is used for deciding whether an item is sequentially - * distinct from another one or not + * @param keySelector a function that projects an emitted item to a key + * value that is used for deciding whether an item is + * sequentially distinct from another one or not * @return an Observable of sequentially distinct items - * @see MSDN: Observable.distinctUntilChanged + * @see RxJava Wiki: distinctUntilChanged() + * @see MSDN: Observable.distinctUntilChanged */ public Observable distinctUntilChanged(Func1 keySelector) { return create(OperationDistinctUntilChanged.distinctUntilChanged(this, keySelector)); } /** - * Returns an Observable that forwards all items emitted from the source Observable that are sequentially distinct according to - * a comparator. + * Returns an Observable that emits all distinct items emitted from the + * source Observable. + *

+ * * - * @param equalityComparator - * a comparator for deciding whether two emitted items are equal or not - * @return an Observable of sequentially distinct items - * @see MSDN: Observable.distinctUntilChanged + * @return an Observable of distinct items + * @see RxJava Wiki: distinct() + * @see MSDN: Observable.distinct */ - public Observable distinctUntilChanged(Comparator equalityComparator) { - return create(OperationDistinctUntilChanged.distinctUntilChanged(this, equalityComparator)); + public Observable distinct() { + return create(OperationDistinct.distinct(this)); } /** - * Returns an Observable that forwards all items emitted from the source Observable that are sequentially distinct according to - * a key selector function and a comparator. + * Returns an Observable that emits all items emitted from the source + * Observable that are distinct according to a key selector function. + *

+ * * - * @param keySelector - * a function that projects an emitted item to a key value which is used for deciding whether an item is sequentially - * distinct from another one or not - * @param equalityComparator - * a comparator for deciding whether two emitted item keys are equal or not - * @return an Observable of sequentially distinct items - * @see MSDN: Observable.distinctUntilChanged + * @param keySelector a function that projects an emitted item to a key + * value that is used to decide whether an item is + * distinct from another one or not + * @return an Observable that emits distinct items + * @see RxJava Wiki: distinct() + * @see MSDN: Observable.distinct */ - public Observable distinctUntilChanged(Func1 keySelector, Comparator equalityComparator) { - return create(OperationDistinctUntilChanged.distinctUntilChanged(this, keySelector, equalityComparator)); + public Observable distinct(Func1 keySelector) { + return create(OperationDistinct.distinct(this, keySelector)); } /** - * Returns an Observable that forwards all distinct items emitted from the source Observable. + * Returns the item at a specified index in a sequence. + *

+ * * - * @return an Observable of distinct items - * @see MSDN: Observable.distinct + * @param index the zero-based index of the item to retrieve + * @return an Observable that emits the item at the specified position in + * the source sequence + * @throws IndexOutOfBoundsException if index is greater than + * or equal to the number of elements in + * the source sequence + * @throws IndexOutOfBoundsException if index is less than 0 + * @see RxJava Wiki: elementAt() */ - public Observable distinct() { - return create(OperationDistinct.distinct(this)); + public Observable elementAt(int index) { + return create(OperationElementAt.elementAt(this, index)); } - + /** - * Returns an Observable that forwards all items emitted from the source Observable that are distinct according to - * a comparator. + * Returns the item at a specified index in a sequence or the default item + * if the index is out of range. + *

+ * * - * @param equalityComparator - * a comparator for deciding whether two emitted items are equal or not - * @return an Observable of distinct items - * @see MSDN: Observable.distinct + * @param index the zero-based index of the item to retrieve + * @param defaultValue the default item + * @return an Observable that emits the item at the specified position in + * the source sequence, or the default item if the index is outside + * the bounds of the source sequence + * @throws IndexOutOfBoundsException if index is less than 0 + * @see RxJava Wiki: elementAtOrDefault() */ - public Observable distinct(Comparator equalityComparator) { - return create(OperationDistinct.distinct(this, equalityComparator)); + public Observable elementAtOrDefault(int index, T defaultValue) { + return create(OperationElementAt.elementAtOrDefault(this, index, defaultValue)); } - + /** - * Returns an Observable that forwards all items emitted from the source Observable that are distinct according to - * a key selector function. + * Returns an {@link Observable} that emits true if any element + * of the source {@link Observable} satisfies the given condition, otherwise + * false. Note: always emits false if the source + * {@link Observable} is empty. + *

+ * In Rx.Net this is the any operator but renamed in RxJava to + * better match Java naming idioms. + *

+ * * - * @param keySelector - * a function that projects an emitted item to a key value which is used for deciding whether an item is - * distinct from another one or not - * @return an Observable of distinct items - * @see MSDN: Observable.distinct + * @param predicate the condition to test every element + * @return a subscription function for creating the target Observable + * @see RxJava Wiki: exists() + * @see MSDN: Observable.Any Note: the description in this page is wrong. */ - public Observable distinct(Func1 keySelector) { - return create(OperationDistinct.distinct(this, keySelector)); + public Observable exists(Func1 predicate) { + return create(OperationAny.exists(this, predicate)); } - + /** - * Returns an Observable that forwards all items emitted from the source Observable that are distinct according to - * a key selector function and a comparator. + * Determines whether an Observable sequence contains a specified item. + *

+ * * - * @param keySelector - * a function that projects an emitted item to a key value which is used for deciding whether an item is - * distinct from another one or not - * @param equalityComparator - * a comparator for deciding whether two emitted item keys are equal or not - * @return an Observable of distinct items - * @see MSDN: Observable.distinct + * @param element the item to search in the sequence + * @return an Observable that emits true if the item is in the + * source sequence + * @see RxJava Wiki: contains() + * @see MSDN: Observable.Contains */ - public Observable distinct(Func1 keySelector, Comparator equalityComparator) { - return create(OperationDistinct.distinct(this, keySelector, equalityComparator)); + public Observable contains(final T element) { + return exists(new Func1() { + public Boolean call(T t1) { + return element == null ? t1 == null : element.equals(t1); + } + }); } - + /** - * Registers an {@link Action0} to be called when this Observable invokes {@link Observer#onCompleted onCompleted} or {@link Observer#onError onError}. + * Registers an {@link Action0} to be called when this Observable invokes + * {@link Observer#onCompleted onCompleted} or + * {@link Observer#onError onError}. *

- * + * * - * @param action - * an {@link Action0} to be invoked when the source Observable finishes - * @return an Observable that emits the same items as the source Observable, then invokes the {@link Action0} - * @see MSDN: Observable.Finally Method + * @param action an {@link Action0} to be invoked when the source + * Observable finishes + * @return an Observable that emits the same items as the source Observable, + * then invokes the {@link Action0} + * @see RxJava Wiki: finallyDo() + * @see MSDN: Observable.Finally Method */ public Observable finallyDo(Action0 action) { return create(OperationFinally.finallyDo(this, action)); } /** - * Creates a new Observable by applying a function that you supply to each item emitted by - * the source Observable, where that function returns an Observable, and then merging those - * resulting Observables and emitting the results of this merger. + * Creates a new Observable by applying a function that you supply to each + * item emitted by the source Observable, where that function returns an + * Observable, and then merging those resulting Observables and emitting the + * results of this merger. *

* *

* Note: {@code mapMany} and {@code flatMap} are equivalent. * - * @param func - * a function that, when applied to an item emitted by the source Observable, returns - * an Observable - * @return an Observable that emits the result of applying the transformation function to each - * item emitted by the source Observable and merging the results of the Observables - * obtained from this transformation. + * @param func a function that, when applied to an item emitted by the + * source Observable, returns an Observable + * @return an Observable that emits the result of applying the + * transformation function to each item emitted by the source + * Observable and merging the results of the Observables obtained + * from this transformation. + * @see RxJava Wiki: flatMap() * @see #mapMany(Func1) */ public Observable flatMap(Func1> func) { @@ -3067,13 +3530,16 @@ public Observable flatMap(Func1 * * - * @param predicate - * a function that evaluates an item emitted by the source Observable, returning {@code true} if it passes the filter - * @return an Observable that emits only those items in the original Observable that the filter - * evaluates as {@code true} + * @param predicate a function that evaluates an item emitted by the source + * Observable, returning {@code true} if it passes the + * filter + * @return an Observable that emits only those items emitted by the original + * Observable that the filter evaluates as {@code true} + * @see RxJava Wiki: where() * @see #filter(Func1) */ public Observable where(Func1 predicate) { @@ -3081,35 +3547,56 @@ public Observable where(Func1 predicate) { } /** - * Returns an Observable that applies the given function to each item emitted by an - * Observable and emits the result. + * Returns an Observable that applies the given function to each item + * emitted by an Observable and emits the result. *

* * - * @param func - * a function to apply to each item emitted by the Observable - * @return an Observable that emits the items from the source Observable, transformed by the - * given function + * @param func a function to apply to each item emitted by the Observable + * @return an Observable that emits the items from the source Observable, + * transformed by the given function + * @see RxJava Wiki: map() + * @see MSDN: Observable.Select */ public Observable map(Func1 func) { return create(OperationMap.map(this, func)); } /** - * Creates a new Observable by applying a function that you supply to each item emitted by - * the source Observable, where that function returns an Observable, and then merging those - * resulting Observables and emitting the results of this merger. + * Returns an Observable that applies the given function to each item + * emitted by an Observable and emits the result. + *

+ * + * + * @param func a function to apply to each item emitted by the Observable. + * The function takes the index of the emitted item as + * additional parameter. + * @return an Observable that emits the items from the source Observable, + * transformed by the given function + * @see RxJava Wiki: mapWithIndex() + * @see MSDN: Observable.Select + */ + public Observable mapWithIndex(Func2 func) { + return create(OperationMap.mapWithIndex(this, func)); + } + + /** + * Creates a new Observable by applying a function that you supply to each + * item emitted by the source Observable, where that function returns an + * Observable, and then merging those resulting Observables and emitting + * the results of this merger. *

* *

* Note: mapMany and flatMap are equivalent. * - * @param func - * a function that, when applied to an item emitted by the source Observable, returns - * an Observable - * @return an Observable that emits the result of applying the transformation function to each - * item emitted by the source Observable and merging the results of the Observables - * obtained from this transformation. + * @param func a function that, when applied to an item emitted by the + * source Observable, returns an Observable + * @return an Observable that emits the result of applying the + * transformation function to each item emitted by the source + * Observable and merging the results of the Observables obtained + * from this transformation. + * @see RxJava Wiki: mapMany() * @see #flatMap(Func1) */ public Observable mapMany(Func1> func) { @@ -3117,12 +3604,15 @@ public Observable mapMany(Func1 * * - * @return an Observable whose items are the result of materializing the items and - * notifications of the source Observable + * @return an Observable whose items are the result of materializing the + * items and notifications of the source Observable + * @see RxJava Wiki: materialize() * @see MSDN: Observable.materialize */ public Observable> materialize() { @@ -3130,44 +3620,50 @@ public Observable> materialize() { } /** - * Asynchronously subscribes and unsubscribes Observers on the specified {@link Scheduler}. + * Asynchronously subscribes and unsubscribes Observers on the specified + * {@link Scheduler}. *

- * + * * - * @param scheduler - * the {@link Scheduler} to perform subscription and unsubscription actions on - * @return the source Observable modified so that its subscriptions and unsubscriptions happen - * on the specified {@link Scheduler} + * @param scheduler the {@link Scheduler} to perform subscription and + * unsubscription actions on + * @return the source Observable modified so that its subscriptions and + * unsubscriptions happen on the specified {@link Scheduler} + * @see RxJava Wiki: subscribeOn() */ public Observable subscribeOn(Scheduler scheduler) { return create(OperationSubscribeOn.subscribeOn(this, scheduler)); } /** - * Asynchronously notify {@link Observer}s on the specified {@link Scheduler}. + * Asynchronously notify {@link Observer}s on the specified + * {@link Scheduler}. *

- * + * * - * @param scheduler - * the {@link Scheduler} to notify {@link Observer}s on - * @return the source Observable modified so that its {@link Observer}s are notified on the - * specified {@link Scheduler} + * @param scheduler the {@link Scheduler} to notify {@link Observer}s on + * @return the source Observable modified so that its {@link Observer}s are + * notified on the specified {@link Scheduler} + * @see RxJava Wiki: observeOn() */ public Observable observeOn(Scheduler scheduler) { return create(OperationObserveOn.observeOn(this, scheduler)); } /** - * Returns an Observable that reverses the effect of {@link #materialize materialize} by - * transforming the {@link Notification} objects emitted by the source Observable into the items - * or notifications they represent. + * Returns an Observable that reverses the effect of + * {@link #materialize materialize} by transforming the {@link Notification} + * objects emitted by the source Observable into the items or notifications + * they represent. *

- * + * * - * @return an Observable that emits the items and notifications embedded in the {@link Notification} objects emitted by the source Observable - * @see MSDN: Observable.dematerialize - * @throws Throwable - * if the source Observable is not of type {@code Observable>}. + * @return an Observable that emits the items and notifications embedded in + * the {@link Notification} objects emitted by the source Observable + * @throws Throwable if the source Observable is not of type + * {@code Observable>} + * @see RxJava Wiki: dematerialize() + * @see MSDN: Observable.dematerialize */ @SuppressWarnings("unchecked") public Observable dematerialize() { @@ -3175,148 +3671,184 @@ public Observable dematerialize() { } /** - * Instruct an Observable to pass control to another Observable rather than invoking {@link Observer#onError onError} if it encounters an error. + * Instruct an Observable to pass control to another Observable rather than + * invoking {@link Observer#onError onError} if it encounters an error. *

* *

- * By default, when an Observable encounters an error that prevents it from emitting the - * expected item to its {@link Observer}, the Observable invokes its Observer's - * onError method, and then quits without invoking any more of its Observer's - * methods. The onErrorResumeNext method changes this behavior. If you pass a - * function that returns an Observable (resumeFunction) to - * onErrorResumeNext, if the original Observable encounters an error, instead of - * invoking its Observer's onError method, it will instead relinquish control to - * the Observable returned from resumeFunction, which will invoke the Observer's {@link Observer#onNext onNext} method if it is able to do so. In such a case, because no - * Observable necessarily invokes onError, the Observer may never know that an - * error happened. - *

- * You can use this to prevent errors from propagating or to supply fallback data should errors - * be encountered. - * - * @param resumeFunction - * a function that returns an Observable that will take over if the source Observable - * encounters an error + * By default, when an Observable encounters an error that prevents it from + * emitting the expected item to its {@link Observer}, the Observable + * invokes its Observer's onError method, and then quits + * without invoking any more of its Observer's methods. The + * onErrorResumeNext method changes this behavior. If you pass + * a function that returns an Observable (resumeFunction) to + * onErrorResumeNext, if the original Observable encounters an + * error, instead of invoking its Observer's onError method, it + * will instead relinquish control to the Observable returned from + * resumeFunction, which will invoke the Observer's + * {@link Observer#onNext onNext} method if it is able to do so. In such a + * case, because no Observable necessarily invokes onError, the + * Observer may never know that an error happened. + *

+ * You can use this to prevent errors from propagating or to supply fallback + * data should errors be encountered. + * + * @param resumeFunction a function that returns an Observable that will + * take over if the source Observable encounters an + * error * @return the original Observable, with appropriately modified behavior + * @see RxJava Wiki: onErrorResumeNext() */ public Observable onErrorResumeNext(final Func1> resumeFunction) { return create(OperationOnErrorResumeNextViaFunction.onErrorResumeNextViaFunction(this, resumeFunction)); } /** - * Instruct an Observable to pass control to another Observable rather than invoking {@link Observer#onError onError} if it encounters an error. + * Instruct an Observable to pass control to another Observable rather than + * invoking {@link Observer#onError onError} if it encounters an error. *

* *

- * By default, when an Observable encounters an error that prevents it from emitting the - * expected item to its {@link Observer}, the Observable invokes its Observer's - * onError method, and then quits without invoking any more of its Observer's - * methods. The onErrorResumeNext method changes this behavior. If you pass + * By default, when an Observable encounters an error that prevents it from + * emitting the expected item to its {@link Observer}, the Observable + * invokes its Observer's onError method, and then quits + * without invoking any more of its Observer's methods. The + * onErrorResumeNext method changes this behavior. If you pass * another Observable (resumeSequence) to an Observable's - * onErrorResumeNext method, if the original Observable encounters an error, - * instead of invoking its Observer's onError method, it will instead relinquish - * control to resumeSequence which will invoke the Observer's {@link Observer#onNext onNext} method if it is able to do so. In such a case, because no - * Observable necessarily invokes onError, the Observer may never know that an - * error happened. - *

- * You can use this to prevent errors from propagating or to supply fallback data should errors - * be encountered. - * - * @param resumeSequence - * a function that returns an Observable that will take over if the source Observable - * encounters an error + * onErrorResumeNext method, if the original Observable + * encounters an error, instead of invoking its Observer's + * onError method, it will instead relinquish control to + * resumeSequence which will invoke the Observer's + * {@link Observer#onNext onNext} method if it is able to do so. In such a + * case, because no Observable necessarily invokes onError, the + * Observer may never know that an error happened. + *

+ * You can use this to prevent errors from propagating or to supply fallback + * data should errors be encountered. + * + * @param resumeSequence a function that returns an Observable that will + * take over if the source Observable encounters an + * error * @return the original Observable, with appropriately modified behavior + * @see RxJava Wiki: onErrorResumeNext() */ public Observable onErrorResumeNext(final Observable resumeSequence) { return create(OperationOnErrorResumeNextViaObservable.onErrorResumeNextViaObservable(this, resumeSequence)); } /** - * Instruct an Observable to pass control to another Observable rather than invoking {@link Observer#onError onError} if it encounters an error of type {@link java.lang.Exception}. + * Instruct an Observable to pass control to another Observable rather than + * invoking {@link Observer#onError onError} if it encounters an error of + * type {@link java.lang.Exception}. *

- * This differs from {@link #onErrorResumeNext} in that this one does not handle {@link java.lang.Throwable} or {@link java.lang.Error} but lets those continue through. + * This differs from {@link #onErrorResumeNext} in that this one does not + * handle {@link java.lang.Throwable} or {@link java.lang.Error} but lets + * those continue through. *

- * + * *

- * By default, when an Observable encounters an error that prevents it from emitting the - * expected item to its {@link Observer}, the Observable invokes its Observer's - * onError method, and then quits without invoking any more of its Observer's - * methods. The onErrorResumeNext method changes this behavior. If you pass + * By default, when an Observable encounters an error that prevents it from + * emitting the expected item to its {@link Observer}, the Observable + * invokes its Observer's onError method, and then quits + * without invoking any more of its Observer's methods. The + * onErrorResumeNext method changes this behavior. If you pass * another Observable (resumeSequence) to an Observable's - * onErrorResumeNext method, if the original Observable encounters an error, - * instead of invoking its Observer's onError method, it will instead relinquish - * control to resumeSequence which will invoke the Observer's {@link Observer#onNext onNext} method if it is able to do so. In such a case, because no - * Observable necessarily invokes onError, the Observer may never know that an - * error happened. - *

- * You can use this to prevent errors from propagating or to supply fallback data should errors - * be encountered. - * - * @param resumeSequence - * a function that returns an Observable that will take over if the source Observable - * encounters an error + * onErrorResumeNext method, if the original Observable + * encounters an error, instead of invoking its Observer's + * onError method, it will instead relinquish control to + * resumeSequence which will invoke the Observer's + * {@link Observer#onNext onNext} method if it is able to do so. In such a + * case, because no Observable necessarily invokes onError, + * the Observer may never know that an error happened. + *

+ * You can use this to prevent errors from propagating or to supply fallback + * data should errors be encountered. + * + * @param resumeSequence a function that returns an Observable that will + * take over if the source Observable encounters an + * error * @return the original Observable, with appropriately modified behavior + * @see RxJava Wiki: onExceptionResumeNextViaObservable() */ public Observable onExceptionResumeNext(final Observable resumeSequence) { return create(OperationOnExceptionResumeNextViaObservable.onExceptionResumeNextViaObservable(this, resumeSequence)); } /** - * Instruct an Observable to emit an item (returned by a specified function) rather than - * invoking {@link Observer#onError onError} if it encounters an error. + * Instruct an Observable to emit an item (returned by a specified function) + * rather than invoking {@link Observer#onError onError} if it encounters an + * error. *

- * + * *

- * By default, when an Observable encounters an error that prevents it from emitting the - * expected item to its {@link Observer}, the Observable invokes its Observer's - * onError method, and then quits without invoking any more of its Observer's - * methods. The onErrorReturn method changes this behavior. If you pass a function - * (resumeFunction) to an Observable's onErrorReturn method, if the - * original Observable encounters an error, instead of invoking its Observer's - * onError method, it will instead pass the return value of - * resumeFunction to the Observer's {@link Observer#onNext onNext} method. + * By default, when an Observable encounters an error that prevents it from + * emitting the expected item to its {@link Observer}, the Observable + * invokes its Observer's onError method, and then quits + * without invoking any more of its Observer's methods. The + * onErrorReturn method changes this behavior. If you pass a + * function (resumeFunction) to an Observable's + * onErrorReturn method, if the original Observable encounters + * an error, instead of invoking its Observer's onError method, + * it will instead pass the return value of resumeFunction to + * the Observer's {@link Observer#onNext onNext} method. *

- * You can use this to prevent errors from propagating or to supply fallback data should errors - * be encountered. + * You can use this to prevent errors from propagating or to supply fallback + * data should errors be encountered. * - * @param resumeFunction - * a function that returns an item that the new Observable will emit if the source - * Observable encounters an error + * @param resumeFunction a function that returns an item that the new + * Observable will emit if the source Observable + * encounters an error * @return the original Observable with appropriately modified behavior + * @see RxJava Wiki: onErrorReturn() */ public Observable onErrorReturn(Func1 resumeFunction) { return create(OperationOnErrorReturn.onErrorReturn(this, resumeFunction)); } /** - * Returns an Observable that applies a function of your choosing to the first item emitted by a - * source Observable, then feeds the result of that function along with the second item emitted - * by the source Observable into the same function, and so on until all items have been emitted - * by the source Observable, and emits the final result from the final call to your function as - * its sole item. + * Returns an Observable that applies a function of your choosing to the + * first item emitted by a source Observable, then feeds the result of that + * function along with the second item emitted by the source Observable into + * the same function, and so on until all items have been emitted by the + * source Observable, and emits the final result from the final call to your + * function as its sole item. *

* *

- * This technique, which is called "reduce" or "aggregate" here, is sometimes called "fold," - * "accumulate," "compress," or "inject" in other programming contexts. Groovy, for instance, - * has an inject method that does a similar operation on lists. - * - * @param accumulator - * An accumulator function to be invoked on each item emitted by the source - * Observable, whose result will be used in the next accumulator call - * @return an Observable that emits a single item that is the result of accumulating the - * output from the source Observable - * @see MSDN: Observable.Aggregate + * This technique, which is called "reduce" or "aggregate" here, is + * sometimes called "fold," "accumulate," "compress," or "inject" in other + * programming contexts. Groovy, for instance, has an inject + * method that does a similar operation on lists. + * + * @param accumulator an accumulator function to be invoked on each item + * emitted by the source Observable, whose result will + * be used in the next accumulator call + * @return an Observable that emits a single item that is the result of + * accumulating the output from the source Observable + * @throws IllegalArgumentException if the Observable sequence is empty + * @see RxJava Wiki: reduce() + * @see MSDN: Observable.Aggregate * @see Wikipedia: Fold (higher-order function) */ public Observable reduce(Func2 accumulator) { - return create(OperationScan.scan(this, accumulator)).takeLast(1); + /* + * Discussion and confirmation of implementation at https://github.com/Netflix/RxJava/issues/423#issuecomment-27642532 + * + * It should use last() not takeLast(1) since it needs to emit an error if the sequence is empty. + */ + return create(OperationScan.scan(this, accumulator)).last(); } /** - * Returns an Observable that counts the total number of elements in the source Observable. - * @return an Observable emitting the number of counted elements of the source Observable - * as its single item. - * @see MSDN: Observable.Count + * Returns an Observable that counts the total number of items in the + * source Observable. + *

+ * + * + * @return an Observable that emits the number of counted elements of the + * source Observable as its single item + * @see RxJava Wiki: count() + * @see MSDN: Observable.Count */ public Observable count() { return reduce(0, new Func2() { @@ -3326,164 +3858,481 @@ public Integer call(Integer t1, T t2) { } }); } - + /** - * Returns an Observable that sums up the elements in the source Observable. - * @param source - * Source observable to compute the sum of. - * @return an Observable emitting the sum of all the elements of the source Observable - * as its single item. - * @see MSDN: Observable.Sum + * Returns an Observable that sums up the integers emitted by the source + * Observable. + *

+ * + * + * @param source source Observable to compute the sum of + * @return an Observable that emits the sum of all the items of the + * source Observable as its single item + * @see RxJava Wiki: sum() + * @see MSDN: Observable.Sum */ public static Observable sum(Observable source) { return OperationSum.sum(source); } - + /** - * @see #sum(Observable) - * @see MSDN: Observable.Sum + * Returns an Observable that sums up the longs emitted by the source + * Observable. + *

+ * + * + * @param source source Observable to compute the sum of + * @return an Observable that emits the sum of all the items of the + * source Observable as its single item + * @see RxJava Wiki: sumLongs() + * @see MSDN: Observable.Sum */ public static Observable sumLongs(Observable source) { return OperationSum.sumLongs(source); } - + /** - * @see #sum(Observable) - * @see MSDN: Observable.Sum + * Returns an Observable that sums up the floats emitted by the source + * Observable. + *

+ * + * + * @param source source Observable to compute the sum of + * @return an Observable that emits the sum of all the items of the + * source Observable as its single item + * @see RxJava Wiki: sumFloats() + * @see MSDN: Observable.Sum */ public static Observable sumFloats(Observable source) { return OperationSum.sumFloats(source); } - + /** - * @see #sum(Observable) - * @see MSDN: Observable.Sum + * Returns an Observable that sums up the doubles emitted by the source + * Observable. + *

+ * + * + * @param source source Observable to compute the sum of + * @return an Observable that emits the sum of all the items of the + * source Observable as its single item + * @see RxJava Wiki: sumDoubles() + * @see MSDN: Observable.Sum */ public static Observable sumDoubles(Observable source) { return OperationSum.sumDoubles(source); } - + /** - * Returns an Observable that computes the average of all elements in the source Observable. - * For an empty source, it causes an ArithmeticException. - * @param source - * Source observable to compute the average of. - * @return an Observable emitting the averageof all the elements of the source Observable - * as its single item. - * @see MSDN: Observable.Average + * Returns an Observable that computes the average of the integers emitted + * by the source Observable. + *

+ * + * + * @param source source observable to compute the average of + * @return an Observable that emits the average of all the items emitted by + * the source Observable as its single item + * @throws IllegalArgumentException if the Observable sequence is empty + * @see RxJava Wiki: average() + * @see MSDN: Observable.Average */ public static Observable average(Observable source) { return OperationAverage.average(source); } - + /** - * @see #average(Observable) - * @see MSDN: Observable.Average + * Returns an Observable that computes the average of the longs emitted by + * the source Observable. + *

+ * + * + * @param source source observable to compute the average of + * @return an Observable that emits the average of all the items emitted by + * the source Observable as its single item + * @see RxJava Wiki: averageLongs() + * @see MSDN: Observable.Average */ public static Observable averageLongs(Observable source) { return OperationAverage.averageLongs(source); } /** - * @see #average(Observable) - * @see MSDN: Observable.Average + * Returns an Observable that computes the average of the floats emitted by + * the source Observable. + *

+ * + * + * @param source source observable to compute the average of + * @return an Observable that emits the average of all the items emitted by + * the source Observable as its single item + * @see RxJava Wiki: averageFloats() + * @see MSDN: Observable.Average */ public static Observable averageFloats(Observable source) { return OperationAverage.averageFloats(source); } /** - * @see #average(Observable) - * @see MSDN: Observable.Average + * Returns an Observable that computes the average of the doubles emitted + * by the source Observable. + *

+ * + * + * @param source source observable to compute the average of + * @return an Observable that emits the average of all the items emitted by + * the source Observable as its single item + * @see RxJava Wiki: averageDoubles() + * @see MSDN: Observable.Average */ public static Observable averageDoubles(Observable source) { return OperationAverage.averageDoubles(source); } /** - * Returns a {@link ConnectableObservable} that shares a single subscription to the underlying - * Observable that will replay all of its items and notifications to any future {@link Observer}. + * Returns the minimum item emitted by an Observable. If there are more than + * one minimum items, its returns the last one. + *

+ * + * + * @param source an Observable sequence to determine the minimum item of + * @return an Observable that emits the minimum item + * @throws IllegalArgumentException if the source is empty + * @see MSDN: Observable.Min + */ + public static > Observable min(Observable source) { + return OperationMinMax.min(source); + } + + /** + * Returns the minimum item emitted by an Observable according to a + * specified comparator. If there are more than one minimum items, it + * returns the last one. + *

+ * + * + * @param comparator the comparer used to compare elements + * @return an Observable that emits the minimum value according to the + * specified comparator + * @throws IllegalArgumentException if the source is empty + * @see RxJava Wiki: min() + * @see MSDN: Observable.Min + */ + public Observable min(Comparator comparator) { + return OperationMinMax.min(this, comparator); + } + + /** + * Returns the items emitted by an Observable sequence with the minimum key + * value. For an empty source, returns an Observable that emits an empty + * List. + *

+ * + * + * @param selector the key selector function + * @return an Observable that emits a List of the items with the minimum key + * value + * @see RxJava Wiki: minBy() + * @see MSDN: Observable.MinBy + */ + public > Observable> minBy(Func1 selector) { + return OperationMinMax.minBy(this, selector); + } + + /** + * Returns the elements emitted by an Observable with the minimum key value + * according to the specified comparator. For an empty source, it returns an + * Observable that emits an empty List. + *

+ * + * + * @param selector the key selector function + * @param comparator the comparator used to compare key values + * @return an Observable that emits a List of the elements with the minimum + * key value according to the specified comparator + * @see RxJava Wiki: minBy() + * @see MSDN: Observable.MinBy + */ + public Observable> minBy(Func1 selector, Comparator comparator) { + return OperationMinMax.minBy(this, selector, comparator); + } + + /** + * Returns the maximum item emitted by an Observable. If there is more + * than one maximum item, it returns the last one. + *

+ * + * + * @param source an Observable to determine the maximum item of + * @return an Observable that emits the maximum element + * @throws IllegalArgumentException if the source is empty + * @see RxJava Wiki: max() + * @see MSDN: Observable.Max + */ + public static > Observable max(Observable source) { + return OperationMinMax.max(source); + } + + /** + * Returns the maximum item emitted by an Observable according to the + * specified comparator. If there is more than one maximum item, it returns + * the last one. + *

+ * + * + * @param comparator the comparer used to compare items + * @return an Observable that emits the maximum item according to the + * specified comparator + * @throws IllegalArgumentException if the source is empty + * @see RxJava Wiki: max() + * @see MSDN: Observable.Max + */ + public Observable max(Comparator comparator) { + return OperationMinMax.max(this, comparator); + } + + /** + * Returns the items emitted by an Observable with the maximum key value. + * For an empty source, it returns an Observable that emits an empty List. + *

+ * + * + * @param selector the key selector function + * @return an Observable that emits a List of the items with the maximum key + * value + * @see RxJava Wiki: maxBy() + * @see MSDN: Observable.MaxBy + */ + public > Observable> maxBy(Func1 selector) { + return OperationMinMax.maxBy(this, selector); + } + + /** + * Returns the items emitted by an Observable with the maximum key value + * according to the specified comparator. For an empty source, it returns an + * Observable that emits an empty List. + *

+ * + * + * @param selector the key selector function + * @param comparator the comparator used to compare key values + * @return an Observable that emits a List of the elements with the maximum + * key value according to the specified comparator + * @see RxJava Wiki: maxBy() + * @see MSDN: Observable.MaxBy + */ + public Observable> maxBy(Func1 selector, Comparator comparator) { + return OperationMinMax.maxBy(this, selector, comparator); + } + + /** + * Returns a {@link ConnectableObservable} that shares a single subscription + * to the underlying Observable that will replay all of its items and + * notifications to any future {@link Observer}. *

* * - * @return a {@link ConnectableObservable} that upon connection causes the source Observable to - * emit items to its {@link Observer}s + * @return a {@link ConnectableObservable} that upon connection causes the + * source Observable to emit items to its {@link Observer}s + * @see RxJava Wiki: replay() */ public ConnectableObservable replay() { return OperationMulticast.multicast(this, ReplaySubject. create()); } - + /** * Retry subscription to origin Observable upto given retry count. *

- * If {@link Observer#onError} is invoked the source Observable will be re-subscribed to as many times as defined by retryCount. + * *

- * Any {@link Observer#onNext} calls received on each attempt will be emitted and concatenated together. + * If {@link Observer#onError} is invoked the source Observable will be + * re-subscribed to as many times as defined by retryCount. *

- * For example, if an Observable fails on first time but emits [1, 2] then succeeds the second time and - * emits [1, 2, 3, 4, 5] then the complete output would be [1, 2, 1, 2, 3, 4, 5, onCompleted]. + * Any {@link Observer#onNext} calls received on each attempt will be + * emitted and concatenated together. + *

+ * For example, if an Observable fails on first time but emits [1, 2] then + * succeeds the second time and emits [1, 2, 3, 4, 5] then the complete + * output would be [1, 2, 1, 2, 3, 4, 5, onCompleted]. * - * @param retryCount - * Number of retry attempts before failing. - * @return Observable with retry logic. + * @param retryCount number of retry attempts before failing + * @return an Observable with retry logic + * @see RxJava Wiki: retry() */ public Observable retry(int retryCount) { return create(OperationRetry.retry(this, retryCount)); } /** - * Retry subscription to origin Observable whenever onError is called (infinite retry count). + * Retry subscription to origin Observable whenever onError is + * called (infinite retry count). + *

+ * *

- * If {@link Observer#onError} is invoked the source Observable will be re-subscribed to. + * If {@link Observer#onError} is invoked the source Observable will be + * re-subscribed to. *

- * Any {@link Observer#onNext} calls received on each attempt will be emitted and concatenated together. + * Any {@link Observer#onNext} calls received on each attempt will be + * emitted and concatenated together. *

- * For example, if an Observable fails on first time but emits [1, 2] then succeeds the second time and - * emits [1, 2, 3, 4, 5] then the complete output would be [1, 2, 1, 2, 3, 4, 5, onCompleted]. - * @return Observable with retry logic. + * For example, if an Observable fails on first time but emits [1, 2] then + * succeeds the second time and emits [1, 2, 3, 4, 5] then the complete + * output would be [1, 2, 1, 2, 3, 4, 5, onCompleted]. + * + * @return an Observable with retry logic + * @see RxJava Wiki: retry() */ public Observable retry() { return create(OperationRetry.retry(this)); } /** - * This method has similar behavior to {@link #replay} except that this auto-subscribes to - * the source Observable rather than returning a {@link ConnectableObservable}. + * This method has similar behavior to {@link #replay} except that this + * auto-subscribes to the source Observable rather than returning a + * {@link ConnectableObservable}. *

- * + * *

- * This is useful when you want an Observable to cache responses and you can't control the - * subscribe/unsubscribe behavior of all the {@link Observer}s. + * This is useful when you want an Observable to cache responses and you + * can't control the subscribe/unsubscribe behavior of all the + * {@link Observer}s. *

- * NOTE: You sacrifice the ability to unsubscribe from the origin when you use the - * cache() operator so be careful not to use this operator on Observables that - * emit an infinite or very large number of items that will use up memory. + * Note: You sacrifice the ability to unsubscribe from the origin when you + * use the cache() operator so be careful not to use this + * operator on Observables that emit an infinite or very large number of + * items that will use up memory. * - * @return an Observable that when first subscribed to, caches all of its notifications for - * the benefit of subsequent subscribers. + * @return an Observable that, when first subscribed to, caches all of its + * notifications for the benefit of subsequent subscribers. + * @see RxJava Wiki: cache() */ public Observable cache() { return create(OperationCache.cache(this)); } /** - * Returns a {@link ConnectableObservable}, which waits until its {@link ConnectableObservable#connect connect} method is called before it begins emitting - * items to those {@link Observer}s that have subscribed to it. + * Perform work in parallel by sharding an {@code Observable} on a + * {@link Schedulers#threadPoolForComputation()} {@link Scheduler} and + * return an {@code Observable} with the output. *

- * + * * - * @return a {@link ConnectableObservable} that upon connection causes the source Observable to - * emit items to its {@link Observer}s + * @param f a {@link Func1} that applies Observable operators to + * {@code Observable} in parallel and returns an + * {@code Observable} + * @return an Observable with the output of the {@link Func1} executed on a + * {@link Scheduler} + * @see RxJava Wiki: parallel() */ - public ConnectableObservable publish() { - return OperationMulticast.multicast(this, PublishSubject. create()); + public Observable parallel(Func1, Observable> f) { + return OperationParallel.parallel(this, f); } /** - * Synonymous with reduce(). + * Perform work in parallel by sharding an {@code Observable} on a + * {@link Scheduler} and return an {@code Observable} with the output. *

- * + * + * + * @param f a {@link Func1} that applies Observable operators to + * {@code Observable} in parallel and returns an + * {@code Observable} + * @param s a {@link Scheduler} to perform the work on + * @return an Observable with the output of the {@link Func1} executed on a + * {@link Scheduler} + * @see RxJava Wiki: parallel() + */ + + public Observable parallel(final Func1, Observable> f, final Scheduler s) { + return OperationParallel.parallel(this, f, s); + } + + + /** + * Merges an Observable<Observable<T>> to + * Observable<Observable<T>> with the number of + * inner Observables defined by parallelObservables. + *

+ * For example, if the original + * Observable<Observable<T>> has 100 Observables to + * be emitted and parallelObservables is 8, the 100 will be + * grouped onto 8 output Observables. + *

+ * This is a mechanism for efficiently processing n number of + * Observables on a smaller m number of resources (typically CPU + * cores). + *

+ * + * + * @param parallelObservables the number of Observables to merge into + * @return an Observable of Observables constrained to number defined by + * parallelObservables + * @see RxJava Wiki: parallelMerge() + */ + public static Observable> parallelMerge(Observable> source, int parallelObservables) { + return OperationParallelMerge.parallelMerge(source, parallelObservables); + } + + /** + * Merges an Observable<Observable<T>> to + * Observable<Observable<T>> with the number of + * inner Observables defined by parallelObservables and runs + * each Observable on the defined Scheduler. + *

+ * For example, if the original + * Observable<Observable<T>> has 100 Observables to + * be emitted and parallelObservables is 8, the 100 will be + * grouped onto 8 output Observables. + *

+ * This is a mechanism for efficiently processing n number of + * Observables on a smaller m number of resources (typically CPU + * cores). + *

+ * + * + * @param parallelObservables the number of Observables to merge into + * @return an Observable of Observables constrained to number defined by + * parallelObservables. + * @see RxJava Wiki: parallelMerge() + */ + public static Observable> parallelMerge(Observable> source, int parallelObservables, Scheduler scheduler) { + return OperationParallelMerge.parallelMerge(source, parallelObservables, scheduler); + } + + /** + * Returns a {@link ConnectableObservable}, which waits until its + * {@link ConnectableObservable#connect connect} method is called before it + * begins emitting items to those {@link Observer}s that have subscribed to + * it. + *

+ * + * + * @return a {@link ConnectableObservable} that upon connection causes the + * source Observable to emit items to its {@link Observer}s + * @see RxJava Wiki: publish() + */ + public ConnectableObservable publish() { + return OperationMulticast.multicast(this, PublishSubject. create()); + } + + /** + * Returns a {@link ConnectableObservable} that shares a single subscription + * that contains the last notification only. + *

+ * * + * @return a {@link ConnectableObservable} + * @see RxJava Wiki: publishLast() + */ + public ConnectableObservable publishLast() { + return OperationMulticast.multicast(this, AsyncSubject. create()); + } + + /** + * Synonymous with reduce(). + *

+ * + * + * @see RxJava Wiki: aggregate() * @see #reduce(Func2) */ public Observable aggregate(Func2 accumulator) { @@ -3491,26 +4340,29 @@ public Observable aggregate(Func2 accumulator) { } /** - * Returns an Observable that applies a function of your choosing to the first item emitted by a - * source Observable, then feeds the result of that function along with the second item emitted - * by an Observable into the same function, and so on until all items have been emitted by the - * source Observable, emitting the final result from the final call to your function as its sole - * item. + * Returns an Observable that applies a function of your choosing to the + * first item emitted by a source Observable, then feeds the result of that + * function along with the second item emitted by an Observable into the + * same function, and so on until all items have been emitted by the source + * Observable, emitting the final result from the final call to your + * function as its sole item. *

* *

- * This technique, which is called "reduce" or "aggregate" here, is sometimes called "fold," - * "accumulate," "compress," or "inject" in other programming contexts. Groovy, for instance, - * has an inject method that does a similar operation on lists. - * - * @param initialValue - * the initial (seed) accumulator value - * @param accumulator - * an accumulator function to be invoked on each item emitted by the source - * Observable, the result of which will be used in the next accumulator call - * @return an Observable that emits a single item that is the result of accumulating the output - * from the items emitted by the source Observable - * @see MSDN: Observable.Aggregate + * This technique, which is called "reduce" or "aggregate" here, is + * sometimes called "fold," "accumulate," "compress," or "inject" in other + * programming contexts. Groovy, for instance, has an inject + * method that does a similar operation on lists. + * + * @param initialValue the initial (seed) accumulator value + * @param accumulator an accumulator function to be invoked on each item + * emitted by the source Observable, the result of which + * will be used in the next accumulator call + * @return an Observable that emits a single item that is the result of + * accumulating the output from the items emitted by the source + * Observable + * @see RxJava Wiki: reduce() + * @see MSDN: Observable.Aggregate * @see Wikipedia: Fold (higher-order function) */ public Observable reduce(R initialValue, Func2 accumulator) { @@ -3522,6 +4374,7 @@ public Observable reduce(R initialValue, Func2 accumulat *

* * + * @see RxJava Wiki: aggregate() * @see #reduce(Object, Func2) */ public Observable aggregate(R initialValue, Func2 accumulator) { @@ -3529,644 +4382,1801 @@ public Observable aggregate(R initialValue, Func2 accumu } /** - * Returns an Observable that applies a function of your choosing to the first item emitted by a - * source Observable, then feeds the result of that function along with the second item emitted - * by an Observable into the same function, and so on until all items have been emitted by the - * source Observable, emitting the result of each of these iterations. + * Returns an Observable that applies a function of your choosing to the + * first item emitted by a source Observable, then feeds the result of that + * function along with the second item emitted by an Observable into the + * same function, and so on until all items have been emitted by the source + * Observable, emitting the result of each of these iterations. *

* *

* This sort of function is sometimes called an accumulator. *

- * Note that when you pass a seed to scan() the resulting Observable will emit - * that seed as its first emitted item. + * Note that when you pass a seed to scan() the resulting + * Observable will emit that seed as its first emitted item. * - * @param accumulator - * an accumulator function to be invoked on each item emitted by the source - * Observable, whose result will be emitted to {@link Observer}s via {@link Observer#onNext onNext} and used in the next accumulator call. - * @return an Observable that emits the results of each call to the accumulator function - * @see MSDN: Observable.Scan + * @param accumulator an accumulator function to be invoked on each item + * emitted by the source Observable, whose result will be + * emitted to {@link Observer}s via + * {@link Observer#onNext onNext} and used in the next + * accumulator call + * @return an Observable that emits the results of each call to the + * accumulator function + * @see RxJava Wiki: scan() + * @see MSDN: Observable.Scan */ public Observable scan(Func2 accumulator) { return create(OperationScan.scan(this, accumulator)); } /** - * Returns an Observable that emits the results of sampling the items emitted by the source - * Observable at a specified time interval. - *

- * - * - * @param period - * the sampling rate + * Returns an Observable that emits the results of shifting the items emitted by the source + * Observable by a specified delay. Only errors emitted by the source Observable are not delayed. + * @param delay + * the delay to shift the source by * @param unit * the {@link TimeUnit} in which period is defined - * @return an Observable that emits the results of sampling the items emitted by the source - * Observable at the specified time interval + * @return the source Observable, but shifted by the specified delay + * @see MSDN: Observable.Delay + */ + public Observable delay(long delay, TimeUnit unit) { + return create(OperationDelay.delay(this, delay, unit)); + } + + /** + * Returns an Observable that emits the results of shifting the items emitted by the source + * Observable by a specified delay. Only errors emitted by the source Observable are not delayed. + * @param delay + * the delay to shift the source by + * @param unit + * the {@link TimeUnit} in which period is defined + * @param scheduler + * the {@link Scheduler} to use for delaying + * @return the source Observable, but shifted by the specified delay + * @see MSDN: Observable.Delay + */ + public Observable delay(long delay, TimeUnit unit, Scheduler scheduler) { + return create(OperationDelay.delay(this, delay, unit, scheduler)); + } + + /** + * Returns an Observable that emits the results of shifting the items emitted by the source + * Observable by a delay specified by the due time at which to begin emitting. + * Only errors emitted by the source Observable are not delayed. + * @param dueTime + * the due time at which to start emitting + * @return the source Observable, but shifted by the specified delay + * @see MSDN: Observable.Delay + */ + public Observable delay(Date dueTime) { + return create(OperationDelay.delay(this, dueTime)); + } + + /** + * Returns an Observable that emits the results of shifting the items emitted by the source + * Observable by a delay specified by the due time at which to begin emitting. + * Only errors emitted by the source Observable are not delayed. + * @param dueTime + * the due time at which to start emitting + * @param scheduler + * the {@link Scheduler} to use for delaying + * @return the source Observable, but shifted by the specified delay + * @see MSDN: Observable.Delay + */ + public Observable delay(Date dueTime, Scheduler scheduler) { + return create(OperationDelay.delay(this, dueTime, scheduler)); + } + + /** + * Returns an Observable that emits the results of sampling the items + * emitted by the source Observable at a specified time interval. + *

+ * + * + * @param period the sampling rate + * @param unit the {@link TimeUnit} in which period is defined + * @return an Observable that emits the results of sampling the items + * emitted by the source Observable at the specified time interval + * @see RxJava Wiki: sample() */ public Observable sample(long period, TimeUnit unit) { return create(OperationSample.sample(this, period, unit)); } /** - * Returns an Observable that emits the results of sampling the items emitted by the source - * Observable at a specified time interval. + * Returns an Observable that emits the results of sampling the items + * emitted by the source Observable at a specified time interval. *

- * + * * - * @param period - * the sampling rate - * @param unit - * the {@link TimeUnit} in which period is defined - * @param scheduler - * the {@link Scheduler} to use when sampling - * @return an Observable that emits the results of sampling the items emitted by the source - * Observable at the specified time interval + * @param period the sampling rate + * @param unit the {@link TimeUnit} in which period is defined + * @param scheduler the {@link Scheduler} to use when sampling + * @return an Observable that emits the results of sampling the items + * emitted by the source Observable at the specified time interval + * @see RxJava Wiki: sample() */ public Observable sample(long period, TimeUnit unit, Scheduler scheduler) { return create(OperationSample.sample(this, period, unit, scheduler)); } /** - * Returns an Observable that applies a function of your choosing to the first item emitted by a - * source Observable, then feeds the result of that function along with the second item emitted - * by an Observable into the same function, and so on until all items have been emitted by the - * source Observable, emitting the result of each of these iterations. + * Returns an Observable that applies a function of your choosing to the + * first item emitted by a source Observable, then feeds the result of that + * function along with the second item emitted by an Observable into the + * same function, and so on until all items have been emitted by the source + * Observable, emitting the result of each of these iterations. *

* *

* This sort of function is sometimes called an accumulator. *

- * Note that when you pass a seed to scan() the resulting Observable will emit - * that seed as its first emitted item. + * Note that when you pass a seed to scan() the resulting + * Observable will emit that seed as its first emitted item. * - * @param initialValue - * the initial (seed) accumulator value - * @param accumulator - * an accumulator function to be invoked on each item emitted by the source - * Observable, whose result will be emitted to {@link Observer}s via {@link Observer#onNext onNext} and used in the next accumulator call. - * @return an Observable that emits the results of each call to the accumulator function - * @see MSDN: Observable.Scan + * @param initialValue the initial (seed) accumulator value + * @param accumulator an accumulator function to be invoked on each item + * emitted by the source Observable, whose result will be + * emitted to {@link Observer}s via + * {@link Observer#onNext onNext} and used in the next + * accumulator call + * @return an Observable that emits the results of each call to the + * accumulator function + * @see RxJava Wiki: scan() + * @see MSDN: Observable.Scan */ public Observable scan(R initialValue, Func2 accumulator) { return create(OperationScan.scan(this, initialValue, accumulator)); } /** - * Returns an Observable that emits a Boolean that indicates whether all of the items emitted by - * the source Observable satisfy a condition. + * Returns an Observable that emits a Boolean that indicates whether all of + * the items emitted by the source Observable satisfy a condition. *

- * + * * - * @param predicate - * a function that evaluates an item and returns a Boolean - * @return an Observable that emits true if all items emitted by the source - * Observable satisfy the predicate; otherwise, false + * @param predicate a function that evaluates an item and returns a Boolean + * @return an Observable that emits true if all items emitted + * by the source Observable satisfy the predicate; otherwise, + * false + * @see RxJava Wiki: all() */ public Observable all(Func1 predicate) { return create(OperationAll.all(this, predicate)); } /** - * Returns an Observable that skips the first num items emitted by the source - * Observable and emits the remainder. + * Returns an Observable that skips the first num items emitted + * by the source Observable and emits the remainder. *

* *

- * You can ignore the first num items emitted by an Observable and attend only to - * those items that come after, by modifying the Observable with the skip method. + * You can ignore the first num items emitted by an Observable + * and attend only to those items that come after, by modifying the + * Observable with the skip method. * - * @param num - * the number of items to skip - * @return an Observable that is identical to the source Observable except that it does not - * emit the first num items that the source emits + * @param num the number of items to skip + * @return an Observable that is identical to the source Observable except + * that it does not emit the first num items that the + * source emits + * @see RxJava Wiki: skip() */ public Observable skip(int num) { return create(OperationSkip.skip(this, num)); } /** - * Returns an Observable that emits only the very first item emitted by the source Observable. + * Returns an Observable that emits only the very first item emitted by the + * source Observable. + *

+ * * - * @return an Observable that emits only the very first item from the source, or none if the - * source Observable completes without emitting a single item. - * @see MSDN: Observable.First + * @return an Observable that emits only the very first item from the + * source, or none if the source Observable completes without + * emitting a single item + * @see RxJava Wiki: first() + * @see MSDN: Observable.First */ public Observable first() { return take(1); } /** - * Returns an Observable that emits only the very first item emitted by the source Observable - * that satisfies a given condition. + * Returns an Observable that emits only the very first item emitted by the + * source Observable that satisfies a given condition. + *

+ * * - * @param predicate - * The condition any source emitted item has to satisfy. - * @return an Observable that emits only the very first item satisfying the given condition from the source, - * or none if the source Observable completes without emitting a single matching item. - * @see MSDN: Observable.First + * @param predicate the condition any source emitted item has to satisfy + * @return an Observable that emits only the very first item satisfying the + * given condition from the source, or none if the source Observable + * completes without emitting a single matching item + * @see RxJava Wiki: first() + * @see MSDN: Observable.First */ public Observable first(Func1 predicate) { return skipWhile(not(predicate)).take(1); } /** - * Returns an Observable that emits only the very first item emitted by the source Observable, or - * a default value. + * Returns an Observable that emits only the very first item emitted by the + * source Observable, or a default value. + *

+ * * - * @param defaultValue - * The default value to emit if the source Observable doesn't emit anything. - * @return an Observable that emits only the very first item from the source, or a default value - * if the source Observable completes without emitting a single item. - * @see MSDN: Observable.FirstOrDefault + * @param defaultValue the default value to emit if the source Observable + * doesn't emit anything + * @return an Observable that emits only the very first item from the + * source, or a default value if the source Observable completes + * without emitting a single item + * @see RxJava Wiki: firstOrDefault() + * @see MSDN: Observable.FirstOrDefault */ public Observable firstOrDefault(T defaultValue) { return create(OperationFirstOrDefault.firstOrDefault(this, defaultValue)); } /** - * Returns an Observable that emits only the very first item emitted by the source Observable - * that satisfies a given condition, or a default value otherwise. + * Returns an Observable that emits only the very first item emitted by the + * source Observable that satisfies a given condition, or a default value + * otherwise. + *

+ * * - * @param predicate - * The condition any source emitted item has to satisfy. - * @param defaultValue - * The default value to emit if the source Observable doesn't emit anything that - * satisfies the given condition. - * @return an Observable that emits only the very first item from the source that satisfies the - * given condition, or a default value otherwise. - * @see MSDN: Observable.FirstOrDefault + * @param predicate the condition any source emitted item has to satisfy + * @param defaultValue the default value to emit if the source Observable + * doesn't emit anything that satisfies the given condition + * @return an Observable that emits only the very first item from the source + * that satisfies the given condition, or a default value otherwise + * @see RxJava Wiki: firstOrDefault() + * @see MSDN: Observable.FirstOrDefault */ public Observable firstOrDefault(Func1 predicate, T defaultValue) { return create(OperationFirstOrDefault.firstOrDefault(this, predicate, defaultValue)); } - /** - * Returns an Observable that emits only the first num items emitted by the source - * Observable. + * Returns the elements of the specified sequence or the specified default + * value in a singleton sequence if the sequence is empty. + *

+ * + * + * @param defaultValue the value to return if the sequence is empty + * @return an Observable that emits the specified default value if the + * source is empty; otherwise, the items emitted by the source + * @see RxJava Wiki: defaultIfEmpty() + * @see MSDN: Observable.DefaultIfEmpty + */ + public Observable defaultIfEmpty(T defaultValue) { + return create(OperationDefaultIfEmpty.defaultIfEmpty(this, defaultValue)); + } + + /** + * Returns an Observable that emits only the first num items + * emitted by the source Observable. *

* *

- * This method returns an Observable that will invoke a subscribing {@link Observer}'s {@link Observer#onNext onNext} function a maximum of num times before invoking + * This method returns an Observable that will invoke a subscribing + * {@link Observer}'s {@link Observer#onNext onNext} function a maximum of + * num times before invoking * {@link Observer#onCompleted onCompleted}. * - * @param num - * the number of items to take - * @return an Observable that emits only the first num items from the source - * Observable, or all of the items from the source Observable if that Observable emits - * fewer than num items + * @param num the number of items to emit + * @return an Observable that emits only the first num items + * from the source Observable, or all of the items from the source + * Observable if that Observable emits fewer than num + * items + * @see RxJava Wiki: take() */ public Observable take(final int num) { return create(OperationTake.take(this, num)); } /** - * Returns an Observable that emits items emitted by the source Observable so long as a - * specified condition is true. + * Returns an Observable that emits items emitted by the source Observable + * so long as a specified condition is true. *

- * + * * - * @param predicate - * a function that evaluates an item emitted by the source Observable and returns a - * Boolean - * @return an Observable that emits the items from the source Observable so long as each item - * satisfies the condition defined by predicate + * @param predicate a function that evaluates an item emitted by the source + * Observable and returns a Boolean + * @return an Observable that emits the items from the source Observable so + * long as each item satisfies the condition defined by + * predicate + * @see RxJava Wiki: takeWhile() */ public Observable takeWhile(final Func1 predicate) { return create(OperationTakeWhile.takeWhile(this, predicate)); } /** - * Returns an Observable that emits the items emitted by a source Observable so long as a given - * predicate remains true, where the predicate can operate on both the item and its index - * relative to the complete sequence. + * Returns an Observable that emits the items emitted by a source Observable + * so long as a given predicate remains true, where the predicate can + * operate on both the item and its index relative to the complete sequence. *

- * + * * - * @param predicate - * a function to test each item emitted by the source Observable for a condition; - * the second parameter of the function represents the index of the source item - * @return an Observable that emits items from the source Observable so long as the predicate - * continues to return true for each item, then completes + * @param predicate a function to test each item emitted by the source + * Observable for a condition; the second parameter of the + * function represents the index of the source item + * @return an Observable that emits items from the source Observable so long + * as the predicate continues to return true for each + * item, then completes + * @see RxJava Wiki: takeWhileWithIndex() */ public Observable takeWhileWithIndex(final Func2 predicate) { return create(OperationTakeWhile.takeWhileWithIndex(this, predicate)); } /** - * Returns an Observable that emits only the very first item emitted by the source Observable. + * Returns an Observable that emits only the very first item emitted by the + * source Observable. + *

+ * * - * @return an Observable that emits only the very first item from the source, or none if the - * source Observable completes without emitting a single item. - * @see MSDN: Observable.First + * @return an Observable that emits only the very first item from the + * source, or none if the source Observable completes without + * emitting a single item + * @see RxJava Wiki: first() + * @see MSDN: Observable.First * @see #first() */ public Observable takeFirst() { return first(); } - + /** - * Returns an Observable that emits only the very first item emitted by the source Observable - * that satisfies a given condition. + * Returns an Observable that emits only the very first item emitted by the + * source Observable that satisfies a given condition. + *

+ * * - * @param predicate - * The condition any source emitted item has to satisfy. - * @return an Observable that emits only the very first item satisfying the given condition from the source, - * or none if the source Observable completes without emitting a single matching item. - * @see MSDN: Observable.First + * @param predicate the condition any source emitted item has to satisfy + * @return an Observable that emits only the very first item satisfying the + * given condition from the source, or none if the source Observable + * completes without emitting a single matching item + * @see RxJava Wiki: first() + * @see MSDN: Observable.First * @see #first(Func1) */ public Observable takeFirst(Func1 predicate) { return first(predicate); } - + /** - * Returns an Observable that emits only the last count items emitted by the source - * Observable. + * Returns an Observable that emits only the last count items + * emitted by the source Observable. *

- * + * * - * @param count - * the number of items to emit from the end of the sequence emitted by the source - * Observable - * @return an Observable that emits only the last count items emitted by the source - * Observable + * @param count the number of items to emit from the end of the sequence + * emitted by the source Observable + * @return an Observable that emits only the last count items + * emitted by the source Observable + * @see RxJava Wiki: takeLast() */ public Observable takeLast(final int count) { return create(OperationTakeLast.takeLast(this, count)); } /** - * Returns an Observable that emits the items from the source Observable only until the - * other Observable emits an item. + * Returns an Observable that emits the items from the source Observable + * only until the other Observable emits an item. *

- * + * * - * @param other - * the Observable whose first emitted item will cause takeUntil to stop - * emitting items from the source Observable - * @param - * the type of items emitted by other - * @return an Observable that emits the items of the source Observable until such time as - * other emits its first item + * @param other the Observable whose first emitted item will cause + * takeUntil to stop emitting items from the + * source Observable + * @param the type of items emitted by other + * @return an Observable that emits the items of the source Observable until + * such time as other emits its first item + * @see RxJava Wiki: takeUntil() */ public Observable takeUntil(Observable other) { return OperationTakeUntil.takeUntil(this, other); } /** - * Returns an Observable that bypasses all items from the source Observable as long as the specified - * condition holds true. Emits all further source items as soon as the condition becomes false. - * @param predicate - * A function to test each item emitted from the source Observable for a condition. - * It receives the emitted item as first parameter and the index of the emitted item as - * second parameter. - * @return an Observable that emits all items from the source Observable as soon as the condition - * becomes false. - * @see MSDN: Observable.SkipWhile + * Returns an Observable that bypasses all items from the source Observable + * as long as the specified condition holds true, but emits all further + * source items as soon as the condition becomes false. + *

+ * + * + * @param predicate a function to test each item emitted from the source + * Observable for a condition. It receives the emitted item + * as the first parameter and the index of the emitted item + * as a second parameter. + * @return an Observable that emits all items from the source Observable as + * soon as the condition becomes false + * @see RxJava Wiki: skipWhileWithIndex() + * @see MSDN: Observable.SkipWhile */ public Observable skipWhileWithIndex(Func2 predicate) { return create(OperationSkipWhile.skipWhileWithIndex(this, predicate)); } /** - * Returns an Observable that bypasses all items from the source Observable as long as the specified - * condition holds true. Emits all further source items as soon as the condition becomes false. - * @param predicate - * A function to test each item emitted from the source Observable for a condition. - * @return an Observable that emits all items from the source Observable as soon as the condition - * becomes false. - * @see MSDN: Observable.SkipWhile + * Returns an Observable that bypasses all items from the source Observable + * as long as the specified condition holds true, but emits all further + * source items as soon as the condition becomes false. + *

+ * + * + * @param predicate a function to test each item emitted from the source + * Observable for a condition + * @return an Observable that emits all items from the source Observable as + * soon as the condition becomes false + * @see RxJava Wiki: skipWhile() + * @see MSDN: Observable.SkipWhile */ public Observable skipWhile(Func1 predicate) { return create(OperationSkipWhile.skipWhile(this, predicate)); } /** - * Returns an Observable that emits a single item, a list composed of all the items emitted by - * the source Observable. + * Bypasses a specified number of items at the end of an Observable + * sequence. + *

+ * This operator accumulates a queue with a length enough to store the first + * count items. As more items are received, items are taken + * from the front of the queue and produced on the result sequence. This + * causes elements to be delayed. + *

+ * + * + * @param count number of elements to bypass at the end of the source + * sequence + * @return an Observable sequence emitting the source sequence items + * except for the bypassed ones at the end + * @throws IndexOutOfBoundsException if count is less than zero + * @see RxJava Wiki: skipLast() + * @see MSDN: Observable.SkipLast + */ + public Observable skipLast(int count) { + return create(OperationSkipLast.skipLast(this, count)); + } + + /** + * Returns an Observable that emits a single item, a list composed of all + * the items emitted by the source Observable. *

* *

- * Normally, an Observable that returns multiple items will do so by invoking its {@link Observer}'s {@link Observer#onNext onNext} method for each such item. You can change - * this behavior, instructing the Observable to compose a list of all of these items and then to - * invoke the Observer's onNext function once, passing it the entire list, by - * calling the Observable's toList method prior to calling its {@link #subscribe} method. + * Normally, an Observable that returns multiple items will do so by + * invoking its {@link Observer}'s {@link Observer#onNext onNext} method for + * each such item. You can change this behavior, instructing the Observable + * to compose a list of all of these items and then to invoke the Observer's + * onNext function once, passing it the entire list, by calling + * the Observable's toList method prior to calling its + * {@link #subscribe} method. *

- * Be careful not to use this operator on Observables that emit infinite or very large numbers - * of items, as you do not have the option to unsubscribe. + * Be careful not to use this operator on Observables that emit infinite or + * very large numbers of items, as you do not have the option to + * unsubscribe. * - * @return an Observable that emits a single item: a List containing all of the items emitted by - * the source Observable. + * @return an Observable that emits a single item: a List containing all of + * the items emitted by the source Observable. + * @see RxJava Wiki: toList() */ public Observable> toList() { return create(OperationToObservableList.toObservableList(this)); } /** - * Return an Observable that emits the items emitted by the source Observable, in a sorted - * order (each item emitted by the Observable must implement {@link Comparable} with respect to - * all other items in the sequence). + * Return an Observable that emits the items emitted by the source + * Observable, in a sorted order (each item emitted by the Observable must + * implement {@link Comparable} with respect to all other items in the + * sequence). *

* * - * @throws ClassCastException - * if any item emitted by the Observable does not implement {@link Comparable} with - * respect to all other items emitted by the Observable - * @return an Observable that emits the items from the source Observable in sorted order + * @throws ClassCastException if any item emitted by the Observable does not + * implement {@link Comparable} with respect to + * all other items emitted by the Observable + * @return an Observable that emits the items from the source Observable in + * sorted order + * @see RxJava Wiki: toSortedList() */ public Observable> toSortedList() { return create(OperationToObservableSortedList.toSortedList(this)); } /** - * Return an Observable that emits the items emitted by the source Observable, in a sorted - * order based on a specified comparison function + * Return an Observable that emits the items emitted by the source + * Observable, in a sorted order based on a specified comparison function *

* * - * @param sortFunction - * a function that compares two items emitted by the source Observable and returns - * an Integer that indicates their sort order - * @return an Observable that emits the items from the source Observable in sorted order + * @param sortFunction a function that compares two items emitted by the + * source Observable and returns an Integer that + * indicates their sort order + * @return an Observable that emits the items from the source Observable in + * sorted order + * @see RxJava Wiki: toSortedList() */ public Observable> toSortedList(Func2 sortFunction) { return create(OperationToObservableSortedList.toSortedList(this, sortFunction)); } /** - * Emit a specified set of items before beginning to emit items from the source Observable. + * Emit a specified set of items before beginning to emit items from the + * source Observable. *

* * - * @param values - * Iterable of the items you want the modified Observable to emit first + * @param values Iterable of the items you want the modified Observable to + * emit first * @return an Observable that exhibits the modified behavior + * @see RxJava Wiki: startWith() */ public Observable startWith(Iterable values) { return concat(Observable. from(values), this); } - + + /** + * Emit a specified set of items with the specified scheduler before + * beginning to emit items from the source Observable. + *

+ * + * + * @param values iterable of the items you want the modified Observable to + * emit first + * @param scheduler the scheduler to emit the prepended values on + * @return an Observable that exhibits the modified behavior + * @see RxJava Wiki: startWith() + * @see MSDN: Observable.StartWith + */ + public Observable startWith(Iterable values, Scheduler scheduler) { + return concat(from(values, scheduler), this); + } + + /** + * Emit a specified array of items with the specified scheduler before + * beginning to emit items from the source Observable. + *

+ * + * + * @param values the items you want the modified Observable to emit first + * @param scheduler the scheduler to emit the prepended values on + * @return an Observable that exhibits the modified behavior + * @see RxJava Wiki: startWith() + * @see MSDN: Observable.StartWith + */ + public Observable startWith(T[] values, Scheduler scheduler) { + return startWith(Arrays.asList(values), scheduler); + } + /** - * Emit a specified set of items before beginning to emit items from the source Observable. + * Emit a specified item before beginning to emit items from the source + * Observable. *

* * - * @param t1 - * item to include + * @param t1 item to emit * @return an Observable that exhibits the modified behavior + * @see RxJava Wiki: startWith() */ public Observable startWith(T t1) { return concat(Observable. from(t1), this); } - + /** - * Emit a specified set of items before beginning to emit items from the source Observable. + * Emit a specified set of items before beginning to emit items from the + * source Observable. *

* * - * @param t1 - * item to include - * @param t2 - * item to include + * @param t1 first item to emit + * @param t2 second item to emit * @return an Observable that exhibits the modified behavior + * @see RxJava Wiki: startWith() */ public Observable startWith(T t1, T t2) { return concat(Observable. from(t1, t2), this); } - + /** - * Emit a specified set of items before beginning to emit items from the source Observable. + * Emit a specified set of items before beginning to emit items from the + * source Observable. *

* * - * @param t1 - * item to include - * @param t2 - * item to include - * @param t3 - * item to include + * @param t1 first item to emit + * @param t2 second item to emit + * @param t3 third item to emit * @return an Observable that exhibits the modified behavior + * @see RxJava Wiki: startWith() */ public Observable startWith(T t1, T t2, T t3) { return concat(Observable. from(t1, t2, t3), this); } - + /** - * Emit a specified set of items before beginning to emit items from the source Observable. + * Emit a specified set of items before beginning to emit items from the + * source Observable. *

* * - * @param t1 - * item to include - * @param t2 - * item to include - * @param t3 - * item to include - * @param t4 - * item to include + * @param t1 first item to emit + * @param t2 second item to emit + * @param t3 third item to emit + * @param t4 fourth item to emit * @return an Observable that exhibits the modified behavior + * @see RxJava Wiki: startWith() */ public Observable startWith(T t1, T t2, T t3, T t4) { return concat(Observable. from(t1, t2, t3, t4), this); } - + /** - * Emit a specified set of items before beginning to emit items from the source Observable. + * Emit a specified set of items before beginning to emit items from the + * source Observable. *

* * - * @param t1 - * item to include - * @param t2 - * item to include - * @param t3 - * item to include - * @param t4 - * item to include - * @param t5 - * item to include + * @param t1 first item to emit + * @param t2 second item to emit + * @param t3 third item to emit + * @param t4 fourth item to emit + * @param t5 fifth item to emit * @return an Observable that exhibits the modified behavior + * @see RxJava Wiki: startWith() */ public Observable startWith(T t1, T t2, T t3, T t4, T t5) { return concat(Observable. from(t1, t2, t3, t4, t5), this); } - + /** - * Emit a specified set of items before beginning to emit items from the source Observable. + * Emit a specified set of items before beginning to emit items from the + * source Observable. *

* * - * @param t1 - * item to include - * @param t2 - * item to include - * @param t3 - * item to include - * @param t4 - * item to include - * @param t5 - * item to include - * @param t6 - * item to include + * @param t1 first item to emit + * @param t2 second item to emit + * @param t3 third item to emit + * @param t4 fourth item to emit + * @param t5 fifth item to emit + * @param t6 sixth item to emit * @return an Observable that exhibits the modified behavior + * @see RxJava Wiki: startWith() */ public Observable startWith(T t1, T t2, T t3, T t4, T t5, T t6) { return concat(Observable. from(t1, t2, t3, t4, t5, t6), this); } - + /** - * Emit a specified set of items before beginning to emit items from the source Observable. + * Emit a specified set of items before beginning to emit items from the + * source Observable. *

* * - * @param t1 - * item to include - * @param t2 - * item to include - * @param t3 - * item to include - * @param t4 - * item to include - * @param t5 - * item to include - * @param t6 - * item to include - * @param t7 - * item to include + * @param t1 first item to emit + * @param t2 second item to emit + * @param t3 third item to emit + * @param t4 fourth item to emit + * @param t5 fifth item to emit + * @param t6 sixth item to emit + * @param t7 seventh item to emit * @return an Observable that exhibits the modified behavior + * @see RxJava Wiki: startWith() */ public Observable startWith(T t1, T t2, T t3, T t4, T t5, T t6, T t7) { return concat(Observable. from(t1, t2, t3, t4, t5, t6, t7), this); } - + /** - * Emit a specified set of items before beginning to emit items from the source Observable. + * Emit a specified set of items before beginning to emit items from the + * source Observable. *

* * - * @param t1 - * item to include - * @param t2 - * item to include - * @param t3 - * item to include - * @param t4 - * item to include - * @param t5 - * item to include - * @param t6 - * item to include - * @param t7 - * item to include - * @param t8 - * item to include + * @param t1 first item to emit + * @param t2 second item to emit + * @param t3 third item to emit + * @param t4 fourth item to emit + * @param t5 fifth item to emit + * @param t6 sixth item to emit + * @param t7 seventh item to emit + * @param t8 eighth item to emit * @return an Observable that exhibits the modified behavior + * @see RxJava Wiki: startWith() */ public Observable startWith(T t1, T t2, T t3, T t4, T t5, T t6, T t7, T t8) { return concat(Observable. from(t1, t2, t3, t4, t5, t6, t7, t8), this); } - + /** - * Emit a specified set of items before beginning to emit items from the source Observable. + * Emit a specified set of items before beginning to emit items from the + * source Observable. *

* * - * @param t1 - * item to include - * @param t2 - * item to include - * @param t3 - * item to include - * @param t4 - * item to include - * @param t5 - * item to include - * @param t6 - * item to include - * @param t7 - * item to include - * @param t8 - * item to include - * @param t9 - * item to include + * @param t1 first item to emit + * @param t2 second item to emit + * @param t3 third item to emit + * @param t4 fourth item to emit + * @param t5 fifth item to emit + * @param t6 sixth item to emit + * @param t7 seventh item to emit + * @param t8 eighth item to emit + * @param t9 ninth item to emit * @return an Observable that exhibits the modified behavior + * @see RxJava Wiki: startWith() */ public Observable startWith(T t1, T t2, T t3, T t4, T t5, T t6, T t7, T t8, T t9) { return concat(Observable. from(t1, t2, t3, t4, t5, t6, t7, t8, t9), this); } /** - * Groups the items emitted by an Observable according to a specified criterion, and emits these - * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. + * Groups the items emitted by an Observable according to a specified + * criterion, and emits these grouped items as {@link GroupedObservable}s, + * one GroupedObservable per group. *

- * + * * - * @param keySelector - * a function that extracts the key from an item - * @param elementSelector - * a function to map a source item to an item in a {@link GroupedObservable} - * @param - * the key type - * @param - * the type of items emitted by the resulting {@link GroupedObservable}s - * @return an Observable that emits {@link GroupedObservable}s, each of which corresponds to a - * unique key value and emits items representing items from the source Observable that - * share that key value + * @param keySelector a function that extracts the key from an item + * @param elementSelector a function to map a source item to an item in a + * {@link GroupedObservable} + * @param the key type + * @param the type of items emitted by the resulting + * {@link GroupedObservable}s + * @return an Observable that emits {@link GroupedObservable}s, each of + * which corresponds to a unique key value and emits items + * representing items from the source Observable that share that key + * value + * @see RxJava Wiki: groupBy */ public Observable> groupBy(final Func1 keySelector, final Func1 elementSelector) { return create(OperationGroupBy.groupBy(this, keySelector, elementSelector)); } /** - * Groups the items emitted by an Observable according to a specified criterion, and emits these - * grouped items as {@link GroupedObservable}s, one GroupedObservable per group. + * Groups the items emitted by an Observable according to a specified + * criterion, and emits these grouped items as {@link GroupedObservable}s, + * one GroupedObservable per group. *

- * + * * - * @param keySelector - * a function that extracts the key for each item - * @param - * the key type - * @return an Observable that emits {@link GroupedObservable}s, each of which corresponds to a - * unique key value and emits items representing items from the source Observable that - * share that key value + * @param keySelector a function that extracts the key for each item + * @param the key type + * @return an Observable that emits {@link GroupedObservable}s, each of + * which corresponds to a unique key value and emits items + * representing items from the source Observable that share that key + * value + * @see RxJava Wiki: groupBy */ public Observable> groupBy(final Func1 keySelector) { return create(OperationGroupBy.groupBy(this, keySelector)); } /** - * Converts an Observable into a {@link BlockingObservable} (an Observable with blocking - * operators). + * Returns an {@link Observable} that emits true if the source + * {@link Observable} is empty, otherwise false. + *

+ * In Rx.Net this is negated as the any operator but renamed in + * RxJava to better match Java naming idioms. + *

+ * + * + * @return an Observable that emits a Boolean + * @see RxJava Wiki: isEmpty() + * @see MSDN: Observable.Any + */ + public Observable isEmpty() { + return create(OperationAny.isEmpty(this)); + } + + /** + * Returns an {@link Observable} that emits the last item emitted by the + * source or an IllegalArgumentException if the source + * {@link Observable} is empty. + *

+ * + * + * @return + * @see RxJava Wiki: last() + */ + public Observable last() { + return create(OperationLast.last(this)); + } + + /** + * Converts an Observable into a {@link BlockingObservable} (an Observable + * with blocking operators). * - * @see Blocking Observable Operators + * @return + * @see RxJava Wiki: Blocking Observable Operators */ public BlockingObservable toBlockingObservable() { return BlockingObservable.from(this); } /** - * Whether a given {@link Function} is an internal implementation inside rx.* packages or not. + * Converts the items emitted by an Observable to the specified type. *

- * For why this is being used see https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator" + * * - * NOTE: If strong reasons for not depending on package names comes up then the implementation of this method can change to looking for a marker interface. + * @param klass the target class type which the items will be converted to + * @return an Observable that emits each item from the source Observable + * converted to the specified type + * @see RxJava Wiki: cast() + * @see MSDN: Observable.Cast + */ + public Observable cast(final Class klass) { + return create(OperationCast.cast(this, klass)); + } + + /** + * Filters the items emitted by an Observable based on the specified type. + *

+ * + * + * @param klass the class type to filter the items emitted by the source + * Observable + * @return an Observable that emits items from the source Observable of + * type klass. + * @see RxJava Wiki: ofType() + * @see MSDN: Observable.OfType + */ + public Observable ofType(final Class klass) { + return filter(new Func1() { + public Boolean call(T t) { + return klass.isInstance(t); + } + }).cast(klass); + } + + /** + * Ignores all items emitted by an Observable and only calls + * onCompleted or onError. + *

+ * + * + * @return an empty Observable that only calls onCompleted or + * onError + * @see RxJava Wiki: ignoreElements() + * @see MSDN: Observable.IgnoreElements + */ + public Observable ignoreElements() { + return filter(alwaysFalse()); + } + + /** + * Applies a timeout policy for each element in the observable sequence, + * using the specified scheduler to run timeout timers. If the next element + * isn't received within the specified timeout duration starting from its + * predecessor, a TimeoutException is propagated to the observer. + *

+ * + * + * @param timeout maximum duration between values before a timeout occurs + * @param timeUnit the unit of time which applies to the + * timeout argument. + * @return the source Observable with a TimeoutException in + * case of a timeout + * @see RxJava Wiki: timeout() + * @see MSDN: Observable.Timeout + */ + public Observable timeout(long timeout, TimeUnit timeUnit) { + return create(OperationTimeout.timeout(this, timeout, timeUnit)); + } + + /** + * Applies a timeout policy for each element in the observable sequence, + * using the specified scheduler to run timeout timers. If the next element + * isn't received within the specified timeout duration starting from its + * predecessor, the other observable sequence is used to produce future + * messages from that point on. + *

+ * + * + * @param timeout maximum duration between values before a timeout occurs + * @param timeUnit the unit of time which applies to the + * timeout argument + * @param other sequence to return in case of a timeout + * @return the source sequence switching to the other sequence in case of a + * timeout + * @see RxJava Wiki: timeout() + * @see MSDN: Observable.Timeout + */ + public Observable timeout(long timeout, TimeUnit timeUnit, Observable other) { + return create(OperationTimeout.timeout(this, timeout, timeUnit, other)); + } + + /** + * Applies a timeout policy for each element in the observable sequence, + * using the specified scheduler to run timeout timers. If the next element + * isn't received within the specified timeout duration starting from its + * predecessor, a TimeoutException is propagated to the observer. + *

+ * + * + * @param timeout maximum duration between values before a timeout occurs + * @param timeUnit the unit of time which applies to the + * timeout argument + * @param scheduler Scheduler to run the timeout timers on + * @return the source sequence with a TimeoutException in case + * of a timeout + * @see RxJava Wiki: timeout() + * @see MSDN: Observable.Timeout + */ + public Observable timeout(long timeout, TimeUnit timeUnit, Scheduler scheduler) { + return create(OperationTimeout.timeout(this, timeout, timeUnit, scheduler)); + } + + /** + * Applies a timeout policy for each element in the observable sequence, + * using the specified scheduler to run timeout timers. If the next element + * isn't received within the specified timeout duration starting from its + * predecessor, the other observable sequence is used to produce future + * messages from that point on. + *

+ * + * + * @param timeout maximum duration between values before a timeout occurs + * @param timeUnit the unit of time which applies to the + * timeout argument + * @param other sequence to return in case of a timeout + * @param scheduler Scheduler to run the timeout timers on + * @return the source sequence switching to the other sequence in case of a + * timeout + * @see RxJava Wiki: timeout() + * @see MSDN: Observable.Timeout + */ + public Observable timeout(long timeout, TimeUnit timeUnit, Observable other, Scheduler scheduler) { + return create(OperationTimeout.timeout(this, timeout, timeUnit, other, scheduler)); + } + + /** + * Records the time interval between consecutive items emitted by an + * Observable. + *

+ * + * + * @return an Observable that emits time interval information items + * @see RxJava Wiki: timeInterval() + * @see MSDN: Observable.TimeInterval + */ + public Observable> timeInterval() { + return create(OperationTimeInterval.timeInterval(this)); + } + + /** + * Records the time interval between consecutive items emitted by an + * Observable, using the specified Scheduler to compute time intervals. + *

+ * + * + * @param scheduler Scheduler used to compute time intervals + * @return an Observable that emits time interval information items + * @see RxJava Wiki: timeInterval() + * @see MSDN: Observable.TimeInterval + */ + public Observable> timeInterval(Scheduler scheduler) { + return create(OperationTimeInterval.timeInterval(this, scheduler)); + } + + /** + * Constructs an Observable that depends on a resource object. + *

+ * + * + * @param resourceFactory the factory function to obtain a resource object + * that depends on the Observable + * @param observableFactory the factory function to obtain an Observable + * @return the Observable whose lifetime controls the lifetime of the + * dependent resource object + * @see RxJava Wiki: using() + * @see MSDN: Observable.Using + */ + public static Observable using(Func0 resourceFactory, Func1> observableFactory) { + return create(OperationUsing.using(resourceFactory, observableFactory)); + } + + /** + * Propagates the Observable sequence that reacts first. + *

+ * + * + * @param o1 an Observable competing to react first + * @param o2 an Observable competing to react first + * @return an Observable that reflects whichever of the given Observables + * reacted first + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb + */ + public static Observable amb(Observable o1, Observable o2) { + return create(OperationAmb.amb(o1, o2)); + } + + /** + * Propagates the Observable sequence that reacts first. + *

+ * + * + * @param o1 an Observable competing to react first + * @param o2 an Observable competing to react first + * @param o3 an Observable competing to react first + * @return an Observable that reflects whichever of the given Observables + * reacted first + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb + */ + public static Observable amb(Observable o1, Observable o2, Observable o3) { + return create(OperationAmb.amb(o1, o2, o3)); + } + + /** + * Propagates the observable sequence that reacts first. + *

+ * + * + * @param o1 an Observable competing to react first + * @param o2 an Observable competing to react first + * @param o3 an Observable competing to react first + * @param o4 an Observable competing to react first + * @return an Observable that reflects whichever of the given Observables + * reacted first + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb + */ + public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4) { + return create(OperationAmb.amb(o1, o2, o3, o4)); + } + + /** + * Propagates the Observable sequence that reacts first. + *

+ * + * + * @param o1 an Observable competing to react first + * @param o2 an Observable competing to react first + * @param o3 an Observable competing to react first + * @param o4 an Observable competing to react first + * @param o5 an Observable competing to react first + * @return an Observable that reflects whichever of the given Observables + * reacted first + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb + */ + public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5) { + return create(OperationAmb.amb(o1, o2, o3, o4, o5)); + } + + /** + * Propagates the observable sequence that reacts first. + *

+ * + * + * @param o1 an Observable competing to react first + * @param o2 an Observable competing to react first + * @param o3 an Observable competing to react first + * @param o4 an Observable competing to react first + * @param o5 an Observable competing to react first + * @param o6 an Observable competing to react first + * @return an Observable that reflects whichever of the given Observables + * reacted first + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb + */ + public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6) { + return create(OperationAmb.amb(o1, o2, o3, o4, o5, o6)); + } + + /** + * Propagates the observable sequence that reacts first. + *

+ * + * + * @param o1 an Observable competing to react first + * @param o2 an Observable competing to react first + * @param o3 an Observable competing to react first + * @param o4 an Observable competing to react first + * @param o5 an Observable competing to react first + * @param o6 an Observable competing to react first + * @param o7 an Observable competing to react first + * @return an Observable that reflects whichever of the given Observables + * reacted first + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb + */ + public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7) { + return create(OperationAmb.amb(o1, o2, o3, o4, o5, o6, o7)); + } + + /** + * Propagates the observable sequence that reacts first. + *

+ * + * + * @param o1 an Observable competing to react first + * @param o2 an Observable competing to react first + * @param o3 an Observable competing to react first + * @param o4 an Observable competing to react first + * @param o5 an Observable competing to react first + * @param o6 an Observable competing to react first + * @param o7 an Observable competing to react first + * @param o8 an observable competing to react first + * @return an Observable that reflects whichever of the given Observables + * reacted first + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb + */ + public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8) { + return create(OperationAmb.amb(o1, o2, o3, o4, o5, o6, o7, o8)); + } + + /** + * Propagates the observable sequence that reacts first. + *

+ * + * + * @param o1 an Observable competing to react first + * @param o2 an Observable competing to react first + * @param o3 an Observable competing to react first + * @param o4 an Observable competing to react first + * @param o5 an Observable competing to react first + * @param o6 an Observable competing to react first + * @param o7 an Observable competing to react first + * @param o8 an Observable competing to react first + * @param o9 an Observable competing to react first + * @return an Observable that reflects whichever of the given Observables + * reacted first + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb + */ + public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, Observable o9) { + return create(OperationAmb.amb(o1, o2, o3, o4, o5, o6, o7, o8, o9)); + } + + /** + * Propagates the observable sequence that reacts first. + *

+ * + * + * @param sources Observable sources competing to react first + * @return an Observable that reflects whichever of the given Observables + * reacted first + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb + */ + public static Observable amb(Iterable> sources) { + return create(OperationAmb.amb(sources)); + } + + /** + * Invokes an action for each item emitted by the Observable. + *

+ * + * + * @param observer the action to invoke for each item emitted in the source + * sequence + * @return the source sequence with the side-effecting behavior applied + * @see RxJava Wiki: doOnEach() + * @see MSDN: Observable.Do + */ + public Observable doOnEach(Observer observer) { + return create(OperationDoOnEach.doOnEach(this, observer)); + } + + /** + * Invokes an action for each item emitted by an Observable. + *

+ * + * + * @param onNext the action to invoke for each item in the source + * sequence + * @return the source sequence with the side-effecting behavior applied + * @see RxJava Wiki: doOnEach() + * @see MSDN: Observable.Do + */ + public Observable doOnEach(final Action1 onNext) { + Observer observer = new Observer() { + @Override + public void onCompleted() {} + + @Override + public void onError(Throwable e) {} + + @Override + public void onNext(T args) { + onNext.call(args); + } + + }; + + + return create(OperationDoOnEach.doOnEach(this, observer)); + } + + /** + * Invokes an action if onError is called from the Observable. + *

+ * + * + * @param onError the action to invoke if onError is invoked + * @return the source sequence with the side-effecting behavior applied + * @see RxJava Wiki: doOnError() + * @see MSDN: Observable.Do + */ + public Observable doOnError(final Action1 onError) { + Observer observer = new Observer() { + @Override + public void onCompleted() {} + + @Override + public void onError(Throwable e) { + onError.call(e); + } + + @Override + public void onNext(T args) { } + + }; + + + return create(OperationDoOnEach.doOnEach(this, observer)); + } + + /** + * Invokes an action when onCompleted is called by the + * Observable. + *

+ * + * + * @param onCompleted the action to invoke when onCompleted is + * called + * @return the source sequence with the side-effecting behavior applied + * @see RxJava Wiki: doOnCompleted() + * @see MSDN: Observable.Do + */ + public Observable doOnCompleted(final Action0 onCompleted) { + Observer observer = new Observer() { + @Override + public void onCompleted() { + onCompleted.call(); + } + + @Override + public void onError(Throwable e) { } + + @Override + public void onNext(T args) { } + + }; + + + return create(OperationDoOnEach.doOnEach(this, observer)); + } + + /** + * Invokes an action for each item emitted by an Observable. + * + * @param onNext the action to invoke for each item in the source sequence + * @param onError the action to invoke when the source Observable calls + * onError + * @return the source sequence with the side-effecting behavior applied + * @see RxJava Wiki: doOnEach() + * @see MSDN: Observable.Do + */ + public Observable doOnEach(final Action1 onNext, final Action1 onError) { + Observer observer = new Observer() { + @Override + public void onCompleted() {} + + @Override + public void onError(Throwable e) { + onError.call(e); + } + + @Override + public void onNext(T args) { + onNext.call(args); + } + + }; + + + return create(OperationDoOnEach.doOnEach(this, observer)); + } + + /** + * Invokes an action for each item emitted by an Observable. + * + * @param onNext the action to invoke for each item in the source sequence + * @param onError the action to invoke when the source Observable calls + * onError + * @param onCompleted the action to invoke when the source Observable calls + * onCompleted + * @return the source sequence with the side-effecting behavior applied + * @see RxJava Wiki: doOnEach() + * @see MSDN: Observable.Do + */ + public Observable doOnEach(final Action1 onNext, final Action1 onError, final Action0 onCompleted) { + Observer observer = new Observer() { + @Override + public void onCompleted() { + onCompleted.call(); + } + + @Override + public void onError(Throwable e) { + onError.call(e); + } + + @Override + public void onNext(T args) { + onNext.call(args); + } + + }; + + + return create(OperationDoOnEach.doOnEach(this, observer)); + } + + /** + * Whether a given {@link Function} is an internal implementation inside + * rx.* packages or not. + *

+ * For why this is being used see + * https://github.com/Netflix/RxJava/issues/216 for discussion on + * "Guideline 6.4: Protect calls to user code from within an operator" + * + * Note: If strong reasons for not depending on package names comes up then + * the implementation of this method can change to looking for a marker + * interface. * * @param o - * @return {@code true} if the given function is an internal implementation, and {@code false} otherwise. + * @return {@code true} if the given function is an internal implementation, + * and {@code false} otherwise. */ private boolean isInternalImplementation(Object o) { if (o == null) { return true; } // prevent double-wrapping (yeah it happens) - if (o instanceof SafeObserver) + if (o instanceof SafeObserver) { return true; - // we treat the following package as "internal" and don't wrap it - Package p = o.getClass().getPackage(); // it can be null - return p != null && p.getName().startsWith("rx.operators"); + } + + Class clazz = o.getClass(); + if (internalClassMap.containsKey(clazz)) { + //don't need to do reflection + return internalClassMap.get(clazz); + } else { + // we treat the following package as "internal" and don't wrap it + Package p = o.getClass().getPackage(); // it can be null + Boolean isInternal = (p != null && p.getName().startsWith("rx.operators")); + internalClassMap.put(clazz, isInternal); + return isInternal; + } } + /** + * Creates a pattern that matches when both Observable sequences have an + * available item. + *

+ * + * + * @param right Observable sequence to match with the left sequence + * @return Pattern object that matches when both Observable sequences have + * an available item + * @throws NullPointerException if right is null + * @see RxJava Wiki: and() + * @see MSDN: Observable.And + */ + public Pattern2 and(Observable right) { + return OperationJoinPatterns.and(this, right); + } + + /** + * Matches when the Observable sequence has an available item and + * projects the item by invoking the selector function. + *

+ * + * + * @param selector Selector that will be invoked for elements in the source + * sequence + * @return Plan that produces the projected results, to be fed (with other + * plans) to the When operator + * @throws NullPointerException if selector is null + * @see RxJava Wiki: then() + * @see MSDN: Observable.Then + */ + public Plan0 then(Func1 selector) { + return OperationJoinPatterns.then(this, selector); + } + + /** + * Joins together the results from several patterns. + *

+ * + * + * @param plans a series of plans created by use of the Then operator on + * patterns + * @return an Observable sequence with the results from matching several + * patterns + * @throws NullPointerException if plans is null + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + public static Observable when(Plan0... plans) { + return create(OperationJoinPatterns.when(plans)); + } + + /** + * Joins together the results from several patterns. + *

+ * + * + * @param plans a series of plans created by use of the Then operator on + * patterns + * @return an Observable sequence with the results from matching several + * patterns + * @throws NullPointerException if plans is null + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + public static Observable when(Iterable> plans) { + if (plans == null) { + throw new NullPointerException("plans"); + } + return create(OperationJoinPatterns.when(plans)); + } + + /** + * Joins the results from a pattern. + *

+ * + * + * @param p1 the plan to join + * @return an Observable sequence with the results from matching a pattern + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + @SuppressWarnings("unchecked") + public static Observable when(Plan0 p1) { + return create(OperationJoinPatterns.when(p1)); + } + + /** + * Joins together the results from several patterns. + *

+ * + * + * @param p1 a plan + * @param p2 a plan + * @return an Observable sequence with the results from matching several + * patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + @SuppressWarnings("unchecked") + public static Observable when(Plan0 p1, Plan0 p2) { + return create(OperationJoinPatterns.when(p1, p2)); + } + + /** + * Joins together the results from several patterns. + *

+ * + * + * @param p1 a plan + * @param p2 a plan + * @param p3 a plan + * @return an Observable sequence with the results from matching several + * patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + @SuppressWarnings("unchecked") + public static Observable when(Plan0 p1, Plan0 p2, Plan0 p3) { + return create(OperationJoinPatterns.when(p1, p2, p3)); + } + + /** + * Joins together the results from several patterns. + *

+ * + * + * @param p1 a plan + * @param p2 a plan + * @param p3 a plan + * @param p4 a plan + * @return an Observable sequence with the results from matching several + * patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + @SuppressWarnings("unchecked") + public static Observable when(Plan0 p1, Plan0 p2, Plan0 p3, Plan0 p4) { + return create(OperationJoinPatterns.when(p1, p2, p3, p4)); + } + + /** + * Joins together the results from several patterns. + *

+ * + * + * @param p1 a plan + * @param p2 a plan + * @param p3 a plan + * @param p4 a plan + * @param p5 a plan + * @return an Observable sequence with the results from matching several + * patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + @SuppressWarnings("unchecked") + public static Observable when(Plan0 p1, Plan0 p2, Plan0 p3, Plan0 p4, Plan0 p5) { + return create(OperationJoinPatterns.when(p1, p2, p3, p4, p5)); + } + + /** + * Joins together the results from several patterns. + *

+ * + * + * @param p1 a plan + * @param p2 a plan + * @param p3 a plan + * @param p4 a plan + * @param p5 a plan + * @param p6 a plan + * @return an Observable sequence with the results from matching several + * patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + @SuppressWarnings("unchecked") + public static Observable when(Plan0 p1, Plan0 p2, Plan0 p3, Plan0 p4, Plan0 p5, Plan0 p6) { + return create(OperationJoinPatterns.when(p1, p2, p3, p4, p5, p6)); + } + + /** + * Joins together the results from several patterns. + *

+ * + * + * @param p1 a plan + * @param p2 a plan + * @param p3 a plan + * @param p4 a plan + * @param p5 a plan + * @param p6 a plan + * @param p7 a plan + * @return an Observable sequence with the results from matching several + * patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + @SuppressWarnings("unchecked") + public static Observable when(Plan0 p1, Plan0 p2, Plan0 p3, Plan0 p4, Plan0 p5, Plan0 p6, Plan0 p7) { + return create(OperationJoinPatterns.when(p1, p2, p3, p4, p5, p6, p7)); + } + + /** + * Joins together the results from several patterns. + *

+ * + * + * @param p1 a plan + * @param p2 a plan + * @param p3 a plan + * @param p4 a plan + * @param p5 a plan + * @param p6 a plan + * @param p7 a plan + * @param p8 a plan + * @return an Observable sequence with the results from matching several + * patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + @SuppressWarnings("unchecked") + public static Observable when(Plan0 p1, Plan0 p2, Plan0 p3, Plan0 p4, Plan0 p5, Plan0 p6, Plan0 p7, Plan0 p8) { + return create(OperationJoinPatterns.when(p1, p2, p3, p4, p5, p6, p7, p8)); + } + + /** + * Joins together the results from several patterns. + *

+ * + * + * @param p1 a plan + * @param p2 a plan + * @param p3 a plan + * @param p4 a plan + * @param p5 a plan + * @param p6 a plan + * @param p7 a plan + * @param p8 a plan + * @param p9 a plan + * @return an Observable sequence with the results from matching several + * patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + @SuppressWarnings("unchecked") + public static Observable when(Plan0 p1, Plan0 p2, Plan0 p3, Plan0 p4, Plan0 p5, Plan0 p6, Plan0 p7, Plan0 p8, Plan0 p9) { + return create(OperationJoinPatterns.when(p1, p2, p3, p4, p5, p6, p7, p8, p9)); + } + + /** + * Correlates the elements of two sequences based on overlapping durations. + *

+ * + * + * @param right the right observable sequence to join elements for + * @param leftDurationSelector a function to select the duration of each + * element of this observable sequence, used to + * determine overlap + * @param rightDurationSelector a function to select the duration of each + * element of the right observable sequence, + * used to determine overlap + * @param resultSelector a function invoked to compute a result element + * for any two overlapping elements of the left and + * right observable sequences + * @return an observable sequence that contains result elements computed + * from source elements that have an overlapping duration + * @see RxJava Wiki: join() + * @see MSDN: Observable.Join + */ + public Observable join(Observable right, Func1> leftDurationSelector, + Func1> rightDurationSelector, + Func2 resultSelector) { + return create(new OperationJoin(this, right, leftDurationSelector, rightDurationSelector, resultSelector)); + } + + /** + * Return an Observable that emits a single HashMap containing all items + * emitted by the source Observable, mapped by the keys returned by the + * {@code keySelector} function. + *

+ * + * + * If a source item maps to the same key, the HashMap will contain the + * latest of those items. + * + * @param keySelector the function that extracts the key from the source + * items to be used as keys in the HashMap + * @return an Observable that emits a single HashMap containing the mapped + * values of the source Observable + * @see RxJava Wiki: toMap() + * @see MSDN: Observable.ToDictionary + */ + public Observable> toMap(Func1 keySelector) { + return create(OperationToMap.toMap(this, keySelector)); + } + + /** + * Return an Observable that emits a single HashMap containing elements with + * key and value extracted from the values emitted by the source Observable. + *

+ * + *

+ * If a source item maps to the same key, the HashMap will contain the + * latest of those items. + * + * @param keySelector the function that extracts the key from the source + * items to be used as key in the HashMap + * @param valueSelector the function that extracts the value from the source + * items to be used as value in the HashMap + * @return an Observable that emits a single HashMap containing the mapped + * values of the source Observable + * @see RxJava Wiki: toMap() + * @see MSDN: Observable.ToDictionary + */ + public Observable> toMap(Func1 keySelector, Func1 valueSelector) { + return create(OperationToMap.toMap(this, keySelector, valueSelector)); + } + + /** + * Return an Observable that emits a single Map, returned by the + * mapFactory function, containing key and value extracted from + * the values emitted by the source Observable. + *

+ * + * + * @param keySelector the function that extracts the key from the source + * items to be used as key in the Map + * @param valueSelector the function that extracts the value from the source + * items to be used as value in the Map + * @param mapFactory the function that returns an Map instance to be used + * @return an Observable that emits a single Map containing the mapped + * values of the source Observable + * @see RxJava Wiki: toMap() + */ + public Observable> toMap(Func1 keySelector, Func1 valueSelector, Func0> mapFactory) { + return create(OperationToMap.toMap(this, keySelector, valueSelector, mapFactory)); + } + + /** + * Return an Observable that emits a single HashMap containing an ArrayList + * of elements, emitted by the source Observable and keyed by the + * keySelector function. + *

+ * + * + * @param keySelector the function that extracts the key from the source + * items to be used as key in the HashMap + * @return an Observable that emits a single HashMap containing an ArrayList + * of elements mapped from the source Observable + * @see RxJava Wiki: toMultiMap() + * @see MSDN: Observable.ToLookup + */ + public Observable>> toMultimap(Func1 keySelector) { + return create(OperationToMultimap.toMultimap(this, keySelector)); + } + + /** + * Return an Observable that emits a single HashMap containing an ArrayList + * of values, extracted by the valueSelector function, emitted + * by the source Observable and keyed by the keySelector + * function. + *

+ * + * + * @param keySelector the function that extracts the key from the source + * items to be used as key in the HashMap + * @param valueSelector the function that extracts the value from the source + * items to be used as value in the Map + * @return an Observable that emits a single HashMap containing an ArrayList + * of elements mapped from the source Observable + * @see RxJava Wiki: toMultiMap() + * @see MSDN: Observable.ToLookup + */ + public Observable>> toMultimap(Func1 keySelector, Func1 valueSelector) { + return create(OperationToMultimap.toMultimap(this, keySelector, valueSelector)); + } + + /** + * Return an Observable that emits a single Map, returned by the + * mapFactory function, containing an ArrayList of values, + * extracted by the valueSelector function, emitted by the + * source Observable and keyed by the keySelector function. + *

+ * + * + * @param keySelector the function that extracts the key from the source + * items to be used as key in the Map + * @param valueSelector the function that extracts the value from the source + * items to be used as value in the Map + * @param mapFactory the function that returns an Map instance to be used + * @return an Observable that emits a single Map containing the list of + * mapped values of the source observable. + * @see RxJava Wiki: toMultiMap() + */ + public Observable>> toMultimap(Func1 keySelector, Func1 valueSelector, Func0>> mapFactory) { + return create(OperationToMultimap.toMultimap(this, keySelector, valueSelector, mapFactory)); + } + + /** + * Return an Observable that emits a single Map, returned by the + * mapFactory function, containing a custom collection of + * values, extracted by the valueSelector function, emitted by + * the source Observable and keyed by the keySelector function. + *

+ * + * + * @param keySelector the function that extracts the key from the source + * items to be used as key in the Map + * @param valueSelector the function that extracts the value from the source + * items to be used as value in the Map + * @param mapFactory the function that returns an Map instance to be used + * @param collectionFactory the function that returns a Collection instance + * for a particular key to be used in the Map + * @return an Observable that emits a single Map containing the collection + * of mapped values of the source Observable. + * @see RxJava Wiki: toMultiMap() + */ + public Observable>> toMultimap(Func1 keySelector, Func1 valueSelector, Func0>> mapFactory, Func1> collectionFactory) { + return create(OperationToMultimap.toMultimap(this, keySelector, valueSelector, mapFactory, collectionFactory)); + } } diff --git a/rxjava-core/src/main/java/rx/Observer.java b/rxjava-core/src/main/java/rx/Observer.java index c18b3d4fde..3d35066ba4 100644 --- a/rxjava-core/src/main/java/rx/Observer.java +++ b/rxjava-core/src/main/java/rx/Observer.java @@ -1,12 +1,12 @@ /** * Copyright 2013 Netflix, Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,12 +18,12 @@ /** * Provides a mechanism for receiving push-based notifications. *

- * After an Observer calls an {@link Observable}'s Observable.subscribe method, the {@link Observable} - * calls the Observer's onNext method to provide notifications. A well-behaved {@link Observable} will + * After an Observer calls an {@link Observable}'s Observable.subscribe method, the {@link Observable} calls the Observer's onNext method to provide notifications. A + * well-behaved {@link Observable} will * call an Observer's onCompleted closure exactly once or the Observer's onError closure exactly once. *

* For more information see the RxJava Wiki - * + * * @param */ public interface Observer { @@ -39,7 +39,7 @@ public interface Observer { * Notifies the Observer that the {@link Observable} has experienced an error condition. *

* If the {@link Observable} calls this closure, it will not thereafter call onNext or onCompleted. - * + * * @param e */ public void onError(Throwable e); @@ -50,7 +50,7 @@ public interface Observer { * The {@link Observable} calls this closure 1 or more times, unless it calls onError in which case this closure may never be called. *

* The {@link Observable} will not call this closure again after it calls either onCompleted or onError. - * + * * @param args */ public void onNext(T args); diff --git a/rxjava-core/src/main/java/rx/Scheduler.java b/rxjava-core/src/main/java/rx/Scheduler.java index 06a708d4d4..b01872f226 100644 --- a/rxjava-core/src/main/java/rx/Scheduler.java +++ b/rxjava-core/src/main/java/rx/Scheduler.java @@ -15,21 +15,15 @@ */ package rx; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - import java.util.Date; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mockito; - -import rx.concurrency.TestScheduler; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.MultipleAssignmentSubscription; import rx.subscriptions.Subscriptions; import rx.util.functions.Action0; -import rx.util.functions.Func1; +import rx.util.functions.Action1; import rx.util.functions.Func2; /** @@ -83,23 +77,23 @@ public abstract class Scheduler { * Schedules a cancelable action to be executed periodically. * This default implementation schedules recursively and waits for actions to complete (instead of potentially executing * long-running actions concurrently). Each scheduler that can do periodic scheduling in a better way should override this. - * - * @param state + * + * @param state * State to pass into the action. - * @param action + * @param action * The action to execute periodically. - * @param initialDelay + * @param initialDelay * Time to wait before executing the action for the first time. - * @param period + * @param period * The time interval to wait each time in between executing the action. - * @param unit + * @param unit * The time unit the interval above is given in. * @return A subscription to be able to unsubscribe from action. */ public Subscription schedulePeriodically(T state, final Func2 action, long initialDelay, long period, TimeUnit unit) { final long periodInNanos = unit.toNanos(period); final AtomicBoolean complete = new AtomicBoolean(); - + final Func2 recursiveAction = new Func2() { @Override public Subscription call(Scheduler scheduler, T state0) { @@ -128,7 +122,7 @@ public void call() { } }); } - + /** * Schedules a cancelable action to be executed at dueTime. * @@ -150,6 +144,40 @@ public Subscription schedule(T state, Func2 action) { + final CompositeSubscription parentSubscription = new CompositeSubscription(); + final MultipleAssignmentSubscription childSubscription = new MultipleAssignmentSubscription(); + parentSubscription.add(childSubscription); + + final Func2 parentAction = new Func2() { + + @Override + public Subscription call(final Scheduler scheduler, final Func2 parentAction) { + action.call(new Action0() { + + @Override + public void call() { + if (!parentSubscription.isUnsubscribed()) { + childSubscription.setSubscription(scheduler.schedule(parentAction, parentAction)); + } + } + + }); + return childSubscription; + } + }; + + parentSubscription.add(schedule(parentAction, parentAction)); + + return parentSubscription; + } /** * Schedules an action to be executed. @@ -187,17 +215,16 @@ public Subscription call(Scheduler scheduler, Void state) { }, delayTime, unit); } - /** * Schedules an action to be executed periodically. * - * @param action + * @param action * The action to execute periodically. - * @param initialDelay + * @param initialDelay * Time to wait before executing the action for the first time. - * @param period + * @param period * The time interval to wait each time in between executing the action. - * @param unit + * @param unit * The time unit the interval above is given in. * @return A subscription to be able to unsubscribe from action. */ @@ -212,49 +239,20 @@ public Subscription call(Scheduler scheduler, Void state) { } /** - * Returns the scheduler's notion of current absolute time in milliseconds. + * @return the scheduler's notion of current absolute time in milliseconds. */ public long now() { return System.currentTimeMillis(); } - public static class UnitTest { - @SuppressWarnings("unchecked") // mocking is unchecked, unfortunately - @Test - public void testPeriodicScheduling() { - final Func1 calledOp = mock(Func1.class); - - final TestScheduler scheduler = new TestScheduler(); - Subscription subscription = scheduler.schedulePeriodically(new Action0() { - @Override public void call() { - System.out.println(scheduler.now()); - calledOp.call(scheduler.now()); - } - }, 1, 2, TimeUnit.SECONDS); - - verify(calledOp, never()).call(anyLong()); - - InOrder inOrder = Mockito.inOrder(calledOp); - - scheduler.advanceTimeBy(999L, TimeUnit.MILLISECONDS); - inOrder.verify(calledOp, never()).call(anyLong()); - - scheduler.advanceTimeBy(1L, TimeUnit.MILLISECONDS); - inOrder.verify(calledOp, times(1)).call(1000L); - - scheduler.advanceTimeBy(1999L, TimeUnit.MILLISECONDS); - inOrder.verify(calledOp, never()).call(3000L); - - scheduler.advanceTimeBy(1L, TimeUnit.MILLISECONDS); - inOrder.verify(calledOp, times(1)).call(3000L); - - scheduler.advanceTimeBy(5L, TimeUnit.SECONDS); - inOrder.verify(calledOp, times(1)).call(5000L); - inOrder.verify(calledOp, times(1)).call(7000L); - - subscription.unsubscribe(); - scheduler.advanceTimeBy(11L, TimeUnit.SECONDS); - inOrder.verify(calledOp, never()).call(anyLong()); - } + /** + * Parallelism available to a Scheduler. + *

+ * This defaults to {@code Runtime.getRuntime().availableProcessors()} but can be overridden for use cases such as scheduling work on a computer cluster. + * + * @return the scheduler's available degree of parallelism. + */ + public int degreeOfParallelism() { + return Runtime.getRuntime().availableProcessors(); } } diff --git a/rxjava-core/src/main/java/rx/concurrency/CurrentThreadScheduler.java b/rxjava-core/src/main/java/rx/concurrency/CurrentThreadScheduler.java index 455b49d04c..9bf1f6ad91 100644 --- a/rxjava-core/src/main/java/rx/concurrency/CurrentThreadScheduler.java +++ b/rxjava-core/src/main/java/rx/concurrency/CurrentThreadScheduler.java @@ -1,12 +1,12 @@ /** * Copyright 2013 Netflix, Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,18 +15,12 @@ */ package rx.concurrency; -import static org.mockito.Mockito.*; - import java.util.PriorityQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Test; -import org.mockito.InOrder; - import rx.Scheduler; import rx.Subscription; -import rx.util.functions.Action0; import rx.util.functions.Func2; /** @@ -41,7 +35,7 @@ public static CurrentThreadScheduler getInstance() { private static final ThreadLocal> QUEUE = new ThreadLocal>(); - private CurrentThreadScheduler() { + /* package accessible for unit tests */CurrentThreadScheduler() { } private final AtomicInteger counter = new AtomicInteger(0); @@ -102,126 +96,4 @@ public int compareTo(TimedAction that) { return result; } } - - public static class UnitTest { - - @Test - public void testNestedActions() { - final CurrentThreadScheduler scheduler = new CurrentThreadScheduler(); - - final Action0 firstStepStart = mock(Action0.class); - final Action0 firstStepEnd = mock(Action0.class); - - final Action0 secondStepStart = mock(Action0.class); - final Action0 secondStepEnd = mock(Action0.class); - - final Action0 thirdStepStart = mock(Action0.class); - final Action0 thirdStepEnd = mock(Action0.class); - - final Action0 firstAction = new Action0() { - @Override - public void call() { - firstStepStart.call(); - firstStepEnd.call(); - } - }; - final Action0 secondAction = new Action0() { - @Override - public void call() { - secondStepStart.call(); - scheduler.schedule(firstAction); - secondStepEnd.call(); - - } - }; - final Action0 thirdAction = new Action0() { - @Override - public void call() { - thirdStepStart.call(); - scheduler.schedule(secondAction); - thirdStepEnd.call(); - } - }; - - InOrder inOrder = inOrder(firstStepStart, firstStepEnd, secondStepStart, secondStepEnd, thirdStepStart, thirdStepEnd); - - scheduler.schedule(thirdAction); - - inOrder.verify(thirdStepStart, times(1)).call(); - inOrder.verify(thirdStepEnd, times(1)).call(); - inOrder.verify(secondStepStart, times(1)).call(); - inOrder.verify(secondStepEnd, times(1)).call(); - inOrder.verify(firstStepStart, times(1)).call(); - inOrder.verify(firstStepEnd, times(1)).call(); - } - - @Test - public void testSequenceOfActions() { - final CurrentThreadScheduler scheduler = new CurrentThreadScheduler(); - - final Action0 first = mock(Action0.class); - final Action0 second = mock(Action0.class); - - scheduler.schedule(first); - scheduler.schedule(second); - - verify(first, times(1)).call(); - verify(second, times(1)).call(); - - } - - @Test - public void testSequenceOfDelayedActions() { - final CurrentThreadScheduler scheduler = new CurrentThreadScheduler(); - - final Action0 first = mock(Action0.class); - final Action0 second = mock(Action0.class); - - scheduler.schedule(new Action0() { - @Override - public void call() { - scheduler.schedule(first, 30, TimeUnit.MILLISECONDS); - scheduler.schedule(second, 10, TimeUnit.MILLISECONDS); - } - }); - - InOrder inOrder = inOrder(first, second); - - inOrder.verify(second, times(1)).call(); - inOrder.verify(first, times(1)).call(); - - - } - - @Test - public void testMixOfDelayedAndNonDelayedActions() { - final CurrentThreadScheduler scheduler = new CurrentThreadScheduler(); - - final Action0 first = mock(Action0.class); - final Action0 second = mock(Action0.class); - final Action0 third = mock(Action0.class); - final Action0 fourth = mock(Action0.class); - - scheduler.schedule(new Action0() { - @Override - public void call() { - scheduler.schedule(first); - scheduler.schedule(second, 300, TimeUnit.MILLISECONDS); - scheduler.schedule(third, 100, TimeUnit.MILLISECONDS); - scheduler.schedule(fourth); - } - }); - - InOrder inOrder = inOrder(first, second, third, fourth); - - inOrder.verify(first, times(1)).call(); - inOrder.verify(fourth, times(1)).call(); - inOrder.verify(third, times(1)).call(); - inOrder.verify(second, times(1)).call(); - - - } - - } - } diff --git a/rxjava-core/src/main/java/rx/concurrency/DelayedScheduler.java b/rxjava-core/src/main/java/rx/concurrency/DelayedScheduler.java new file mode 100644 index 0000000000..86aff00da0 --- /dev/null +++ b/rxjava-core/src/main/java/rx/concurrency/DelayedScheduler.java @@ -0,0 +1,114 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.concurrency; + +import static org.mockito.Mockito.*; +import static org.mockito.MockitoAnnotations.initMocks; + +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; + +import rx.Scheduler; +import rx.Subscription; +import rx.util.functions.Action0; +import rx.util.functions.Func2; + +/** + * Scheduler that delays the underlying scheduler by a fixed time delay. + */ +public class DelayedScheduler extends Scheduler { + private final Scheduler underlying; + private final long delay; + private final TimeUnit unit; + + public DelayedScheduler(Scheduler underlying, long delay, TimeUnit unit) { + this.underlying = underlying; + this.delay = delay; + this.unit = unit; + } + + @Override + public Subscription schedule(T state, Func2 action) { + return underlying.schedule(state, action, delay, unit); + } + + @Override + public Subscription schedule(T state, Func2 action, long delay, TimeUnit unit) { + long newDelay = unit.toNanos(delay) + this.unit.toNanos(this.delay); + return underlying.schedule(state, action, newDelay, TimeUnit.NANOSECONDS); + } + + public static class UnitTest { + @Mock + Action0 action; + + private TestScheduler scheduler = new TestScheduler(); + + @Before + public void before() { + initMocks(this); + } + + @Test + public void testNotDelayingAnAction() { + Scheduler delayed = new DelayedScheduler(scheduler, 0, TimeUnit.SECONDS); + delayed.schedule(action); + delayed.schedule(action, 1L, TimeUnit.SECONDS); + + InOrder inOrder = inOrder(action); + + scheduler.triggerActions(); + inOrder.verify(action, times(1)).call(); + + scheduler.advanceTimeTo(999L, TimeUnit.MILLISECONDS); + inOrder.verify(action, never()).call(); + + scheduler.advanceTimeTo(1L, TimeUnit.SECONDS); + inOrder.verify(action, times(1)).call(); + + scheduler.advanceTimeTo(5L, TimeUnit.SECONDS); + inOrder.verify(action, never()).call(); + } + + @Test + public void testdelayingAnAction() { + Scheduler delayed = new DelayedScheduler(scheduler, 500, TimeUnit.MILLISECONDS); + delayed.schedule(action); + delayed.schedule(action, 1L, TimeUnit.SECONDS); + + InOrder inOrder = inOrder(action); + + scheduler.advanceTimeTo(499L, TimeUnit.MILLISECONDS); + inOrder.verify(action, never()).call(); + + scheduler.advanceTimeTo(500L, TimeUnit.MILLISECONDS); + inOrder.verify(action, times(1)).call(); + + scheduler.advanceTimeTo(1499L, TimeUnit.MILLISECONDS); + inOrder.verify(action, never()).call(); + + scheduler.advanceTimeTo(1500L, TimeUnit.MILLISECONDS); + inOrder.verify(action, times(1)).call(); + + scheduler.advanceTimeTo(5L, TimeUnit.SECONDS); + inOrder.verify(action, never()).call(); + } + } +} diff --git a/rxjava-core/src/main/java/rx/concurrency/ExecutorScheduler.java b/rxjava-core/src/main/java/rx/concurrency/ExecutorScheduler.java index a1fd36179e..1e35735c67 100644 --- a/rxjava-core/src/main/java/rx/concurrency/ExecutorScheduler.java +++ b/rxjava-core/src/main/java/rx/concurrency/ExecutorScheduler.java @@ -48,7 +48,7 @@ public ExecutorScheduler(ScheduledExecutorService executor) { public Subscription schedulePeriodically(final T state, final Func2 action, long initialDelay, long period, TimeUnit unit) { if (executor instanceof ScheduledExecutorService) { final CompositeSubscription subscriptions = new CompositeSubscription(); - + ScheduledFuture f = ((ScheduledExecutorService) executor).scheduleAtFixedRate(new Runnable() { @Override public void run() { @@ -56,15 +56,15 @@ public void run() { subscriptions.add(s); } }, initialDelay, period, unit); - - subscriptions.add(Subscriptions.create(f)); + + subscriptions.add(Subscriptions.from(f)); return subscriptions; - + } else { return super.schedulePeriodically(state, action, initialDelay, period, unit); } } - + @Override public Subscription schedule(final T state, final Func2 action, long delayTime, TimeUnit unit) { final DiscardableAction discardableAction = new DiscardableAction(state, action); @@ -84,7 +84,7 @@ public void run() { } }, delayTime, unit); // add the ScheduledFuture as a subscription so we can cancel the scheduled action if an unsubscribe happens - subscription.add(Subscriptions.create(f)); + subscription.add(Subscriptions.from(f)); } else { // we are not a ScheduledExecutorService so can't directly schedule if (delayTime == 0) { @@ -106,7 +106,7 @@ public void run() { } }, delayTime, unit); // add the ScheduledFuture as a subscription so we can cancel the scheduled action if an unsubscribe happens - subscription.add(Subscriptions.create(f)); + subscription.add(Subscriptions.from(f)); } } return subscription; @@ -134,7 +134,7 @@ public void run() { // we are an ExecutorService so get a Future back that supports unsubscribe Future f = ((ExecutorService) executor).submit(r); // add the Future as a subscription so we can cancel the scheduled action if an unsubscribe happens - subscription.add(Subscriptions.create(f)); + subscription.add(Subscriptions.from(f)); } else { // we are the lowest common denominator so can't unsubscribe once we execute executor.execute(r); diff --git a/rxjava-core/src/main/java/rx/concurrency/ImmediateScheduler.java b/rxjava-core/src/main/java/rx/concurrency/ImmediateScheduler.java index f3f4353bb7..c5a4cef9d4 100644 --- a/rxjava-core/src/main/java/rx/concurrency/ImmediateScheduler.java +++ b/rxjava-core/src/main/java/rx/concurrency/ImmediateScheduler.java @@ -15,16 +15,10 @@ */ package rx.concurrency; -import static org.mockito.Mockito.*; - import java.util.concurrent.TimeUnit; -import org.junit.Test; -import org.mockito.InOrder; - import rx.Scheduler; import rx.Subscription; -import rx.util.functions.Action0; import rx.util.functions.Func2; /** @@ -37,7 +31,7 @@ public static ImmediateScheduler getInstance() { return INSTANCE; } - private ImmediateScheduler() { + /* package accessible for unit tests */ImmediateScheduler() { } @Override @@ -52,59 +46,4 @@ public Subscription schedule(T state, Func2(action, this, execTime)); } - - public static class UnitTest { - - @Test - public void testNestedActions() { - final ImmediateScheduler scheduler = new ImmediateScheduler(); - - final Action0 firstStepStart = mock(Action0.class); - final Action0 firstStepEnd = mock(Action0.class); - - final Action0 secondStepStart = mock(Action0.class); - final Action0 secondStepEnd = mock(Action0.class); - - final Action0 thirdStepStart = mock(Action0.class); - final Action0 thirdStepEnd = mock(Action0.class); - - final Action0 firstAction = new Action0() { - @Override - public void call() { - firstStepStart.call(); - firstStepEnd.call(); - } - }; - final Action0 secondAction = new Action0() { - @Override - public void call() { - secondStepStart.call(); - scheduler.schedule(firstAction); - secondStepEnd.call(); - - } - }; - final Action0 thirdAction = new Action0() { - @Override - public void call() { - thirdStepStart.call(); - scheduler.schedule(secondAction); - thirdStepEnd.call(); - } - }; - - InOrder inOrder = inOrder(firstStepStart, firstStepEnd, secondStepStart, secondStepEnd, thirdStepStart, thirdStepEnd); - - scheduler.schedule(thirdAction); - - inOrder.verify(thirdStepStart, times(1)).call(); - inOrder.verify(secondStepStart, times(1)).call(); - inOrder.verify(firstStepStart, times(1)).call(); - inOrder.verify(firstStepEnd, times(1)).call(); - inOrder.verify(secondStepEnd, times(1)).call(); - inOrder.verify(thirdStepEnd, times(1)).call(); - } - - } - } diff --git a/rxjava-core/src/main/java/rx/concurrency/NewThreadScheduler.java b/rxjava-core/src/main/java/rx/concurrency/NewThreadScheduler.java index d8e178bc0d..036ba62127 100644 --- a/rxjava-core/src/main/java/rx/concurrency/NewThreadScheduler.java +++ b/rxjava-core/src/main/java/rx/concurrency/NewThreadScheduler.java @@ -15,12 +15,15 @@ */ package rx.concurrency; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; import rx.Scheduler; import rx.Subscription; -import rx.operators.SafeObservableSubscription; import rx.subscriptions.CompositeSubscription; import rx.subscriptions.Subscriptions; import rx.util.functions.Func2; @@ -29,27 +32,81 @@ * Schedules work on a new thread. */ public class NewThreadScheduler extends Scheduler { - private static final NewThreadScheduler INSTANCE = new NewThreadScheduler(); + + private final static NewThreadScheduler INSTANCE = new NewThreadScheduler(); + private final static AtomicLong count = new AtomicLong(); public static NewThreadScheduler getInstance() { return INSTANCE; } - @Override - public Subscription schedule(final T state, final Func2 action) { - final SafeObservableSubscription subscription = new SafeObservableSubscription(); - final Scheduler _scheduler = this; + private NewThreadScheduler() { - Thread t = new Thread(new Runnable() { - @Override - public void run() { - subscription.wrap(action.call(_scheduler, state)); - } - }, "RxNewThreadScheduler"); + } - t.start(); + private static class EventLoopScheduler extends Scheduler { + private final ExecutorService executor; - return subscription; + private EventLoopScheduler() { + executor = Executors.newFixedThreadPool(1, new ThreadFactory() { + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "RxNewThreadScheduler-" + count.incrementAndGet()); + } + }); + } + + @Override + public Subscription schedule(T state, Func2 action) { + final DiscardableAction discardableAction = new DiscardableAction(state, action); + // all subscriptions that may need to be unsubscribed + final CompositeSubscription subscription = new CompositeSubscription(discardableAction); + + final Scheduler _scheduler = this; + subscription.add(Subscriptions.from(executor.submit(new Runnable() { + + @Override + public void run() { + Subscription s = discardableAction.call(_scheduler); + subscription.add(s); + } + }))); + + return subscription; + } + + @Override + public Subscription schedule(final T state, final Func2 action, final long delayTime, final TimeUnit unit) { + // we will use the system scheduler since it doesn't make sense to launch a new Thread and then sleep + // we will instead schedule the event then launch the thread after the delay has passed + final Scheduler _scheduler = this; + final CompositeSubscription subscription = new CompositeSubscription(); + ScheduledFuture f = GenericScheduledExecutorService.getInstance().schedule(new Runnable() { + + @Override + public void run() { + if (!subscription.isUnsubscribed()) { + // when the delay has passed we now do the work on the actual scheduler + Subscription s = _scheduler.schedule(state, action); + // add the subscription to the CompositeSubscription so it is unsubscribed + subscription.add(s); + } + } + }, delayTime, unit); + + // add the ScheduledFuture as a subscription so we can cancel the scheduled action if an unsubscribe happens + subscription.add(Subscriptions.from(f)); + + return subscription; + } + + } + + @Override + public Subscription schedule(T state, Func2 action) { + EventLoopScheduler s = new EventLoopScheduler(); + return s.schedule(state, action); } @Override @@ -72,7 +129,7 @@ public void run() { }, delay, unit); // add the ScheduledFuture as a subscription so we can cancel the scheduled action if an unsubscribe happens - subscription.add(Subscriptions.create(f)); + subscription.add(Subscriptions.from(f)); return subscription; } diff --git a/rxjava-core/src/main/java/rx/concurrency/SleepingAction.java b/rxjava-core/src/main/java/rx/concurrency/SleepingAction.java index eea75639b8..925403e846 100644 --- a/rxjava-core/src/main/java/rx/concurrency/SleepingAction.java +++ b/rxjava-core/src/main/java/rx/concurrency/SleepingAction.java @@ -1,12 +1,12 @@ /** * Copyright 2013 Netflix, Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -30,16 +30,14 @@ public SleepingAction(Func2 scheduler.now()) { long delay = execTime - scheduler.now(); - if (delay> 0) { + if (delay > 0) { try { Thread.sleep(delay); - } - catch (InterruptedException e) { + } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } diff --git a/rxjava-core/src/main/java/rx/joins/ActivePlan0.java b/rxjava-core/src/main/java/rx/joins/ActivePlan0.java new file mode 100644 index 0000000000..d8690136b7 --- /dev/null +++ b/rxjava-core/src/main/java/rx/joins/ActivePlan0.java @@ -0,0 +1,37 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.joins; + +import java.util.HashMap; +import java.util.Map; + +/** + * Represents an activated plan. + */ +public abstract class ActivePlan0 { + protected final Map joinObservers = new HashMap(); + + public abstract void match(); + + protected void addJoinObserver(JoinObserver joinObserver) { + joinObservers.put(joinObserver, joinObserver); + } + protected void dequeue() { + for (JoinObserver jo : joinObservers.values()) { + jo.dequeue(); + } + } +} diff --git a/rxjava-core/src/main/java/rx/joins/ActivePlan1.java b/rxjava-core/src/main/java/rx/joins/ActivePlan1.java new file mode 100644 index 0000000000..65e50e9908 --- /dev/null +++ b/rxjava-core/src/main/java/rx/joins/ActivePlan1.java @@ -0,0 +1,49 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package rx.joins; + +import rx.Notification; +import rx.util.functions.Action0; +import rx.util.functions.Action1; + +/** + * Represents an active plan. + */ +public class ActivePlan1 extends ActivePlan0 { + private final Action1 onNext; + private final Action0 onCompleted; + private final JoinObserver1 first; + public ActivePlan1(JoinObserver1 first, Action1 onNext, Action0 onCompleted) { + this.onNext = onNext; + this.onCompleted = onCompleted; + this.first = first; + addJoinObserver(first); + } + + @Override + public void match() { + if (!first.queue().isEmpty()) { + Notification n1 = first.queue().peek(); + if (n1.isOnCompleted()) { + onCompleted.call(); + } else { + dequeue(); + onNext.call(n1.getValue()); + } + } + } + +} diff --git a/rxjava-core/src/main/java/rx/joins/ActivePlan2.java b/rxjava-core/src/main/java/rx/joins/ActivePlan2.java new file mode 100644 index 0000000000..3ae19714ce --- /dev/null +++ b/rxjava-core/src/main/java/rx/joins/ActivePlan2.java @@ -0,0 +1,54 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package rx.joins; + +import rx.Notification; +import rx.util.functions.Action0; +import rx.util.functions.Action2; + +/** + * Represents an active plan. + */ +public class ActivePlan2 extends ActivePlan0 { + private final Action2 onNext; + private final Action0 onCompleted; + private final JoinObserver1 first; + private final JoinObserver1 second; + public ActivePlan2(JoinObserver1 first, JoinObserver1 second, Action2 onNext, Action0 onCompleted) { + this.onNext = onNext; + this.onCompleted = onCompleted; + this.first = first; + this.second = second; + addJoinObserver(first); + addJoinObserver(second); + } + + @Override + public void match() { + if (!first.queue().isEmpty() && !second.queue().isEmpty()) { + Notification n1 = first.queue().peek(); + Notification n2 = second.queue().peek(); + + if (n1.isOnCompleted() || n2.isOnCompleted()) { + onCompleted.call(); + } else { + dequeue(); + onNext.call(n1.getValue(), n2.getValue()); + } + } + } + +} diff --git a/rxjava-core/src/main/java/rx/joins/ActivePlan3.java b/rxjava-core/src/main/java/rx/joins/ActivePlan3.java new file mode 100644 index 0000000000..cff23dd752 --- /dev/null +++ b/rxjava-core/src/main/java/rx/joins/ActivePlan3.java @@ -0,0 +1,64 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package rx.joins; + +import rx.Notification; +import rx.util.functions.Action0; +import rx.util.functions.Action3; + +/** + * Represents an active plan. + */ +public class ActivePlan3 extends ActivePlan0 { + private final Action3 onNext; + private final Action0 onCompleted; + private final JoinObserver1 first; + private final JoinObserver1 second; + private final JoinObserver1 third; + public ActivePlan3(JoinObserver1 first, + JoinObserver1 second, + JoinObserver1 third, + Action3 onNext, + Action0 onCompleted) { + this.onNext = onNext; + this.onCompleted = onCompleted; + this.first = first; + this.second = second; + this.third = third; + addJoinObserver(first); + addJoinObserver(second); + addJoinObserver(third); + } + + @Override + public void match() { + if (!first.queue().isEmpty() + && !second.queue().isEmpty() + && !third.queue().isEmpty()) { + Notification n1 = first.queue().peek(); + Notification n2 = second.queue().peek(); + Notification n3 = third.queue().peek(); + + if (n1.isOnCompleted() || n2.isOnCompleted() || n3.isOnCompleted()) { + onCompleted.call(); + } else { + dequeue(); + onNext.call(n1.getValue(), n2.getValue(), n3.getValue()); + } + } + } + +} diff --git a/rxjava-core/src/main/java/rx/joins/JoinObserver.java b/rxjava-core/src/main/java/rx/joins/JoinObserver.java new file mode 100644 index 0000000000..d191ed8958 --- /dev/null +++ b/rxjava-core/src/main/java/rx/joins/JoinObserver.java @@ -0,0 +1,26 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package rx.joins; + +import rx.Subscription; + +/** + * Base interface to manage joined observations. + */ +public interface JoinObserver extends Subscription { + void subscribe(Object gate); + void dequeue(); +} diff --git a/rxjava-core/src/main/java/rx/joins/JoinObserver1.java b/rxjava-core/src/main/java/rx/joins/JoinObserver1.java new file mode 100644 index 0000000000..873d3d1a7f --- /dev/null +++ b/rxjava-core/src/main/java/rx/joins/JoinObserver1.java @@ -0,0 +1,107 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.joins; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import rx.Notification; +import rx.Observable; +import rx.subscriptions.SingleAssignmentSubscription; +import rx.util.functions.Action1; + +/** + * Default implementation of a join observer. + */ +public final class JoinObserver1 extends ObserverBase> implements JoinObserver { + private Object gate; + private final Observable source; + private final Action1 onError; + private final List activePlans; + private final Queue> queue; + private final SingleAssignmentSubscription subscription; + private volatile boolean done; + + public JoinObserver1(Observable source, Action1 onError) { + this.source = source; + this.onError = onError; + queue = new LinkedList>(); + subscription = new SingleAssignmentSubscription(); + activePlans = new ArrayList(); + } + public Queue> queue() { + return queue; + } + public void addActivePlan(ActivePlan0 activePlan) { + activePlans.add(activePlan); + } + @Override + public void subscribe(Object gate) { + this.gate = gate; + subscription.set(source.materialize().subscribe(this)); + } + + @Override + public void dequeue() { + queue.remove(); + } + + @Override + protected void onNextCore(Notification args) { + synchronized (gate) { + if (!done) { + if (args.isOnError()) { + onError.call(args.getThrowable()); + return; + } + queue.add(args); + + // remark: activePlans might change while iterating + for (ActivePlan0 a : new ArrayList(activePlans)) { + a.match(); + } + } + } + } + + @Override + protected void onErrorCore(Throwable e) { + // not expected + } + + @Override + protected void onCompletedCore() { + // not expected or ignored + } + + + void removeActivePlan(ActivePlan0 activePlan) { + activePlans.remove(activePlan); + if (activePlans.isEmpty()) { + unsubscribe(); + } + } + + @Override + public void unsubscribe() { + if (!done) { + done = true; + subscription.unsubscribe(); + } + } + +} diff --git a/rxjava-core/src/main/java/rx/joins/ObserverBase.java b/rxjava-core/src/main/java/rx/joins/ObserverBase.java new file mode 100644 index 0000000000..f1144a8ad2 --- /dev/null +++ b/rxjava-core/src/main/java/rx/joins/ObserverBase.java @@ -0,0 +1,72 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.joins; + +import java.util.concurrent.atomic.AtomicBoolean; +import rx.Observer; + +/** + * Implements an observer that ensures proper event delivery + * semantics to its abstract onXxxxCore methods. + */ +public abstract class ObserverBase implements Observer { + private final AtomicBoolean completed = new AtomicBoolean(); + + @Override + public void onNext(T args) { + if (!completed.get()) { + onNextCore(args); + } + } + + @Override + public void onError(Throwable e) { + if (completed.compareAndSet(false, true)) { + onErrorCore(e); + } + } + + @Override + public void onCompleted() { + if (completed.compareAndSet(false, true)) { + onCompletedCore(); + } + } + /** + * Implement this method to react to the receival of a new element in the sequence. + */ + protected abstract void onNextCore(T args); + /** + * Implement this method to react to the occurrence of an exception. + */ + protected abstract void onErrorCore(Throwable e); + /** + * Implement this method to react to the end of the sequence. + */ + protected abstract void onCompletedCore(); + /** + * Try to trigger the error state. + * @param t + * @return false if already completed + */ + protected boolean fail(Throwable t) { + if (completed.compareAndSet(false, true)) { + onErrorCore(t); + return true; + } + return false; + } +} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/observables/ConnectableObservable.scala b/rxjava-core/src/main/java/rx/joins/Pattern.java similarity index 66% rename from language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/observables/ConnectableObservable.scala rename to rxjava-core/src/main/java/rx/joins/Pattern.java index 7740ad043f..1dc65ca3b8 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/observables/ConnectableObservable.scala +++ b/rxjava-core/src/main/java/rx/joins/Pattern.java @@ -1,25 +1,25 @@ -/** - * Copyright 2013 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.lang.scala.observables - - -class ConnectableObservable[+T](val asJava: rx.observables.ConnectableObservable[_ <: T]) extends AnyVal { - import rx.lang.scala._ - import rx.lang.scala.util._ - import rx.{Observable => JObservable} - import rx.lang.scala.internal.ImplicitFunctionConversions._ - -} \ No newline at end of file +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.joins; + +/** + * Base interface for join patterns. + * @see MSDN: Pattern + */ +public interface Pattern { + +} diff --git a/rxjava-core/src/main/java/rx/joins/Pattern1.java b/rxjava-core/src/main/java/rx/joins/Pattern1.java new file mode 100644 index 0000000000..324daf79fa --- /dev/null +++ b/rxjava-core/src/main/java/rx/joins/Pattern1.java @@ -0,0 +1,45 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package rx.joins; + +import rx.Observable; +import rx.util.functions.Func1; + +/** + * Represents a join pattern over one observable sequence. + */ +public class Pattern1 implements Pattern { + private final Observable first; + public Pattern1(Observable first) { + this.first = first; + } + public Observable first() { + return first; + } + /** + * Matches when all observable sequences have an available + * element and projects the elements by invoking the selector function. + * @param selector the function that will be invoked for elements in the source sequences. + * @return + * @throws NullPointerException if selector is null + */ + public Plan0 then(Func1 selector) { + if (selector == null) { + throw new NullPointerException(); + } + return new Plan1(this, selector); + } +} diff --git a/rxjava-core/src/main/java/rx/joins/Pattern2.java b/rxjava-core/src/main/java/rx/joins/Pattern2.java new file mode 100644 index 0000000000..6f82a66d49 --- /dev/null +++ b/rxjava-core/src/main/java/rx/joins/Pattern2.java @@ -0,0 +1,54 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package rx.joins; + +import rx.Observable; +import rx.util.functions.Func2; + +/** + * Represents a join pattern over observable sequences. + */ +public class Pattern2 implements Pattern { + private final Observable first; + private final Observable second; + public Pattern2(Observable first, Observable second) { + this.first = first; + this.second = second; + } + public Observable first() { + return first; + } + public Observable second() { + return second; + } + /** + * Creates a pattern that matches when all three observable sequences have an available element. + * @param other Observable sequence to match with the two previous sequences. + * @return Pattern object that matches when all observable sequences have an available element. + */ + public Pattern3 and(Observable other) { + if (other == null) { + throw new NullPointerException(); + } + return new Pattern3(first, second, other); + } + public Plan0 then(Func2 selector) { + if (selector == null) { + throw new NullPointerException(); + } + return new Plan2(this, selector); + } +} diff --git a/rxjava-core/src/main/java/rx/joins/Pattern3.java b/rxjava-core/src/main/java/rx/joins/Pattern3.java new file mode 100644 index 0000000000..c43b9d0b9e --- /dev/null +++ b/rxjava-core/src/main/java/rx/joins/Pattern3.java @@ -0,0 +1,55 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package rx.joins; + +import rx.Observable; +import rx.util.functions.Func3; + +/** + * Represents a join pattern over observable sequences. + */ +public class Pattern3 implements Pattern { + private final Observable first; + private final Observable second; + private final Observable third; + public Pattern3(Observable first, Observable second, + Observable third) { + this.first = first; + this.second = second; + this.third = third; + } + public Observable first() { + return first; + } + public Observable second() { + return second; + } + public Observable third() { + return third; + } +// public Pattern4 and(Observable other) { +// if (other == null) { +// throw new NullPointerException(); +// } +// return new Pattern4(first, second, third, other); +// } + public Plan0 then(Func3 selector) { + if (selector == null) { + throw new NullPointerException(); + } + return new Plan3(this, selector); + } +} diff --git a/rxjava-core/src/main/java/rx/joins/Plan0.java b/rxjava-core/src/main/java/rx/joins/Plan0.java new file mode 100644 index 0000000000..2a647aff3d --- /dev/null +++ b/rxjava-core/src/main/java/rx/joins/Plan0.java @@ -0,0 +1,46 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.joins; + +import java.util.Map; +import rx.Observable; +import rx.Observer; +import rx.util.functions.Action1; + +/** + * Represents an execution plan for join patterns. + */ +public abstract class Plan0 { + public abstract ActivePlan0 activate(Map externalSubscriptions, + Observer observer, Action1 deactivate); + + @SuppressWarnings("unchecked") + public static JoinObserver1 createObserver( + Map externalSubscriptions, + Observable observable, + Action1 onError + ) { + JoinObserver1 observer; + JoinObserver nonGeneric = externalSubscriptions.get(observable); + if (nonGeneric == null) { + observer = new JoinObserver1(observable, onError); + externalSubscriptions.put(observable, observer); + } else { + observer = (JoinObserver1)nonGeneric; + } + return observer; + } +} diff --git a/rxjava-core/src/main/java/rx/joins/Plan1.java b/rxjava-core/src/main/java/rx/joins/Plan1.java new file mode 100644 index 0000000000..b64742e27b --- /dev/null +++ b/rxjava-core/src/main/java/rx/joins/Plan1.java @@ -0,0 +1,80 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.joins; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import rx.Observer; +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Func1; +import rx.util.functions.Actions; + +/** + * Represents an execution plan for join patterns. + */ +public class Plan1 extends Plan0 { + protected Pattern1 expression; + protected Func1 selector; + + public Plan1(Pattern1 expression, Func1 selector) { + this.expression = expression; + this.selector = selector; + } + + public Pattern1 expression() { + return expression; + } + public Func1 selector() { + return selector; + } + + @Override + public ActivePlan0 activate(Map externalSubscriptions, final Observer observer, final Action1 deactivate) { + Action1 onError = Actions.onErrorFrom(observer); + + final JoinObserver1 firstJoinObserver = createObserver(externalSubscriptions, expression.first(), onError); + + final AtomicReference> self = new AtomicReference>(); + + ActivePlan1 activePlan = new ActivePlan1(firstJoinObserver, new Action1() { + @Override + public void call(T1 t1) { + R result; + try { + result = selector.call(t1); + } catch (Throwable t) { + observer.onError(t); + return; + } + observer.onNext(result); + } + }, + new Action0() { + @Override + public void call() { + firstJoinObserver.removeActivePlan(self.get()); + deactivate.call(self.get()); + } + }); + + self.set(activePlan); + + firstJoinObserver.addActivePlan(activePlan); + return activePlan; + } + +} diff --git a/rxjava-core/src/main/java/rx/joins/Plan2.java b/rxjava-core/src/main/java/rx/joins/Plan2.java new file mode 100644 index 0000000000..71b931ea0d --- /dev/null +++ b/rxjava-core/src/main/java/rx/joins/Plan2.java @@ -0,0 +1,79 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package rx.joins; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import rx.Observer; +import static rx.joins.Plan0.createObserver; +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Action2; +import rx.util.functions.Actions; +import rx.util.functions.Func2; + +/** + * Represents an execution plan for join patterns. + */ +public class Plan2 extends Plan0 { + protected Pattern2 expression; + protected Func2 selector; + public Plan2(Pattern2 expression, Func2 selector) { + this.expression = expression; + this.selector = selector; + } + + @Override + public ActivePlan0 activate(Map externalSubscriptions, + final Observer observer, final Action1 deactivate) { + Action1 onError = Actions.onErrorFrom(observer); + + final JoinObserver1 firstJoinObserver = createObserver(externalSubscriptions, expression.first(), onError); + final JoinObserver1 secondJoinObserver = createObserver(externalSubscriptions, expression.second(), onError); + + final AtomicReference> self = new AtomicReference>(); + + ActivePlan2 activePlan = new ActivePlan2(firstJoinObserver, secondJoinObserver, new Action2() { + @Override + public void call(T1 t1, T2 t2) { + R result; + try { + result = selector.call(t1, t2); + } catch (Throwable t) { + observer.onError(t); + return; + } + observer.onNext(result); + } + }, + new Action0() { + @Override + public void call() { + firstJoinObserver.removeActivePlan(self.get()); + secondJoinObserver.removeActivePlan(self.get()); + deactivate.call(self.get()); + } + }); + + self.set(activePlan); + + firstJoinObserver.addActivePlan(activePlan); + secondJoinObserver.addActivePlan(activePlan); + + return activePlan; + } + +} diff --git a/rxjava-core/src/main/java/rx/joins/Plan3.java b/rxjava-core/src/main/java/rx/joins/Plan3.java new file mode 100644 index 0000000000..386ef82dfe --- /dev/null +++ b/rxjava-core/src/main/java/rx/joins/Plan3.java @@ -0,0 +1,83 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package rx.joins; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import rx.Observer; +import static rx.joins.Plan0.createObserver; +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Action3; +import rx.util.functions.Actions; +import rx.util.functions.Func3; + +/** + * Represents an execution plan for join patterns. + */ +public class Plan3 extends Plan0 { + protected Pattern3 expression; + protected Func3 selector; + public Plan3(Pattern3 expression, Func3 selector) { + this.expression = expression; + this.selector = selector; + } + + @Override + public ActivePlan0 activate(Map externalSubscriptions, + final Observer observer, final Action1 deactivate) { + Action1 onError = Actions.onErrorFrom(observer); + + final JoinObserver1 firstJoinObserver = createObserver(externalSubscriptions, expression.first(), onError); + final JoinObserver1 secondJoinObserver = createObserver(externalSubscriptions, expression.second(), onError); + final JoinObserver1 thirdJoinObserver = createObserver(externalSubscriptions, expression.third(), onError); + + final AtomicReference> self = new AtomicReference>(); + + ActivePlan3 activePlan = new ActivePlan3(firstJoinObserver, secondJoinObserver, + thirdJoinObserver, new Action3() { + @Override + public void call(T1 t1, T2 t2, T3 t3) { + R result; + try { + result = selector.call(t1, t2, t3); + } catch (Throwable t) { + observer.onError(t); + return; + } + observer.onNext(result); + } + }, + new Action0() { + @Override + public void call() { + firstJoinObserver.removeActivePlan(self.get()); + secondJoinObserver.removeActivePlan(self.get()); + thirdJoinObserver.removeActivePlan(self.get()); + deactivate.call(self.get()); + } + }); + + self.set(activePlan); + + firstJoinObserver.addActivePlan(activePlan); + secondJoinObserver.addActivePlan(activePlan); + thirdJoinObserver.addActivePlan(activePlan); + + return activePlan; + } + +} diff --git a/rxjava-core/src/main/java/rx/observables/BlockingObservable.java b/rxjava-core/src/main/java/rx/observables/BlockingObservable.java index 32e2db7400..8fe13c9e88 100644 --- a/rxjava-core/src/main/java/rx/observables/BlockingObservable.java +++ b/rxjava-core/src/main/java/rx/observables/BlockingObservable.java @@ -15,20 +15,12 @@ */ package rx.observables; -import static org.junit.Assert.*; - import java.util.Iterator; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - import rx.Observable; -import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; import rx.operators.OperationMostRecent; @@ -37,8 +29,6 @@ import rx.operators.OperationToIterator; import rx.operators.SafeObservableSubscription; import rx.operators.SafeObserver; -import rx.subscriptions.BooleanSubscription; -import rx.subscriptions.Subscriptions; import rx.util.functions.Action1; import rx.util.functions.Func1; @@ -188,13 +178,10 @@ public Iterator getIterator() { * * * @return the last item emitted by the source {@link Observable} + * @throws IllegalArgumentException if source contains no elements */ public T last() { - T result = null; - for (T value : toIterable()) { - result = value; - } - return result; + return new BlockingObservable(o.last()).single(); } /** @@ -367,235 +354,4 @@ public Iterator iterator() { } }; } - - public static class UnitTest { - - @Mock - Observer w; - - @Before - public void before() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testLast() { - BlockingObservable obs = BlockingObservable.from(Observable.from("one", "two", "three")); - - assertEquals("three", obs.last()); - } - - @Test - public void testLastEmptyObservable() { - BlockingObservable obs = BlockingObservable.from(Observable.empty()); - - assertNull(obs.last()); - } - - @Test - public void testLastOrDefault() { - BlockingObservable observable = BlockingObservable.from(Observable.from(1, 0, -1)); - int last = observable.lastOrDefault(-100, new Func1() { - @Override - public Boolean call(Integer args) { - return args >= 0; - } - }); - assertEquals(0, last); - } - - @Test - public void testLastOrDefault1() { - BlockingObservable observable = BlockingObservable.from(Observable.from("one", "two", "three")); - assertEquals("three", observable.lastOrDefault("default")); - } - - @Test - public void testLastOrDefault2() { - BlockingObservable observable = BlockingObservable.from(Observable.empty()); - assertEquals("default", observable.lastOrDefault("default")); - } - - @Test - public void testLastOrDefaultWithPredicate() { - BlockingObservable observable = BlockingObservable.from(Observable.from(1, 0, -1)); - int last = observable.lastOrDefault(0, new Func1() { - @Override - public Boolean call(Integer args) { - return args < 0; - } - }); - - assertEquals(-1, last); - } - - @Test - public void testLastOrDefaultWrongPredicate() { - BlockingObservable observable = BlockingObservable.from(Observable.from(-1, -2, -3)); - int last = observable.lastOrDefault(0, new Func1() { - @Override - public Boolean call(Integer args) { - return args >= 0; - } - }); - assertEquals(0, last); - } - - @Test - public void testLastWithPredicate() { - BlockingObservable obs = BlockingObservable.from(Observable.from("one", "two", "three")); - - assertEquals("two", obs.last(new Func1() { - @Override - public Boolean call(String s) { - return s.length() == 3; - } - })); - } - - public void testSingle() { - BlockingObservable observable = BlockingObservable.from(Observable.from("one")); - assertEquals("one", observable.single()); - } - - @Test - public void testSingleDefault() { - BlockingObservable observable = BlockingObservable.from(Observable.empty()); - assertEquals("default", observable.singleOrDefault("default")); - } - - @Test(expected = IllegalStateException.class) - public void testSingleDefaultPredicateMatchesMoreThanOne() { - BlockingObservable.from(Observable.from("one", "two")).singleOrDefault("default", new Func1() { - @Override - public Boolean call(String args) { - return args.length() == 3; - } - }); - } - - @Test - public void testSingleDefaultPredicateMatchesNothing() { - BlockingObservable observable = BlockingObservable.from(Observable.from("one", "two")); - String result = observable.singleOrDefault("default", new Func1() { - @Override - public Boolean call(String args) { - return args.length() == 4; - } - }); - assertEquals("default", result); - } - - @Test(expected = IllegalStateException.class) - public void testSingleDefaultWithMoreThanOne() { - BlockingObservable observable = BlockingObservable.from(Observable.from("one", "two", "three")); - observable.singleOrDefault("default"); - } - - @Test - public void testSingleWithPredicateDefault() { - BlockingObservable observable = BlockingObservable.from(Observable.from("one", "two", "four")); - assertEquals("four", observable.single(new Func1() { - @Override - public Boolean call(String s) { - return s.length() == 4; - } - })); - } - - @Test(expected = IllegalStateException.class) - public void testSingleWrong() { - BlockingObservable observable = BlockingObservable.from(Observable.from(1, 2)); - observable.single(); - } - - @Test(expected = IllegalStateException.class) - public void testSingleWrongPredicate() { - BlockingObservable observable = BlockingObservable.from(Observable.from(-1)); - observable.single(new Func1() { - @Override - public Boolean call(Integer args) { - return args > 0; - } - }); - } - - @Test - public void testToIterable() { - BlockingObservable obs = BlockingObservable.from(Observable.from("one", "two", "three")); - - Iterator it = obs.toIterable().iterator(); - - assertEquals(true, it.hasNext()); - assertEquals("one", it.next()); - - assertEquals(true, it.hasNext()); - assertEquals("two", it.next()); - - assertEquals(true, it.hasNext()); - assertEquals("three", it.next()); - - assertEquals(false, it.hasNext()); - - } - - @Test(expected = TestException.class) - public void testToIterableWithException() { - BlockingObservable obs = BlockingObservable.from(Observable.create(new OnSubscribeFunc() { - - @Override - public Subscription onSubscribe(Observer observer) { - observer.onNext("one"); - observer.onError(new TestException()); - return Subscriptions.empty(); - } - })); - - Iterator it = obs.toIterable().iterator(); - - assertEquals(true, it.hasNext()); - assertEquals("one", it.next()); - - assertEquals(true, it.hasNext()); - it.next(); - - } - - @Test - public void testForEachWithError() { - try { - BlockingObservable.from(Observable.create(new OnSubscribeFunc() { - - @Override - public Subscription onSubscribe(final Observer observer) { - final BooleanSubscription subscription = new BooleanSubscription(); - new Thread(new Runnable() { - - @Override - public void run() { - observer.onNext("one"); - observer.onNext("two"); - observer.onNext("three"); - observer.onCompleted(); - } - }).start(); - return subscription; - } - })).forEach(new Action1() { - - @Override - public void call(String t1) { - throw new RuntimeException("fail"); - } - }); - fail("we expect an exception to be thrown"); - } catch (Throwable e) { - // do nothing as we expect this - } - } - - private static class TestException extends RuntimeException { - private static final long serialVersionUID = 1L; - } - } } diff --git a/rxjava-core/src/main/java/rx/observables/ConnectableObservable.java b/rxjava-core/src/main/java/rx/observables/ConnectableObservable.java index 839c5bb4b2..1826905eaf 100644 --- a/rxjava-core/src/main/java/rx/observables/ConnectableObservable.java +++ b/rxjava-core/src/main/java/rx/observables/ConnectableObservable.java @@ -1,12 +1,12 @@ /** * Copyright 2013 Netflix, Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,6 +18,7 @@ import rx.Observable; import rx.Observer; import rx.Subscription; +import rx.operators.OperationRefCount; /** * A ConnectableObservable resembles an ordinary {@link Observable}, except that it does not begin @@ -30,7 +31,7 @@ * For more information see * Connectable * Observable Operators at the RxJava Wiki - * + * * @param */ @@ -42,8 +43,17 @@ protected ConnectableObservable(OnSubscribeFunc onSubscribe) { /** * Call a ConnectableObservable's connect() method to instruct it to begin emitting the - * items from its underlying {@link Observable} to its {@link Observer}s. + * items from its underlying {@link Observable} to its {@link Observer}s. */ public abstract Subscription connect(); + /** + * Returns an observable sequence that stays connected to the source as long + * as there is at least one subscription to the observable sequence. + * + * @return a {@link Observable} + */ + public Observable refCount() { + return Observable.create(OperationRefCount.refCount(this)); + } } diff --git a/rxjava-core/src/main/java/rx/observables/GroupedObservable.java b/rxjava-core/src/main/java/rx/observables/GroupedObservable.java index 940d68e85b..f40e8c40af 100644 --- a/rxjava-core/src/main/java/rx/observables/GroupedObservable.java +++ b/rxjava-core/src/main/java/rx/observables/GroupedObservable.java @@ -1,12 +1,12 @@ /** * Copyright 2013 Netflix, Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -19,13 +19,14 @@ import rx.util.functions.Func1; /** - * An {@link Observable} that has been grouped by a key whose value can be obtained using - * {@link #getKey()}

- * + * An {@link Observable} that has been grouped by a key whose value can be obtained using {@link #getKey()}

+ * * @see Observable#groupBy(Func1) - * - * @param the type of the key - * @param the type of the elements in the group + * + * @param + * the type of the key + * @param + * the type of the elements in the group */ public class GroupedObservable extends Observable { private final K key; @@ -37,11 +38,10 @@ public GroupedObservable(K key, OnSubscribeFunc onSubscribe) { /** * Returns the key the elements in this observable were grouped by. - * + * * @return the key the elements in this observable were grouped by */ public K getKey() { return key; } } - diff --git a/rxjava-core/src/main/java/rx/operators/ChunkedOperation.java b/rxjava-core/src/main/java/rx/operators/ChunkedOperation.java index 5cd0845ea5..cabc1309e6 100644 --- a/rxjava-core/src/main/java/rx/operators/ChunkedOperation.java +++ b/rxjava-core/src/main/java/rx/operators/ChunkedOperation.java @@ -61,7 +61,7 @@ protected interface ChunkCreator { * * @param * The type of objects which this {@link Chunk} can hold. - * @param + * @param * The type of object being tracked by the {@link Chunk} */ protected abstract static class Chunk { @@ -187,8 +187,11 @@ public void emitChunk(Chunk chunk) { return; } - subscription.unsubscribe(); + // Fixed issue 428. + // As unsubscribe will cancel the Future, and the currrent thread's interrupt status + // will be set. So we need to emit the chunk before unsubscribe. super.emitChunk(chunk); + subscription.unsubscribe(); createChunk(); } diff --git a/rxjava-core/src/main/java/rx/operators/OperationAll.java b/rxjava-core/src/main/java/rx/operators/OperationAll.java index e0a18154c0..f3e0f0ebcd 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationAll.java +++ b/rxjava-core/src/main/java/rx/operators/OperationAll.java @@ -15,12 +15,8 @@ */ package rx.operators; -import static org.mockito.Mockito.*; - import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.Test; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -45,13 +41,11 @@ private static class AllObservable implements OnSubscribeFunc { private final SafeObservableSubscription subscription = new SafeObservableSubscription(); - private AllObservable(Observable sequence, Func1 predicate) { this.sequence = sequence; this.predicate = predicate; } - @Override public Subscription onSubscribe(final Observer observer) { return subscription.wrap(sequence.subscribe(new AllObserver(observer))); @@ -94,79 +88,4 @@ public void onNext(T args) { } } - - public static class UnitTest { - - @Test - @SuppressWarnings("unchecked") - public void testAll() { - Observable obs = Observable.from("one", "two", "six"); - - Observer observer = mock(Observer.class); - Observable.create(all(obs, new Func1() { - @Override - public Boolean call(String s) { - return s.length() == 3; - } - })).subscribe(observer); - - verify(observer).onNext(true); - verify(observer).onCompleted(); - verifyNoMoreInteractions(observer); - } - - @Test - @SuppressWarnings("unchecked") - public void testNotAll() { - Observable obs = Observable.from("one", "two", "three", "six"); - - Observer observer = mock(Observer.class); - Observable.create(all(obs, new Func1() { - @Override - public Boolean call(String s) { - return s.length() == 3; - } - })).subscribe(observer); - - verify(observer).onNext(false); - verify(observer).onCompleted(); - verifyNoMoreInteractions(observer); - } - - @Test - @SuppressWarnings("unchecked") - public void testEmpty() { - Observable obs = Observable.empty(); - - Observer observer = mock(Observer.class); - Observable.create(all(obs, new Func1() { - @Override - public Boolean call(String s) { - return s.length() == 3; - } - })).subscribe(observer); - - verify(observer).onNext(true); - verify(observer).onCompleted(); - verifyNoMoreInteractions(observer); - } - - @Test - @SuppressWarnings("unchecked") - public void testError() { - Throwable error = new Throwable(); - Observable obs = Observable.error(error); - - Observer observer = mock(Observer.class); - Observable.create(all(obs, new Func1() { - @Override - public Boolean call(String s) { - return s.length() == 3; - } - })).subscribe(observer); - - verify(observer).onError(error); - verifyNoMoreInteractions(observer); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationAmb.java b/rxjava-core/src/main/java/rx/operators/OperationAmb.java new file mode 100644 index 0000000000..639de2fa85 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationAmb.java @@ -0,0 +1,191 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.CompositeSubscription; + +/** + * Propagates the observable sequence that reacts first. + */ +public class OperationAmb { + + public static OnSubscribeFunc amb(Observable o1, Observable o2) { + List> sources = new ArrayList>(); + sources.add(o1); + sources.add(o2); + return amb(sources); + } + + public static OnSubscribeFunc amb(Observable o1, Observable o2, Observable o3) { + List> sources = new ArrayList>(); + sources.add(o1); + sources.add(o2); + sources.add(o3); + return amb(sources); + } + + public static OnSubscribeFunc amb(Observable o1, Observable o2, Observable o3, Observable o4) { + List> sources = new ArrayList>(); + sources.add(o1); + sources.add(o2); + sources.add(o3); + sources.add(o4); + return amb(sources); + } + + public static OnSubscribeFunc amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5) { + List> sources = new ArrayList>(); + sources.add(o1); + sources.add(o2); + sources.add(o3); + sources.add(o4); + sources.add(o5); + return amb(sources); + } + + public static OnSubscribeFunc amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6) { + List> sources = new ArrayList>(); + sources.add(o1); + sources.add(o2); + sources.add(o3); + sources.add(o4); + sources.add(o5); + sources.add(o6); + return amb(sources); + } + + public static OnSubscribeFunc amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7) { + List> sources = new ArrayList>(); + sources.add(o1); + sources.add(o2); + sources.add(o3); + sources.add(o4); + sources.add(o5); + sources.add(o6); + sources.add(o7); + return amb(sources); + } + + public static OnSubscribeFunc amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8) { + List> sources = new ArrayList>(); + sources.add(o1); + sources.add(o2); + sources.add(o3); + sources.add(o4); + sources.add(o5); + sources.add(o6); + sources.add(o7); + sources.add(o8); + return amb(sources); + } + + public static OnSubscribeFunc amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, Observable o9) { + List> sources = new ArrayList>(); + sources.add(o1); + sources.add(o2); + sources.add(o3); + sources.add(o4); + sources.add(o5); + sources.add(o6); + sources.add(o7); + sources.add(o8); + sources.add(o9); + return amb(sources); + } + + public static OnSubscribeFunc amb( + final Iterable> sources) { + return new OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(final Observer observer) { + AtomicInteger choice = new AtomicInteger(AmbObserver.NONE); + int index = 0; + CompositeSubscription parentSubscription = new CompositeSubscription(); + for (Observable source : sources) { + SafeObservableSubscription subscription = new SafeObservableSubscription(); + AmbObserver ambObserver = new AmbObserver( + subscription, observer, index, choice); + parentSubscription.add(subscription.wrap(source + .subscribe(ambObserver))); + index++; + } + return parentSubscription; + } + }; + } + + private static class AmbObserver implements Observer { + + private static final int NONE = -1; + + private Subscription subscription; + private Observer observer; + private int index; + private AtomicInteger choice; + + private AmbObserver(Subscription subscription, + Observer observer, int index, AtomicInteger choice) { + this.subscription = subscription; + this.observer = observer; + this.choice = choice; + this.index = index; + } + + @Override + public void onNext(T args) { + if (!isSelected()) { + subscription.unsubscribe(); + return; + } + observer.onNext(args); + } + + @Override + public void onCompleted() { + if (!isSelected()) { + subscription.unsubscribe(); + return; + } + observer.onCompleted(); + } + + @Override + public void onError(Throwable e) { + if (!isSelected()) { + subscription.unsubscribe(); + return; + } + observer.onError(e); + } + + private boolean isSelected() { + if (choice.get() == NONE) { + return choice.compareAndSet(NONE, index); + } + return choice.get() == index; + } + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationAny.java b/rxjava-core/src/main/java/rx/operators/OperationAny.java new file mode 100644 index 0000000000..a06058bece --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationAny.java @@ -0,0 +1,126 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static rx.util.functions.Functions.*; + +import java.util.concurrent.atomic.AtomicBoolean; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.util.functions.Func1; + +/** + * Returns an {@link Observable} that emits true if any element of + * an observable sequence satisfies a condition, otherwise false. + */ +public final class OperationAny { + + /** + * Returns an {@link Observable} that emits true if the source {@link Observable} is not empty, otherwise false. + * + * @param source + * The source {@link Observable} to check if not empty. + * @return A subscription function for creating the target Observable. + */ + public static OnSubscribeFunc any(Observable source) { + return new Any(source, alwaysTrue(), false); + } + + public static OnSubscribeFunc isEmpty(Observable source) { + return new Any(source, alwaysTrue(), true); + } + + /** + * Returns an {@link Observable} that emits true if any element + * of the source {@link Observable} satisfies the given condition, otherwise + * false. Note: always emit false if the source {@link Observable} is empty. + * + * @param source + * The source {@link Observable} to check if any element + * satisfies the given condition. + * @param predicate + * The condition to test every element. + * @return A subscription function for creating the target Observable. + */ + public static OnSubscribeFunc any(Observable source, Func1 predicate) { + return new Any(source, predicate, false); + } + + public static OnSubscribeFunc exists(Observable source, Func1 predicate) { + return any(source, predicate); + } + + private static class Any implements OnSubscribeFunc { + + private final Observable source; + private final Func1 predicate; + private final boolean returnOnEmpty; + + private Any(Observable source, Func1 predicate, boolean returnOnEmpty) { + this.source = source; + this.predicate = predicate; + this.returnOnEmpty = returnOnEmpty; + } + + @Override + public Subscription onSubscribe(final Observer observer) { + final SafeObservableSubscription subscription = new SafeObservableSubscription(); + return subscription.wrap(source.subscribe(new Observer() { + + private final AtomicBoolean hasEmitted = new AtomicBoolean(false); + + @Override + public void onNext(T value) { + try { + if (hasEmitted.get() == false) { + if (predicate.call(value) == true + && hasEmitted.getAndSet(true) == false) { + observer.onNext(!returnOnEmpty); + observer.onCompleted(); + // this will work if the sequence is asynchronous, it + // will have no effect on a synchronous observable + subscription.unsubscribe(); + } + } + } catch (Throwable ex) { + observer.onError(ex); + // this will work if the sequence is asynchronous, it + // will have no effect on a synchronous observable + subscription.unsubscribe(); + } + + } + + @Override + public void onError(Throwable ex) { + observer.onError(ex); + } + + @Override + public void onCompleted() { + if (!hasEmitted.get()) { + observer.onNext(returnOnEmpty); + observer.onCompleted(); + } + } + })); + } + + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationAverage.java b/rxjava-core/src/main/java/rx/operators/OperationAverage.java index 054a117053..35abc99eb5 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationAverage.java +++ b/rxjava-core/src/main/java/rx/operators/OperationAverage.java @@ -15,30 +15,26 @@ */ package rx.operators; -import static org.mockito.Mockito.*; - -import org.junit.Test; - import rx.Observable; -import rx.Observer; import rx.util.functions.Func1; import rx.util.functions.Func2; /** * A few operators for implementing the averaging operation. + * * @see MSDN: Observable.Average */ public final class OperationAverage { private static final class Tuple2 { private final T current; private final Integer count; - + private Tuple2(T v1, Integer v2) { current = v1; count = v2; } } - + public static Observable average(Observable source) { return source.reduce(new Tuple2(0, 0), new Func2, Integer, Tuple2>() { @Override @@ -48,7 +44,10 @@ public Tuple2 call(Tuple2 accu, Integer next) { }).map(new Func1, Integer>() { @Override public Integer call(Tuple2 result) { - return result.current / result.count; // may throw DivisionByZero, this should be correct... + if (result.count == 0) { + throw new IllegalArgumentException("Sequence contains no elements"); + } + return result.current / result.count; } }); } @@ -62,7 +61,10 @@ public Tuple2 call(Tuple2 accu, Long next) { }).map(new Func1, Long>() { @Override public Long call(Tuple2 result) { - return result.current / result.count; // may throw DivisionByZero, this should be correct... + if (result.count == 0) { + throw new IllegalArgumentException("Sequence contains no elements"); + } + return result.current / result.count; } }); } @@ -77,7 +79,7 @@ public Tuple2 call(Tuple2 accu, Float next) { @Override public Float call(Tuple2 result) { if (result.count == 0) { - throw new ArithmeticException("divide by zero"); + throw new IllegalArgumentException("Sequence contains no elements"); } return result.current / result.count; } @@ -94,105 +96,10 @@ public Tuple2 call(Tuple2 accu, Double next) { @Override public Double call(Tuple2 result) { if (result.count == 0) { - throw new ArithmeticException("divide by zero"); + throw new IllegalArgumentException("Sequence contains no elements"); } return result.current / result.count; } }); } - - public static class UnitTest { - @SuppressWarnings("unchecked") - Observer w = mock(Observer.class); - @SuppressWarnings("unchecked") - Observer wl = mock(Observer.class); - @SuppressWarnings("unchecked") - Observer wf = mock(Observer.class); - @SuppressWarnings("unchecked") - Observer wd = mock(Observer.class); - - @Test - public void testAverageOfAFewInts() throws Throwable { - Observable src = Observable.from(1, 2, 3, 4, 6); - average(src).subscribe(w); - - verify(w, times(1)).onNext(anyInt()); - verify(w).onNext(3); - verify(w, never()).onError(any(Throwable.class)); - verify(w, times(1)).onCompleted(); - } - - @Test - public void testEmptyAverage() throws Throwable { - Observable src = Observable.empty(); - average(src).subscribe(w); - - verify(w, never()).onNext(anyInt()); - verify(w, times(1)).onError(any(ArithmeticException.class)); - verify(w, never()).onCompleted(); - } - - @Test - public void testAverageOfAFewLongs() throws Throwable { - Observable src = Observable.from(1L, 2L, 3L, 4L, 6L); - averageLongs(src).subscribe(wl); - - verify(wl, times(1)).onNext(anyLong()); - verify(wl).onNext(3L); - verify(wl, never()).onError(any(Throwable.class)); - verify(wl, times(1)).onCompleted(); - } - - @Test - public void testEmptyAverageLongs() throws Throwable { - Observable src = Observable.empty(); - averageLongs(src).subscribe(wl); - - verify(wl, never()).onNext(anyLong()); - verify(wl, times(1)).onError(any(ArithmeticException.class)); - verify(wl, never()).onCompleted(); - } - - @Test - public void testAverageOfAFewFloats() throws Throwable { - Observable src = Observable.from(1.0f, 2.0f); - averageFloats(src).subscribe(wf); - - verify(wf, times(1)).onNext(anyFloat()); - verify(wf).onNext(1.5f); - verify(wf, never()).onError(any(Throwable.class)); - verify(wf, times(1)).onCompleted(); - } - - @Test - public void testEmptyAverageFloats() throws Throwable { - Observable src = Observable.empty(); - averageFloats(src).subscribe(wf); - - verify(wf, never()).onNext(anyFloat()); - verify(wf, times(1)).onError(any(ArithmeticException.class)); - verify(wf, never()).onCompleted(); - } - - @Test - public void testAverageOfAFewDoubles() throws Throwable { - Observable src = Observable.from(1.0d, 2.0d); - averageDoubles(src).subscribe(wd); - - verify(wd, times(1)).onNext(anyDouble()); - verify(wd).onNext(1.5d); - verify(wd, never()).onError(any(Throwable.class)); - verify(wd, times(1)).onCompleted(); - } - - @Test - public void testEmptyAverageDoubles() throws Throwable { - Observable src = Observable.empty(); - averageDoubles(src).subscribe(wd); - - verify(wd, never()).onNext(anyDouble()); - verify(wd, times(1)).onError(any(ArithmeticException.class)); - verify(wd, never()).onCompleted(); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationBuffer.java b/rxjava-core/src/main/java/rx/operators/OperationBuffer.java index 1cc559fb57..c9aba14411 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationBuffer.java +++ b/rxjava-core/src/main/java/rx/operators/OperationBuffer.java @@ -15,28 +15,17 @@ */ package rx.operators; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mockito; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Scheduler; import rx.Subscription; import rx.concurrency.Schedulers; -import rx.concurrency.TestScheduler; -import rx.subscriptions.Subscriptions; import rx.util.Closing; -import rx.util.Closings; import rx.util.Opening; -import rx.util.Openings; -import rx.util.functions.Action0; import rx.util.functions.Func0; import rx.util.functions.Func1; @@ -76,7 +65,7 @@ public static OnSubscribeFunc> buffer(final Observable source, fi @Override public Subscription onSubscribe(Observer> observer) { - NonOverlappingChunks> buffers = new NonOverlappingChunks>(observer, OperationBuffer.bufferMaker()); + NonOverlappingChunks> buffers = new NonOverlappingChunks>(observer, OperationBuffer. bufferMaker()); ChunkCreator creator = new ObservableBasedSingleChunkCreator>(buffers, bufferClosingSelector); return source.subscribe(new ChunkObserver>(buffers, observer, creator)); } @@ -112,7 +101,7 @@ public static OnSubscribeFunc> buffer(final Observable source, fi return new OnSubscribeFunc>() { @Override public Subscription onSubscribe(final Observer> observer) { - OverlappingChunks> buffers = new OverlappingChunks>(observer, OperationBuffer.bufferMaker()); + OverlappingChunks> buffers = new OverlappingChunks>(observer, OperationBuffer. bufferMaker()); ChunkCreator creator = new ObservableBasedMultiChunkCreator>(buffers, bufferOpenings, bufferClosingSelector); return source.subscribe(new ChunkObserver>(buffers, observer, creator)); } @@ -167,7 +156,7 @@ public static OnSubscribeFunc> buffer(final Observable source, fi return new OnSubscribeFunc>() { @Override public Subscription onSubscribe(final Observer> observer) { - Chunks> chunks = new SizeBasedChunks>(observer, OperationBuffer.bufferMaker(), count); + Chunks> chunks = new SizeBasedChunks>(observer, OperationBuffer. bufferMaker(), count); ChunkCreator creator = new SkippingChunkCreator>(chunks, skip); return source.subscribe(new ChunkObserver>(chunks, observer, creator)); } @@ -222,7 +211,7 @@ public static OnSubscribeFunc> buffer(final Observable source, fi return new OnSubscribeFunc>() { @Override public Subscription onSubscribe(final Observer> observer) { - NonOverlappingChunks> buffers = new NonOverlappingChunks>(observer, OperationBuffer.bufferMaker()); + NonOverlappingChunks> buffers = new NonOverlappingChunks>(observer, OperationBuffer. bufferMaker()); ChunkCreator creator = new TimeBasedChunkCreator>(buffers, timespan, unit, scheduler); return source.subscribe(new ChunkObserver>(buffers, observer, creator)); } @@ -283,7 +272,7 @@ public static OnSubscribeFunc> buffer(final Observable source, fi return new OnSubscribeFunc>() { @Override public Subscription onSubscribe(final Observer> observer) { - Chunks> chunks = new TimeAndSizeBasedChunks>(observer, OperationBuffer.bufferMaker(), count, timespan, unit, scheduler); + Chunks> chunks = new TimeAndSizeBasedChunks>(observer, OperationBuffer. bufferMaker(), count, timespan, unit, scheduler); ChunkCreator creator = new SingleChunkCreator>(chunks); return source.subscribe(new ChunkObserver>(chunks, observer, creator)); } @@ -344,7 +333,7 @@ public static OnSubscribeFunc> buffer(final Observable source, fi return new OnSubscribeFunc>() { @Override public Subscription onSubscribe(final Observer> observer) { - OverlappingChunks> buffers = new TimeBasedChunks>(observer, OperationBuffer.bufferMaker(), timespan, unit, scheduler); + OverlappingChunks> buffers = new TimeBasedChunks>(observer, OperationBuffer. bufferMaker(), timespan, unit, scheduler); ChunkCreator creator = new TimeBasedChunkCreator>(buffers, timeshift, unit, scheduler); return source.subscribe(new ChunkObserver>(buffers, observer, creator)); } @@ -368,293 +357,4 @@ public List getContents() { return contents; } } - - public static class UnitTest { - - private Observer> observer; - private TestScheduler scheduler; - - @Before - @SuppressWarnings("unchecked") - public void before() { - observer = Mockito.mock(Observer.class); - scheduler = new TestScheduler(); - } - - @Test - public void testComplete() { - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - observer.onCompleted(); - return Subscriptions.empty(); - } - }); - - Observable> buffered = Observable.create(buffer(source, 3, 3)); - buffered.subscribe(observer); - - Mockito.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); - Mockito.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - Mockito.verify(observer, Mockito.times(1)).onCompleted(); - } - - @Test - public void testSkipAndCountOverlappingBuffers() { - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - observer.onNext("one"); - observer.onNext("two"); - observer.onNext("three"); - observer.onNext("four"); - observer.onNext("five"); - return Subscriptions.empty(); - } - }); - - Observable> buffered = Observable.create(buffer(source, 3, 1)); - buffered.subscribe(observer); - - InOrder inOrder = Mockito.inOrder(observer); - inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two", "three")); - inOrder.verify(observer, Mockito.times(1)).onNext(list("two", "three", "four")); - inOrder.verify(observer, Mockito.times(1)).onNext(list("three", "four", "five")); - inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); - inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - inOrder.verify(observer, Mockito.never()).onCompleted(); - } - - @Test - public void testSkipAndCountGaplessBuffers() { - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - observer.onNext("one"); - observer.onNext("two"); - observer.onNext("three"); - observer.onNext("four"); - observer.onNext("five"); - observer.onCompleted(); - return Subscriptions.empty(); - } - }); - - Observable> buffered = Observable.create(buffer(source, 3, 3)); - buffered.subscribe(observer); - - InOrder inOrder = Mockito.inOrder(observer); - inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two", "three")); - inOrder.verify(observer, Mockito.times(1)).onNext(list("four", "five")); - inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); - inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - inOrder.verify(observer, Mockito.times(1)).onCompleted(); - } - - @Test - public void testSkipAndCountBuffersWithGaps() { - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - observer.onNext("one"); - observer.onNext("two"); - observer.onNext("three"); - observer.onNext("four"); - observer.onNext("five"); - observer.onCompleted(); - return Subscriptions.empty(); - } - }); - - Observable> buffered = Observable.create(buffer(source, 2, 3)); - buffered.subscribe(observer); - - InOrder inOrder = Mockito.inOrder(observer); - inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two")); - inOrder.verify(observer, Mockito.times(1)).onNext(list("four", "five")); - inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); - inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - inOrder.verify(observer, Mockito.times(1)).onCompleted(); - } - - @Test - public void testTimedAndCount() { - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - push(observer, "one", 10); - push(observer, "two", 90); - push(observer, "three", 110); - push(observer, "four", 190); - push(observer, "five", 210); - complete(observer, 250); - return Subscriptions.empty(); - } - }); - - Observable> buffered = Observable.create(buffer(source, 100, TimeUnit.MILLISECONDS, 2, scheduler)); - buffered.subscribe(observer); - - InOrder inOrder = Mockito.inOrder(observer); - scheduler.advanceTimeTo(100, TimeUnit.MILLISECONDS); - inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two")); - - scheduler.advanceTimeTo(200, TimeUnit.MILLISECONDS); - inOrder.verify(observer, Mockito.times(1)).onNext(list("three", "four")); - - scheduler.advanceTimeTo(300, TimeUnit.MILLISECONDS); - inOrder.verify(observer, Mockito.times(1)).onNext(list("five")); - inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); - inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - inOrder.verify(observer, Mockito.times(1)).onCompleted(); - } - - @Test - public void testTimed() { - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - push(observer, "one", 98); - push(observer, "two", 99); - push(observer, "three", 100); - push(observer, "four", 101); - push(observer, "five", 102); - complete(observer, 150); - return Subscriptions.empty(); - } - }); - - Observable> buffered = Observable.create(buffer(source, 100, TimeUnit.MILLISECONDS, scheduler)); - buffered.subscribe(observer); - - InOrder inOrder = Mockito.inOrder(observer); - scheduler.advanceTimeTo(101, TimeUnit.MILLISECONDS); - inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two", "three")); - - scheduler.advanceTimeTo(201, TimeUnit.MILLISECONDS); - inOrder.verify(observer, Mockito.times(1)).onNext(list("four", "five")); - inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); - inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - inOrder.verify(observer, Mockito.times(1)).onCompleted(); - } - - @Test - public void testObservableBasedOpenerAndCloser() { - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - push(observer, "one", 10); - push(observer, "two", 60); - push(observer, "three", 110); - push(observer, "four", 160); - push(observer, "five", 210); - complete(observer, 500); - return Subscriptions.empty(); - } - }); - - Observable openings = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - push(observer, Openings.create(), 50); - push(observer, Openings.create(), 200); - complete(observer, 250); - return Subscriptions.empty(); - } - }); - - Func1> closer = new Func1>() { - @Override - public Observable call(Opening opening) { - return Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - push(observer, Closings.create(), 100); - complete(observer, 101); - return Subscriptions.empty(); - } - }); - } - }; - - Observable> buffered = Observable.create(buffer(source, openings, closer)); - buffered.subscribe(observer); - - InOrder inOrder = Mockito.inOrder(observer); - scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); - inOrder.verify(observer, Mockito.times(1)).onNext(list("two", "three")); - inOrder.verify(observer, Mockito.times(1)).onNext(list("five")); - inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); - inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - inOrder.verify(observer, Mockito.times(1)).onCompleted(); - } - - @Test - public void testObservableBasedCloser() { - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - push(observer, "one", 10); - push(observer, "two", 60); - push(observer, "three", 110); - push(observer, "four", 160); - push(observer, "five", 210); - complete(observer, 250); - return Subscriptions.empty(); - } - }); - - Func0> closer = new Func0>() { - @Override - public Observable call() { - return Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - push(observer, Closings.create(), 100); - complete(observer, 101); - return Subscriptions.empty(); - } - }); - } - }; - - Observable> buffered = Observable.create(buffer(source, closer)); - buffered.subscribe(observer); - - InOrder inOrder = Mockito.inOrder(observer); - scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); - inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two")); - inOrder.verify(observer, Mockito.times(1)).onNext(list("three", "four")); - inOrder.verify(observer, Mockito.times(1)).onNext(list("five")); - inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); - inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); - inOrder.verify(observer, Mockito.times(1)).onCompleted(); - } - - private List list(String... args) { - List list = new ArrayList(); - for (String arg : args) { - list.add(arg); - } - return list; - } - - private void push(final Observer observer, final T value, int delay) { - scheduler.schedule(new Action0() { - @Override - public void call() { - observer.onNext(value); - } - }, delay, TimeUnit.MILLISECONDS); - } - - private void complete(final Observer observer, int delay) { - scheduler.schedule(new Action0() { - @Override - public void call() { - observer.onCompleted(); - } - }, delay, TimeUnit.MILLISECONDS); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationCache.java b/rxjava-core/src/main/java/rx/operators/OperationCache.java index e5f8ce69e3..71c681a179 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationCache.java +++ b/rxjava-core/src/main/java/rx/operators/OperationCache.java @@ -15,22 +15,13 @@ */ package rx.operators; -import static org.junit.Assert.*; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.Test; import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; import rx.subjects.ReplaySubject; -import rx.subscriptions.BooleanSubscription; -import rx.util.functions.Action1; /** * This method has similar behavior to {@link Observable#replay()} except that this auto-subscribes @@ -70,61 +61,4 @@ public Subscription onSubscribe(Observer observer) { }; } - - public static class UnitTest { - - @Test - public void testCache() throws InterruptedException { - final AtomicInteger counter = new AtomicInteger(); - Observable o = Observable.create(cache(Observable.create(new OnSubscribeFunc() { - - @Override - public Subscription onSubscribe(final Observer observer) { - final BooleanSubscription subscription = new BooleanSubscription(); - new Thread(new Runnable() { - - @Override - public void run() { - counter.incrementAndGet(); - System.out.println("published observable being executed"); - observer.onNext("one"); - observer.onCompleted(); - } - }).start(); - return subscription; - } - }))); - - // we then expect the following 2 subscriptions to get that same value - final CountDownLatch latch = new CountDownLatch(2); - - // subscribe once - o.subscribe(new Action1() { - - @Override - public void call(String v) { - assertEquals("one", v); - System.out.println("v: " + v); - latch.countDown(); - } - }); - - // subscribe again - o.subscribe(new Action1() { - - @Override - public void call(String v) { - assertEquals("one", v); - System.out.println("v: " + v); - latch.countDown(); - } - }); - - if (!latch.await(1000, TimeUnit.MILLISECONDS)) { - fail("subscriptions did not receive values"); - } - assertEquals(1, counter.get()); - } - } - } diff --git a/rxjava-core/src/main/java/rx/operators/OperationCast.java b/rxjava-core/src/main/java/rx/operators/OperationCast.java new file mode 100644 index 0000000000..dc54c204f6 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationCast.java @@ -0,0 +1,35 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.util.functions.Func1; + +/** + * Converts the elements of an observable sequence to the specified type. + */ +public class OperationCast { + + public static OnSubscribeFunc cast( + Observable source, final Class klass) { + return OperationMap.map(source, new Func1() { + public R call(T t) { + return klass.cast(t); + } + }); + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java b/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java index 020254d8a0..8a581667ae 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java +++ b/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java @@ -15,10 +15,6 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -26,15 +22,10 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Matchers; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.subscriptions.Subscriptions; import rx.util.functions.Func2; import rx.util.functions.Func3; import rx.util.functions.Func4; @@ -59,12 +50,13 @@ public class OperationCombineLatest { /** * Combines the two given observables, emitting an event containing an aggregation of the latest values of each of the source observables * each time an event is received from one of the source observables, where the aggregation is defined by the given function. - * @param w0 - * The first source observable. - * @param w1 - * The second source observable. - * @param combineLatestFunction - * The aggregation function used to combine the source observable values. + * + * @param w0 + * The first source observable. + * @param w1 + * The second source observable. + * @param combineLatestFunction + * The aggregation function used to combine the source observable values. * @return A function from an observer to a subscription. This can be used to create an observable from. */ public static OnSubscribeFunc combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) { @@ -77,7 +69,7 @@ public static OnSubscribeFunc combineLatest(Observable OnSubscribeFunc combineLatest(Observable w0, Observable w1, Observable w2, + public static OnSubscribeFunc combineLatest(Observable w0, Observable w1, Observable w2, Func3 combineLatestFunction) { Aggregator a = new Aggregator(Functions.fromFunc(combineLatestFunction)); a.addObserver(new CombineObserver(a, w0)); @@ -89,7 +81,7 @@ public static OnSubscribeFunc combineLatest(Observable OnSubscribeFunc combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, + public static OnSubscribeFunc combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Func4 combineLatestFunction) { Aggregator a = new Aggregator(Functions.fromFunc(combineLatestFunction)); a.addObserver(new CombineObserver(a, w0)); @@ -102,7 +94,7 @@ public static OnSubscribeFunc combineLatest(Observable OnSubscribeFunc combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Observable w4, + public static OnSubscribeFunc combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Observable w4, Func5 combineLatestFunction) { Aggregator a = new Aggregator(Functions.fromFunc(combineLatestFunction)); a.addObserver(new CombineObserver(a, w0)); @@ -116,7 +108,7 @@ public static OnSubscribeFunc combineLatest(Observabl /** * @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) */ - public static OnSubscribeFunc combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Observable w4, Observable w5, + public static OnSubscribeFunc combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Observable w4, Observable w5, Func6 combineLatestFunction) { Aggregator a = new Aggregator(Functions.fromFunc(combineLatestFunction)); a.addObserver(new CombineObserver(a, w0)); @@ -131,7 +123,7 @@ public static OnSubscribeFunc combineLatest(Obser /** * @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) */ - public static OnSubscribeFunc combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Observable w4, Observable w5, Observable w6, + public static OnSubscribeFunc combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Observable w4, Observable w5, Observable w6, Func7 combineLatestFunction) { Aggregator a = new Aggregator(Functions.fromFunc(combineLatestFunction)); a.addObserver(new CombineObserver(a, w0)); @@ -147,7 +139,7 @@ public static OnSubscribeFunc combineLatest(O /** * @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) */ - public static OnSubscribeFunc combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Observable w4, Observable w5, Observable w6, Observable w7, + public static OnSubscribeFunc combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Observable w4, Observable w5, Observable w6, Observable w7, Func8 combineLatestFunction) { Aggregator a = new Aggregator(Functions.fromFunc(combineLatestFunction)); a.addObserver(new CombineObserver(a, w0)); @@ -164,7 +156,8 @@ public static OnSubscribeFunc combineLate /** * @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) */ - public static OnSubscribeFunc combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Observable w4, Observable w5, Observable w6, Observable w7, Observable w8, + public static OnSubscribeFunc combineLatest(Observable w0, Observable w1, Observable w2, Observable w3, Observable w4, Observable w5, Observable w6, Observable w7, + Observable w8, Func9 combineLatestFunction) { Aggregator a = new Aggregator(Functions.fromFunc(combineLatestFunction)); a.addObserver(new CombineObserver(a, w0)); @@ -179,7 +172,7 @@ public static OnSubscribeFunc combine return a; } - private static class CombineObserver implements Observer { + /* package accessible for unit tests */static class CombineObserver implements Observer { final Observable w; final Aggregator a; private Subscription subscription; @@ -213,25 +206,25 @@ public void onNext(T args) { } /** - * Receive notifications from each of the observables we are reducing and execute the combineLatestFunction - * whenever we have received an event from one of the observables, as soon as each Observable has received + * Receive notifications from each of the observables we are reducing and execute the combineLatestFunction + * whenever we have received an event from one of the observables, as soon as each Observable has received * at least one event. */ - private static class Aggregator implements OnSubscribeFunc { + /* package accessible for unit tests */static class Aggregator implements OnSubscribeFunc { private volatile Observer observer; private final FuncN combineLatestFunction; private final AtomicBoolean running = new AtomicBoolean(true); - + // Stores how many observers have already completed private final AtomicInteger numCompleted = new AtomicInteger(0); - + /** * The latest value from each observer. */ private final Map, Object> latestValue = new ConcurrentHashMap, Object>(); - + /** * Ordered list of observers to combine. * No synchronization is necessary as these can not be added or changed asynchronously. @@ -245,7 +238,8 @@ public Aggregator(FuncN combineLatestFunction) { /** * Receive notification of a Observer starting (meaning we should require it for aggregation) * - * @param w The observer to add. + * @param w + * The observer to add. */ void addObserver(CombineObserver w) { observers.add(w); @@ -254,7 +248,8 @@ void addObserver(CombineObserver w) { /** * Receive notification of a Observer completing its iterations. * - * @param w The observer that has completed. + * @param w + * The observer that has completed. */ void complete(CombineObserver w) { int completed = numCompleted.incrementAndGet(); @@ -298,22 +293,22 @@ void next(CombineObserver w, T arg) { // remember this as the latest value for this observer latestValue.put(w, arg); - + if (latestValue.size() < observers.size()) { - // we don't have a value yet for each observer to combine, so we don't have a combined value yet either - return; + // we don't have a value yet for each observer to combine, so we don't have a combined value yet either + return; } - + Object[] argsToCombineLatest = new Object[observers.size()]; int i = 0; for (CombineObserver _w : observers) { argsToCombineLatest[i++] = latestValue.get(_w); } - + try { R combinedValue = combineLatestFunction.call(argsToCombineLatest); observer.onNext(combinedValue); - } catch(Throwable ex) { + } catch (Throwable ex) { observer.onError(ex); } } @@ -323,7 +318,7 @@ public Subscription onSubscribe(Observer observer) { if (this.observer != null) { throw new IllegalStateException("Only one Observer can subscribe to this Observable."); } - + SafeObservableSubscription subscription = new SafeObservableSubscription(new Subscription() { @Override public void unsubscribe() { @@ -351,603 +346,4 @@ private void stop() { } } } - - public static class UnitTest { - - @Test - public void testCombineLatestWithFunctionThatThrowsAnException() { - @SuppressWarnings("unchecked") // mock calls don't do generics - Observer w = mock(Observer.class); - - TestObservable w1 = new TestObservable(); - TestObservable w2 = new TestObservable(); - - Observable combined = Observable.create(combineLatest(Observable.create(w1), Observable.create(w2), new Func2() { - @Override - public String call(String v1, String v2) { - throw new RuntimeException("I don't work."); - } - })); - combined.subscribe(w); - - w1.observer.onNext("first value of w1"); - w2.observer.onNext("first value of w2"); - - verify(w, never()).onNext(anyString()); - verify(w, never()).onCompleted(); - verify(w, times(1)).onError(Matchers.any()); - } - - @Test - public void testCombineLatestDifferentLengthObservableSequences1() { - @SuppressWarnings("unchecked") // mock calls don't do generics - Observer w = mock(Observer.class); - - TestObservable w1 = new TestObservable(); - TestObservable w2 = new TestObservable(); - TestObservable w3 = new TestObservable(); - - Observable combineLatestW = Observable.create(combineLatest(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsCombineLatestFunction())); - combineLatestW.subscribe(w); - - /* simulate sending data */ - // once for w1 - w1.observer.onNext("1a"); - w2.observer.onNext("2a"); - w3.observer.onNext("3a"); - w1.observer.onCompleted(); - // twice for w2 - w2.observer.onNext("2b"); - w2.observer.onCompleted(); - // 4 times for w3 - w3.observer.onNext("3b"); - w3.observer.onNext("3c"); - w3.observer.onNext("3d"); - w3.observer.onCompleted(); - - /* we should have been called 4 times on the Observer */ - InOrder inOrder = inOrder(w); - inOrder.verify(w).onNext("1a2a3a"); - inOrder.verify(w).onNext("1a2b3a"); - inOrder.verify(w).onNext("1a2b3b"); - inOrder.verify(w).onNext("1a2b3c"); - inOrder.verify(w).onNext("1a2b3d"); - inOrder.verify(w, never()).onNext(anyString()); - inOrder.verify(w, times(1)).onCompleted(); - } - - @Test - public void testCombineLatestDifferentLengthObservableSequences2() { - @SuppressWarnings("unchecked") - Observer w = mock(Observer.class); - - TestObservable w1 = new TestObservable(); - TestObservable w2 = new TestObservable(); - TestObservable w3 = new TestObservable(); - - Observable combineLatestW = Observable.create(combineLatest(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsCombineLatestFunction())); - combineLatestW.subscribe(w); - - /* simulate sending data */ - // 4 times for w1 - w1.observer.onNext("1a"); - w1.observer.onNext("1b"); - w1.observer.onNext("1c"); - w1.observer.onNext("1d"); - w1.observer.onCompleted(); - // twice for w2 - w2.observer.onNext("2a"); - w2.observer.onNext("2b"); - w2.observer.onCompleted(); - // 1 times for w3 - w3.observer.onNext("3a"); - w3.observer.onCompleted(); - - /* we should have been called 1 time only on the Observer since we only combine the "latest" we don't go back and loop through others once completed */ - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext("1d2b3a"); - inOrder.verify(w, never()).onNext(anyString()); - - inOrder.verify(w, times(1)).onCompleted(); - - } - - @Test - public void testCombineLatestWithInterleavingSequences() { - @SuppressWarnings("unchecked") - Observer w = mock(Observer.class); - - TestObservable w1 = new TestObservable(); - TestObservable w2 = new TestObservable(); - TestObservable w3 = new TestObservable(); - - Observable combineLatestW = Observable.create(combineLatest(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsCombineLatestFunction())); - combineLatestW.subscribe(w); - - /* simulate sending data */ - w1.observer.onNext("1a"); - w2.observer.onNext("2a"); - w2.observer.onNext("2b"); - w3.observer.onNext("3a"); - - w1.observer.onNext("1b"); - w2.observer.onNext("2c"); - w2.observer.onNext("2d"); - w3.observer.onNext("3b"); - - w1.observer.onCompleted(); - w2.observer.onCompleted(); - w3.observer.onCompleted(); - - /* we should have been called 5 times on the Observer */ - InOrder inOrder = inOrder(w); - inOrder.verify(w).onNext("1a2b3a"); - inOrder.verify(w).onNext("1b2b3a"); - inOrder.verify(w).onNext("1b2c3a"); - inOrder.verify(w).onNext("1b2d3a"); - inOrder.verify(w).onNext("1b2d3b"); - - inOrder.verify(w, never()).onNext(anyString()); - inOrder.verify(w, times(1)).onCompleted(); - } - - /** - * Testing internal private logic due to the complexity so I want to use TDD to test as a I build it rather than relying purely on the overall functionality expected by the public methods. - */ - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregatorSimple() { - FuncN combineLatestFunction = getConcatCombineLatestFunction(); - /* create the aggregator which will execute the combineLatest function when all Observables provide values */ - Aggregator a = new Aggregator(combineLatestFunction); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - Observable.create(a).subscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - CombineObserver r1 = mock(CombineObserver.class); - CombineObserver r2 = mock(CombineObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "hello"); - a.next(r2, "world"); - - InOrder inOrder = inOrder(aObserver); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - inOrder.verify(aObserver, times(1)).onNext("helloworld"); - - a.next(r1, "hello "); - a.next(r2, "again"); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - inOrder.verify(aObserver, times(1)).onNext("hello again"); - - a.complete(r1); - a.complete(r2); - - inOrder.verify(aObserver, never()).onNext(anyString()); - verify(aObserver, times(1)).onCompleted(); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregatorDifferentSizedResultsWithOnComplete() { - FuncN combineLatestFunction = getConcatCombineLatestFunction(); - /* create the aggregator which will execute the combineLatest function when all Observables provide values */ - Aggregator a = new Aggregator(combineLatestFunction); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - Observable.create(a).subscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - CombineObserver r1 = mock(CombineObserver.class); - CombineObserver r2 = mock(CombineObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "hello"); - a.next(r2, "world"); - a.complete(r2); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - verify(aObserver, times(1)).onNext("helloworld"); - - a.next(r1, "hi"); - a.complete(r1); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verify(aObserver, times(1)).onNext("hiworld"); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregateMultipleTypes() { - FuncN combineLatestFunction = getConcatCombineLatestFunction(); - /* create the aggregator which will execute the combineLatest function when all Observables provide values */ - Aggregator a = new Aggregator(combineLatestFunction); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - Observable.create(a).subscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - CombineObserver r1 = mock(CombineObserver.class); - CombineObserver r2 = mock(CombineObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "hello"); - a.next(r2, "world"); - a.complete(r2); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - verify(aObserver, times(1)).onNext("helloworld"); - - a.next(r1, "hi"); - a.complete(r1); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verify(aObserver, times(1)).onNext("hiworld"); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregate3Types() { - FuncN combineLatestFunction = getConcatCombineLatestFunction(); - /* create the aggregator which will execute the combineLatest function when all Observables provide values */ - Aggregator a = new Aggregator(combineLatestFunction); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - Observable.create(a).subscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - CombineObserver r1 = mock(CombineObserver.class); - CombineObserver r2 = mock(CombineObserver.class); - CombineObserver r3 = mock(CombineObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - a.addObserver(r3); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "hello"); - a.next(r2, 2); - a.next(r3, new int[] { 5, 6, 7 }); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - verify(aObserver, times(1)).onNext("hello2[5, 6, 7]"); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregatorsWithDifferentSizesAndTiming() { - FuncN combineLatestFunction = getConcatCombineLatestFunction(); - /* create the aggregator which will execute the combineLatest function when all Observables provide values */ - Aggregator a = new Aggregator(combineLatestFunction); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - Observable.create(a).subscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - CombineObserver r1 = mock(CombineObserver.class); - CombineObserver r2 = mock(CombineObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "one"); - a.next(r1, "two"); - a.next(r1, "three"); - a.next(r2, "A"); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - verify(aObserver, times(1)).onNext("threeA"); - - a.next(r1, "four"); - a.complete(r1); - a.next(r2, "B"); - verify(aObserver, times(1)).onNext("fourB"); - a.next(r2, "C"); - verify(aObserver, times(1)).onNext("fourC"); - a.next(r2, "D"); - verify(aObserver, times(1)).onNext("fourD"); - a.next(r2, "E"); - verify(aObserver, times(1)).onNext("fourE"); - a.complete(r2); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregatorError() { - FuncN combineLatestFunction = getConcatCombineLatestFunction(); - /* create the aggregator which will execute the combineLatest function when all Observables provide values */ - Aggregator a = new Aggregator(combineLatestFunction); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - Observable.create(a).subscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - CombineObserver r1 = mock(CombineObserver.class); - CombineObserver r2 = mock(CombineObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "hello"); - a.next(r2, "world"); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - verify(aObserver, times(1)).onNext("helloworld"); - - a.error(new RuntimeException("")); - a.next(r1, "hello"); - a.next(r2, "again"); - - verify(aObserver, times(1)).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - // we don't want to be called again after an error - verify(aObserver, times(0)).onNext("helloagain"); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregatorUnsubscribe() { - FuncN combineLatestFunction = getConcatCombineLatestFunction(); - /* create the aggregator which will execute the combineLatest function when all Observables provide values */ - Aggregator a = new Aggregator(combineLatestFunction); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - Subscription subscription = Observable.create(a).subscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - CombineObserver r1 = mock(CombineObserver.class); - CombineObserver r2 = mock(CombineObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "hello"); - a.next(r2, "world"); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - verify(aObserver, times(1)).onNext("helloworld"); - - subscription.unsubscribe(); - a.next(r1, "hello"); - a.next(r2, "again"); - - verify(aObserver, times(0)).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - // we don't want to be called again after an error - verify(aObserver, times(0)).onNext("helloagain"); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregatorEarlyCompletion() { - FuncN combineLatestFunction = getConcatCombineLatestFunction(); - /* create the aggregator which will execute the combineLatest function when all Observables provide values */ - Aggregator a = new Aggregator(combineLatestFunction); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - Observable.create(a).subscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - CombineObserver r1 = mock(CombineObserver.class); - CombineObserver r2 = mock(CombineObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "one"); - a.next(r1, "two"); - a.complete(r1); - a.next(r2, "A"); - - InOrder inOrder = inOrder(aObserver); - - inOrder.verify(aObserver, never()).onError(any(Throwable.class)); - inOrder.verify(aObserver, never()).onCompleted(); - inOrder.verify(aObserver, times(1)).onNext("twoA"); - - a.complete(r2); - - inOrder.verify(aObserver, never()).onError(any(Throwable.class)); - inOrder.verify(aObserver, times(1)).onCompleted(); - // we shouldn't get this since completed is called before any other onNext calls could trigger this - inOrder.verify(aObserver, never()).onNext(anyString()); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testCombineLatest2Types() { - Func2 combineLatestFunction = getConcatStringIntegerCombineLatestFunction(); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - - Observable w = Observable.create(combineLatest(Observable.from("one", "two"), Observable.from(2, 3, 4), combineLatestFunction)); - w.subscribe(aObserver); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verify(aObserver, times(1)).onNext("two2"); - verify(aObserver, times(1)).onNext("two3"); - verify(aObserver, times(1)).onNext("two4"); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testCombineLatest3TypesA() { - Func3 combineLatestFunction = getConcatStringIntegerIntArrayCombineLatestFunction(); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - - Observable w = Observable.create(combineLatest(Observable.from("one", "two"), Observable.from(2), Observable.from(new int[] { 4, 5, 6 }), combineLatestFunction)); - w.subscribe(aObserver); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verify(aObserver, times(1)).onNext("two2[4, 5, 6]"); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testCombineLatest3TypesB() { - Func3 combineLatestFunction = getConcatStringIntegerIntArrayCombineLatestFunction(); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - - Observable w = Observable.create(combineLatest(Observable.from("one"), Observable.from(2), Observable.from(new int[] { 4, 5, 6 }, new int[] { 7, 8 }), combineLatestFunction)); - w.subscribe(aObserver); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verify(aObserver, times(1)).onNext("one2[4, 5, 6]"); - verify(aObserver, times(1)).onNext("one2[7, 8]"); - } - - private Func3 getConcat3StringsCombineLatestFunction() { - Func3 combineLatestFunction = new Func3() { - - @Override - public String call(String a1, String a2, String a3) { - if (a1 == null) { - a1 = ""; - } - if (a2 == null) { - a2 = ""; - } - if (a3 == null) { - a3 = ""; - } - return a1 + a2 + a3; - } - - }; - return combineLatestFunction; - } - - private FuncN getConcatCombineLatestFunction() { - FuncN combineLatestFunction = new FuncN() { - - @Override - public String call(Object... args) { - String returnValue = ""; - for (Object o : args) { - if (o != null) { - returnValue += getStringValue(o); - } - } - System.out.println("returning: " + returnValue); - return returnValue; - } - - }; - return combineLatestFunction; - } - - private Func2 getConcatStringIntegerCombineLatestFunction() { - Func2 combineLatestFunction = new Func2() { - - @Override - public String call(String s, Integer i) { - return getStringValue(s) + getStringValue(i); - } - - }; - return combineLatestFunction; - } - - private Func3 getConcatStringIntegerIntArrayCombineLatestFunction() { - Func3 combineLatestFunction = new Func3() { - - @Override - public String call(String s, Integer i, int[] iArray) { - return getStringValue(s) + getStringValue(i) + getStringValue(iArray); - } - - }; - return combineLatestFunction; - } - - private static String getStringValue(Object o) { - if (o == null) { - return ""; - } else { - if (o instanceof int[]) { - return Arrays.toString((int[]) o); - } else { - return String.valueOf(o); - } - } - } - - private static class TestObservable implements OnSubscribeFunc { - - Observer observer; - - @Override - public Subscription onSubscribe(Observer observer) { - // just store the variable where it can be accessed so we can manually trigger it - this.observer = observer; - return Subscriptions.empty(); - } - - } - } - } diff --git a/rxjava-core/src/main/java/rx/operators/OperationConcat.java b/rxjava-core/src/main/java/rx/operators/OperationConcat.java index 734751e9d0..9838a8073b 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationConcat.java +++ b/rxjava-core/src/main/java/rx/operators/OperationConcat.java @@ -15,28 +15,14 @@ */ package rx.operators; -import static org.junit.Assert.*; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; - -import org.junit.Test; -import org.mockito.InOrder; import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.subscriptions.BooleanSubscription; /** * Returns an Observable that emits the items emitted by two or more Observables, one after the @@ -166,529 +152,4 @@ public void unsubscribe() { }; } } - - public static class UnitTest { - - @Test - public void testConcat() { - @SuppressWarnings("unchecked") - Observer observer = mock(Observer.class); - - final String[] o = { "1", "3", "5", "7" }; - final String[] e = { "2", "4", "6" }; - - final Observable odds = Observable.from(o); - final Observable even = Observable.from(e); - - @SuppressWarnings("unchecked") - Observable concat = Observable.create(concat(odds, even)); - concat.subscribe(observer); - - verify(observer, times(7)).onNext(anyString()); - } - - @Test - public void testConcatWithList() { - @SuppressWarnings("unchecked") - Observer observer = mock(Observer.class); - - final String[] o = { "1", "3", "5", "7" }; - final String[] e = { "2", "4", "6" }; - - final Observable odds = Observable.from(o); - final Observable even = Observable.from(e); - final List> list = new ArrayList>(); - list.add(odds); - list.add(even); - Observable concat = Observable.create(concat(list)); - concat.subscribe(observer); - - verify(observer, times(7)).onNext(anyString()); - } - - @Test - public void testConcatObservableOfObservables() { - @SuppressWarnings("unchecked") - Observer observer = mock(Observer.class); - - final String[] o = { "1", "3", "5", "7" }; - final String[] e = { "2", "4", "6" }; - - final Observable odds = Observable.from(o); - final Observable even = Observable.from(e); - - Observable> observableOfObservables = Observable.create(new OnSubscribeFunc>() { - - @Override - public Subscription onSubscribe(Observer> observer) { - // simulate what would happen in an observable - observer.onNext(odds); - observer.onNext(even); - observer.onCompleted(); - - return new Subscription() { - - @Override - public void unsubscribe() { - // unregister ... will never be called here since we are executing synchronously - } - - }; - } - - }); - Observable concat = Observable.create(concat(observableOfObservables)); - - concat.subscribe(observer); - - verify(observer, times(7)).onNext(anyString()); - } - - /** - * Simple concat of 2 asynchronous observables ensuring it emits in correct order. - */ - @SuppressWarnings("unchecked") - @Test - public void testSimpleAsyncConcat() { - Observer observer = mock(Observer.class); - - TestObservable o1 = new TestObservable("one", "two", "three"); - TestObservable o2 = new TestObservable("four", "five", "six"); - - Observable.concat(Observable.create(o1), Observable.create(o2)).subscribe(observer); - - try { - // wait for async observables to complete - o1.t.join(); - o2.t.join(); - } catch (Throwable e) { - throw new RuntimeException("failed waiting on threads"); - } - - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("one"); - inOrder.verify(observer, times(1)).onNext("two"); - inOrder.verify(observer, times(1)).onNext("three"); - inOrder.verify(observer, times(1)).onNext("four"); - inOrder.verify(observer, times(1)).onNext("five"); - inOrder.verify(observer, times(1)).onNext("six"); - } - - /** - * Test an async Observable that emits more async Observables - */ - @SuppressWarnings("unchecked") - @Test - public void testNestedAsyncConcat() throws Throwable { - Observer observer = mock(Observer.class); - - final TestObservable o1 = new TestObservable("one", "two", "three"); - final TestObservable o2 = new TestObservable("four", "five", "six"); - final TestObservable o3 = new TestObservable("seven", "eight", "nine"); - final CountDownLatch allowThird = new CountDownLatch(1); - - final AtomicReference parent = new AtomicReference(); - Observable> observableOfObservables = Observable.create(new OnSubscribeFunc>() { - - @Override - public Subscription onSubscribe(final Observer> observer) { - final BooleanSubscription s = new BooleanSubscription(); - parent.set(new Thread(new Runnable() { - - @Override - public void run() { - try { - // emit first - if (!s.isUnsubscribed()) { - System.out.println("Emit o1"); - observer.onNext(Observable.create(o1)); - } - // emit second - if (!s.isUnsubscribed()) { - System.out.println("Emit o2"); - observer.onNext(Observable.create(o2)); - } - - // wait until sometime later and emit third - try { - allowThird.await(); - } catch (InterruptedException e) { - observer.onError(e); - } - if (!s.isUnsubscribed()) { - System.out.println("Emit o3"); - observer.onNext(Observable.create(o3)); - } - - } catch (Throwable e) { - observer.onError(e); - } finally { - System.out.println("Done parent Observable"); - observer.onCompleted(); - } - } - })); - parent.get().start(); - return s; - } - }); - - Observable.create(concat(observableOfObservables)).subscribe(observer); - - // wait for parent to start - while (parent.get() == null) { - Thread.sleep(1); - } - - try { - // wait for first 2 async observables to complete - while (o1.t == null) { - Thread.sleep(1); - } - System.out.println("Thread1 started ... waiting for it to complete ..."); - o1.t.join(); - while (o2.t == null) { - Thread.sleep(1); - } - System.out.println("Thread2 started ... waiting for it to complete ..."); - o2.t.join(); - } catch (Throwable e) { - throw new RuntimeException("failed waiting on threads", e); - } - - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("one"); - inOrder.verify(observer, times(1)).onNext("two"); - inOrder.verify(observer, times(1)).onNext("three"); - inOrder.verify(observer, times(1)).onNext("four"); - inOrder.verify(observer, times(1)).onNext("five"); - inOrder.verify(observer, times(1)).onNext("six"); - // we shouldn't have the following 3 yet - inOrder.verify(observer, never()).onNext("seven"); - inOrder.verify(observer, never()).onNext("eight"); - inOrder.verify(observer, never()).onNext("nine"); - // we should not be completed yet - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - // now allow the third - allowThird.countDown(); - - try { - while (o3.t == null) { - Thread.sleep(1); - } - // wait for 3rd to complete - o3.t.join(); - } catch (Throwable e) { - throw new RuntimeException("failed waiting on threads", e); - } - - inOrder.verify(observer, times(1)).onNext("seven"); - inOrder.verify(observer, times(1)).onNext("eight"); - inOrder.verify(observer, times(1)).onNext("nine"); - - inOrder.verify(observer, times(1)).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - } - - @SuppressWarnings("unchecked") - @Test - public void testBlockedObservableOfObservables() { - Observer observer = mock(Observer.class); - - final String[] o = { "1", "3", "5", "7" }; - final String[] e = { "2", "4", "6" }; - final Observable odds = Observable.from(o); - final Observable even = Observable.from(e); - final CountDownLatch callOnce = new CountDownLatch(1); - final CountDownLatch okToContinue = new CountDownLatch(1); - TestObservable> observableOfObservables = new TestObservable>(callOnce, okToContinue, odds, even); - OnSubscribeFunc concatF = concat(Observable.create(observableOfObservables)); - Observable concat = Observable.create(concatF); - concat.subscribe(observer); - try { - //Block main thread to allow observables to serve up o1. - callOnce.await(); - } catch (Throwable ex) { - ex.printStackTrace(); - fail(ex.getMessage()); - } - // The concated observable should have served up all of the odds. - verify(observer, times(1)).onNext("1"); - verify(observer, times(1)).onNext("3"); - verify(observer, times(1)).onNext("5"); - verify(observer, times(1)).onNext("7"); - - try { - // unblock observables so it can serve up o2 and complete - okToContinue.countDown(); - observableOfObservables.t.join(); - } catch (Throwable ex) { - ex.printStackTrace(); - fail(ex.getMessage()); - } - // The concatenated observable should now have served up all the evens. - verify(observer, times(1)).onNext("2"); - verify(observer, times(1)).onNext("4"); - verify(observer, times(1)).onNext("6"); - } - - @Test - public void testConcatConcurrentWithInfinity() { - final TestObservable w1 = new TestObservable("one", "two", "three"); - //This observable will send "hello" MAX_VALUE time. - final TestObservable w2 = new TestObservable("hello", Integer.MAX_VALUE); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - @SuppressWarnings("unchecked") - TestObservable> observableOfObservables = new TestObservable>(Observable.create(w1), Observable.create(w2)); - OnSubscribeFunc concatF = concat(Observable.create(observableOfObservables)); - - Observable concat = Observable.create(concatF); - - concat.take(50).subscribe(aObserver); - - //Wait for the thread to start up. - try { - Thread.sleep(25); - w1.t.join(); - w2.t.join(); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - InOrder inOrder = inOrder(aObserver); - inOrder.verify(aObserver, times(1)).onNext("one"); - inOrder.verify(aObserver, times(1)).onNext("two"); - inOrder.verify(aObserver, times(1)).onNext("three"); - inOrder.verify(aObserver, times(47)).onNext("hello"); - verify(aObserver, times(1)).onCompleted(); - verify(aObserver, never()).onError(any(Throwable.class)); - } - - @Test - public void testConcatNonBlockingObservables() { - - final CountDownLatch okToContinueW1 = new CountDownLatch(1); - final CountDownLatch okToContinueW2 = new CountDownLatch(1); - - final TestObservable w1 = new TestObservable(null, okToContinueW1, "one", "two", "three"); - final TestObservable w2 = new TestObservable(null, okToContinueW2, "four", "five", "six"); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - Observable> observableOfObservables = Observable.create(new OnSubscribeFunc>() { - - @Override - public Subscription onSubscribe(Observer> observer) { - // simulate what would happen in an observable - observer.onNext(Observable.create(w1)); - observer.onNext(Observable.create(w2)); - observer.onCompleted(); - - return new Subscription() { - - @Override - public void unsubscribe() { - } - - }; - } - - }); - Observable concat = Observable.create(concat(observableOfObservables)); - concat.subscribe(aObserver); - - verify(aObserver, times(0)).onCompleted(); - - try { - // release both threads - okToContinueW1.countDown(); - okToContinueW2.countDown(); - // wait for both to finish - w1.t.join(); - w2.t.join(); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - InOrder inOrder = inOrder(aObserver); - inOrder.verify(aObserver, times(1)).onNext("one"); - inOrder.verify(aObserver, times(1)).onNext("two"); - inOrder.verify(aObserver, times(1)).onNext("three"); - inOrder.verify(aObserver, times(1)).onNext("four"); - inOrder.verify(aObserver, times(1)).onNext("five"); - inOrder.verify(aObserver, times(1)).onNext("six"); - verify(aObserver, times(1)).onCompleted(); - - } - - /** - * Test unsubscribing the concatenated Observable in a single thread. - */ - @Test - public void testConcatUnsubscribe() { - final CountDownLatch callOnce = new CountDownLatch(1); - final CountDownLatch okToContinue = new CountDownLatch(1); - final TestObservable w1 = new TestObservable("one", "two", "three"); - final TestObservable w2 = new TestObservable(callOnce, okToContinue, "four", "five", "six"); - - @SuppressWarnings("unchecked") - final Observer aObserver = mock(Observer.class); - @SuppressWarnings("unchecked") - final Observable concat = Observable.create(concat(Observable.create(w1), Observable.create(w2))); - final SafeObservableSubscription s1 = new SafeObservableSubscription(); - - try { - // Subscribe - s1.wrap(concat.subscribe(aObserver)); - //Block main thread to allow observable "w1" to complete and observable "w2" to call onNext once. - callOnce.await(); - // Unsubcribe - s1.unsubscribe(); - //Unblock the observable to continue. - okToContinue.countDown(); - w1.t.join(); - w2.t.join(); - } catch (Throwable e) { - e.printStackTrace(); - fail(e.getMessage()); - } - - InOrder inOrder = inOrder(aObserver); - inOrder.verify(aObserver, times(1)).onNext("one"); - inOrder.verify(aObserver, times(1)).onNext("two"); - inOrder.verify(aObserver, times(1)).onNext("three"); - inOrder.verify(aObserver, times(1)).onNext("four"); - inOrder.verify(aObserver, never()).onNext("five"); - inOrder.verify(aObserver, never()).onNext("six"); - inOrder.verify(aObserver, never()).onCompleted(); - - } - - /** - * All observables will be running in different threads so subscribe() is unblocked. CountDownLatch is only used in order to call unsubscribe() in a predictable manner. - */ - @Test - public void testConcatUnsubscribeConcurrent() { - final CountDownLatch callOnce = new CountDownLatch(1); - final CountDownLatch okToContinue = new CountDownLatch(1); - final TestObservable w1 = new TestObservable("one", "two", "three"); - final TestObservable w2 = new TestObservable(callOnce, okToContinue, "four", "five", "six"); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - @SuppressWarnings("unchecked") - TestObservable> observableOfObservables = new TestObservable>(Observable.create(w1), Observable.create(w2)); - OnSubscribeFunc concatF = concat(Observable.create(observableOfObservables)); - - Observable concat = Observable.create(concatF); - - Subscription s1 = concat.subscribe(aObserver); - - try { - //Block main thread to allow observable "w1" to complete and observable "w2" to call onNext exactly once. - callOnce.await(); - //"four" from w2 has been processed by onNext() - s1.unsubscribe(); - //"five" and "six" will NOT be processed by onNext() - //Unblock the observable to continue. - okToContinue.countDown(); - w1.t.join(); - w2.t.join(); - } catch (Throwable e) { - e.printStackTrace(); - fail(e.getMessage()); - } - - InOrder inOrder = inOrder(aObserver); - inOrder.verify(aObserver, times(1)).onNext("one"); - inOrder.verify(aObserver, times(1)).onNext("two"); - inOrder.verify(aObserver, times(1)).onNext("three"); - inOrder.verify(aObserver, times(1)).onNext("four"); - inOrder.verify(aObserver, never()).onNext("five"); - inOrder.verify(aObserver, never()).onNext("six"); - verify(aObserver, never()).onCompleted(); - verify(aObserver, never()).onError(any(Throwable.class)); - } - - private static class TestObservable implements OnSubscribeFunc { - - private final Subscription s = new Subscription() { - - @Override - public void unsubscribe() { - subscribed = false; - } - - }; - private final List values; - private Thread t = null; - private int count = 0; - private boolean subscribed = true; - private final CountDownLatch once; - private final CountDownLatch okToContinue; - private final T seed; - private final int size; - - public TestObservable(T... values) { - this(null, null, values); - } - - public TestObservable(CountDownLatch once, CountDownLatch okToContinue, T... values) { - this.values = Arrays.asList(values); - this.size = this.values.size(); - this.once = once; - this.okToContinue = okToContinue; - this.seed = null; - } - - public TestObservable(T seed, int size) { - values = null; - once = null; - okToContinue = null; - this.seed = seed; - this.size = size; - } - - @Override - public Subscription onSubscribe(final Observer observer) { - t = new Thread(new Runnable() { - - @Override - public void run() { - try { - while (count < size && subscribed) { - if (null != values) - observer.onNext(values.get(count)); - else - observer.onNext(seed); - count++; - //Unblock the main thread to call unsubscribe. - if (null != once) - once.countDown(); - //Block until the main thread has called unsubscribe. - if (null != okToContinue) - okToContinue.await(5, TimeUnit.SECONDS); - } - if (subscribed) - observer.onCompleted(); - } catch (InterruptedException e) { - e.printStackTrace(); - fail(e.getMessage()); - } - } - - }); - t.start(); - return s; - } - - } - - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationDebounce.java b/rxjava-core/src/main/java/rx/operators/OperationDebounce.java index d225477069..3dbdd81f20 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationDebounce.java +++ b/rxjava-core/src/main/java/rx/operators/OperationDebounce.java @@ -15,24 +15,15 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Scheduler; import rx.Subscription; import rx.concurrency.Schedulers; -import rx.concurrency.TestScheduler; -import rx.subscriptions.Subscriptions; import rx.util.functions.Action0; import rx.util.functions.Func1; @@ -160,135 +151,4 @@ public void call() { } } } - - public static class UnitTest { - - private TestScheduler scheduler; - private Observer observer; - - @Before - @SuppressWarnings("unchecked") - public void before() { - scheduler = new TestScheduler(); - observer = mock(Observer.class); - } - - @Test - public void testDebounceWithCompleted() { - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - publishNext(observer, 100, "one"); // Should be skipped since "two" will arrive before the timeout expires. - publishNext(observer, 400, "two"); // Should be published since "three" will arrive after the timeout expires. - publishNext(observer, 900, "three"); // Should be skipped since onCompleted will arrive before the timeout expires. - publishCompleted(observer, 1000); // Should be published as soon as the timeout expires. - - return Subscriptions.empty(); - } - }); - - Observable sampled = Observable.create(OperationDebounce.debounce(source, 400, TimeUnit.MILLISECONDS, scheduler)); - sampled.subscribe(observer); - - scheduler.advanceTimeTo(0, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - // must go to 800 since it must be 400 after when two is sent, which is at 400 - scheduler.advanceTimeTo(800, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("two"); - scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onCompleted(); - inOrder.verifyNoMoreInteractions(); - } - - @Test - public void testDebounceNeverEmits() { - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - // all should be skipped since they are happening faster than the 200ms timeout - publishNext(observer, 100, "a"); // Should be skipped - publishNext(observer, 200, "b"); // Should be skipped - publishNext(observer, 300, "c"); // Should be skipped - publishNext(observer, 400, "d"); // Should be skipped - publishNext(observer, 500, "e"); // Should be skipped - publishNext(observer, 600, "f"); // Should be skipped - publishNext(observer, 700, "g"); // Should be skipped - publishNext(observer, 800, "h"); // Should be skipped - publishCompleted(observer, 900); // Should be published as soon as the timeout expires. - - return Subscriptions.empty(); - } - }); - - Observable sampled = Observable.create(OperationDebounce.debounce(source, 200, TimeUnit.MILLISECONDS, scheduler)); - sampled.subscribe(observer); - - scheduler.advanceTimeTo(0, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(0)).onNext(anyString()); - scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onCompleted(); - inOrder.verifyNoMoreInteractions(); - } - - @Test - public void testDebounceWithError() { - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - Exception error = new TestException(); - publishNext(observer, 100, "one"); // Should be published since "two" will arrive after the timeout expires. - publishNext(observer, 600, "two"); // Should be skipped since onError will arrive before the timeout expires. - publishError(observer, 700, error); // Should be published as soon as the timeout expires. - - return Subscriptions.empty(); - } - }); - - Observable sampled = Observable.create(OperationDebounce.debounce(source, 400, TimeUnit.MILLISECONDS, scheduler)); - sampled.subscribe(observer); - - scheduler.advanceTimeTo(0, TimeUnit.MILLISECONDS); - InOrder inOrder = inOrder(observer); - // 100 + 400 means it triggers at 500 - scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); - inOrder.verify(observer).onNext("one"); - scheduler.advanceTimeTo(701, TimeUnit.MILLISECONDS); - inOrder.verify(observer).onError(any(TestException.class)); - inOrder.verifyNoMoreInteractions(); - } - - private void publishCompleted(final Observer observer, long delay) { - scheduler.schedule(new Action0() { - @Override - public void call() { - observer.onCompleted(); - } - }, delay, TimeUnit.MILLISECONDS); - } - - private void publishError(final Observer observer, long delay, final Exception error) { - scheduler.schedule(new Action0() { - @Override - public void call() { - observer.onError(error); - } - }, delay, TimeUnit.MILLISECONDS); - } - - private void publishNext(final Observer observer, final long delay, final T value) { - scheduler.schedule(new Action0() { - @Override - public void call() { - observer.onNext(value); - } - }, delay, TimeUnit.MILLISECONDS); - } - - @SuppressWarnings("serial") - private class TestException extends Exception { - } - - } - } diff --git a/rxjava-core/src/main/java/rx/operators/OperationDefaultIfEmpty.java b/rxjava-core/src/main/java/rx/operators/OperationDefaultIfEmpty.java new file mode 100644 index 0000000000..7bc74ac156 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationDefaultIfEmpty.java @@ -0,0 +1,93 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; + +/** + * Returns the elements of the specified sequence or the specified default value + * in a singleton sequence if the sequence is empty. + */ +public class OperationDefaultIfEmpty { + + /** + * Returns the elements of the specified sequence or the specified default + * value in a singleton sequence if the sequence is empty. + * + * @param source + * The sequence to return the specified value for if it is empty. + * @param defaultValue + * The value to return if the sequence is empty. + * @return An observable sequence that contains the specified default value + * if the source is empty; otherwise, the elements of the source + * itself. + */ + public static OnSubscribeFunc defaultIfEmpty( + Observable source, T defaultValue) { + return new DefaultIfEmpty(source, defaultValue); + } + + private static class DefaultIfEmpty implements OnSubscribeFunc { + + private final Observable source; + private final T defaultValue; + + private DefaultIfEmpty(Observable source, T defaultValue) { + this.source = source; + this.defaultValue = defaultValue; + } + + @Override + public Subscription onSubscribe(final Observer observer) { + final SafeObservableSubscription subscription = new SafeObservableSubscription(); + return subscription.wrap(source.subscribe(new Observer() { + + private volatile boolean hasEmitted = false; + + @Override + public void onNext(T value) { + try { + hasEmitted = true; + observer.onNext(value); + } catch (Throwable ex) { + observer.onError(ex); + // this will work if the sequence is asynchronous, it + // will have no effect on a synchronous observable + subscription.unsubscribe(); + } + } + + @Override + public void onError(Throwable ex) { + observer.onError(ex); + } + + @Override + public void onCompleted() { + if (hasEmitted) { + observer.onCompleted(); + } else { + observer.onNext(defaultValue); + observer.onCompleted(); + } + } + })); + } + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationDefer.java b/rxjava-core/src/main/java/rx/operators/OperationDefer.java index 4be1ff01df..0d326b364e 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationDefer.java +++ b/rxjava-core/src/main/java/rx/operators/OperationDefer.java @@ -15,10 +15,6 @@ */ package rx.operators; -import static org.mockito.Mockito.*; - -import org.junit.Test; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -48,43 +44,4 @@ public Subscription onSubscribe(Observer observer) { }; } - - public static class UnitTest { - @Test - @SuppressWarnings("unchecked") - public void testDefer() throws Throwable { - - Func0> factory = mock(Func0.class); - - Observable firstObservable = Observable.from("one", "two"); - Observable secondObservable = Observable.from("three", "four"); - when(factory.call()).thenReturn(firstObservable, secondObservable); - - Observable deferred = Observable.defer(factory); - - verifyZeroInteractions(factory); - - Observer firstObserver = mock(Observer.class); - deferred.subscribe(firstObserver); - - verify(factory, times(1)).call(); - verify(firstObserver, times(1)).onNext("one"); - verify(firstObserver, times(1)).onNext("two"); - verify(firstObserver, times(0)).onNext("three"); - verify(firstObserver, times(0)).onNext("four"); - verify(firstObserver, times(1)).onCompleted(); - - Observer secondObserver = mock(Observer.class); - deferred.subscribe(secondObserver); - - verify(factory, times(2)).call(); - verify(secondObserver, times(0)).onNext("one"); - verify(secondObserver, times(0)).onNext("two"); - verify(secondObserver, times(1)).onNext("three"); - verify(secondObserver, times(1)).onNext("four"); - verify(secondObserver, times(1)).onCompleted(); - - } - } - } diff --git a/rxjava-core/src/main/java/rx/operators/OperationDelay.java b/rxjava-core/src/main/java/rx/operators/OperationDelay.java new file mode 100644 index 0000000000..603be7080a --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationDelay.java @@ -0,0 +1,314 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static org.mockito.MockitoAnnotations.initMocks; +import static rx.Observable.interval; + +import java.util.Date; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Scheduler; +import rx.Subscription; +import rx.concurrency.Schedulers; +import rx.concurrency.TestScheduler; +import rx.util.functions.Action0; +import rx.util.functions.Func1; + +/** + * Returns an Observable that emits the results of shifting the items emitted by the source + * Observable by a specified delay. + */ +public final class OperationDelay { + + /** + * Delays the observable sequence by the given time interval. + */ + public static OnSubscribeFunc delay(final Observable source, long delay, TimeUnit unit) { + return delay(source, delay, unit, Schedulers.threadPoolForComputation()); + } + + /** + * Delays the observable sequence by a time interval so that it starts at the given due time. + */ + public static OnSubscribeFunc delay(final Observable source, Date dueTime) { + return delay(source, dueTime, Schedulers.threadPoolForComputation()); + } + + /** + * Delays the observable sequence by a time interval so that it starts at the given due time. + */ + public static OnSubscribeFunc delay(final Observable source, Date dueTime, final Scheduler scheduler) { + long scheduledTime = dueTime.getTime(); + long delay = scheduledTime - scheduler.now(); + if (delay < 0L) { + delay = 0L; + } + return new Delay(source, delay, TimeUnit.MILLISECONDS, scheduler); + } + + /** + * Delays the observable sequence by the given time interval. + */ + public static OnSubscribeFunc delay(final Observable source, final long period, final TimeUnit unit, final Scheduler scheduler) { + return new Delay(source, period, unit, scheduler); + } + + private static class Delay implements OnSubscribeFunc { + private final Observable source; + private final long delay; + private final TimeUnit unit; + private final Scheduler scheduler; + + private Delay(Observable source, long delay, TimeUnit unit, Scheduler scheduler) { + this.source = source; + this.delay = delay; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public Subscription onSubscribe(final Observer observer) { + return source.subscribe(new Observer() { + private AtomicBoolean errorOccurred = new AtomicBoolean(); + + @Override + public void onCompleted() { + if (!errorOccurred.get()) { + scheduler.schedule(new Action0() { + @Override + public void call() { + observer.onCompleted(); + } + }, delay, unit); + } + } + + @Override + public void onError(Throwable e) { + // errors get propagated without delay + errorOccurred.set(true); + observer.onError(e); + } + + @Override + public void onNext(final T value) { + if (!errorOccurred.get()) { + scheduler.schedule(new Action0() { + @Override + public void call() { + try { + observer.onNext(value); + } catch (Throwable t) { + errorOccurred.set(true); + observer.onError(t); + } + } + }, delay, unit); + } + } + }); + } + } + + public static class UnitTest { + @Mock + private Observer observer; + @Mock + private Observer observer2; + + private TestScheduler scheduler; + + @Before + public void before() { + initMocks(this); + scheduler = new TestScheduler(); + } + + @Test + public void testDelay() { + Observable source = interval(1L, TimeUnit.SECONDS, scheduler).take(3); + Observable delayed = Observable.create(OperationDelay.delay(source, 500L, TimeUnit.MILLISECONDS, scheduler)); + delayed.subscribe(observer); + + InOrder inOrder = inOrder(observer); + scheduler.advanceTimeTo(1499L, TimeUnit.MILLISECONDS); + verify(observer, never()).onNext(anyLong()); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(1500L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext(0L); + inOrder.verify(observer, never()).onNext(anyLong()); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(2400L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onNext(anyLong()); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(2500L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext(1L); + inOrder.verify(observer, never()).onNext(anyLong()); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(3400L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onNext(anyLong()); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(3500L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext(2L); + verify(observer, times(1)).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void testDelayWithDueTime() { + Observable source = interval(1L, TimeUnit.SECONDS, scheduler).first(); + Observable delayed = Observable.create(OperationDelay.delay(source, new Date(1500L), scheduler)); + delayed.subscribe(observer); + + InOrder inOrder = inOrder(observer); + + scheduler.advanceTimeTo(2499L, TimeUnit.MILLISECONDS); + verify(observer, never()).onNext(anyLong()); + verify(observer, never()).onCompleted(); + + scheduler.advanceTimeTo(2500L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext(0L); + inOrder.verify(observer, times(1)).onCompleted(); + + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void testLongDelay() { + Observable source = interval(1L, TimeUnit.SECONDS, scheduler).take(3); + Observable delayed = Observable.create(OperationDelay.delay(source, 5L, TimeUnit.SECONDS, scheduler)); + delayed.subscribe(observer); + + InOrder inOrder = inOrder(observer); + + scheduler.advanceTimeTo(5999L, TimeUnit.MILLISECONDS); + verify(observer, never()).onNext(anyLong()); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(6000L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext(0L); + scheduler.advanceTimeTo(6999L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onNext(anyLong()); + scheduler.advanceTimeTo(7000L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext(1L); + scheduler.advanceTimeTo(7999L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onNext(anyLong()); + scheduler.advanceTimeTo(8000L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext(2L); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verify(observer, never()).onNext(anyLong()); + inOrder.verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void testDelayWithError() { + Observable source = interval(1L, TimeUnit.SECONDS, scheduler).map(new Func1() { + @Override + public Long call(Long value) { + if (value == 1L) { + throw new RuntimeException("error!"); + } + return value; + } + }); + Observable delayed = Observable.create(OperationDelay.delay(source, 1L, TimeUnit.SECONDS, scheduler)); + delayed.subscribe(observer); + + InOrder inOrder = inOrder(observer); + + scheduler.advanceTimeTo(1999L, TimeUnit.MILLISECONDS); + verify(observer, never()).onNext(anyLong()); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(2000L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onError(any(Throwable.class)); + inOrder.verify(observer, never()).onNext(anyLong()); + verify(observer, never()).onCompleted(); + + scheduler.advanceTimeTo(5000L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onNext(anyLong()); + inOrder.verify(observer, never()).onError(any(Throwable.class)); + verify(observer, never()).onCompleted(); + } + + @Test + public void testDelayWithMultipleSubscriptions() { + Observable source = interval(1L, TimeUnit.SECONDS, scheduler).take(3); + Observable delayed = Observable.create(OperationDelay.delay(source, 500L, TimeUnit.MILLISECONDS, scheduler)); + delayed.subscribe(observer); + delayed.subscribe(observer2); + + InOrder inOrder = inOrder(observer); + InOrder inOrder2 = inOrder(observer2); + + scheduler.advanceTimeTo(1499L, TimeUnit.MILLISECONDS); + verify(observer, never()).onNext(anyLong()); + verify(observer2, never()).onNext(anyLong()); + + scheduler.advanceTimeTo(1500L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext(0L); + inOrder2.verify(observer2, times(1)).onNext(0L); + + scheduler.advanceTimeTo(2499L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onNext(anyLong()); + inOrder2.verify(observer2, never()).onNext(anyLong()); + + scheduler.advanceTimeTo(2500L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext(1L); + inOrder2.verify(observer2, times(1)).onNext(1L); + + verify(observer, never()).onCompleted(); + verify(observer2, never()).onCompleted(); + + scheduler.advanceTimeTo(3500L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext(2L); + inOrder2.verify(observer2, times(1)).onNext(2L); + inOrder.verify(observer, never()).onNext(anyLong()); + inOrder2.verify(observer2, never()).onNext(anyLong()); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder2.verify(observer2, times(1)).onCompleted(); + + verify(observer, never()).onError(any(Throwable.class)); + verify(observer2, never()).onError(any(Throwable.class)); + } + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationDematerialize.java b/rxjava-core/src/main/java/rx/operators/OperationDematerialize.java index 529c507b14..8e958cbc14 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationDematerialize.java +++ b/rxjava-core/src/main/java/rx/operators/OperationDematerialize.java @@ -15,11 +15,6 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import org.junit.Test; - import rx.Notification; import rx.Observable; import rx.Observable.OnSubscribeFunc; @@ -85,52 +80,4 @@ public void onNext(Notification value) { }); } } - - public static class UnitTest { - - @Test - @SuppressWarnings("unchecked") - public void testDematerialize1() { - Observable> notifications = Observable.from(1, 2).materialize(); - Observable dematerialize = notifications.dematerialize(); - - Observer aObserver = mock(Observer.class); - dematerialize.subscribe(aObserver); - - verify(aObserver, times(1)).onNext(1); - verify(aObserver, times(1)).onNext(2); - verify(aObserver, times(1)).onCompleted(); - verify(aObserver, never()).onError(any(Throwable.class)); - } - - @Test - @SuppressWarnings("unchecked") - public void testDematerialize2() { - Throwable exception = new Throwable("test"); - Observable observable = Observable.error(exception); - Observable dematerialize = Observable.create(dematerialize(observable.materialize())); - - Observer aObserver = mock(Observer.class); - dematerialize.subscribe(aObserver); - - verify(aObserver, times(1)).onError(exception); - verify(aObserver, times(0)).onCompleted(); - verify(aObserver, times(0)).onNext(any(Integer.class)); - } - - @Test - @SuppressWarnings("unchecked") - public void testDematerialize3() { - Exception exception = new Exception("test"); - Observable observable = Observable.error(exception); - Observable dematerialize = Observable.create(dematerialize(observable.materialize())); - - Observer aObserver = mock(Observer.class); - dematerialize.subscribe(aObserver); - - verify(aObserver, times(1)).onError(exception); - verify(aObserver, times(0)).onCompleted(); - verify(aObserver, times(0)).onNext(any(Integer.class)); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationDistinct.java b/rxjava-core/src/main/java/rx/operators/OperationDistinct.java index b56f0cd22f..d2db484254 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationDistinct.java +++ b/rxjava-core/src/main/java/rx/operators/OperationDistinct.java @@ -15,24 +15,12 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; -import static org.mockito.MockitoAnnotations.initMocks; -import static rx.Observable.create; -import static rx.Observable.empty; -import static rx.Observable.from; - import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mock; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -45,13 +33,14 @@ /** * Returns an Observable that emits all distinct items emitted by the source. * - * Be careful with this operation when using infinite or very large observables + * Be careful with this operation when using infinite or very large observables * as it has to store all distinct values it has received. */ public final class OperationDistinct { /** * Returns an Observable that emits all distinct items emitted by the source + * * @param source * The source Observable to emit the distinct items for. * @return A subscription function for creating the target Observable. @@ -59,9 +48,10 @@ public final class OperationDistinct { public static OnSubscribeFunc distinct(Observable source, Func1 keySelector) { return new Distinct(source, keySelector); } - + /** * Returns an Observable that emits all distinct items emitted by the source + * * @param source * The source Observable to emit the distinct items for. * @param equalityComparator @@ -69,11 +59,12 @@ public static OnSubscribeFunc distinct(Observable source, * @return A subscription function for creating the target Observable. */ public static OnSubscribeFunc distinct(Observable source, Comparator equalityComparator) { - return new DistinctWithComparator(source, Functions.identity(), equalityComparator); + return new DistinctWithComparator(source, Functions. identity(), equalityComparator); } - + /** * Returns an Observable that emits all distinct items emitted by the source + * * @param source * The source Observable to emit the distinct items for. * @param equalityComparator @@ -83,21 +74,22 @@ public static OnSubscribeFunc distinct(Observable source, Co public static OnSubscribeFunc distinct(Observable source, Func1 keySelector, Comparator equalityComparator) { return new DistinctWithComparator(source, keySelector, equalityComparator); } - + /** * Returns an Observable that emits all distinct items emitted by the source + * * @param source * The source Observable to emit the distinct items for. * @return A subscription function for creating the target Observable. */ public static OnSubscribeFunc distinct(Observable source) { - return new Distinct(source, Functions.identity()); + return new Distinct(source, Functions. identity()); } - + private static class Distinct implements OnSubscribeFunc { private final Observable source; private final Func1 keySelector; - + private Distinct(Observable source, Func1 keySelector) { this.source = source; this.keySelector = keySelector; @@ -107,7 +99,7 @@ private Distinct(Observable source, Func1 k public Subscription onSubscribe(final Observer observer) { final Subscription sourceSub = source.subscribe(new Observer() { private final Set emittedKeys = new HashSet(); - + @Override public void onCompleted() { observer.onCompleted(); @@ -127,7 +119,7 @@ public void onNext(T next) { } } }); - + return Subscriptions.create(new Action0() { @Override public void call() { @@ -136,12 +128,12 @@ public void call() { }); } } - + private static class DistinctWithComparator implements OnSubscribeFunc { private final Observable source; private final Func1 keySelector; private final Comparator equalityComparator; - + private DistinctWithComparator(Observable source, Func1 keySelector, Comparator equalityComparator) { this.source = source; this.keySelector = keySelector; @@ -151,10 +143,10 @@ private DistinctWithComparator(Observable source, Func1 observer) { final Subscription sourceSub = source.subscribe(new Observer() { - + // due to the totally arbitrary equality comparator, we can't use anything more efficient than lists here private final List emittedKeys = new ArrayList(); - + @Override public void onCompleted() { observer.onCompleted(); @@ -173,9 +165,9 @@ public void onNext(T next) { observer.onNext(next); } } - + private boolean alreadyEmitted(U newKey) { - for (U key: emittedKeys) { + for (U key : emittedKeys) { if (equalityComparator.compare(key, newKey) == 0) { return true; } @@ -183,7 +175,7 @@ private boolean alreadyEmitted(U newKey) { return false; } }); - + return Subscriptions.create(new Action0() { @Override public void call() { @@ -192,166 +184,4 @@ public void call() { }); } } - - public static class UnitTest { - @Mock - Observer w; - @Mock - Observer w2; - - // nulls lead to exceptions - final Func1 TO_UPPER_WITH_EXCEPTION = new Func1() { - @Override - public String call(String s) { - if (s.equals("x")) { - return "XX"; - } - return s.toUpperCase(); - } - }; - - final Comparator COMPARE_LENGTH = new Comparator() { - @Override - public int compare(String s1, String s2) { - return s1.length() - s2.length(); - } - }; - - @Before - public void before() { - initMocks(this); - } - - @Test - public void testDistinctOfNone() { - Observable src = empty(); - create(distinct(src)).subscribe(w); - - verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - verify(w, times(1)).onCompleted(); - } - - @Test - public void testDistinctOfNoneWithKeySelector() { - Observable src = empty(); - create(distinct(src, TO_UPPER_WITH_EXCEPTION)).subscribe(w); - - verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - verify(w, times(1)).onCompleted(); - } - - @Test - public void testDistinctOfNormalSource() { - Observable src = from("a", "b", "c", "c", "c", "b", "b", "a", "e"); - create(distinct(src)).subscribe(w); - - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext("a"); - inOrder.verify(w, times(1)).onNext("b"); - inOrder.verify(w, times(1)).onNext("c"); - inOrder.verify(w, times(1)).onNext("e"); - inOrder.verify(w, times(1)).onCompleted(); - inOrder.verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - } - - @Test - public void testDistinctOfNormalSourceWithKeySelector() { - Observable src = from("a", "B", "c", "C", "c", "B", "b", "a", "E"); - create(distinct(src, TO_UPPER_WITH_EXCEPTION)).subscribe(w); - - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext("a"); - inOrder.verify(w, times(1)).onNext("B"); - inOrder.verify(w, times(1)).onNext("c"); - inOrder.verify(w, times(1)).onNext("E"); - inOrder.verify(w, times(1)).onCompleted(); - inOrder.verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - } - - @Test - public void testDistinctOfNormalSourceWithComparator() { - Observable src = from("1", "12", "123", "aaa", "321", "12", "21", "1", "12345"); - create(distinct(src, COMPARE_LENGTH)).subscribe(w); - - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext("1"); - inOrder.verify(w, times(1)).onNext("12"); - inOrder.verify(w, times(1)).onNext("123"); - inOrder.verify(w, times(1)).onNext("12345"); - inOrder.verify(w, times(1)).onCompleted(); - inOrder.verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - } - - @Test - public void testDistinctOfNormalSourceWithKeySelectorAndComparator() { - Observable src = from("a", "x", "ab", "abc", "cba", "de", "x", "a", "abcd"); - create(distinct(src, TO_UPPER_WITH_EXCEPTION, COMPARE_LENGTH)).subscribe(w); - - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext("a"); - inOrder.verify(w, times(1)).onNext("x"); - inOrder.verify(w, times(1)).onNext("abc"); - inOrder.verify(w, times(1)).onNext("abcd"); - inOrder.verify(w, times(1)).onCompleted(); - inOrder.verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - } - - @Test - public void testDistinctOfNormalSourceWithKeySelectorAndComparatorAndTwoSubscriptions() { - Observable src = from("a", "x", "ab", "abc", "cba", "de", "x", "a", "abcd"); - create(distinct(src, TO_UPPER_WITH_EXCEPTION, COMPARE_LENGTH)).subscribe(w); - - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext("a"); - inOrder.verify(w, times(1)).onNext("x"); - create(distinct(src, TO_UPPER_WITH_EXCEPTION, COMPARE_LENGTH)).subscribe(w2); - inOrder.verify(w, times(1)).onNext("abc"); - inOrder.verify(w, times(1)).onNext("abcd"); - inOrder.verify(w, times(1)).onCompleted(); - inOrder.verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - - InOrder inOrder2 = inOrder(w2); - inOrder2.verify(w2, times(1)).onNext("a"); - inOrder2.verify(w2, times(1)).onNext("x"); - inOrder2.verify(w2, times(1)).onNext("abc"); - inOrder2.verify(w2, times(1)).onNext("abcd"); - inOrder2.verify(w2, times(1)).onCompleted(); - inOrder2.verify(w2, never()).onNext(anyString()); - verify(w2, never()).onError(any(Throwable.class)); - } - - @Test - public void testDistinctOfSourceWithNulls() { - Observable src = from(null, "a", "a", null, null, "b", null); - create(distinct(src)).subscribe(w); - - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext(null); - inOrder.verify(w, times(1)).onNext("a"); - inOrder.verify(w, times(1)).onNext("b"); - inOrder.verify(w, times(1)).onCompleted(); - inOrder.verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - } - - @Test - public void testDistinctOfSourceWithExceptionsFromKeySelector() { - Observable src = from("a", "b", null, "c"); - create(distinct(src, TO_UPPER_WITH_EXCEPTION)).subscribe(w); - - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext("a"); - inOrder.verify(w, times(1)).onNext("b"); - inOrder.verify(w, times(1)).onError(any(NullPointerException.class)); - inOrder.verify(w, never()).onNext(anyString()); - inOrder.verify(w, never()).onCompleted(); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationDistinctUntilChanged.java b/rxjava-core/src/main/java/rx/operators/OperationDistinctUntilChanged.java index c43edc0202..eb1d6ed083 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationDistinctUntilChanged.java +++ b/rxjava-core/src/main/java/rx/operators/OperationDistinctUntilChanged.java @@ -15,20 +15,8 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; -import static org.mockito.MockitoAnnotations.initMocks; -import static rx.Observable.create; -import static rx.Observable.empty; -import static rx.Observable.from; - import java.util.Comparator; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mock; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -45,6 +33,7 @@ public final class OperationDistinctUntilChanged { /** * Returns an Observable that emits all sequentially distinct items emitted by the source. + * * @param source * The source Observable to emit the sequentially distinct items for. * @param equalityComparator @@ -52,11 +41,12 @@ public final class OperationDistinctUntilChanged { * @return A subscription function for creating the target Observable. */ public static OnSubscribeFunc distinctUntilChanged(Observable source, Comparator equalityComparator) { - return new DistinctUntilChanged(source, Functions.identity(), equalityComparator); + return new DistinctUntilChanged(source, Functions. identity(), equalityComparator); } - + /** * Returns an Observable that emits all sequentially distinct items emitted by the source. + * * @param source * The source Observable to emit the sequentially distinct items for. * @param keySelector @@ -68,9 +58,10 @@ public static OnSubscribeFunc distinctUntilChanged(Observable OnSubscribeFunc distinctUntilChanged(Observable source, Func1 keySelector, Comparator equalityComparator) { return new DistinctUntilChanged(source, keySelector, equalityComparator); } - + /** * Returns an Observable that emits all sequentially distinct items emitted by the source. + * * @param source * The source Observable to emit the sequentially distinct items for. * @param keySelector @@ -80,15 +71,16 @@ public static OnSubscribeFunc distinctUntilChanged(Observable OnSubscribeFunc distinctUntilChanged(Observable source, Func1 keySelector) { return new DistinctUntilChanged(source, keySelector, new DefaultEqualityComparator()); } - + /** * Returns an Observable that emits all sequentially distinct items emitted by the source. + * * @param source * The source Observable to emit the sequentially distinct items for. * @return A subscription function for creating the target Observable. */ public static OnSubscribeFunc distinctUntilChanged(Observable source) { - return new DistinctUntilChanged(source, Functions.identity(), new DefaultEqualityComparator()); + return new DistinctUntilChanged(source, Functions. identity(), new DefaultEqualityComparator()); } // does not define a useful ordering; it's only used for equality tests here @@ -102,12 +94,12 @@ public int compare(T t1, T t2) { } } } - + private static class DistinctUntilChanged implements OnSubscribeFunc { private final Observable source; private final Func1 keySelector; private final Comparator equalityComparator; - + private DistinctUntilChanged(Observable source, Func1 keySelector, Comparator equalityComparator) { this.source = source; this.keySelector = keySelector; @@ -119,7 +111,7 @@ public Subscription onSubscribe(final Observer observer) { final Subscription sourceSub = source.subscribe(new Observer() { private U lastEmittedKey; private boolean hasEmitted; - + @Override public void onCompleted() { observer.onCompleted(); @@ -143,7 +135,7 @@ public void onNext(T next) { } } }); - + return Subscriptions.create(new Action0() { @Override public void call() { @@ -152,169 +144,4 @@ public void call() { }); } } - - public static class UnitTest { - @Mock - Observer w; - @Mock - Observer w2; - - // nulls lead to exceptions - final Func1 TO_UPPER_WITH_EXCEPTION = new Func1() { - @Override - public String call(String s) { - if (s.equals("x")) { - return "xx"; - } - return s.toUpperCase(); - } - }; - - final Comparator COMPARE_LENGTH = new Comparator() { - @Override - public int compare(String s1, String s2) { - return s1.length() - s2.length(); - } - }; - - @Before - public void before() { - initMocks(this); - } - - @Test - public void testDistinctUntilChangedOfNone() { - Observable src = empty(); - create(distinctUntilChanged(src)).subscribe(w); - - verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - verify(w, times(1)).onCompleted(); - } - - @Test - public void testDistinctUntilChangedOfNoneWithKeySelector() { - Observable src = empty(); - create(distinctUntilChanged(src, TO_UPPER_WITH_EXCEPTION)).subscribe(w); - - verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - verify(w, times(1)).onCompleted(); - } - - @Test - public void testDistinctUntilChangedOfNormalSource() { - Observable src = from("a", "b", "c", "c", "c", "b", "b", "a", "e"); - create(distinctUntilChanged(src)).subscribe(w); - - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext("a"); - inOrder.verify(w, times(1)).onNext("b"); - inOrder.verify(w, times(1)).onNext("c"); - inOrder.verify(w, times(1)).onNext("b"); - inOrder.verify(w, times(1)).onNext("a"); - inOrder.verify(w, times(1)).onNext("e"); - inOrder.verify(w, times(1)).onCompleted(); - inOrder.verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - } - - @Test - public void testDistinctUntilChangedOfNormalSourceWithKeySelector() { - Observable src = from("a", "b", "c", "C", "c", "B", "b", "a", "e"); - create(distinctUntilChanged(src, TO_UPPER_WITH_EXCEPTION)).subscribe(w); - - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext("a"); - inOrder.verify(w, times(1)).onNext("b"); - inOrder.verify(w, times(1)).onNext("c"); - inOrder.verify(w, times(1)).onNext("B"); - inOrder.verify(w, times(1)).onNext("a"); - inOrder.verify(w, times(1)).onNext("e"); - inOrder.verify(w, times(1)).onCompleted(); - inOrder.verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - } - - @Test - public void testDistinctUntilChangedOfSourceWithNulls() { - Observable src = from(null, "a", "a", null, null, "b", null, null); - create(distinctUntilChanged(src)).subscribe(w); - - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext(null); - inOrder.verify(w, times(1)).onNext("a"); - inOrder.verify(w, times(1)).onNext(null); - inOrder.verify(w, times(1)).onNext("b"); - inOrder.verify(w, times(1)).onNext(null); - inOrder.verify(w, times(1)).onCompleted(); - inOrder.verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - } - - @Test - public void testDistinctUntilChangedOfSourceWithExceptionsFromKeySelector() { - Observable src = from("a", "b", null, "c"); - create(distinctUntilChanged(src, TO_UPPER_WITH_EXCEPTION)).subscribe(w); - - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext("a"); - inOrder.verify(w, times(1)).onNext("b"); - verify(w, times(1)).onError(any(NullPointerException.class)); - inOrder.verify(w, never()).onNext(anyString()); - inOrder.verify(w, never()).onCompleted(); - } - - @Test - public void testDistinctUntilChangedWithComparator() { - Observable src = from("a", "b", "c", "aa", "bb", "c", "ddd"); - create(distinctUntilChanged(src, COMPARE_LENGTH)).subscribe(w); - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext("a"); - inOrder.verify(w, times(1)).onNext("aa"); - inOrder.verify(w, times(1)).onNext("c"); - inOrder.verify(w, times(1)).onNext("ddd"); - inOrder.verify(w, times(1)).onCompleted(); - inOrder.verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - } - - @Test - public void testDistinctUntilChangedWithComparatorAndKeySelector() { - Observable src = from("a", "b", "x", "aa", "bb", "c", "ddd"); - create(distinctUntilChanged(src, TO_UPPER_WITH_EXCEPTION, COMPARE_LENGTH)).subscribe(w); - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext("a"); - inOrder.verify(w, times(1)).onNext("x"); - inOrder.verify(w, times(1)).onNext("c"); - inOrder.verify(w, times(1)).onNext("ddd"); - inOrder.verify(w, times(1)).onCompleted(); - inOrder.verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - } - - @Test - public void testDistinctUntilChangedWithComparatorAndKeySelectorandTwoSubscriptions() { - Observable src = from("a", "b", "x", "aa", "bb", "c", "ddd"); - create(distinctUntilChanged(src, TO_UPPER_WITH_EXCEPTION, COMPARE_LENGTH)).subscribe(w); - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext("a"); - inOrder.verify(w, times(1)).onNext("x"); - create(distinctUntilChanged(src, TO_UPPER_WITH_EXCEPTION, COMPARE_LENGTH)).subscribe(w2); - inOrder.verify(w, times(1)).onNext("c"); - inOrder.verify(w, times(1)).onNext("ddd"); - inOrder.verify(w, times(1)).onCompleted(); - inOrder.verify(w, never()).onNext(anyString()); - verify(w, never()).onError(any(Throwable.class)); - - InOrder inOrder2 = inOrder(w2); - inOrder2.verify(w2, times(1)).onNext("a"); - inOrder2.verify(w2, times(1)).onNext("x"); - inOrder2.verify(w2, times(1)).onNext("c"); - inOrder2.verify(w2, times(1)).onNext("ddd"); - inOrder2.verify(w2, times(1)).onCompleted(); - inOrder2.verify(w2, never()).onNext(anyString()); - verify(w2, never()).onError(any(Throwable.class)); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationDoOnEach.java b/rxjava-core/src/main/java/rx/operators/OperationDoOnEach.java new file mode 100644 index 0000000000..1b0aafb578 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationDoOnEach.java @@ -0,0 +1,66 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import rx.Observable; +import rx.Observer; +import rx.Observable.OnSubscribeFunc; +import rx.Subscription; + +/** + * Converts the elements of an observable sequence to the specified type. + */ +public class OperationDoOnEach { + public static OnSubscribeFunc doOnEach(Observable sequence, Observer observer) { + return new DoOnEachObservable(sequence, observer); + } + + private static class DoOnEachObservable implements OnSubscribeFunc { + + private final Observable sequence; + private final Observer doOnEachObserver; + + public DoOnEachObservable(Observable sequence, Observer doOnEachObserver) { + this.sequence = sequence; + this.doOnEachObserver = doOnEachObserver; + } + + @Override + public Subscription onSubscribe(final Observer observer) { + final SafeObservableSubscription subscription = new SafeObservableSubscription(); + return subscription.wrap(sequence.subscribe(new SafeObserver(subscription, new Observer() { + @Override + public void onCompleted() { + doOnEachObserver.onCompleted(); + observer.onCompleted(); + } + + @Override + public void onError(Throwable e) { + doOnEachObserver.onError(e); + observer.onError(e); + } + + @Override + public void onNext(T value) { + doOnEachObserver.onNext(value); + observer.onNext(value); + } + }))); + } + + } +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/operators/OperationElementAt.java b/rxjava-core/src/main/java/rx/operators/OperationElementAt.java new file mode 100644 index 0000000000..3001043c80 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationElementAt.java @@ -0,0 +1,137 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import java.util.concurrent.atomic.AtomicInteger; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; + +/** + * Returns the element at a specified index in a sequence. + */ +public class OperationElementAt { + + /** + * Returns the element at a specified index in a sequence. + * + * @param source + * Observable sequence to return the element from. + * @param index + * The zero-based index of the element to retrieve. + * + * @return An observable sequence that produces the element at the specified + * position in the source sequence. + * + * @throws IndexOutOfBoundsException + * Index is greater than or equal to the number of elements in + * the source sequence. + * @throws IndexOutOfBoundsException + * Index is less than 0. + */ + public static OnSubscribeFunc elementAt(Observable source, int index) { + return new ElementAt(source, index, null, false); + } + + /** + * Returns the element at a specified index in a sequence or the default + * value if the index is out of range. + * + * @param source + * Observable sequence to return the element from. + * @param index + * The zero-based index of the element to retrieve. + * @param defaultValue + * The default value. + * + * @return An observable sequence that produces the element at the specified + * position in the source sequence, or the default value if the + * index is outside the bounds of the source sequence. + * + * @throws IndexOutOfBoundsException + * Index is less than 0. + */ + public static OnSubscribeFunc elementAtOrDefault(Observable source, int index, T defaultValue) { + return new ElementAt(source, index, defaultValue, true); + } + + private static class ElementAt implements OnSubscribeFunc { + + private final Observable source; + private final int index; + private final boolean hasDefault; + private final T defaultValue; + + private ElementAt(Observable source, int index, + T defaultValue, boolean hasDefault) { + this.source = source; + this.index = index; + this.defaultValue = defaultValue; + this.hasDefault = hasDefault; + } + + @Override + public Subscription onSubscribe(final Observer observer) { + final SafeObservableSubscription subscription = new SafeObservableSubscription(); + return subscription.wrap(source.subscribe(new Observer() { + + private AtomicInteger counter = new AtomicInteger(); + + @Override + public void onNext(T value) { + try { + int currentIndex = counter.getAndIncrement(); + if (currentIndex == index) { + observer.onNext(value); + observer.onCompleted(); + } else if (currentIndex > index) { + // this will work if the sequence is asynchronous, + // it will have no effect on a synchronous observable + subscription.unsubscribe(); + } + } catch (Throwable ex) { + observer.onError(ex); + // this will work if the sequence is asynchronous, it + // will have no effect on a synchronous observable + subscription.unsubscribe(); + } + + } + + @Override + public void onError(Throwable ex) { + observer.onError(ex); + } + + @Override + public void onCompleted() { + if (index < 0) { + observer.onError(new IndexOutOfBoundsException(index + " is out of bounds")); + } else if (counter.get() <= index) { + if (hasDefault) { + observer.onNext(defaultValue); + observer.onCompleted(); + } else { + observer.onError(new IndexOutOfBoundsException(index + " is out of bounds")); + } + } + } + })); + } + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationFilter.java b/rxjava-core/src/main/java/rx/operators/OperationFilter.java index 5026f11d0c..560600dc9c 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationFilter.java +++ b/rxjava-core/src/main/java/rx/operators/OperationFilter.java @@ -15,12 +15,6 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import org.junit.Test; -import org.mockito.Mockito; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -74,28 +68,4 @@ public void onCompleted() { } } - - public static class UnitTest { - - @Test - public void testFilter() { - Observable w = Observable.from("one", "two", "three"); - Observable observable = Observable.create(filter(w, new Func1() { - - @Override - public Boolean call(String t1) { - return t1.equals("two"); - } - })); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - observable.subscribe(aObserver); - verify(aObserver, Mockito.never()).onNext("one"); - verify(aObserver, times(1)).onNext("two"); - verify(aObserver, Mockito.never()).onNext("three"); - verify(aObserver, Mockito.never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationFinally.java b/rxjava-core/src/main/java/rx/operators/OperationFinally.java index ad711b08ca..f8237cbb20 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationFinally.java +++ b/rxjava-core/src/main/java/rx/operators/OperationFinally.java @@ -1,12 +1,12 @@ /** * Copyright 2013 Netflix, Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,11 +15,6 @@ */ package rx.operators; -import static org.mockito.Mockito.*; - -import org.junit.Before; -import org.junit.Test; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -38,14 +33,16 @@ public final class OperationFinally { /** * Call a given action when a sequence completes (with or without an - * exception). The returned observable is exactly as threadsafe as the + * exception). The returned observable is exactly as threadsafe as the * source observable. *

* Note that "finally" is a Java reserved word and cannot be an identifier, * so we use "finallyDo". - * - * @param sequence An observable sequence of elements - * @param action An action to be taken when the sequence is complete or throws an exception + * + * @param sequence + * An observable sequence of elements + * @param action + * An action to be taken when the sequence is complete or throws an exception * @return An observable sequence with the same elements as the input. * After the last element is consumed (and {@link Observer#onCompleted} has been called), * or after an exception is thrown (and {@link Observer#onError} has been called), @@ -105,31 +102,4 @@ public void onNext(T args) { } } } - - public static class UnitTest { - private Action0 aAction0; - private Observer aObserver; - - @SuppressWarnings("unchecked") // mocking has to be unchecked, unfortunately - @Before - public void before() { - aAction0 = mock(Action0.class); - aObserver = mock(Observer.class); - } - - private void checkActionCalled(Observable input) { - Observable.create(finallyDo(input, aAction0)).subscribe(aObserver); - verify(aAction0, times(1)).call(); - } - - @Test - public void testFinallyCalledOnComplete() { - checkActionCalled(Observable.from(new String[] {"1", "2", "3"})); - } - - @Test - public void testFinallyCalledOnError() { - checkActionCalled(Observable.error(new RuntimeException("expected"))); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationFirstOrDefault.java b/rxjava-core/src/main/java/rx/operators/OperationFirstOrDefault.java index 73283a8a3b..1a37d68d73 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationFirstOrDefault.java +++ b/rxjava-core/src/main/java/rx/operators/OperationFirstOrDefault.java @@ -15,20 +15,10 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; -import static org.mockito.MockitoAnnotations.initMocks; -import static rx.Observable.create; -import static rx.Observable.empty; -import static rx.Observable.from; -import static rx.util.functions.Functions.alwaysTrue; +import static rx.util.functions.Functions.*; import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -45,7 +35,7 @@ public final class OperationFirstOrDefault { /** * Returns an Observable that emits the first item emitted by the source - * Observable that satisfies the given condition, + * Observable that satisfies the given condition, * or a default value if the source emits no items that satisfy the given condition. * * @param source @@ -59,7 +49,7 @@ public final class OperationFirstOrDefault { public static OnSubscribeFunc firstOrDefault(Observable source, Func1 predicate, T defaultValue) { return new FirstOrElse(source, predicate, defaultValue); } - + /** * Returns an Observable that emits the first item emitted by the source * Observable, or a default value if the source emits nothing. @@ -89,7 +79,7 @@ private FirstOrElse(Observable source, Func1 pr public Subscription onSubscribe(final Observer observer) { final Subscription sourceSub = source.subscribe(new Observer() { private final AtomicBoolean hasEmitted = new AtomicBoolean(false); - + @Override public void onCompleted() { if (!hasEmitted.get()) { @@ -117,7 +107,7 @@ public void onNext(T next) { } } }); - + return Subscriptions.create(new Action0() { @Override public void call() { @@ -126,65 +116,4 @@ public void call() { }); } } - - public static class UnitTest { - @Mock - Observer w; - - private static final Func1 IS_D = new Func1() { - @Override - public Boolean call(String value) { - return "d".equals(value); - } - }; - - @Before - public void before() { - initMocks(this); - } - - @Test - public void testFirstOrElseOfNone() { - Observable src = empty(); - create(firstOrDefault(src, "default")).subscribe(w); - - verify(w, times(1)).onNext(anyString()); - verify(w, times(1)).onNext("default"); - verify(w, never()).onError(any(Throwable.class)); - verify(w, times(1)).onCompleted(); - } - - @Test - public void testFirstOrElseOfSome() { - Observable src = from("a", "b", "c"); - create(firstOrDefault(src, "default")).subscribe(w); - - verify(w, times(1)).onNext(anyString()); - verify(w, times(1)).onNext("a"); - verify(w, never()).onError(any(Throwable.class)); - verify(w, times(1)).onCompleted(); - } - - @Test - public void testFirstOrElseWithPredicateOfNoneMatchingThePredicate() { - Observable src = from("a", "b", "c"); - create(firstOrDefault(src, IS_D, "default")).subscribe(w); - - verify(w, times(1)).onNext(anyString()); - verify(w, times(1)).onNext("default"); - verify(w, never()).onError(any(Throwable.class)); - verify(w, times(1)).onCompleted(); - } - - @Test - public void testFirstOrElseWithPredicateOfSome() { - Observable src = from("a", "b", "c", "d", "e", "f"); - create(firstOrDefault(src, IS_D, "default")).subscribe(w); - - verify(w, times(1)).onNext(anyString()); - verify(w, times(1)).onNext("d"); - verify(w, never()).onError(any(Throwable.class)); - verify(w, times(1)).onCompleted(); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationGroupBy.java b/rxjava-core/src/main/java/rx/operators/OperationGroupBy.java index 96a6861406..a30b97523f 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationGroupBy.java +++ b/rxjava-core/src/main/java/rx/operators/OperationGroupBy.java @@ -15,29 +15,16 @@ */ package rx.operators; -import static org.junit.Assert.*; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import org.junit.Test; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; import rx.observables.GroupedObservable; -import rx.subscriptions.BooleanSubscription; -import rx.subscriptions.Subscriptions; -import rx.util.functions.Action1; import rx.util.functions.Func1; import rx.util.functions.Functions; @@ -174,7 +161,7 @@ private void unsubscribeKey(K key) { private static class GroupedSubject extends GroupedObservable implements Observer { static GroupedSubject create(final K key, final GroupBy parent) { - final AtomicReference> subscribedObserver = new AtomicReference>(OperationGroupBy.emptyObserver()); + final AtomicReference> subscribedObserver = new AtomicReference>(OperationGroupBy. emptyObserver()); return new GroupedSubject(key, new OnSubscribeFunc() { private final SafeObservableSubscription subscription = new SafeObservableSubscription(); @@ -190,7 +177,7 @@ public Subscription onSubscribe(Observer observer) { @Override public void unsubscribe() { // we remove the Observer so we stop emitting further events (they will be ignored if parent continues to send) - subscribedObserver.set(OperationGroupBy.emptyObserver()); + subscribedObserver.set(OperationGroupBy. emptyObserver()); // now we need to notify the parent that we're unsubscribed parent.unsubscribeKey(key); } @@ -223,18 +210,18 @@ public void onNext(T v) { } - private static Observer emptyObserver() { + private static Observer emptyObserver() { return new Observer() { @Override public void onCompleted() { // do nothing } - + @Override public void onError(Throwable e) { // do nothing } - + @Override public void onNext(T t) { // do nothing @@ -251,312 +238,4 @@ private KeyValue(K key, V value) { this.value = value; } } - - public static class UnitTest { - final Func1 length = new Func1() { - @Override - public Integer call(String s) { - return s.length(); - } - }; - - @Test - public void testGroupBy() { - Observable source = Observable.from("one", "two", "three", "four", "five", "six"); - Observable> grouped = Observable.create(groupBy(source, length)); - - Map> map = toMap(grouped); - - assertEquals(3, map.size()); - assertArrayEquals(Arrays.asList("one", "two", "six").toArray(), map.get(3).toArray()); - assertArrayEquals(Arrays.asList("four", "five").toArray(), map.get(4).toArray()); - assertArrayEquals(Arrays.asList("three").toArray(), map.get(5).toArray()); - } - - @Test - public void testEmpty() { - Observable source = Observable.empty(); - Observable> grouped = Observable.create(groupBy(source, length)); - - Map> map = toMap(grouped); - - assertTrue(map.isEmpty()); - } - - @Test - public void testError() { - Observable sourceStrings = Observable.from("one", "two", "three", "four", "five", "six"); - Observable errorSource = Observable.error(new RuntimeException("forced failure")); - Observable source = Observable.concat(sourceStrings, errorSource); - - Observable> grouped = Observable.create(groupBy(source, length)); - - final AtomicInteger groupCounter = new AtomicInteger(); - final AtomicInteger eventCounter = new AtomicInteger(); - final AtomicReference error = new AtomicReference(); - - grouped.mapMany(new Func1, Observable>() { - - @Override - public Observable call(final GroupedObservable o) { - groupCounter.incrementAndGet(); - return o.map(new Func1() { - - @Override - public String call(String v) { - return "Event => key: " + o.getKey() + " value: " + v; - } - }); - } - }).subscribe(new Observer() { - - @Override - public void onCompleted() { - - } - - @Override - public void onError(Throwable e) { - e.printStackTrace(); - error.set(e); - } - - @Override - public void onNext(String v) { - eventCounter.incrementAndGet(); - System.out.println(v); - - } - }); - - assertEquals(3, groupCounter.get()); - assertEquals(6, eventCounter.get()); - assertNotNull(error.get()); - } - - private static Map> toMap(Observable> observable) { - - final ConcurrentHashMap> result = new ConcurrentHashMap>(); - - observable.toBlockingObservable().forEach(new Action1>() { - - @Override - public void call(final GroupedObservable o) { - result.put(o.getKey(), new ConcurrentLinkedQueue()); - o.subscribe(new Action1() { - - @Override - public void call(V v) { - result.get(o.getKey()).add(v); - } - - }); - } - }); - - return result; - } - - /** - * Assert that only a single subscription to a stream occurs and that all events are received. - * - * @throws Throwable - */ - @Test - public void testGroupedEventStream() throws Throwable { - - final AtomicInteger eventCounter = new AtomicInteger(); - final AtomicInteger subscribeCounter = new AtomicInteger(); - final AtomicInteger groupCounter = new AtomicInteger(); - final CountDownLatch latch = new CountDownLatch(1); - final int count = 100; - final int groupCount = 2; - - Observable es = Observable.create(new OnSubscribeFunc() { - - @Override - public Subscription onSubscribe(final Observer observer) { - System.out.println("*** Subscribing to EventStream ***"); - subscribeCounter.incrementAndGet(); - new Thread(new Runnable() { - - @Override - public void run() { - for (int i = 0; i < count; i++) { - Event e = new Event(); - e.source = i % groupCount; - e.message = "Event-" + i; - observer.onNext(e); - } - observer.onCompleted(); - } - - }).start(); - return Subscriptions.empty(); - } - - }); - - es.groupBy(new Func1() { - - @Override - public Integer call(Event e) { - return e.source; - } - }).mapMany(new Func1, Observable>() { - - @Override - public Observable call(GroupedObservable eventGroupedObservable) { - System.out.println("GroupedObservable Key: " + eventGroupedObservable.getKey()); - groupCounter.incrementAndGet(); - - return eventGroupedObservable.map(new Func1() { - - @Override - public String call(Event event) { - return "Source: " + event.source + " Message: " + event.message; - } - }); - - } - }).subscribe(new Observer() { - - @Override - public void onCompleted() { - latch.countDown(); - } - - @Override - public void onError(Throwable e) { - e.printStackTrace(); - latch.countDown(); - } - - @Override - public void onNext(String outputMessage) { - System.out.println(outputMessage); - eventCounter.incrementAndGet(); - } - }); - - latch.await(5000, TimeUnit.MILLISECONDS); - assertEquals(1, subscribeCounter.get()); - assertEquals(groupCount, groupCounter.get()); - assertEquals(count, eventCounter.get()); - - } - - /* - * We will only take 1 group with 20 events from it and then unsubscribe. - */ - @Test - public void testUnsubscribe() throws InterruptedException { - - final AtomicInteger eventCounter = new AtomicInteger(); - final AtomicInteger subscribeCounter = new AtomicInteger(); - final AtomicInteger groupCounter = new AtomicInteger(); - final AtomicInteger sentEventCounter = new AtomicInteger(); - final CountDownLatch latch = new CountDownLatch(1); - final int count = 100; - final int groupCount = 2; - - Observable es = Observable.create(new OnSubscribeFunc() { - - @Override - public Subscription onSubscribe(final Observer observer) { - final BooleanSubscription s = new BooleanSubscription(); - System.out.println("testUnsubscribe => *** Subscribing to EventStream ***"); - subscribeCounter.incrementAndGet(); - new Thread(new Runnable() { - - @Override - public void run() { - for (int i = 0; i < count; i++) { - if (s.isUnsubscribed()) { - break; - } - Event e = new Event(); - e.source = i % groupCount; - e.message = "Event-" + i; - observer.onNext(e); - sentEventCounter.incrementAndGet(); - } - observer.onCompleted(); - } - - }).start(); - return s; - } - - }); - - es.groupBy(new Func1() { - - @Override - public Integer call(Event e) { - return e.source; - } - }) - .take(1) // we want only the first group - .mapMany(new Func1, Observable>() { - - @Override - public Observable call(GroupedObservable eventGroupedObservable) { - System.out.println("testUnsubscribe => GroupedObservable Key: " + eventGroupedObservable.getKey()); - groupCounter.incrementAndGet(); - - return eventGroupedObservable - .take(20) // limit to only 20 events on this group - .map(new Func1() { - - @Override - public String call(Event event) { - return "testUnsubscribe => Source: " + event.source + " Message: " + event.message; - } - }); - - } - }).subscribe(new Observer() { - - @Override - public void onCompleted() { - latch.countDown(); - } - - @Override - public void onError(Throwable e) { - e.printStackTrace(); - latch.countDown(); - } - - @Override - public void onNext(String outputMessage) { - System.out.println(outputMessage); - eventCounter.incrementAndGet(); - } - }); - - latch.await(5000, TimeUnit.MILLISECONDS); - assertEquals(1, subscribeCounter.get()); - assertEquals(1, groupCounter.get()); - assertEquals(20, eventCounter.get()); - // sentEvents will go until 'eventCounter' hits 20 and then unsubscribes - // which means it will also send (but ignore) the 19/20 events for the other group - // It will not however send all 100 events. - assertEquals(39, sentEventCounter.get(), 10); - // gave it a delta of 10 to account for the threading/unsubscription race condition which can vary depending on a machines performance, thread-scheduler, etc - } - - private static class Event { - int source; - String message; - - @Override - public String toString() { - return "Event => source: " + source + " message: " + message; - } - } - - } - } diff --git a/rxjava-core/src/main/java/rx/operators/OperationInterval.java b/rxjava-core/src/main/java/rx/operators/OperationInterval.java index 0f53c884ba..75e50c845d 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationInterval.java +++ b/rxjava-core/src/main/java/rx/operators/OperationInterval.java @@ -15,26 +15,24 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; - -import rx.Observable; import rx.Observable.OnSubscribeFunc; +import rx.Observable; +import rx.observables.ConnectableObservable; import rx.Observer; import rx.Scheduler; import rx.Subscription; import rx.concurrency.Schedulers; -import rx.concurrency.TestScheduler; -import rx.observables.ConnectableObservable; import rx.subscriptions.Subscriptions; import rx.util.functions.Action0; +import rx.util.functions.Func1; +import rx.concurrency.TestScheduler; + +import org.junit.Before; +import org.junit.Test; +import static org.mockito.Mockito.*; +import org.mockito.InOrder; /** * Returns an observable sequence that produces a value after each period. @@ -46,7 +44,7 @@ public final class OperationInterval { * Creates an event each time interval. */ public static OnSubscribeFunc interval(long interval, TimeUnit unit) { - return interval(interval, unit, Schedulers.executor(Executors.newSingleThreadScheduledExecutor())); + return interval(interval, unit, Schedulers.threadPoolForComputation()); } /** @@ -66,8 +64,9 @@ private static class Interval implements OnSubscribeFunc { private final long period; private final TimeUnit unit; private final Scheduler scheduler; - + private long currentValue; + private boolean errorOccurred; private Interval(long period, TimeUnit unit, Scheduler scheduler) { this.period = period; @@ -80,177 +79,27 @@ public Subscription onSubscribe(final Observer observer) { final Subscription wrapped = scheduler.schedulePeriodically(new Action0() { @Override public void call() { - observer.onNext(currentValue); - currentValue++; + if (!errorOccurred) { + try { + observer.onNext(currentValue); + currentValue++; + } catch (Throwable t) { + errorOccurred = true; + observer.onError(t); + } + } } }, period, period, unit); - + return Subscriptions.create(new Action0() { @Override public void call() { wrapped.unsubscribe(); - observer.onCompleted(); + if (!errorOccurred) { + observer.onCompleted(); + } } }); } } - - public static class UnitTest { - private TestScheduler scheduler; - private Observer observer; - private Observer observer2; - - @Before - @SuppressWarnings("unchecked") // due to mocking - public void before() { - scheduler = new TestScheduler(); - observer = mock(Observer.class); - observer2 = mock(Observer.class); - } - - @Test - public void testInterval() { - Observable w = Observable.create(OperationInterval.interval(1, TimeUnit.SECONDS, scheduler)); - Subscription sub = w.subscribe(observer); - - verify(observer, never()).onNext(0L); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - scheduler.advanceTimeTo(2, TimeUnit.SECONDS); - - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext(0L); - inOrder.verify(observer, times(1)).onNext(1L); - inOrder.verify(observer, never()).onNext(2L); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - sub.unsubscribe(); - scheduler.advanceTimeTo(4, TimeUnit.SECONDS); - verify(observer, never()).onNext(2L); - verify(observer, times(1)).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - } - - @Test - public void testWithMultipleSubscribersStartingAtSameTime() { - Observable w = Observable.create(OperationInterval.interval(1, TimeUnit.SECONDS, scheduler)); - Subscription sub1 = w.subscribe(observer); - Subscription sub2 = w.subscribe(observer2); - - verify(observer, never()).onNext(anyLong()); - verify(observer2, never()).onNext(anyLong()); - - scheduler.advanceTimeTo(2, TimeUnit.SECONDS); - - InOrder inOrder1 = inOrder(observer); - InOrder inOrder2 = inOrder(observer2); - - inOrder1.verify(observer, times(1)).onNext(0L); - inOrder1.verify(observer, times(1)).onNext(1L); - inOrder1.verify(observer, never()).onNext(2L); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - inOrder2.verify(observer2, times(1)).onNext(0L); - inOrder2.verify(observer2, times(1)).onNext(1L); - inOrder2.verify(observer2, never()).onNext(2L); - verify(observer2, never()).onCompleted(); - verify(observer2, never()).onError(any(Throwable.class)); - - sub1.unsubscribe(); - sub2.unsubscribe(); - scheduler.advanceTimeTo(4, TimeUnit.SECONDS); - - verify(observer, never()).onNext(2L); - verify(observer, times(1)).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - verify(observer2, never()).onNext(2L); - verify(observer2, times(1)).onCompleted(); - verify(observer2, never()).onError(any(Throwable.class)); - } - - @Test - public void testWithMultipleStaggeredSubscribers() { - Observable w = Observable.create(OperationInterval.interval(1, TimeUnit.SECONDS, scheduler)); - Subscription sub1 = w.subscribe(observer); - - verify(observer, never()).onNext(anyLong()); - - scheduler.advanceTimeTo(2, TimeUnit.SECONDS); - Subscription sub2 = w.subscribe(observer2); - - InOrder inOrder1 = inOrder(observer); - inOrder1.verify(observer, times(1)).onNext(0L); - inOrder1.verify(observer, times(1)).onNext(1L); - inOrder1.verify(observer, never()).onNext(2L); - - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer2, never()).onNext(anyLong()); - - scheduler.advanceTimeTo(4, TimeUnit.SECONDS); - - inOrder1.verify(observer, times(1)).onNext(2L); - inOrder1.verify(observer, times(1)).onNext(3L); - - InOrder inOrder2 = inOrder(observer2); - inOrder2.verify(observer2, times(1)).onNext(0L); - inOrder2.verify(observer2, times(1)).onNext(1L); - - sub1.unsubscribe(); - sub2.unsubscribe(); - - inOrder1.verify(observer, never()).onNext(anyLong()); - inOrder1.verify(observer, times(1)).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - inOrder2.verify(observer2, never()).onNext(anyLong()); - inOrder2.verify(observer2, times(1)).onCompleted(); - verify(observer2, never()).onError(any(Throwable.class)); - } - - @Test - public void testWithMultipleStaggeredSubscribersAndPublish() { - ConnectableObservable w = Observable.create(OperationInterval.interval(1, TimeUnit.SECONDS, scheduler)).publish(); - Subscription sub1 = w.subscribe(observer); - w.connect(); - - verify(observer, never()).onNext(anyLong()); - - scheduler.advanceTimeTo(2, TimeUnit.SECONDS); - Subscription sub2 = w.subscribe(observer2); - - InOrder inOrder1 = inOrder(observer); - inOrder1.verify(observer, times(1)).onNext(0L); - inOrder1.verify(observer, times(1)).onNext(1L); - inOrder1.verify(observer, never()).onNext(2L); - - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - verify(observer2, never()).onNext(anyLong()); - - scheduler.advanceTimeTo(4, TimeUnit.SECONDS); - - inOrder1.verify(observer, times(1)).onNext(2L); - inOrder1.verify(observer, times(1)).onNext(3L); - - InOrder inOrder2 = inOrder(observer2); - inOrder2.verify(observer2, times(1)).onNext(2L); - inOrder2.verify(observer2, times(1)).onNext(3L); - - sub1.unsubscribe(); - sub2.unsubscribe(); - - inOrder1.verify(observer, never()).onNext(anyLong()); - inOrder1.verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - inOrder2.verify(observer2, never()).onNext(anyLong()); - inOrder2.verify(observer2, never()).onCompleted(); - verify(observer2, never()).onError(any(Throwable.class)); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationJoin.java b/rxjava-core/src/main/java/rx/operators/OperationJoin.java new file mode 100644 index 0000000000..b75b8498b0 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationJoin.java @@ -0,0 +1,277 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import java.util.HashMap; +import java.util.Map; +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.SerialSubscription; +import rx.util.functions.Func1; +import rx.util.functions.Func2; + +/** + * Correlates the elements of two sequences based on overlapping durations. + */ +public class OperationJoin implements OnSubscribeFunc { + final Observable left; + final Observable right; + final Func1> leftDurationSelector; + final Func1> rightDurationSelector; + final Func2 resultSelector; + public OperationJoin( + Observable left, + Observable right, + Func1> leftDurationSelector, + Func1> rightDurationSelector, + Func2 resultSelector) { + this.left = left; + this.right = right; + this.leftDurationSelector = leftDurationSelector; + this.rightDurationSelector = rightDurationSelector; + this.resultSelector = resultSelector; + } + + @Override + public Subscription onSubscribe(Observer t1) { + SerialSubscription cancel = new SerialSubscription(); + ResultSink result = new ResultSink(t1, cancel); + cancel.setSubscription(result.run()); + return cancel; + } + /** Manage the left and right sources. */ + class ResultSink { + final Object gate = new Object(); + final CompositeSubscription group = new CompositeSubscription(); + boolean leftDone; + int leftId; + final Map leftMap = new HashMap(); + boolean rightDone; + int rightId; + final Map rightMap = new HashMap(); + final Observer observer; + final Subscription cancel; + public ResultSink(Observer observer, Subscription cancel) { + this.observer = observer; + this.cancel = cancel; + } + public Subscription run() { + SerialSubscription leftCancel = new SerialSubscription(); + SerialSubscription rightCancel = new SerialSubscription(); + + group.add(leftCancel); + group.add(rightCancel); + + leftCancel.setSubscription(left.subscribe(new LeftObserver(leftCancel))); + rightCancel.setSubscription(right.subscribe(new RightObserver(rightCancel))); + + return group; + } + /** Observes the left values. */ + class LeftObserver implements Observer { + final Subscription self; + public LeftObserver(Subscription self) { + this.self = self; + } + protected void expire(int id, Subscription resource) { + synchronized (gate) { + if (leftMap.remove(id) != null && leftMap.isEmpty() && leftDone) { + observer.onCompleted(); + cancel.unsubscribe(); + } + } + group.remove(resource); + } + @Override + public void onNext(TLeft args) { + int id; + synchronized (gate) { + id = leftId++; + leftMap.put(id, args); + } + SerialSubscription md = new SerialSubscription(); + group.add(md); + + Observable duration; + try { + duration = leftDurationSelector.call(args); + } catch (Throwable t) { + observer.onError(t); + cancel.unsubscribe(); + return; + } + + md.setSubscription(duration.subscribe(new LeftDurationObserver(id, md))); + + synchronized (gate) { + for (TRight r : rightMap.values()) { + R result; + try { + result = resultSelector.call(args, r); + } catch (Throwable t) { + observer.onError(t); + cancel.unsubscribe(); + return; + } + observer.onNext(result); + } + } + } + @Override + public void onError(Throwable e) { + synchronized (gate) { + observer.onError(e); + cancel.unsubscribe(); + } + } + @Override + public void onCompleted() { + synchronized (gate) { + leftDone = true; + if (rightDone || leftMap.isEmpty()) { + observer.onCompleted(); + cancel.unsubscribe(); + } else { + self.unsubscribe(); + } + } + } + /** Observes the left duration. */ + class LeftDurationObserver implements Observer { + final int id; + final Subscription handle; + public LeftDurationObserver(int id, Subscription handle) { + this.id = id; + this.handle = handle; + } + + @Override + public void onNext(TLeftDuration args) { + expire(id, handle); + } + + @Override + public void onError(Throwable e) { + LeftObserver.this.onError(e); + } + + @Override + public void onCompleted() { + expire(id, handle); + } + + } + } + /** Observes the right values. */ + class RightObserver implements Observer { + final Subscription self; + public RightObserver(Subscription self) { + this.self = self; + } + void expire(int id, Subscription resource) { + synchronized (gate) { + if (rightMap.remove(id) != null && rightMap.isEmpty() && rightDone) { + observer.onCompleted(); + cancel.unsubscribe(); + } + } + group.remove(resource); + } + @Override + public void onNext(TRight args) { + int id = 0; + synchronized (gate) { + id = rightId++; + rightMap.put(id, args); + } + SerialSubscription md = new SerialSubscription(); + group.add(md); + + Observable duration; + try { + duration = rightDurationSelector.call(args); + } catch (Throwable t) { + observer.onError(t); + cancel.unsubscribe(); + return; + } + + md.setSubscription(duration.subscribe(new RightDurationObserver(id, md))); + + synchronized (gate) { + for (TLeft lv : leftMap.values()) { + R result; + try { + result = resultSelector.call(lv, args); + } catch (Throwable t) { + observer.onError(t); + cancel.unsubscribe(); + return; + } + observer.onNext(result); + } + } + } + @Override + public void onError(Throwable e) { + synchronized (gate) { + observer.onError(e); + cancel.unsubscribe(); + } + } + @Override + public void onCompleted() { + synchronized (gate) { + rightDone = true; + if (leftDone || rightMap.isEmpty()) { + observer.onCompleted(); + cancel.unsubscribe(); + } else { + self.unsubscribe(); + } + } + } + /** Observe the right duration. */ + class RightDurationObserver implements Observer { + final int id; + final Subscription handle; + public RightDurationObserver(int id, Subscription handle) { + this.id = id; + this.handle = handle; + } + + @Override + public void onNext(TRightDuration args) { + expire(id, handle); + } + + @Override + public void onError(Throwable e) { + RightObserver.this.onError(e); + } + + @Override + public void onCompleted() { + expire(id, handle); + } + + } + } + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationJoinPatterns.java b/rxjava-core/src/main/java/rx/operators/OperationJoinPatterns.java new file mode 100644 index 0000000000..6ff1c4346e --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationJoinPatterns.java @@ -0,0 +1,131 @@ + /** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package rx.operators; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.joins.ActivePlan0; +import rx.joins.JoinObserver; +import rx.joins.Pattern1; +import rx.joins.Pattern2; +import rx.joins.Plan0; +import rx.subjects.PublishSubject; +import rx.subscriptions.CompositeSubscription; +import rx.util.functions.Action1; +import rx.util.functions.Func1; +import rx.util.functions.Func2; + +/** + * Join patterns: And, Then, When. + */ +public class OperationJoinPatterns { + /** + * Creates a pattern that matches when both observable sequences have an available element. + */ + public static Pattern2 and(/* this */Observable left, Observable right) { + if (left == null) { + throw new NullPointerException("left"); + } + if (right == null) { + throw new NullPointerException("right"); + } + return new Pattern2(left, right); + } + /** + * Matches when the observable sequence has an available element and projects the element by invoking the selector function. + */ + public static Plan0 then(/* this */Observable source, Func1 selector) { + if (source == null) { + throw new NullPointerException("source"); + } + if (selector == null) { + throw new NullPointerException("selector"); + } + return new Pattern1(source).then(selector); + } + /** + * Joins together the results from several patterns. + */ + public static OnSubscribeFunc when(Plan0... plans) { + if (plans == null) { + throw new NullPointerException("plans"); + } + return when(Arrays.asList(plans)); + } + /** + * Joins together the results from several patterns. + */ + public static OnSubscribeFunc when(final Iterable> plans) { + if (plans == null) { + throw new NullPointerException("plans"); + } + return new OnSubscribeFunc() { + @Override + public Subscription onSubscribe(final Observer t1) { + final Map externalSubscriptions = new HashMap(); + final Object gate = new Object(); + final List activePlans = new ArrayList(); + + final Observer out = new Observer() { + @Override + public void onNext(R args) { + t1.onNext(args); + } + @Override + public void onError(Throwable e) { + for (JoinObserver po : externalSubscriptions.values()) { + po.unsubscribe(); + } + t1.onError(e); + } + @Override + public void onCompleted() { + t1.onCompleted(); + } + }; + + try { + for (Plan0 plan : plans) { + activePlans.add(plan.activate(externalSubscriptions, out, new Action1() { + @Override + public void call(ActivePlan0 activePlan) { + activePlans.remove(activePlan); + if (activePlans.isEmpty()) { + out.onCompleted(); + } + } + })); + } + } catch (Throwable t) { + return Observable.error(t).subscribe(t1); + } + CompositeSubscription group = new CompositeSubscription(); + for (JoinObserver jo : externalSubscriptions.values()) { + jo.subscribe(gate); + group.add(jo); + } + return group; + } + }; + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationLast.java b/rxjava-core/src/main/java/rx/operators/OperationLast.java new file mode 100644 index 0000000000..964afd5176 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationLast.java @@ -0,0 +1,82 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; + +/** + * Emit an Observable with the last emitted item + * or onError(new IllegalArgumentException("Sequence contains no elements")) if no elements received. + */ +public class OperationLast { + + /** + * Accepts a sequence and returns a sequence that is the last emitted item + * or an error if no items are emitted (empty sequence). + * + * @param sequence + * the input sequence. + * @param + * the type of the sequence. + * @return a sequence containing the last emitted item or that has onError invoked on it if no items + */ + public static OnSubscribeFunc last(final Observable sequence) { + return new OnSubscribeFunc() { + final AtomicReference last = new AtomicReference(); + final AtomicBoolean hasLast = new AtomicBoolean(false); + + @Override + public Subscription onSubscribe(final Observer observer) { + return sequence.subscribe(new Observer() { + + @Override + public void onCompleted() { + /* + * We don't need to worry about the following being non-atomic + * since an Observable sequence is serial so we will not receive + * concurrent executions. + */ + if (hasLast.get()) { + observer.onNext(last.get()); + observer.onCompleted(); + } else { + observer.onError(new IllegalArgumentException("Sequence contains no elements")); + } + } + + @Override + public void onError(Throwable e) { + observer.onError(e); + } + + @Override + public void onNext(T value) { + last.set(value); + hasLast.set(true); + } + }); + } + + }; + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationMap.java b/rxjava-core/src/main/java/rx/operators/OperationMap.java index fa7fca2758..d78b5dc6ef 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationMap.java +++ b/rxjava-core/src/main/java/rx/operators/OperationMap.java @@ -15,24 +15,12 @@ */ package rx.operators; -import static org.junit.Assert.*; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; import rx.util.functions.Func1; +import rx.util.functions.Func2; /** * Applies a function of your choosing to every item emitted by an Observable, and returns this @@ -56,8 +44,37 @@ public final class OperationMap { * the type of the output sequence. * @return a sequence that is the result of applying the transformation function to each item in the input sequence. */ - public static OnSubscribeFunc map(Observable sequence, Func1 func) { - return new MapObservable(sequence, func); + public static OnSubscribeFunc map(final Observable sequence, final Func1 func) { + return mapWithIndex(sequence, new Func2() { + @Override + public R call(T value, @SuppressWarnings("unused") Integer unused) { + return func.call(value); + } + }); + } + + /** + * Accepts a sequence and a transformation function. Returns a sequence that is the result of + * applying the transformation function to each item in the sequence. + * + * @param sequence + * the input sequence. + * @param func + * a function to apply to each item in the sequence. The function gets the index of the emitted item + * as additional parameter. + * @param + * the type of the input sequence. + * @param + * the type of the output sequence. + * @return a sequence that is the result of applying the transformation function to each item in the input sequence. + */ + public static OnSubscribeFunc mapWithIndex(final Observable sequence, final Func2 func) { + return new OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + return new MapObservable(sequence, func).onSubscribe(observer); + } + }; } /** @@ -89,203 +106,35 @@ public static OnSubscribeFunc mapMany(Observable sequence * the type of the output sequence. */ private static class MapObservable implements OnSubscribeFunc { - public MapObservable(Observable sequence, Func1 func) { + public MapObservable(Observable sequence, Func2 func) { this.sequence = sequence; this.func = func; } - private Observable sequence; - - private Func1 func; - - public Subscription onSubscribe(Observer observer) { - return sequence.subscribe(new MapObserver(observer, func)); - } - } - - /** - * An observer that applies a transformation function to each item and forwards the result to an inner observer. - * - * @param - * the type of the observer items. - * @param - * the type of the inner observer items. - */ - private static class MapObserver implements Observer { - public MapObserver(Observer observer, Func1 func) { - this.observer = observer; - this.func = func; - } - - Observer observer; - - Func1 func; - - public void onNext(T value) { - // let the exception be thrown if func fails as a SafeObserver wrapping this will handle it - observer.onNext(func.call(value)); - } - - public void onError(Throwable ex) { - observer.onError(ex); - } - - public void onCompleted() { - observer.onCompleted(); - } - } - - public static class UnitTest { - @Mock - Observer stringObserver; - - @Before - public void before() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testMap() { - Map m1 = getMap("One"); - Map m2 = getMap("Two"); - Observable> observable = Observable.from(m1, m2); - - Observable m = Observable.create(map(observable, new Func1, String>() { + private final Observable sequence; + private final Func2 func; + private int index; + @Override + public Subscription onSubscribe(final Observer observer) { + final SafeObservableSubscription subscription = new SafeObservableSubscription(); + return subscription.wrap(sequence.subscribe(new SafeObserver(subscription, new Observer() { @Override - public String call(Map map) { - return map.get("firstName"); + public void onNext(T value) { + observer.onNext(func.call(value, index)); + index++; } - })); - m.subscribe(stringObserver); - - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onNext("OneFirst"); - verify(stringObserver, times(1)).onNext("TwoFirst"); - verify(stringObserver, times(1)).onCompleted(); - - } - - @Test - public void testMapMany() { - /* simulate a top-level async call which returns IDs */ - Observable ids = Observable.from(1, 2); - - /* now simulate the behavior to take those IDs and perform nested async calls based on them */ - Observable m = Observable.create(mapMany(ids, new Func1>() { - @Override - public Observable call(Integer id) { - /* simulate making a nested async call which creates another Observable */ - Observable> subObservable = null; - if (id == 1) { - Map m1 = getMap("One"); - Map m2 = getMap("Two"); - subObservable = Observable.from(m1, m2); - } else { - Map m3 = getMap("Three"); - Map m4 = getMap("Four"); - subObservable = Observable.from(m3, m4); - } - - /* simulate kicking off the async call and performing a select on it to transform the data */ - return Observable.create(map(subObservable, new Func1, String>() { - @Override - public String call(Map map) { - return map.get("firstName"); - } - })); + public void onError(Throwable ex) { + observer.onError(ex); } - })); - m.subscribe(stringObserver); - - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onNext("OneFirst"); - verify(stringObserver, times(1)).onNext("TwoFirst"); - verify(stringObserver, times(1)).onNext("ThreeFirst"); - verify(stringObserver, times(1)).onNext("FourFirst"); - verify(stringObserver, times(1)).onCompleted(); - } - - @Test - public void testMapMany2() { - Map m1 = getMap("One"); - Map m2 = getMap("Two"); - Observable> observable1 = Observable.from(m1, m2); - - Map m3 = getMap("Three"); - Map m4 = getMap("Four"); - Observable> observable2 = Observable.from(m3, m4); - - Observable>> observable = Observable.from(observable1, observable2); - - Observable m = Observable.create(mapMany(observable, new Func1>, Observable>() { - @Override - public Observable call(Observable> o) { - return Observable.create(map(o, new Func1, String>() { - - @Override - public String call(Map map) { - return map.get("firstName"); - } - })); - } - - })); - m.subscribe(stringObserver); - - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onNext("OneFirst"); - verify(stringObserver, times(1)).onNext("TwoFirst"); - verify(stringObserver, times(1)).onNext("ThreeFirst"); - verify(stringObserver, times(1)).onNext("FourFirst"); - verify(stringObserver, times(1)).onCompleted(); - - } - - @Test - public void testMapWithSynchronousObservableContainingError() { - Observable w = Observable.from("one", "fail", "two", "three", "fail"); - final AtomicInteger c1 = new AtomicInteger(); - final AtomicInteger c2 = new AtomicInteger(); - Observable m = Observable.create(map(w, new Func1() { - public String call(String s) { - if ("fail".equals(s)) - throw new RuntimeException("Forced Failure"); - System.out.println("BadMapper:" + s); - c1.incrementAndGet(); - return s; - } - })).map(new Func1() { - public String call(String s) { - System.out.println("SecondMapper:" + s); - c2.incrementAndGet(); - return s; + public void onCompleted() { + observer.onCompleted(); } - }); - - m.subscribe(stringObserver); - - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, never()).onNext("two"); - verify(stringObserver, never()).onNext("three"); - verify(stringObserver, never()).onCompleted(); - verify(stringObserver, times(1)).onError(any(Throwable.class)); - - // we should have only returned 1 value: "one" - assertEquals(1, c1.get()); - assertEquals(1, c2.get()); - } - - private Map getMap(String prefix) { - Map m = new HashMap(); - m.put("firstName", prefix + "First"); - m.put("lastName", prefix + "Last"); - return m; + }))); } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationMaterialize.java b/rxjava-core/src/main/java/rx/operators/OperationMaterialize.java index 7d906720fa..f1998ffe64 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationMaterialize.java +++ b/rxjava-core/src/main/java/rx/operators/OperationMaterialize.java @@ -15,14 +15,6 @@ */ package rx.operators; -import static org.junit.Assert.*; - -import java.util.List; -import java.util.Vector; -import java.util.concurrent.ExecutionException; - -import org.junit.Test; - import rx.Notification; import rx.Observable; import rx.Observable.OnSubscribeFunc; @@ -85,140 +77,4 @@ public void onNext(T value) { } } - - public static class UnitTest { - @Test - public void testMaterialize1() { - // null will cause onError to be triggered before "three" can be returned - final TestAsyncErrorObservable o1 = new TestAsyncErrorObservable("one", "two", null, "three"); - - TestObserver Observer = new TestObserver(); - Observable> m = Observable.create(materialize(Observable.create(o1))); - m.subscribe(Observer); - - try { - o1.t.join(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - assertFalse(Observer.onError); - assertTrue(Observer.onCompleted); - assertEquals(3, Observer.notifications.size()); - assertEquals("one", Observer.notifications.get(0).getValue()); - assertTrue(Observer.notifications.get(0).isOnNext()); - assertEquals("two", Observer.notifications.get(1).getValue()); - assertTrue(Observer.notifications.get(1).isOnNext()); - assertEquals(NullPointerException.class, Observer.notifications.get(2).getThrowable().getClass()); - assertTrue(Observer.notifications.get(2).isOnError()); - } - - @Test - public void testMaterialize2() { - final TestAsyncErrorObservable o1 = new TestAsyncErrorObservable("one", "two", "three"); - - TestObserver Observer = new TestObserver(); - Observable> m = Observable.create(materialize(Observable.create(o1))); - m.subscribe(Observer); - - try { - o1.t.join(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - assertFalse(Observer.onError); - assertTrue(Observer.onCompleted); - assertEquals(4, Observer.notifications.size()); - assertEquals("one", Observer.notifications.get(0).getValue()); - assertTrue(Observer.notifications.get(0).isOnNext()); - assertEquals("two", Observer.notifications.get(1).getValue()); - assertTrue(Observer.notifications.get(1).isOnNext()); - assertEquals("three", Observer.notifications.get(2).getValue()); - assertTrue(Observer.notifications.get(2).isOnNext()); - assertTrue(Observer.notifications.get(3).isOnCompleted()); - } - - @Test - public void testMultipleSubscribes() throws InterruptedException, ExecutionException { - final TestAsyncErrorObservable o = new TestAsyncErrorObservable("one", "two", null, "three"); - - Observable> m = Observable.create(materialize(Observable.create(o))); - - assertEquals(3, m.toList().toBlockingObservable().toFuture().get().size()); - assertEquals(3, m.toList().toBlockingObservable().toFuture().get().size()); - } - - } - - private static class TestObserver implements Observer> { - - boolean onCompleted = false; - boolean onError = false; - List> notifications = new Vector>(); - - @Override - public void onCompleted() { - this.onCompleted = true; - } - - @Override - public void onError(Throwable e) { - this.onError = true; - } - - @Override - public void onNext(Notification value) { - this.notifications.add(value); - } - - } - - private static class TestAsyncErrorObservable implements OnSubscribeFunc { - - String[] valuesToReturn; - - TestAsyncErrorObservable(String... values) { - valuesToReturn = values; - } - - volatile Thread t; - - @Override - public Subscription onSubscribe(final Observer observer) { - t = new Thread(new Runnable() { - - @Override - public void run() { - for (String s : valuesToReturn) { - if (s == null) { - System.out.println("throwing exception"); - try { - Thread.sleep(100); - } catch (Throwable e) { - - } - observer.onError(new NullPointerException()); - return; - } else { - observer.onNext(s); - } - } - System.out.println("subscription complete"); - observer.onCompleted(); - } - - }); - t.start(); - - return new Subscription() { - - @Override - public void unsubscribe() { - - } - - }; - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationMerge.java b/rxjava-core/src/main/java/rx/operators/OperationMerge.java index 1152ee5637..12619e9176 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationMerge.java +++ b/rxjava-core/src/main/java/rx/operators/OperationMerge.java @@ -15,27 +15,15 @@ */ package rx.operators; -import static org.junit.Assert.*; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; +import rx.subscriptions.CompositeSubscription; /** * Flattens a list of Observables into one Observable sequence, without any transformation. @@ -125,6 +113,7 @@ private MergeObservable(Observable> sequences) } public Subscription onSubscribe(Observer actualObserver) { + CompositeSubscription completeSubscription = new CompositeSubscription(); /** * We must synchronize a merge because we subscribe to multiple sequences in parallel that will each be emitting. @@ -134,15 +123,16 @@ public Subscription onSubscribe(Observer actualObserver) { * Bug report: https://github.com/Netflix/RxJava/issues/200 */ SafeObservableSubscription subscription = new SafeObservableSubscription(ourSubscription); + completeSubscription.add(subscription); SynchronizedObserver synchronizedObserver = new SynchronizedObserver(actualObserver, subscription); /** * Subscribe to the parent Observable to get to the children Observables */ - sequences.subscribe(new ParentObserver(synchronizedObserver)); + completeSubscription.add(sequences.subscribe(new ParentObserver(synchronizedObserver))); /* return our subscription to allow unsubscribing */ - return subscription; + return completeSubscription; } /** @@ -280,371 +270,4 @@ public void onNext(T args) { } } - - public static class UnitTest { - @Mock - Observer stringObserver; - - @Before - public void before() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testMergeObservableOfObservables() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); - - Observable> observableOfObservables = Observable.create(new OnSubscribeFunc>() { - - @Override - public Subscription onSubscribe(Observer> observer) { - // simulate what would happen in an observable - observer.onNext(o1); - observer.onNext(o2); - observer.onCompleted(); - - return new Subscription() { - - @Override - public void unsubscribe() { - // unregister ... will never be called here since we are executing synchronously - } - - }; - } - - }); - Observable m = Observable.create(merge(observableOfObservables)); - m.subscribe(stringObserver); - - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onCompleted(); - verify(stringObserver, times(2)).onNext("hello"); - } - - @Test - public void testMergeArray() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); - - @SuppressWarnings("unchecked") - Observable m = Observable.create(merge(o1, o2)); - m.subscribe(stringObserver); - - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(2)).onNext("hello"); - verify(stringObserver, times(1)).onCompleted(); - } - - @Test - public void testMergeList() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); - List> listOfObservables = new ArrayList>(); - listOfObservables.add(o1); - listOfObservables.add(o2); - - Observable m = Observable.create(merge(listOfObservables)); - m.subscribe(stringObserver); - - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onCompleted(); - verify(stringObserver, times(2)).onNext("hello"); - } - - @Test - public void testUnSubscribe() { - TestObservable tA = new TestObservable(); - TestObservable tB = new TestObservable(); - - @SuppressWarnings("unchecked") - Observable m = Observable.create(merge(Observable.create(tA), Observable.create(tB))); - Subscription s = m.subscribe(stringObserver); - - tA.sendOnNext("Aone"); - tB.sendOnNext("Bone"); - s.unsubscribe(); - tA.sendOnNext("Atwo"); - tB.sendOnNext("Btwo"); - tA.sendOnCompleted(); - tB.sendOnCompleted(); - - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onNext("Aone"); - verify(stringObserver, times(1)).onNext("Bone"); - assertTrue(tA.unsubscribed); - assertTrue(tB.unsubscribed); - verify(stringObserver, never()).onNext("Atwo"); - verify(stringObserver, never()).onNext("Btwo"); - verify(stringObserver, never()).onCompleted(); - } - - @Test - public void testMergeArrayWithThreading() { - final TestASynchronousObservable o1 = new TestASynchronousObservable(); - final TestASynchronousObservable o2 = new TestASynchronousObservable(); - - @SuppressWarnings("unchecked") - Observable m = Observable.create(merge(Observable.create(o1), Observable.create(o2))); - m.subscribe(stringObserver); - - try { - o1.t.join(); - o2.t.join(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(2)).onNext("hello"); - verify(stringObserver, times(1)).onCompleted(); - } - - @Test - public void testSynchronizationOfMultipleSequences() throws Throwable { - final TestASynchronousObservable o1 = new TestASynchronousObservable(); - final TestASynchronousObservable o2 = new TestASynchronousObservable(); - - // use this latch to cause onNext to wait until we're ready to let it go - final CountDownLatch endLatch = new CountDownLatch(1); - - final AtomicInteger concurrentCounter = new AtomicInteger(); - final AtomicInteger totalCounter = new AtomicInteger(); - - @SuppressWarnings("unchecked") - Observable m = Observable.create(merge(Observable.create(o1), Observable.create(o2))); - m.subscribe(new Observer() { - - @Override - public void onCompleted() { - - } - - @Override - public void onError(Throwable e) { - throw new RuntimeException("failed", e); - } - - @Override - public void onNext(String v) { - totalCounter.incrementAndGet(); - concurrentCounter.incrementAndGet(); - try { - // wait here until we're done asserting - endLatch.await(); - } catch (InterruptedException e) { - e.printStackTrace(); - throw new RuntimeException("failed", e); - } finally { - concurrentCounter.decrementAndGet(); - } - } - - }); - - // wait for both observables to send (one should be blocked) - o1.onNextBeingSent.await(); - o2.onNextBeingSent.await(); - - // I can't think of a way to know for sure that both threads have or are trying to send onNext - // since I can't use a CountDownLatch for "after" onNext since I want to catch during it - // but I can't know for sure onNext is invoked - // so I'm unfortunately reverting to using a Thread.sleep to allow the process scheduler time - // to make sure after o1.onNextBeingSent and o2.onNextBeingSent are hit that the following - // onNext is invoked. - - Thread.sleep(300); - - try { // in try/finally so threads are released via latch countDown even if assertion fails - assertEquals(1, concurrentCounter.get()); - } finally { - // release so it can finish - endLatch.countDown(); - } - - try { - o1.t.join(); - o2.t.join(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - assertEquals(2, totalCounter.get()); - assertEquals(0, concurrentCounter.get()); - } - - /** - * unit test from OperationMergeDelayError backported here to show how these use cases work with normal merge - */ - @Test - public void testError1() { - // we are using synchronous execution to test this exactly rather than non-deterministic concurrent behavior - final Observable o1 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" - final Observable o2 = Observable.create(new TestErrorObservable("one", "two", "three")); // we expect to lose all of these since o1 is done first and fails - - @SuppressWarnings("unchecked") - Observable m = Observable.create(merge(o1, o2)); - m.subscribe(stringObserver); - - verify(stringObserver, times(1)).onError(any(NullPointerException.class)); - verify(stringObserver, never()).onCompleted(); - verify(stringObserver, times(0)).onNext("one"); - verify(stringObserver, times(0)).onNext("two"); - verify(stringObserver, times(0)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(0)).onNext("five"); - verify(stringObserver, times(0)).onNext("six"); - } - - /** - * unit test from OperationMergeDelayError backported here to show how these use cases work with normal merge - */ - @Test - public void testError2() { - // we are using synchronous execution to test this exactly rather than non-deterministic concurrent behavior - final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); - final Observable o2 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" - final Observable o3 = Observable.create(new TestErrorObservable("seven", "eight", null));// we expect to lose all of these since o2 is done first and fails - final Observable o4 = Observable.create(new TestErrorObservable("nine"));// we expect to lose all of these since o2 is done first and fails - - @SuppressWarnings("unchecked") - Observable m = Observable.create(merge(o1, o2, o3, o4)); - m.subscribe(stringObserver); - - verify(stringObserver, times(1)).onError(any(NullPointerException.class)); - verify(stringObserver, never()).onCompleted(); - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, times(1)).onNext("two"); - verify(stringObserver, times(1)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(0)).onNext("five"); - verify(stringObserver, times(0)).onNext("six"); - verify(stringObserver, times(0)).onNext("seven"); - verify(stringObserver, times(0)).onNext("eight"); - verify(stringObserver, times(0)).onNext("nine"); - } - - private static class TestSynchronousObservable implements OnSubscribeFunc { - - @Override - public Subscription onSubscribe(Observer observer) { - - observer.onNext("hello"); - observer.onCompleted(); - - return new Subscription() { - - @Override - public void unsubscribe() { - // unregister ... will never be called here since we are executing synchronously - } - - }; - } - } - - private static class TestASynchronousObservable implements OnSubscribeFunc { - Thread t; - final CountDownLatch onNextBeingSent = new CountDownLatch(1); - - @Override - public Subscription onSubscribe(final Observer observer) { - t = new Thread(new Runnable() { - - @Override - public void run() { - onNextBeingSent.countDown(); - observer.onNext("hello"); - // I can't use a countDownLatch to prove we are actually sending 'onNext' - // since it will block if synchronized and I'll deadlock - observer.onCompleted(); - } - - }); - t.start(); - - return new Subscription() { - - @Override - public void unsubscribe() { - - } - - }; - } - } - - /** - * A Observable that doesn't do the right thing on UnSubscribe/Error/etc in that it will keep sending events down the pipe regardless of what happens. - */ - private static class TestObservable implements OnSubscribeFunc { - - Observer observer = null; - volatile boolean unsubscribed = false; - Subscription s = new Subscription() { - - @Override - public void unsubscribe() { - unsubscribed = true; - - } - - }; - - /* used to simulate subscription */ - public void sendOnCompleted() { - observer.onCompleted(); - } - - /* used to simulate subscription */ - public void sendOnNext(String value) { - observer.onNext(value); - } - - /* used to simulate subscription */ - @SuppressWarnings("unused") - public void sendOnError(Throwable e) { - observer.onError(e); - } - - @Override - public Subscription onSubscribe(final Observer observer) { - this.observer = observer; - return s; - } - } - - private static class TestErrorObservable implements OnSubscribeFunc { - - String[] valuesToReturn; - - TestErrorObservable(String... values) { - valuesToReturn = values; - } - - @Override - public Subscription onSubscribe(Observer observer) { - - for (String s : valuesToReturn) { - if (s == null) { - System.out.println("throwing exception"); - observer.onError(new NullPointerException()); - } else { - observer.onNext(s); - } - } - observer.onCompleted(); - - return new Subscription() { - - @Override - public void unsubscribe() { - // unregister ... will never be called here since we are executing synchronously - } - - }; - } - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationMergeDelayError.java b/rxjava-core/src/main/java/rx/operators/OperationMergeDelayError.java index abb450dd4d..dbca17522d 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationMergeDelayError.java +++ b/rxjava-core/src/main/java/rx/operators/OperationMergeDelayError.java @@ -15,21 +15,11 @@ */ package rx.operators; -import static org.junit.Assert.*; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -342,484 +332,4 @@ public void onNext(T args) { } } - - public static class UnitTest { - @Mock - Observer stringObserver; - - @Before - public void before() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testErrorDelayed1() { - final Observable o1 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called - final Observable o2 = Observable.create(new TestErrorObservable("one", "two", "three")); - - @SuppressWarnings("unchecked") - Observable m = Observable.create(mergeDelayError(o1, o2)); - m.subscribe(stringObserver); - - verify(stringObserver, times(1)).onError(any(NullPointerException.class)); - verify(stringObserver, never()).onCompleted(); - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, times(1)).onNext("two"); - verify(stringObserver, times(1)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(0)).onNext("five"); - verify(stringObserver, times(0)).onNext("six"); - } - - @Test - public void testErrorDelayed2() { - final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); - final Observable o2 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called - final Observable o3 = Observable.create(new TestErrorObservable("seven", "eight", null)); - final Observable o4 = Observable.create(new TestErrorObservable("nine")); - - @SuppressWarnings("unchecked") - Observable m = Observable.create(mergeDelayError(o1, o2, o3, o4)); - m.subscribe(stringObserver); - - verify(stringObserver, times(1)).onError(any(NullPointerException.class)); - verify(stringObserver, never()).onCompleted(); - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, times(1)).onNext("two"); - verify(stringObserver, times(1)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(0)).onNext("five"); - verify(stringObserver, times(0)).onNext("six"); - verify(stringObserver, times(1)).onNext("seven"); - verify(stringObserver, times(1)).onNext("eight"); - verify(stringObserver, times(1)).onNext("nine"); - } - - @Test - public void testErrorDelayed3() { - final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); - final Observable o2 = Observable.create(new TestErrorObservable("four", "five", "six")); - final Observable o3 = Observable.create(new TestErrorObservable("seven", "eight", null)); - final Observable o4 = Observable.create(new TestErrorObservable("nine")); - - @SuppressWarnings("unchecked") - Observable m = Observable.create(mergeDelayError(o1, o2, o3, o4)); - m.subscribe(stringObserver); - - verify(stringObserver, times(1)).onError(any(NullPointerException.class)); - verify(stringObserver, never()).onCompleted(); - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, times(1)).onNext("two"); - verify(stringObserver, times(1)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(1)).onNext("five"); - verify(stringObserver, times(1)).onNext("six"); - verify(stringObserver, times(1)).onNext("seven"); - verify(stringObserver, times(1)).onNext("eight"); - verify(stringObserver, times(1)).onNext("nine"); - } - - @Test - public void testErrorDelayed4() { - final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); - final Observable o2 = Observable.create(new TestErrorObservable("four", "five", "six")); - final Observable o3 = Observable.create(new TestErrorObservable("seven", "eight")); - final Observable o4 = Observable.create(new TestErrorObservable("nine", null)); - - @SuppressWarnings("unchecked") - Observable m = Observable.create(mergeDelayError(o1, o2, o3, o4)); - m.subscribe(stringObserver); - - verify(stringObserver, times(1)).onError(any(NullPointerException.class)); - verify(stringObserver, never()).onCompleted(); - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, times(1)).onNext("two"); - verify(stringObserver, times(1)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(1)).onNext("five"); - verify(stringObserver, times(1)).onNext("six"); - verify(stringObserver, times(1)).onNext("seven"); - verify(stringObserver, times(1)).onNext("eight"); - verify(stringObserver, times(1)).onNext("nine"); - } - - @Test - public void testErrorDelayed4WithThreading() { - final TestAsyncErrorObservable o1 = new TestAsyncErrorObservable("one", "two", "three"); - final TestAsyncErrorObservable o2 = new TestAsyncErrorObservable("four", "five", "six"); - final TestAsyncErrorObservable o3 = new TestAsyncErrorObservable("seven", "eight"); - // throw the error at the very end so no onComplete will be called after it - final TestAsyncErrorObservable o4 = new TestAsyncErrorObservable("nine", null); - - @SuppressWarnings("unchecked") - Observable m = Observable.create(mergeDelayError(Observable.create(o1), Observable.create(o2), Observable.create(o3), Observable.create(o4))); - m.subscribe(stringObserver); - - try { - o1.t.join(); - o2.t.join(); - o3.t.join(); - o4.t.join(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - verify(stringObserver, times(1)).onError(any(NullPointerException.class)); - verify(stringObserver, never()).onCompleted(); - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, times(1)).onNext("two"); - verify(stringObserver, times(1)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(1)).onNext("five"); - verify(stringObserver, times(1)).onNext("six"); - verify(stringObserver, times(1)).onNext("seven"); - verify(stringObserver, times(1)).onNext("eight"); - verify(stringObserver, times(1)).onNext("nine"); - } - - @Test - public void testCompositeErrorDelayed1() { - final Observable o1 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called - final Observable o2 = Observable.create(new TestErrorObservable("one", "two", null)); - - @SuppressWarnings("unchecked") - Observable m = Observable.create(mergeDelayError(o1, o2)); - m.subscribe(stringObserver); - - verify(stringObserver, times(1)).onError(any(CompositeException.class)); - verify(stringObserver, never()).onCompleted(); - verify(stringObserver, times(1)).onNext("one"); - verify(stringObserver, times(1)).onNext("two"); - verify(stringObserver, times(0)).onNext("three"); - verify(stringObserver, times(1)).onNext("four"); - verify(stringObserver, times(0)).onNext("five"); - verify(stringObserver, times(0)).onNext("six"); - } - - @Test - public void testCompositeErrorDelayed2() { - final Observable o1 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called - final Observable o2 = Observable.create(new TestErrorObservable("one", "two", null)); - - @SuppressWarnings("unchecked") - Observable m = Observable.create(mergeDelayError(o1, o2)); - CaptureObserver w = new CaptureObserver(); - m.subscribe(w); - - assertNotNull(w.e); - if (w.e instanceof CompositeException) { - assertEquals(2, ((CompositeException) w.e).getExceptions().size()); - w.e.printStackTrace(); - } else { - fail("Expecting CompositeException"); - } - - } - - /** - * The unit tests below are from OperationMerge and should ensure the normal merge functionality is correct. - */ - - @Test - public void testMergeObservableOfObservables() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); - - Observable> observableOfObservables = Observable.create(new OnSubscribeFunc>() { - - @Override - public Subscription onSubscribe(Observer> observer) { - // simulate what would happen in an observable - observer.onNext(o1); - observer.onNext(o2); - observer.onCompleted(); - - return new Subscription() { - - @Override - public void unsubscribe() { - // unregister ... will never be called here since we are executing synchronously - } - - }; - } - - }); - Observable m = Observable.create(mergeDelayError(observableOfObservables)); - m.subscribe(stringObserver); - - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onCompleted(); - verify(stringObserver, times(2)).onNext("hello"); - } - - @Test - public void testMergeArray() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); - - @SuppressWarnings("unchecked") - Observable m = Observable.create(mergeDelayError(o1, o2)); - m.subscribe(stringObserver); - - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(2)).onNext("hello"); - verify(stringObserver, times(1)).onCompleted(); - } - - @Test - public void testMergeList() { - final Observable o1 = Observable.create(new TestSynchronousObservable()); - final Observable o2 = Observable.create(new TestSynchronousObservable()); - List> listOfObservables = new ArrayList>(); - listOfObservables.add(o1); - listOfObservables.add(o2); - - Observable m = Observable.create(mergeDelayError(listOfObservables)); - m.subscribe(stringObserver); - - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onCompleted(); - verify(stringObserver, times(2)).onNext("hello"); - } - - @Test - public void testUnSubscribe() { - TestObservable tA = new TestObservable(); - TestObservable tB = new TestObservable(); - - @SuppressWarnings("unchecked") - Observable m = Observable.create(mergeDelayError(Observable.create(tA), Observable.create(tB))); - Subscription s = m.subscribe(stringObserver); - - tA.sendOnNext("Aone"); - tB.sendOnNext("Bone"); - s.unsubscribe(); - tA.sendOnNext("Atwo"); - tB.sendOnNext("Btwo"); - tA.sendOnCompleted(); - tB.sendOnCompleted(); - - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(1)).onNext("Aone"); - verify(stringObserver, times(1)).onNext("Bone"); - assertTrue(tA.unsubscribed); - assertTrue(tB.unsubscribed); - verify(stringObserver, never()).onNext("Atwo"); - verify(stringObserver, never()).onNext("Btwo"); - verify(stringObserver, never()).onCompleted(); - } - - @Test - public void testMergeArrayWithThreading() { - final TestASynchronousObservable o1 = new TestASynchronousObservable(); - final TestASynchronousObservable o2 = new TestASynchronousObservable(); - - @SuppressWarnings("unchecked") - Observable m = Observable.create(mergeDelayError(Observable.create(o1), Observable.create(o2))); - m.subscribe(stringObserver); - - try { - o1.t.join(); - o2.t.join(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - - verify(stringObserver, never()).onError(any(Throwable.class)); - verify(stringObserver, times(2)).onNext("hello"); - verify(stringObserver, times(1)).onCompleted(); - } - - private static class TestSynchronousObservable implements OnSubscribeFunc { - - @Override - public Subscription onSubscribe(Observer observer) { - - observer.onNext("hello"); - observer.onCompleted(); - - return new Subscription() { - - @Override - public void unsubscribe() { - // unregister ... will never be called here since we are executing synchronously - } - - }; - } - } - - private static class TestASynchronousObservable implements OnSubscribeFunc { - Thread t; - - @Override - public Subscription onSubscribe(final Observer observer) { - t = new Thread(new Runnable() { - - @Override - public void run() { - observer.onNext("hello"); - observer.onCompleted(); - } - - }); - t.start(); - - return new Subscription() { - - @Override - public void unsubscribe() { - - } - - }; - } - } - - /** - * A Observable that doesn't do the right thing on UnSubscribe/Error/etc in that it will keep sending events down the pipe regardless of what happens. - */ - private static class TestObservable implements OnSubscribeFunc { - - Observer observer = null; - volatile boolean unsubscribed = false; - Subscription s = new Subscription() { - - @Override - public void unsubscribe() { - unsubscribed = true; - - } - - }; - - /* used to simulate subscription */ - public void sendOnCompleted() { - observer.onCompleted(); - } - - /* used to simulate subscription */ - public void sendOnNext(String value) { - observer.onNext(value); - } - - /* used to simulate subscription */ - @SuppressWarnings("unused") - public void sendOnError(Throwable e) { - observer.onError(e); - } - - @Override - public Subscription onSubscribe(final Observer observer) { - this.observer = observer; - return s; - } - } - - private static class TestErrorObservable implements OnSubscribeFunc { - - String[] valuesToReturn; - - TestErrorObservable(String... values) { - valuesToReturn = values; - } - - @Override - public Subscription onSubscribe(Observer observer) { - boolean errorThrown = false; - for (String s : valuesToReturn) { - if (s == null) { - System.out.println("throwing exception"); - observer.onError(new NullPointerException()); - errorThrown = true; - // purposefully not returning here so it will continue calling onNext - // so that we also test that we handle bad sequences like this - } else { - observer.onNext(s); - } - } - if (!errorThrown) { - observer.onCompleted(); - } - - return new Subscription() { - - @Override - public void unsubscribe() { - // unregister ... will never be called here since we are executing synchronously - } - - }; - } - } - - private static class TestAsyncErrorObservable implements OnSubscribeFunc { - - String[] valuesToReturn; - - TestAsyncErrorObservable(String... values) { - valuesToReturn = values; - } - - Thread t; - - @Override - public Subscription onSubscribe(final Observer observer) { - t = new Thread(new Runnable() { - - @Override - public void run() { - for (String s : valuesToReturn) { - if (s == null) { - System.out.println("throwing exception"); - try { - Thread.sleep(100); - } catch (Throwable e) { - - } - observer.onError(new NullPointerException()); - return; - } else { - observer.onNext(s); - } - } - System.out.println("subscription complete"); - observer.onCompleted(); - } - - }); - t.start(); - - return new Subscription() { - - @Override - public void unsubscribe() { - - } - - }; - } - } - - private static class CaptureObserver implements Observer { - volatile Throwable e; - - @Override - public void onCompleted() { - - } - - @Override - public void onError(Throwable e) { - this.e = e; - } - - @Override - public void onNext(String args) { - - } - - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationMinMax.java b/rxjava-core/src/main/java/rx/operators/OperationMinMax.java new file mode 100644 index 0000000000..0d5a1c9378 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationMinMax.java @@ -0,0 +1,147 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import rx.Observable; +import rx.util.functions.Func1; +import rx.util.functions.Func2; + +/** + * Returns the minimum element in an observable sequence. + */ +public class OperationMinMax { + + public static > Observable min( + Observable source) { + return minMax(source, -1L); + } + + public static Observable min(Observable source, + final Comparator comparator) { + return minMax(source, comparator, -1L); + } + + public static > Observable> minBy( + Observable source, final Func1 selector) { + return minMaxBy(source, selector, -1L); + } + + public static Observable> minBy(Observable source, + final Func1 selector, final Comparator comparator) { + return minMaxBy(source, selector, comparator, -1L); + } + + public static > Observable max( + Observable source) { + return minMax(source, 1L); + } + + public static Observable max(Observable source, + final Comparator comparator) { + return minMax(source, comparator, 1L); + } + + public static > Observable> maxBy( + Observable source, final Func1 selector) { + return minMaxBy(source, selector, 1L); + } + + public static Observable> maxBy(Observable source, + final Func1 selector, final Comparator comparator) { + return minMaxBy(source, selector, comparator, 1L); + } + + private static > Observable minMax( + Observable source, final long flag) { + return source.reduce(new Func2() { + @Override + public T call(T acc, T value) { + if (flag * acc.compareTo(value) > 0) { + return acc; + } + return value; + } + }); + } + + private static Observable minMax(Observable source, + final Comparator comparator, final long flag) { + return source.reduce(new Func2() { + @Override + public T call(T acc, T value) { + if (flag * comparator.compare(acc, value) > 0) { + return acc; + } + return value; + } + }); + } + + private static > Observable> minMaxBy( + Observable source, final Func1 selector, final long flag) { + return source.reduce(new ArrayList(), + new Func2, T, List>() { + + @Override + public List call(List acc, T value) { + if (acc.isEmpty()) { + acc.add(value); + } else { + int compareResult = selector.call(acc.get(0)) + .compareTo(selector.call(value)); + if (compareResult == 0) { + acc.add(value); + } else if (flag * compareResult < 0) { + acc.clear(); + acc.add(value); + } + } + return acc; + } + }); + } + + private static Observable> minMaxBy(Observable source, + final Func1 selector, final Comparator comparator, + final long flag) { + return source.reduce(new ArrayList(), + new Func2, T, List>() { + + @Override + public List call(List acc, T value) { + if (acc.isEmpty()) { + acc.add(value); + } else { + int compareResult = comparator.compare( + selector.call(acc.get(0)), + selector.call(value)); + if (compareResult == 0) { + acc.add(value); + } else if (flag * compareResult < 0) { + acc.clear(); + acc.add(value); + } + } + return acc; + } + }); + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationMostRecent.java b/rxjava-core/src/main/java/rx/operators/OperationMostRecent.java index b2eabb2893..15406aeece 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationMostRecent.java +++ b/rxjava-core/src/main/java/rx/operators/OperationMostRecent.java @@ -15,18 +15,12 @@ */ package rx.operators; -import static org.junit.Assert.*; - import java.util.Iterator; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import org.junit.Test; - import rx.Observable; import rx.Observer; -import rx.subjects.PublishSubject; -import rx.subjects.Subject; import rx.util.Exceptions; /** @@ -117,53 +111,4 @@ private T getRecentValue() { } } - - public static class UnitTest { - @Test - public void testMostRecent() { - Subject observable = PublishSubject.create(); - - Iterator it = mostRecent(observable, "default").iterator(); - - assertTrue(it.hasNext()); - assertEquals("default", it.next()); - assertEquals("default", it.next()); - - observable.onNext("one"); - assertTrue(it.hasNext()); - assertEquals("one", it.next()); - assertEquals("one", it.next()); - - observable.onNext("two"); - assertTrue(it.hasNext()); - assertEquals("two", it.next()); - assertEquals("two", it.next()); - - observable.onCompleted(); - assertFalse(it.hasNext()); - - } - - @Test(expected = TestException.class) - public void testMostRecentWithException() { - Subject observable = PublishSubject.create(); - - Iterator it = mostRecent(observable, "default").iterator(); - - assertTrue(it.hasNext()); - assertEquals("default", it.next()); - assertEquals("default", it.next()); - - observable.onError(new TestException()); - assertTrue(it.hasNext()); - - it.next(); - } - - private static class TestException extends RuntimeException { - private static final long serialVersionUID = 1L; - } - - } - } diff --git a/rxjava-core/src/main/java/rx/operators/OperationMulticast.java b/rxjava-core/src/main/java/rx/operators/OperationMulticast.java index e83cfdaa03..b634b2dbac 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationMulticast.java +++ b/rxjava-core/src/main/java/rx/operators/OperationMulticast.java @@ -1,12 +1,12 @@ /** * Copyright 2013 Netflix, Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,19 +15,14 @@ */ package rx.operators; -import static org.mockito.Mockito.*; - -import org.junit.Test; - import rx.Observable; import rx.Observer; import rx.Subscription; import rx.observables.ConnectableObservable; -import rx.subjects.PublishSubject; import rx.subjects.Subject; public class OperationMulticast { - public static ConnectableObservable multicast(Observable source, final Subject subject) { + public static ConnectableObservable multicast(Observable source, final Subject subject) { return new MulticastConnectableObservable(source, subject); } @@ -35,11 +30,11 @@ private static class MulticastConnectableObservable extends ConnectableObs private final Object lock = new Object(); private final Observable source; - private final Subject subject; + private final Subject subject; private Subscription subscription; - public MulticastConnectableObservable(Observable source, final Subject subject) { + public MulticastConnectableObservable(Observable source, final Subject subject) { super(new OnSubscribeFunc() { @Override public Subscription onSubscribe(Observer observer) { @@ -72,7 +67,6 @@ public void onNext(T args) { } } - return new Subscription() { @Override public void unsubscribe() { @@ -86,94 +80,5 @@ public void unsubscribe() { }; } - - } - - public static class UnitTest { - - @Test - public void testMulticast() { - Subject source = PublishSubject.create(); - - ConnectableObservable multicasted = OperationMulticast.multicast(source, - PublishSubject.create()); - - @SuppressWarnings("unchecked") - Observer observer = mock(Observer.class); - multicasted.subscribe(observer); - - source.onNext("one"); - source.onNext("two"); - - multicasted.connect(); - - source.onNext("three"); - source.onNext("four"); - source.onCompleted(); - - verify(observer, never()).onNext("one"); - verify(observer, never()).onNext("two"); - verify(observer, times(1)).onNext("three"); - verify(observer, times(1)).onNext("four"); - verify(observer, times(1)).onCompleted(); - - } - - @Test - public void testMulticastConnectTwice() { - Subject source = PublishSubject.create(); - - ConnectableObservable multicasted = OperationMulticast.multicast(source, - PublishSubject.create()); - - @SuppressWarnings("unchecked") - Observer observer = mock(Observer.class); - multicasted.subscribe(observer); - - source.onNext("one"); - - multicasted.connect(); - multicasted.connect(); - - source.onNext("two"); - source.onCompleted(); - - verify(observer, never()).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, times(1)).onCompleted(); - - } - - @Test - public void testMulticastDisconnect() { - Subject source = PublishSubject.create(); - - ConnectableObservable multicasted = OperationMulticast.multicast(source, - PublishSubject.create()); - - @SuppressWarnings("unchecked") - Observer observer = mock(Observer.class); - multicasted.subscribe(observer); - - source.onNext("one"); - - Subscription connection = multicasted.connect(); - source.onNext("two"); - - connection.unsubscribe(); - source.onNext("three"); - - multicasted.connect(); - source.onNext("four"); - source.onCompleted(); - - verify(observer, never()).onNext("one"); - verify(observer, times(1)).onNext("two"); - verify(observer, never()).onNext("three"); - verify(observer, times(1)).onNext("four"); - verify(observer, times(1)).onCompleted(); - - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationNext.java b/rxjava-core/src/main/java/rx/operators/OperationNext.java index 73a3b3dc13..4f5e9dfd31 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationNext.java +++ b/rxjava-core/src/main/java/rx/operators/OperationNext.java @@ -15,31 +15,15 @@ */ package rx.operators; -import static org.junit.Assert.*; - import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.Test; import rx.Notification; import rx.Observable; -import rx.Observable.OnSubscribeFunc; import rx.Observer; -import rx.Subscription; -import rx.subjects.PublishSubject; -import rx.subjects.Subject; -import rx.subscriptions.Subscriptions; import rx.util.Exceptions; /** @@ -68,6 +52,10 @@ public Iterator iterator() { private static class NextIterator implements Iterator { private final NextObserver observer; + private T next; + private boolean hasNext = true; + private boolean isNextConsumed = true; + private Throwable error = null; private NextIterator(NextObserver observer) { this.observer = observer; @@ -75,24 +63,62 @@ private NextIterator(NextObserver observer) { @Override public boolean hasNext() { - return !observer.isCompleted(false); - } - - @Override - public T next() { - if (observer.isCompleted(true)) { - throw new IllegalStateException("Observable is completed"); + if (error != null) { + // If any error has already been thrown, throw it again. + throw Exceptions.propagate(error); } + // Since an iterator should not be used in different thread, + // so we do not need any synchronization. + if (hasNext == false) { + // the iterator has reached the end. + return false; + } + if (isNextConsumed == false) { + // next has not been used yet. + return true; + } + return moveToNext(); + } - observer.await(); - + private boolean moveToNext() { try { - return observer.takeNext(); + Notification nextNotification = observer.takeNext(); + if (nextNotification.isOnNext()) { + isNextConsumed = false; + next = nextNotification.getValue(); + return true; + } + // If an observable is completed or fails, + // hasNext() always return false. + hasNext = false; + if (nextNotification.isOnCompleted()) { + return false; + } + if (nextNotification.isOnError()) { + error = nextNotification.getThrowable(); + throw Exceptions.propagate(error); + } + throw new IllegalStateException("Should not reach here"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw Exceptions.propagate(e); + error = e; + throw Exceptions.propagate(error); } + } + @Override + public T next() { + if (error != null) { + // If any error has already been thrown, throw it again. + throw Exceptions.propagate(error); + } + if (hasNext()) { + isNextConsumed = true; + return next; + } + else { + throw new NoSuchElementException("No more elements"); + } } @Override @@ -124,7 +150,7 @@ public void onNext(Notification args) { Notification concurrentItem = buf.poll(); // in case if we won race condition with onComplete/onError method - if (!concurrentItem.isOnNext()) { + if (concurrentItem != null && !concurrentItem.isOnNext()) { toOffer = concurrentItem; } } @@ -132,214 +158,10 @@ public void onNext(Notification args) { } - public void await() { + public Notification takeNext() throws InterruptedException { waiting.set(true); - } - - public boolean isCompleted(boolean rethrowExceptionIfExists) { - Notification lastItem = buf.peek(); - if (lastItem == null) { - return false; - } - - if (lastItem.isOnError()) { - if (rethrowExceptionIfExists) { - throw Exceptions.propagate(lastItem.getThrowable()); - } else { - return true; - } - } - - return lastItem.isOnCompleted(); - } - - public T takeNext() throws InterruptedException { - Notification next = buf.take(); - - if (next.isOnError()) { - throw Exceptions.propagate(next.getThrowable()); - } - - if (next.isOnCompleted()) { - throw new IllegalStateException("Observable is completed"); - } - - return next.getValue(); - - } - - } - - public static class UnitTest { - private final ExecutorService executor = Executors.newSingleThreadExecutor(); - - @Test - public void testNext() throws Throwable { - Subject obs = PublishSubject.create(); - - Iterator it = next(obs).iterator(); - - assertTrue(it.hasNext()); - - Future next = nextAsync(it); - Thread.sleep(100); - obs.onNext("one"); - assertEquals("one", next.get()); - - assertTrue(it.hasNext()); - - next = nextAsync(it); - Thread.sleep(100); - obs.onNext("two"); - assertEquals("two", next.get()); - - assertTrue(it.hasNext()); - - obs.onCompleted(); - - assertFalse(it.hasNext()); - } - - @Test(expected = TestException.class) - public void testOnError() throws Throwable { - Subject obs = PublishSubject.create(); - - Iterator it = next(obs).iterator(); - - assertTrue(it.hasNext()); - - Future next = nextAsync(it); - Thread.sleep(100); - obs.onNext("one"); - assertEquals("one", next.get()); - - assertTrue(it.hasNext()); - - next = nextAsync(it); - Thread.sleep(100); - obs.onError(new TestException()); - - try { - next.get(); - } catch (ExecutionException e) { - throw e.getCause(); - } - } - - @Test - public void testOnErrorViaHasNext() throws Throwable { - Subject obs = PublishSubject.create(); - - Iterator it = next(obs).iterator(); - - assertTrue(it.hasNext()); - - Future next = nextAsync(it); - Thread.sleep(100); - obs.onNext("one"); - assertEquals("one", next.get()); - - assertTrue(it.hasNext()); - - next = nextAsync(it); - Thread.sleep(100); - obs.onError(new TestException()); - - // this should not throw an exception but instead just return false - try { - assertFalse(it.hasNext()); - } catch (Throwable e) { - fail("should not have received exception"); - e.printStackTrace(); - } - } - - private Future nextAsync(final Iterator it) throws Throwable { - - return executor.submit(new Callable() { - - @Override - public String call() throws Exception { - return it.next(); - } - }); - } - - @SuppressWarnings("serial") - private static class TestException extends RuntimeException { - - } - - /** - * Confirm that no buffering or blocking of the Observable onNext calls occurs and it just grabs the next emitted value. - * - * This results in output such as => a: 1 b: 2 c: 89 - * - * @throws Throwable - */ - @Test - public void testNoBufferingOrBlockingOfSequence() throws Throwable { - final CountDownLatch finished = new CountDownLatch(1); - final int COUNT = 30; - final CountDownLatch timeHasPassed = new CountDownLatch(COUNT); - final AtomicBoolean running = new AtomicBoolean(true); - final AtomicInteger count = new AtomicInteger(0); - final Observable obs = Observable.create(new OnSubscribeFunc() { - - @Override - public Subscription onSubscribe(final Observer o) { - new Thread(new Runnable() { - - @Override - public void run() { - try { - while (running.get()) { - o.onNext(count.incrementAndGet()); - timeHasPassed.countDown(); - } - o.onCompleted(); - } catch (Throwable e) { - o.onError(e); - } finally { - finished.countDown(); - } - } - }).start(); - return Subscriptions.empty(); - } - - }); - - Iterator it = next(obs).iterator(); - - assertTrue(it.hasNext()); - int a = it.next(); - assertTrue(it.hasNext()); - int b = it.next(); - // we should have a different value - assertTrue("a and b should be different", a != b); - - // wait for some time (if times out we are blocked somewhere so fail ... set very high for very slow, constrained machines) - timeHasPassed.await(8000, TimeUnit.MILLISECONDS); - - assertTrue(it.hasNext()); - int c = it.next(); - - assertTrue("c should not just be the next in sequence", c != (b + 1)); - assertTrue("expected that c [" + c + "] is higher than or equal to " + COUNT, c >= COUNT); - - assertTrue(it.hasNext()); - - // shut down the thread - running.set(false); - - finished.await(); - - assertFalse(it.hasNext()); - - System.out.println("a: " + a + " b: " + b + " c: " + c); + return buf.take(); } } - } diff --git a/rxjava-core/src/main/java/rx/operators/OperationObserveOn.java b/rxjava-core/src/main/java/rx/operators/OperationObserveOn.java index f947333456..fbfda6ef1b 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationObserveOn.java +++ b/rxjava-core/src/main/java/rx/operators/OperationObserveOn.java @@ -15,24 +15,22 @@ */ package rx.operators; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicInteger; +import rx.Notification; import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Scheduler; import rx.Subscription; +import rx.concurrency.CurrentThreadScheduler; import rx.concurrency.ImmediateScheduler; -import rx.concurrency.Schedulers; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Func2; /** * Asynchronously notify Observers on the specified Scheduler. @@ -48,6 +46,10 @@ public static OnSubscribeFunc observeOn(Observable source, S private static class ObserveOn implements OnSubscribeFunc { private final Observable source; private final Scheduler scheduler; + private volatile Scheduler recursiveScheduler; + + final ConcurrentLinkedQueue> queue = new ConcurrentLinkedQueue>(); + final AtomicInteger counter = new AtomicInteger(0); public ObserveOn(Observable source, Scheduler scheduler) { this.source = source; @@ -59,63 +61,75 @@ public Subscription onSubscribe(final Observer observer) { if (scheduler instanceof ImmediateScheduler) { // do nothing if we request ImmediateScheduler so we don't invoke overhead return source.subscribe(observer); + } else if (scheduler instanceof CurrentThreadScheduler) { + // do nothing if we request CurrentThreadScheduler so we don't invoke overhead + return source.subscribe(observer); } else { - return source.subscribe(new ScheduledObserver(observer, scheduler)); + return observeOn(observer, scheduler); } } - } - public static class UnitTest { + public Subscription observeOn(final Observer observer, final Scheduler scheduler) { + final CompositeSubscription s = new CompositeSubscription(); - /** - * This is testing a no-op path since it uses Schedulers.immediate() which will not do scheduling. - */ - @Test - @SuppressWarnings("unchecked") - public void testObserveOn() { - Observer observer = mock(Observer.class); - Observable.create(observeOn(Observable.from(1, 2, 3), Schedulers.immediate())).subscribe(observer); - - verify(observer, times(1)).onNext(1); - verify(observer, times(1)).onNext(2); - verify(observer, times(1)).onNext(3); - verify(observer, times(1)).onCompleted(); - } + s.add(source.materialize().subscribe(new Action1>() { - @Test - @SuppressWarnings("unchecked") - public void testOrdering() throws InterruptedException { - Observable obs = Observable.from("one", null, "two", "three", "four"); + @Override + public void call(Notification e) { + // this must happen before 'counter' is used to provide synchronization between threads + queue.offer(e); + + // we now use counter to atomically determine if we need to start processing or not + // it will be 0 if it's the first notification or the scheduler has finished processing work + // and we need to start doing it again + if (counter.getAndIncrement() == 0) { + if (recursiveScheduler == null) { + s.add(scheduler.schedule(null, new Func2() { + + @Override + public Subscription call(Scheduler innerScheduler, T state) { + // record innerScheduler so 'processQueue' can use it for all subsequent executions + recursiveScheduler = innerScheduler; + + processQueue(s, observer); + + return Subscriptions.empty(); + } + })); + } else { + processQueue(s, observer); + } + } - Observer observer = mock(Observer.class); + } + })); - InOrder inOrder = inOrder(observer); + return s; + } - final CountDownLatch completedLatch = new CountDownLatch(1); - doAnswer(new Answer() { + /** + * This uses 'recursiveScheduler' NOT 'scheduler' as it should reuse the same scheduler each time it processes. + * This means it must first get the recursiveScheduler when it first executes. + */ + private void processQueue(final CompositeSubscription s, final Observer observer) { + s.add(recursiveScheduler.schedule(new Action1() { @Override - public Void answer(InvocationOnMock invocation) throws Throwable { - completedLatch.countDown(); - return null; - } - }).when(observer).onCompleted(); - - obs.observeOn(Schedulers.threadPoolForComputation()).subscribe(observer); + public void call(Action0 self) { + Notification not = queue.poll(); + if (not != null) { + not.accept(observer); + } + + // decrement count and if we still have work to do + // recursively schedule ourselves to process again + if (counter.decrementAndGet() > 0) { + self.call(); + } - if (!completedLatch.await(1000, TimeUnit.MILLISECONDS)) { - fail("timed out waiting"); - } - - inOrder.verify(observer, times(1)).onNext("one"); - inOrder.verify(observer, times(1)).onNext(null); - inOrder.verify(observer, times(1)).onNext("two"); - inOrder.verify(observer, times(1)).onNext("three"); - inOrder.verify(observer, times(1)).onNext("four"); - inOrder.verify(observer, times(1)).onCompleted(); - inOrder.verifyNoMoreInteractions(); + } + })); } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationOnErrorResumeNextViaFunction.java b/rxjava-core/src/main/java/rx/operators/OperationOnErrorResumeNextViaFunction.java index 28bf8be311..0122a8c5e1 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationOnErrorResumeNextViaFunction.java +++ b/rxjava-core/src/main/java/rx/operators/OperationOnErrorResumeNextViaFunction.java @@ -15,21 +15,13 @@ */ package rx.operators; -import static org.junit.Assert.*; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; -import org.junit.Test; -import org.mockito.Mockito; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.subscriptions.Subscriptions; import rx.util.CompositeException; import rx.util.functions.Func1; @@ -122,155 +114,4 @@ public void unsubscribe() { }; } } - - public static class UnitTest { - - @Test - public void testResumeNextWithSynchronousExecution() { - final AtomicReference receivedException = new AtomicReference(); - Observable w = Observable.create(new OnSubscribeFunc() { - - @Override - public Subscription onSubscribe(Observer observer) { - observer.onNext("one"); - observer.onError(new Throwable("injected failure")); - return Subscriptions.empty(); - } - }); - - Func1> resume = new Func1>() { - - @Override - public Observable call(Throwable t1) { - receivedException.set(t1); - return Observable.from("twoResume", "threeResume"); - } - - }; - Observable observable = Observable.create(onErrorResumeNextViaFunction(w, resume)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - observable.subscribe(aObserver); - - verify(aObserver, Mockito.never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, Mockito.never()).onNext("two"); - verify(aObserver, Mockito.never()).onNext("three"); - verify(aObserver, times(1)).onNext("twoResume"); - verify(aObserver, times(1)).onNext("threeResume"); - assertNotNull(receivedException.get()); - } - - @Test - public void testResumeNextWithAsyncExecution() { - final AtomicReference receivedException = new AtomicReference(); - Subscription s = mock(Subscription.class); - TestObservable w = new TestObservable(s, "one"); - Func1> resume = new Func1>() { - - @Override - public Observable call(Throwable t1) { - receivedException.set(t1); - return Observable.from("twoResume", "threeResume"); - } - - }; - Observable observable = Observable.create(onErrorResumeNextViaFunction(Observable.create(w), resume)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - observable.subscribe(aObserver); - - try { - w.t.join(); - } catch (InterruptedException e) { - fail(e.getMessage()); - } - - verify(aObserver, Mockito.never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, Mockito.never()).onNext("two"); - verify(aObserver, Mockito.never()).onNext("three"); - verify(aObserver, times(1)).onNext("twoResume"); - verify(aObserver, times(1)).onNext("threeResume"); - assertNotNull(receivedException.get()); - } - - /** - * Test that when a function throws an exception this is propagated through onError - */ - @Test - public void testFunctionThrowsError() { - Subscription s = mock(Subscription.class); - TestObservable w = new TestObservable(s, "one"); - Func1> resume = new Func1>() { - - @Override - public Observable call(Throwable t1) { - throw new RuntimeException("exception from function"); - } - - }; - Observable observable = Observable.create(onErrorResumeNextViaFunction(Observable.create(w), resume)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - observable.subscribe(aObserver); - - try { - w.t.join(); - } catch (InterruptedException e) { - fail(e.getMessage()); - } - - // we should get the "one" value before the error - verify(aObserver, times(1)).onNext("one"); - - // we should have received an onError call on the Observer since the resume function threw an exception - verify(aObserver, times(1)).onError(any(Throwable.class)); - verify(aObserver, times(0)).onCompleted(); - } - - private static class TestObservable implements OnSubscribeFunc { - - final Subscription s; - final String[] values; - Thread t = null; - - public TestObservable(Subscription s, String... values) { - this.s = s; - this.values = values; - } - - @Override - public Subscription onSubscribe(final Observer observer) { - System.out.println("TestObservable subscribed to ..."); - t = new Thread(new Runnable() { - - @Override - public void run() { - try { - System.out.println("running TestObservable thread"); - for (String s : values) { - System.out.println("TestObservable onNext: " + s); - observer.onNext(s); - } - throw new RuntimeException("Forced Failure"); - } catch (Throwable e) { - observer.onError(e); - } - } - - }); - System.out.println("starting TestObservable thread"); - t.start(); - System.out.println("done starting TestObservable thread"); - return s; - } - - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationOnErrorResumeNextViaObservable.java b/rxjava-core/src/main/java/rx/operators/OperationOnErrorResumeNextViaObservable.java index 3b49ce5f9c..6c14d3618a 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationOnErrorResumeNextViaObservable.java +++ b/rxjava-core/src/main/java/rx/operators/OperationOnErrorResumeNextViaObservable.java @@ -15,20 +15,12 @@ */ package rx.operators; -import static org.junit.Assert.*; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - import java.util.concurrent.atomic.AtomicReference; -import org.junit.Test; -import org.mockito.Mockito; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.util.functions.Func1; /** * Instruct an Observable to pass control to another Observable rather than invoking @@ -75,7 +67,7 @@ public Subscription onSubscribe(final Observer observer) { subscription.wrap(originalSequence.subscribe(new Observer() { public void onNext(T value) { // forward the successful calls unless resumed - if (subscriptionRef.get()==subscription) + if (subscriptionRef.get() == subscription) observer.onNext(value); } @@ -100,7 +92,7 @@ public void onError(Throwable ex) { public void onCompleted() { // forward the successful calls unless resumed - if (subscriptionRef.get()==subscription) + if (subscriptionRef.get() == subscription) observer.onCompleted(); } })); @@ -116,119 +108,4 @@ public void unsubscribe() { }; } } - - public static class UnitTest { - - @Test - public void testResumeNext() { - Subscription s = mock(Subscription.class); - // Trigger failure on second element - TestObservable f = new TestObservable(s, "one", "fail", "two", "three"); - Observable w = Observable.create(f); - Observable resume = Observable.from("twoResume", "threeResume"); - Observable observable = Observable.create(onErrorResumeNextViaObservable(w, resume)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - observable.subscribe(aObserver); - - try { - f.t.join(); - } catch (InterruptedException e) { - fail(e.getMessage()); - } - - verify(aObserver, Mockito.never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, Mockito.never()).onNext("two"); - verify(aObserver, Mockito.never()).onNext("three"); - verify(aObserver, times(1)).onNext("twoResume"); - verify(aObserver, times(1)).onNext("threeResume"); - } - - @Test - public void testMapResumeAsyncNext() { - Subscription sr = mock(Subscription.class); - // Trigger multiple failures - Observable w = Observable.from("one", "fail", "two", "three", "fail"); - // Resume Observable is async - TestObservable f = new TestObservable(sr, "twoResume", "threeResume"); - Observable resume = Observable.create(f); - - // Introduce map function that fails intermittently (Map does not prevent this when the observer is a - // rx.operator incl onErrorResumeNextViaObservable) - w = w.map(new Func1() { - public String call(String s) { - if ("fail".equals(s)) - throw new RuntimeException("Forced Failure"); - System.out.println("BadMapper:" + s); - return s; - } - }); - - Observable observable = Observable.create(onErrorResumeNextViaObservable(w, resume)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - observable.subscribe(aObserver); - - try { - f.t.join(); - } catch (InterruptedException e) { - fail(e.getMessage()); - } - - verify(aObserver, Mockito.never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, Mockito.never()).onNext("two"); - verify(aObserver, Mockito.never()).onNext("three"); - verify(aObserver, times(1)).onNext("twoResume"); - verify(aObserver, times(1)).onNext("threeResume"); - } - - private static class TestObservable implements OnSubscribeFunc { - - final Subscription s; - final String[] values; - Thread t = null; - - public TestObservable(Subscription s, String... values) { - this.s = s; - this.values = values; - } - - @Override - public Subscription onSubscribe(final Observer observer) { - System.out.println("TestObservable subscribed to ..."); - t = new Thread(new Runnable() { - - @Override - public void run() { - try { - System.out.println("running TestObservable thread"); - for (String s : values) { - if ("fail".equals(s)) - throw new RuntimeException("Forced Failure"); - System.out.println("TestObservable onNext: " + s); - observer.onNext(s); - } - System.out.println("TestObservable onCompleted"); - observer.onCompleted(); - } catch (Throwable e) { - System.out.println("TestObservable onError: " + e); - observer.onError(e); - } - } - - }); - System.out.println("starting TestObservable thread"); - t.start(); - System.out.println("done starting TestObservable thread"); - return s; - } - - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationOnErrorReturn.java b/rxjava-core/src/main/java/rx/operators/OperationOnErrorReturn.java index 02a268c04a..d5d6a9fbc7 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationOnErrorReturn.java +++ b/rxjava-core/src/main/java/rx/operators/OperationOnErrorReturn.java @@ -15,16 +15,9 @@ */ package rx.operators; -import static org.junit.Assert.*; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - import java.util.Arrays; import java.util.concurrent.atomic.AtomicReference; -import org.junit.Test; -import org.mockito.Mockito; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -125,119 +118,4 @@ public void unsubscribe() { }; } } - - public static class UnitTest { - - @Test - public void testResumeNext() { - Subscription s = mock(Subscription.class); - TestObservable f = new TestObservable(s, "one"); - Observable w = Observable.create(f); - final AtomicReference capturedException = new AtomicReference(); - - Observable observable = Observable.create(onErrorReturn(w, new Func1() { - - @Override - public String call(Throwable e) { - capturedException.set(e); - return "failure"; - } - - })); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - observable.subscribe(aObserver); - - try { - f.t.join(); - } catch (InterruptedException e) { - fail(e.getMessage()); - } - - verify(aObserver, Mockito.never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, times(1)).onNext("failure"); - assertNotNull(capturedException.get()); - } - - /** - * Test that when a function throws an exception this is propagated through onError - */ - @Test - public void testFunctionThrowsError() { - Subscription s = mock(Subscription.class); - TestObservable f = new TestObservable(s, "one"); - Observable w = Observable.create(f); - final AtomicReference capturedException = new AtomicReference(); - - Observable observable = Observable.create(onErrorReturn(w, new Func1() { - - @Override - public String call(Throwable e) { - capturedException.set(e); - throw new RuntimeException("exception from function"); - } - - })); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - observable.subscribe(aObserver); - - try { - f.t.join(); - } catch (InterruptedException e) { - fail(e.getMessage()); - } - - // we should get the "one" value before the error - verify(aObserver, times(1)).onNext("one"); - - // we should have received an onError call on the Observer since the resume function threw an exception - verify(aObserver, times(1)).onError(any(Throwable.class)); - verify(aObserver, times(0)).onCompleted(); - assertNotNull(capturedException.get()); - } - - private static class TestObservable implements OnSubscribeFunc { - - final Subscription s; - final String[] values; - Thread t = null; - - public TestObservable(Subscription s, String... values) { - this.s = s; - this.values = values; - } - - @Override - public Subscription onSubscribe(final Observer observer) { - System.out.println("TestObservable subscribed to ..."); - t = new Thread(new Runnable() { - - @Override - public void run() { - try { - System.out.println("running TestObservable thread"); - for (String s : values) { - System.out.println("TestObservable onNext: " + s); - observer.onNext(s); - } - throw new RuntimeException("Forced Failure"); - } catch (Throwable e) { - observer.onError(e); - } - } - - }); - System.out.println("starting TestObservable thread"); - t.start(); - System.out.println("done starting TestObservable thread"); - return s; - } - - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationOnExceptionResumeNextViaObservable.java b/rxjava-core/src/main/java/rx/operators/OperationOnExceptionResumeNextViaObservable.java index 9642ed9269..864ba0730e 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationOnExceptionResumeNextViaObservable.java +++ b/rxjava-core/src/main/java/rx/operators/OperationOnExceptionResumeNextViaObservable.java @@ -15,20 +15,12 @@ */ package rx.operators; -import static org.junit.Assert.*; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - import java.util.concurrent.atomic.AtomicReference; -import org.junit.Test; -import org.mockito.Mockito; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.util.functions.Func1; /** * Instruct an Observable to pass control to another Observable rather than invoking @@ -123,216 +115,4 @@ public void unsubscribe() { }; } } - - public static class UnitTest { - - @Test - public void testResumeNextWithException() { - Subscription s = mock(Subscription.class); - // Trigger failure on second element - TestObservable f = new TestObservable(s, "one", "EXCEPTION", "two", "three"); - Observable w = Observable.create(f); - Observable resume = Observable.from("twoResume", "threeResume"); - Observable observable = Observable.create(onExceptionResumeNextViaObservable(w, resume)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - observable.subscribe(aObserver); - - try { - f.t.join(); - } catch (InterruptedException e) { - fail(e.getMessage()); - } - - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, Mockito.never()).onNext("two"); - verify(aObserver, Mockito.never()).onNext("three"); - verify(aObserver, times(1)).onNext("twoResume"); - verify(aObserver, times(1)).onNext("threeResume"); - verify(aObserver, Mockito.never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verifyNoMoreInteractions(aObserver); - } - - @Test - public void testResumeNextWithRuntimeException() { - Subscription s = mock(Subscription.class); - // Trigger failure on second element - TestObservable f = new TestObservable(s, "one", "RUNTIMEEXCEPTION", "two", "three"); - Observable w = Observable.create(f); - Observable resume = Observable.from("twoResume", "threeResume"); - Observable observable = Observable.create(onExceptionResumeNextViaObservable(w, resume)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - observable.subscribe(aObserver); - - try { - f.t.join(); - } catch (InterruptedException e) { - fail(e.getMessage()); - } - - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, Mockito.never()).onNext("two"); - verify(aObserver, Mockito.never()).onNext("three"); - verify(aObserver, times(1)).onNext("twoResume"); - verify(aObserver, times(1)).onNext("threeResume"); - verify(aObserver, Mockito.never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verifyNoMoreInteractions(aObserver); - } - - @Test - public void testThrowablePassesThru() { - Subscription s = mock(Subscription.class); - // Trigger failure on second element - TestObservable f = new TestObservable(s, "one", "THROWABLE", "two", "three"); - Observable w = Observable.create(f); - Observable resume = Observable.from("twoResume", "threeResume"); - Observable observable = Observable.create(onExceptionResumeNextViaObservable(w, resume)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - observable.subscribe(aObserver); - - try { - f.t.join(); - } catch (InterruptedException e) { - fail(e.getMessage()); - } - - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, never()).onNext("two"); - verify(aObserver, never()).onNext("three"); - verify(aObserver, never()).onNext("twoResume"); - verify(aObserver, never()).onNext("threeResume"); - verify(aObserver, times(1)).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - verifyNoMoreInteractions(aObserver); - } - - @Test - public void testErrorPassesThru() { - Subscription s = mock(Subscription.class); - // Trigger failure on second element - TestObservable f = new TestObservable(s, "one", "ERROR", "two", "three"); - Observable w = Observable.create(f); - Observable resume = Observable.from("twoResume", "threeResume"); - Observable observable = Observable.create(onExceptionResumeNextViaObservable(w, resume)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - observable.subscribe(aObserver); - - try { - f.t.join(); - } catch (InterruptedException e) { - fail(e.getMessage()); - } - - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, never()).onNext("two"); - verify(aObserver, never()).onNext("three"); - verify(aObserver, never()).onNext("twoResume"); - verify(aObserver, never()).onNext("threeResume"); - verify(aObserver, times(1)).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - verifyNoMoreInteractions(aObserver); - } - - @Test - public void testMapResumeAsyncNext() { - Subscription sr = mock(Subscription.class); - // Trigger multiple failures - Observable w = Observable.from("one", "fail", "two", "three", "fail"); - // Resume Observable is async - TestObservable f = new TestObservable(sr, "twoResume", "threeResume"); - Observable resume = Observable.create(f); - - // Introduce map function that fails intermittently (Map does not prevent this when the observer is a - // rx.operator incl onErrorResumeNextViaObservable) - w = w.map(new Func1() { - public String call(String s) { - if ("fail".equals(s)) - throw new RuntimeException("Forced Failure"); - System.out.println("BadMapper:" + s); - return s; - } - }); - - Observable observable = Observable.create(onExceptionResumeNextViaObservable(w, resume)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - observable.subscribe(aObserver); - - try { - // if the thread gets started (which it shouldn't if it's working correctly) - if (f.t != null) { - f.t.join(); - } - } catch (InterruptedException e) { - fail(e.getMessage()); - } - - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, never()).onNext("two"); - verify(aObserver, never()).onNext("three"); - verify(aObserver, times(1)).onNext("twoResume"); - verify(aObserver, times(1)).onNext("threeResume"); - verify(aObserver, Mockito.never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - private static class TestObservable implements OnSubscribeFunc { - - final Subscription s; - final String[] values; - Thread t = null; - - public TestObservable(Subscription s, String... values) { - this.s = s; - this.values = values; - } - - @Override - public Subscription onSubscribe(final Observer observer) { - System.out.println("TestObservable subscribed to ..."); - t = new Thread(new Runnable() { - - @Override - public void run() { - try { - System.out.println("running TestObservable thread"); - for (String s : values) { - if ("EXCEPTION".equals(s)) - throw new Exception("Forced Exception"); - else if ("RUNTIMEEXCEPTION".equals(s)) - throw new RuntimeException("Forced RuntimeException"); - else if ("ERROR".equals(s)) - throw new Error("Forced Error"); - else if ("THROWABLE".equals(s)) - throw new Throwable("Forced Throwable"); - System.out.println("TestObservable onNext: " + s); - observer.onNext(s); - } - System.out.println("TestObservable onCompleted"); - observer.onCompleted(); - } catch (Throwable e) { - System.out.println("TestObservable onError: " + e); - observer.onError(e); - } - } - - }); - System.out.println("starting TestObservable thread"); - t.start(); - System.out.println("done starting TestObservable thread"); - return s; - } - - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationParallel.java b/rxjava-core/src/main/java/rx/operators/OperationParallel.java new file mode 100644 index 0000000000..5a584ce370 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationParallel.java @@ -0,0 +1,59 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import java.util.concurrent.atomic.AtomicInteger; + +import rx.Observable; +import rx.Scheduler; +import rx.concurrency.Schedulers; +import rx.observables.GroupedObservable; +import rx.util.functions.Func0; +import rx.util.functions.Func1; + +/** + * Identifies unit of work that can be executed in parallel on a given Scheduler. + */ +public final class OperationParallel { + + public static Observable parallel(Observable source, Func1, Observable> f) { + return parallel(source, f, Schedulers.threadPoolForComputation()); + } + + public static Observable parallel(final Observable source, final Func1, Observable> f, final Scheduler s) { + return Observable.defer(new Func0>() { + + @Override + public Observable call() { + final AtomicInteger i = new AtomicInteger(0); + return source.groupBy(new Func1() { + + @Override + public Integer call(T t) { + return i.incrementAndGet() % s.degreeOfParallelism(); + } + + }).flatMap(new Func1, Observable>() { + + @Override + public Observable call(GroupedObservable group) { + return f.call(group.observeOn(s)); + } + }).synchronize(); + } + }); + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationParallelMerge.java b/rxjava-core/src/main/java/rx/operators/OperationParallelMerge.java new file mode 100644 index 0000000000..cb2dd0bc34 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationParallelMerge.java @@ -0,0 +1,52 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import java.util.concurrent.atomic.AtomicLong; + +import rx.Observable; +import rx.Scheduler; +import rx.concurrency.Schedulers; +import rx.observables.GroupedObservable; +import rx.util.functions.Func1; + +public class OperationParallelMerge { + + public static Observable> parallelMerge(final Observable> source, final int parallelObservables) { + return parallelMerge(source, parallelObservables, Schedulers.currentThread()); + } + + public static Observable> parallelMerge(final Observable> source, final int parallelObservables, final Scheduler scheduler) { + + return source.groupBy(new Func1, Integer>() { + final AtomicLong rollingCount = new AtomicLong(); + + @Override + public Integer call(Observable o) { + return (int) rollingCount.incrementAndGet() % parallelObservables; + } + }).map(new Func1>, Observable>() { + + @Override + public Observable call(GroupedObservable> o) { + return Observable.merge(o).observeOn(scheduler); + } + + }); + + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationParallelMergeTest.java b/rxjava-core/src/main/java/rx/operators/OperationParallelMergeTest.java new file mode 100644 index 0000000000..a001877a71 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationParallelMergeTest.java @@ -0,0 +1,135 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.concurrency.Schedulers; +import rx.subjects.PublishSubject; +import rx.util.functions.Action1; +import rx.util.functions.Func1; + +public class OperationParallelMergeTest { + + @Test + public void testParallelMerge() { + PublishSubject p1 = PublishSubject. create(); + PublishSubject p2 = PublishSubject. create(); + PublishSubject p3 = PublishSubject. create(); + PublishSubject p4 = PublishSubject. create(); + + Observable> fourStreams = Observable.> from(p1, p2, p3, p4); + + Observable> twoStreams = OperationParallelMerge.parallelMerge(fourStreams, 2); + Observable> threeStreams = OperationParallelMerge.parallelMerge(fourStreams, 3); + + List> fourList = fourStreams.toList().toBlockingObservable().last(); + List> threeList = threeStreams.toList().toBlockingObservable().last(); + List> twoList = twoStreams.toList().toBlockingObservable().last(); + + System.out.println("two list: " + twoList); + System.out.println("three list: " + threeList); + System.out.println("four list: " + fourList); + + assertEquals(4, fourList.size()); + assertEquals(3, threeList.size()); + assertEquals(2, twoList.size()); + } + + @Test + public void testNumberOfThreads() { + final ConcurrentHashMap threads = new ConcurrentHashMap(); + Observable.merge(getStreams()) + .toBlockingObservable().forEach(new Action1() { + + @Override + public void call(String o) { + System.out.println("o: " + o + " Thread: " + Thread.currentThread()); + threads.put(Thread.currentThread().getName(), Thread.currentThread().getName()); + } + }); + + // without injecting anything, the getStream() method uses Interval which runs on a default scheduler + assertEquals(Runtime.getRuntime().availableProcessors(), threads.keySet().size()); + + // clear + threads.clear(); + + // now we parallelMerge into 3 streams and observeOn for each + // we expect 3 threads in the output + OperationParallelMerge.parallelMerge(getStreams(), 3) + .flatMap(new Func1, Observable>() { + + @Override + public Observable call(Observable o) { + // for each of the parallel + return o.observeOn(Schedulers.newThread()); + } + }) + .toBlockingObservable().forEach(new Action1() { + + @Override + public void call(String o) { + System.out.println("o: " + o + " Thread: " + Thread.currentThread()); + threads.put(Thread.currentThread().getName(), Thread.currentThread().getName()); + } + }); + + assertEquals(3, threads.keySet().size()); + } + + @Test + public void testNumberOfThreadsOnScheduledMerge() { + final ConcurrentHashMap threads = new ConcurrentHashMap(); + + // now we parallelMerge into 3 streams and observeOn for each + // we expect 3 threads in the output + Observable.merge(OperationParallelMerge.parallelMerge(getStreams(), 3, Schedulers.newThread())) + .toBlockingObservable().forEach(new Action1() { + + @Override + public void call(String o) { + System.out.println("o: " + o + " Thread: " + Thread.currentThread()); + threads.put(Thread.currentThread().getName(), Thread.currentThread().getName()); + } + }); + + assertEquals(3, threads.keySet().size()); + } + + private static Observable> getStreams() { + return Observable.range(0, 10).map(new Func1>() { + + @Override + public Observable call(final Integer i) { + return Observable.interval(10, TimeUnit.MILLISECONDS).map(new Func1() { + + @Override + public String call(Long l) { + return "Stream " + i + " Value: " + l; + } + }).take(5); + } + }); + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationRefCount.java b/rxjava-core/src/main/java/rx/operators/OperationRefCount.java new file mode 100644 index 0000000000..d19b700afd --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationRefCount.java @@ -0,0 +1,66 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.observables.ConnectableObservable; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action0; + +/** + * Returns an observable sequence that stays connected to the source as long + * as there is at least one subscription to the observable sequence. + */ +public final class OperationRefCount { + public static Observable.OnSubscribeFunc refCount(ConnectableObservable connectableObservable) { + return new RefCount(connectableObservable); + } + + private static class RefCount implements Observable.OnSubscribeFunc { + private final ConnectableObservable innerConnectableObservable; + private final Object gate = new Object(); + private int count = 0; + private Subscription connection = null; + + public RefCount(ConnectableObservable innerConnectableObservable) { + this.innerConnectableObservable = innerConnectableObservable; + } + + @Override + public Subscription onSubscribe(Observer observer) { + final Subscription subscription = innerConnectableObservable.subscribe(observer); + synchronized (gate) { + if (count++ == 0) { + connection = innerConnectableObservable.connect(); + } + } + return Subscriptions.create(new Action0() { + @Override + public void call() { + synchronized (gate) { + if (--count == 0) { + connection.unsubscribe(); + connection = null; + } + } + subscription.unsubscribe(); + } + }); + } + } +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/operators/OperationRetry.java b/rxjava-core/src/main/java/rx/operators/OperationRetry.java index 977373cb01..430ef53ce4 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationRetry.java +++ b/rxjava-core/src/main/java/rx/operators/OperationRetry.java @@ -15,22 +15,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Test; -import org.mockito.InOrder; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; +import rx.Scheduler; import rx.Subscription; import rx.concurrency.Schedulers; import rx.subscriptions.CompositeSubscription; -import rx.subscriptions.Subscriptions; -import rx.util.functions.Action0; +import rx.subscriptions.MultipleAssignmentSubscription; +import rx.util.functions.Func2; public class OperationRetry { @@ -58,17 +54,19 @@ public Retry(Observable source, int retryCount) { @Override public Subscription onSubscribe(Observer observer) { - subscription.add(Schedulers.currentThread().schedule(attemptSubscription(observer))); + MultipleAssignmentSubscription rescursiveSubscription = new MultipleAssignmentSubscription(); + subscription.add(Schedulers.currentThread().schedule(rescursiveSubscription, attemptSubscription(observer))); + subscription.add(rescursiveSubscription); return subscription; } - private Action0 attemptSubscription(final Observer observer) { - return new Action0() { + private Func2 attemptSubscription(final Observer observer) { + return new Func2() { @Override - public void call() { + public Subscription call(final Scheduler scheduler, final MultipleAssignmentSubscription rescursiveSubscription) { attempts.incrementAndGet(); - source.subscribe(new Observer() { + return source.subscribe(new Observer() { @Override public void onCompleted() { @@ -79,10 +77,8 @@ public void onCompleted() { public void onError(Throwable e) { if ((retryCount == INFINITE_RETRY || attempts.get() <= retryCount) && !subscription.isUnsubscribed()) { // retry again - // remove the last subscription since we have completed (so as we retry we don't build up a huge list) - subscription.removeLast(); - // add the new subscription and schedule a retry - subscription.add(Schedulers.currentThread().schedule(attemptSubscription(observer))); + // add the new subscription and schedule a retry recursively + rescursiveSubscription.setSubscription(scheduler.schedule(rescursiveSubscription, attemptSubscription(observer))); } else { // give up and pass the failure observer.onError(e); @@ -96,108 +92,9 @@ public void onNext(T v) { }); } - }; - } - - } - - public static class UnitTest { - - @Test - public void testOriginFails() { - @SuppressWarnings("unchecked") - Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(2)); - origin.subscribe(observer); - - InOrder inOrder = inOrder(observer); - inOrder.verify(observer, times(1)).onNext("beginningEveryTime"); - inOrder.verify(observer, times(1)).onError(any(RuntimeException.class)); - inOrder.verify(observer, never()).onNext("onSuccessOnly"); - inOrder.verify(observer, never()).onCompleted(); - } - @Test - public void testRetryFail() { - int NUM_RETRIES = 1; - int NUM_FAILURES = 2; - @SuppressWarnings("unchecked") - Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(NUM_FAILURES)); - Observable.create(retry(origin, NUM_RETRIES)).subscribe(observer); - - InOrder inOrder = inOrder(observer); - // should show 2 attempts (first time fail, second time (1st retry) fail) - inOrder.verify(observer, times(1 + NUM_RETRIES)).onNext("beginningEveryTime"); - // should only retry once, fail again and emit onError - inOrder.verify(observer, times(1)).onError(any(RuntimeException.class)); - // no success - inOrder.verify(observer, never()).onNext("onSuccessOnly"); - inOrder.verify(observer, never()).onCompleted(); - inOrder.verifyNoMoreInteractions(); - } - - @Test - public void testRetrySuccess() { - int NUM_RETRIES = 3; - int NUM_FAILURES = 2; - @SuppressWarnings("unchecked") - Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(NUM_FAILURES)); - Observable.create(retry(origin, NUM_RETRIES)).subscribe(observer); - - InOrder inOrder = inOrder(observer); - // should show 3 attempts - inOrder.verify(observer, times(1 + NUM_FAILURES)).onNext("beginningEveryTime"); - // should have no errors - inOrder.verify(observer, never()).onError(any(Throwable.class)); - // should have a single success - inOrder.verify(observer, times(1)).onNext("onSuccessOnly"); - // should have a single successful onCompleted - inOrder.verify(observer, times(1)).onCompleted(); - inOrder.verifyNoMoreInteractions(); - } - - @Test - public void testInfiniteRetry() { - int NUM_FAILURES = 20; - @SuppressWarnings("unchecked") - Observer observer = mock(Observer.class); - Observable origin = Observable.create(new FuncWithErrors(NUM_FAILURES)); - Observable.create(retry(origin)).subscribe(observer); - - InOrder inOrder = inOrder(observer); - // should show 3 attempts - inOrder.verify(observer, times(1 + NUM_FAILURES)).onNext("beginningEveryTime"); - // should have no errors - inOrder.verify(observer, never()).onError(any(Throwable.class)); - // should have a single success - inOrder.verify(observer, times(1)).onNext("onSuccessOnly"); - // should have a single successful onCompleted - inOrder.verify(observer, times(1)).onCompleted(); - inOrder.verifyNoMoreInteractions(); + }; } - public static class FuncWithErrors implements OnSubscribeFunc { - - private final int numFailures; - private final AtomicInteger count = new AtomicInteger(0); - - FuncWithErrors(int count) { - this.numFailures = count; - } - - @Override - public Subscription onSubscribe(Observer o) { - o.onNext("beginningEveryTime"); - if (count.incrementAndGet() <= numFailures) { - o.onError(new RuntimeException("forced failure: " + count.get())); - } else { - o.onNext("onSuccessOnly"); - o.onCompleted(); - } - return Subscriptions.empty(); - } - } } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationSample.java b/rxjava-core/src/main/java/rx/operators/OperationSample.java index cb95737fa8..89da00e5fb 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSample.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSample.java @@ -15,25 +15,16 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Scheduler; import rx.Subscription; import rx.concurrency.Schedulers; -import rx.concurrency.TestScheduler; import rx.subscriptions.Subscriptions; import rx.util.functions.Action0; @@ -49,7 +40,7 @@ public final class OperationSample { * Samples the observable sequence at each interval. */ public static OnSubscribeFunc sample(final Observable source, long period, TimeUnit unit) { - return new Sample(source, period, unit, Schedulers.executor(Executors.newSingleThreadScheduledExecutor())); + return new Sample(source, period, unit, Schedulers.threadPoolForComputation()); } /** @@ -58,16 +49,16 @@ public static OnSubscribeFunc sample(final Observable source public static OnSubscribeFunc sample(final Observable source, long period, TimeUnit unit, Scheduler scheduler) { return new Sample(source, period, unit, scheduler); } - + private static class Sample implements OnSubscribeFunc { private final Observable source; private final long period; private final TimeUnit unit; private final Scheduler scheduler; - + private final AtomicBoolean hasValue = new AtomicBoolean(); private final AtomicReference latestValue = new AtomicReference(); - + private Sample(Observable source, long interval, TimeUnit unit, Scheduler scheduler) { this.source = source; this.period = interval; @@ -80,11 +71,13 @@ public Subscription onSubscribe(final Observer observer) { Observable clock = Observable.create(OperationInterval.interval(period, unit, scheduler)); final Subscription clockSubscription = clock.subscribe(new Observer() { @Override - public void onCompleted() { /* the clock never completes */ } - + public void onCompleted() { /* the clock never completes */ + } + @Override - public void onError(Throwable e) { /* the clock has no errors */ } - + public void onError(Throwable e) { /* the clock has no errors */ + } + @Override public void onNext(Long tick) { if (hasValue.get()) { @@ -92,27 +85,27 @@ public void onNext(Long tick) { } } }); - + final Subscription sourceSubscription = source.subscribe(new Observer() { @Override public void onCompleted() { clockSubscription.unsubscribe(); observer.onCompleted(); } - + @Override public void onError(Throwable e) { clockSubscription.unsubscribe(); observer.onError(e); } - + @Override public void onNext(T value) { latestValue.set(value); hasValue.set(true); } }); - + return Subscriptions.create(new Action0() { @Override public void call() { @@ -122,79 +115,4 @@ public void call() { }); } } - - public static class UnitTest { - private TestScheduler scheduler; - private Observer observer; - - @Before - @SuppressWarnings("unchecked") // due to mocking - public void before() { - scheduler = new TestScheduler(); - observer = mock(Observer.class); - } - - @Test - public void testSample() { - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(final Observer observer1) { - scheduler.schedule(new Action0() { - @Override - public void call() { - observer1.onNext(1L); - } - }, 1, TimeUnit.SECONDS); - scheduler.schedule(new Action0() { - @Override - public void call() { - observer1.onNext(2L); - } - }, 2, TimeUnit.SECONDS); - scheduler.schedule(new Action0() { - @Override - public void call() { - observer1.onCompleted(); - } - }, 3, TimeUnit.SECONDS); - - return Subscriptions.empty(); - } - }); - - Observable sampled = Observable.create(OperationSample.sample(source, 400L, TimeUnit.MILLISECONDS, scheduler)); - sampled.subscribe(observer); - - InOrder inOrder = inOrder(observer); - - scheduler.advanceTimeTo(800L, TimeUnit.MILLISECONDS); - verify(observer, never()).onNext(any(Long.class)); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - scheduler.advanceTimeTo(1200L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext(1L); - verify(observer, never()).onNext(2L); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - scheduler.advanceTimeTo(1600L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext(1L); - verify(observer, never()).onNext(2L); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - scheduler.advanceTimeTo(2000L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(1L); - inOrder.verify(observer, times(1)).onNext(2L); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - scheduler.advanceTimeTo(3000L, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(1L); - inOrder.verify(observer, times(2)).onNext(2L); - verify(observer, times(1)).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationScan.java b/rxjava-core/src/main/java/rx/operators/OperationScan.java index 513646b8ef..a5eedfee46 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationScan.java +++ b/rxjava-core/src/main/java/rx/operators/OperationScan.java @@ -15,13 +15,6 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.MockitoAnnotations; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -53,7 +46,8 @@ public final class OperationScan { * An accumulator function to be invoked on each element from the sequence. * * @return An observable sequence whose elements are the result of accumulating the output from the list of Observables. - * @see Observable.Scan(TSource, TAccumulate) Method (IObservable(TSource), TAccumulate, Func(TAccumulate, TSource, TAccumulate)) + * @see Observable.Scan(TSource, TAccumulate) Method (IObservable(TSource), TAccumulate, Func(TAccumulate, TSource, + * TAccumulate)) */ public static OnSubscribeFunc scan(Observable sequence, R initialValue, Func2 accumulator) { return new Accumulator(sequence, initialValue, accumulator); @@ -77,18 +71,18 @@ public static OnSubscribeFunc scan(Observable sequence, Func private static class AccuWithoutInitialValue implements OnSubscribeFunc { private final Observable sequence; private final Func2 accumulatorFunction; - + private AccumulatingObserver accumulatingObserver; - + private AccuWithoutInitialValue(Observable sequence, Func2 accumulator) { this.sequence = sequence; this.accumulatorFunction = accumulator; } - + @Override public Subscription onSubscribe(final Observer observer) { return sequence.subscribe(new Observer() { - + // has to be synchronized so that the initial value is always sent only once. @Override public synchronized void onNext(T value) { @@ -99,7 +93,7 @@ public synchronized void onNext(T value) { accumulatingObserver.onNext(value); } } - + @Override public void onError(Throwable e) { observer.onError(e); @@ -112,7 +106,7 @@ public void onCompleted() { }); } } - + private static class Accumulator implements OnSubscribeFunc { private final Observable sequence; private final R initialValue; @@ -140,7 +134,7 @@ private static class AccumulatingObserver implements Observer { private AccumulatingObserver(Observer observer, R initialValue, Func2 accumulator) { this.observer = observer; this.accumulatorFunction = accumulator; - + this.acc = initialValue; } @@ -160,103 +154,15 @@ public synchronized void onNext(T value) { observer.onError(ex); } } - + @Override public void onError(Throwable e) { observer.onError(e); } - + @Override public void onCompleted() { observer.onCompleted(); } } - - public static class UnitTest { - - @Before - public void before() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testScanIntegersWithInitialValue() { - @SuppressWarnings("unchecked") - Observer observer = mock(Observer.class); - - Observable observable = Observable.from(1, 2, 3); - - Observable m = Observable.create(scan(observable, "", new Func2() { - - @Override - public String call(String s, Integer n) { - return s + n.toString(); - } - - })); - m.subscribe(observer); - - verify(observer, never()).onError(any(Throwable.class)); - verify(observer, times(1)).onNext(""); - verify(observer, times(1)).onNext("1"); - verify(observer, times(1)).onNext("12"); - verify(observer, times(1)).onNext("123"); - verify(observer, times(4)).onNext(anyString()); - verify(observer, times(1)).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - } - - @Test - public void testScanIntegersWithoutInitialValue() { - @SuppressWarnings("unchecked") - Observer Observer = mock(Observer.class); - - Observable observable = Observable.from(1, 2, 3); - - Observable m = Observable.create(scan(observable, new Func2() { - - @Override - public Integer call(Integer t1, Integer t2) { - return t1 + t2; - } - - })); - m.subscribe(Observer); - - verify(Observer, never()).onError(any(Throwable.class)); - verify(Observer, never()).onNext(0); - verify(Observer, times(1)).onNext(1); - verify(Observer, times(1)).onNext(3); - verify(Observer, times(1)).onNext(6); - verify(Observer, times(3)).onNext(anyInt()); - verify(Observer, times(1)).onCompleted(); - verify(Observer, never()).onError(any(Throwable.class)); - } - - @Test - public void testScanIntegersWithoutInitialValueAndOnlyOneValue() { - @SuppressWarnings("unchecked") - Observer Observer = mock(Observer.class); - - Observable observable = Observable.from(1); - - Observable m = Observable.create(scan(observable, new Func2() { - - @Override - public Integer call(Integer t1, Integer t2) { - return t1 + t2; - } - - })); - m.subscribe(Observer); - - verify(Observer, never()).onError(any(Throwable.class)); - verify(Observer, never()).onNext(0); - verify(Observer, times(1)).onNext(1); - verify(Observer, times(1)).onNext(anyInt()); - verify(Observer, times(1)).onCompleted(); - verify(Observer, never()).onError(any(Throwable.class)); - } - } - } diff --git a/rxjava-core/src/main/java/rx/operators/OperationSkip.java b/rxjava-core/src/main/java/rx/operators/OperationSkip.java index 11fb10f1b5..4dc3359815 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSkip.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSkip.java @@ -15,13 +15,8 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Test; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -112,39 +107,4 @@ public void onNext(T args) { } } - - public static class UnitTest { - - @Test - public void testSkip1() { - Observable w = Observable.from("one", "two", "three"); - Observable skip = Observable.create(skip(w, 2)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - skip.subscribe(aObserver); - verify(aObserver, never()).onNext("one"); - verify(aObserver, never()).onNext("two"); - verify(aObserver, times(1)).onNext("three"); - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - @Test - public void testSkip2() { - Observable w = Observable.from("one", "two", "three"); - Observable skip = Observable.create(skip(w, 1)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - skip.subscribe(aObserver); - verify(aObserver, never()).onNext("one"); - verify(aObserver, times(1)).onNext("two"); - verify(aObserver, times(1)).onNext("three"); - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - } - } diff --git a/rxjava-core/src/main/java/rx/operators/OperationSkipLast.java b/rxjava-core/src/main/java/rx/operators/OperationSkipLast.java new file mode 100644 index 0000000000..f3cb462e55 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationSkipLast.java @@ -0,0 +1,126 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.concurrent.locks.ReentrantLock; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; + +/** + * Bypasses a specified number of elements at the end of an observable sequence. + */ +public class OperationSkipLast { + + /** + * Bypasses a specified number of elements at the end of an observable + * sequence. + *

+ * This operator accumulates a queue with a length enough to store the first + * count elements. As more elements are received, elements are taken from + * the front of the queue and produced on the result sequence. This causes + * elements to be delayed. + * + * @param source + * the source sequence. + * @param count + * number of elements to bypass at the end of the source + * sequence. + * @return An observable sequence containing the source sequence elements + * except for the bypassed ones at the end. + * + * @throws IndexOutOfBoundsException + * count is less than zero. + */ + public static OnSubscribeFunc skipLast( + Observable source, int count) { + return new SkipLast(source, count); + } + + private static class SkipLast implements OnSubscribeFunc { + private final int count; + private final Observable source; + + private SkipLast(Observable source, int count) { + this.count = count; + this.source = source; + } + + public Subscription onSubscribe(final Observer observer) { + if (count < 0) { + throw new IndexOutOfBoundsException( + "count could not be negative"); + } + final SafeObservableSubscription subscription = new SafeObservableSubscription(); + return subscription.wrap(source.subscribe(new Observer() { + + private final ReentrantLock lock = new ReentrantLock(); + + /** + * Store the last count elements until now. + */ + private final Deque deque = new LinkedList(); + + @Override + public void onCompleted() { + observer.onCompleted(); + } + + @Override + public void onError(Throwable e) { + observer.onError(e); + } + + @Override + public void onNext(T value) { + if (count == 0) { + // If count == 0, we do not need to put value into deque + // and remove it at once. We can emit the value + // directly. + try { + observer.onNext(value); + } catch (Throwable ex) { + observer.onError(ex); + subscription.unsubscribe(); + } + return; + } + lock.lock(); + try { + deque.offerLast(value); + if (deque.size() > count) { + // Now deque has count + 1 elements, so the first + // element in the deque definitely does not belong + // to the last count elements of the source + // sequence. We can emit it now. + observer.onNext(deque.removeFirst()); + } + } catch (Throwable ex) { + observer.onError(ex); + subscription.unsubscribe(); + } finally { + lock.unlock(); + } + } + + })); + } + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationSkipWhile.java b/rxjava-core/src/main/java/rx/operators/OperationSkipWhile.java index 8277f80fc2..6bc747e9bf 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSkipWhile.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSkipWhile.java @@ -15,16 +15,9 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; -import static rx.Observable.create; - import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Test; -import org.mockito.InOrder; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -40,7 +33,7 @@ public final class OperationSkipWhile { public static OnSubscribeFunc skipWhileWithIndex(Observable source, Func2 predicate) { return new SkipWhile(source, predicate); } - + public static OnSubscribeFunc skipWhile(Observable source, final Func1 predicate) { return new SkipWhile(source, new Func2() { @Override @@ -55,7 +48,7 @@ private static class SkipWhile implements OnSubscribeFunc { private final Func2 predicate; private final AtomicBoolean skipping = new AtomicBoolean(true); private final AtomicInteger index = new AtomicInteger(0); - + SkipWhile(Observable source, Func2 pred) { this.source = source; this.predicate = pred; @@ -93,7 +86,7 @@ public void onNext(T next) { observer.onNext(next); } else { } - } catch(Throwable t) { + } catch (Throwable t) { observer.onError(t); } } @@ -102,92 +95,4 @@ public void onNext(T next) { } } - - public static class UnitTest { - @SuppressWarnings("unchecked") - Observer w = mock(Observer.class); - - private static final Func1 LESS_THAN_FIVE = new Func1() { - @Override - public Boolean call(Integer v) { - if (v == 42) throw new RuntimeException("that's not the answer to everything!"); - return v < 5; - } - }; - - private static final Func2 INDEX_LESS_THAN_THREE = new Func2() { - @Override - public Boolean call(Integer value, Integer index) { - return index < 3; - } - }; - - @Test - public void testSkipWithIndex() { - Observable src = Observable.from(1, 2, 3, 4, 5); - create(skipWhileWithIndex(src, INDEX_LESS_THAN_THREE)).subscribe(w); - - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext(4); - inOrder.verify(w, times(1)).onNext(5); - inOrder.verify(w, times(1)).onCompleted(); - inOrder.verify(w, never()).onError(any(Throwable.class)); - } - - @Test - public void testSkipEmpty() { - Observable src = Observable.empty(); - create(skipWhile(src, LESS_THAN_FIVE)).subscribe(w); - verify(w, never()).onNext(anyInt()); - verify(w, never()).onError(any(Throwable.class)); - verify(w, times(1)).onCompleted(); - } - - @Test - public void testSkipEverything() { - Observable src = Observable.from(1, 2, 3, 4, 3, 2, 1); - create(skipWhile(src, LESS_THAN_FIVE)).subscribe(w); - verify(w, never()).onNext(anyInt()); - verify(w, never()).onError(any(Throwable.class)); - verify(w, times(1)).onCompleted(); - } - - @Test - public void testSkipNothing() { - Observable src = Observable.from(5, 3, 1); - create(skipWhile(src, LESS_THAN_FIVE)).subscribe(w); - - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext(5); - inOrder.verify(w, times(1)).onNext(3); - inOrder.verify(w, times(1)).onNext(1); - inOrder.verify(w, times(1)).onCompleted(); - inOrder.verify(w, never()).onError(any(Throwable.class)); - } - - @Test - public void testSkipSome() { - Observable src = Observable.from(1, 2, 3, 4, 5, 3, 1, 5); - create(skipWhile(src, LESS_THAN_FIVE)).subscribe(w); - - InOrder inOrder = inOrder(w); - inOrder.verify(w, times(1)).onNext(5); - inOrder.verify(w, times(1)).onNext(3); - inOrder.verify(w, times(1)).onNext(1); - inOrder.verify(w, times(1)).onNext(5); - inOrder.verify(w, times(1)).onCompleted(); - inOrder.verify(w, never()).onError(any(Throwable.class)); - } - - @Test - public void testSkipError() { - Observable src = Observable.from(1, 2, 42, 5, 3, 1); - create(skipWhile(src, LESS_THAN_FIVE)).subscribe(w); - - InOrder inOrder = inOrder(w); - inOrder.verify(w, never()).onNext(anyInt()); - inOrder.verify(w, never()).onCompleted(); - inOrder.verify(w, times(1)).onError(any(RuntimeException.class)); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationSubscribeOn.java b/rxjava-core/src/main/java/rx/operators/OperationSubscribeOn.java index 6f75a32b57..fe7aa00216 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSubscribeOn.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSubscribeOn.java @@ -1,12 +1,12 @@ /** * Copyright 2013 Netflix, Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,17 +15,11 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import org.junit.Test; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Scheduler; import rx.Subscription; -import rx.concurrency.Schedulers; import rx.util.functions.Action0; import rx.util.functions.Func2; @@ -79,30 +73,4 @@ public void call() { }); } } - - public static class UnitTest { - - @Test - @SuppressWarnings("unchecked") - public void testSubscribeOn() { - Observable w = Observable.from(1, 2, 3); - - Scheduler scheduler = spy(OperatorTester.UnitTest.forwardingScheduler(Schedulers.immediate())); - - Observer observer = mock(Observer.class); - Subscription subscription = Observable.create(subscribeOn(w, scheduler)).subscribe(observer); - - verify(scheduler, times(1)).schedule(isNull(), any(Func2.class)); - subscription.unsubscribe(); - verify(scheduler, times(1)).schedule(any(Action0.class)); - verifyNoMoreInteractions(scheduler); - - verify(observer, times(1)).onNext(1); - verify(observer, times(1)).onNext(2); - verify(observer, times(1)).onNext(3); - verify(observer, times(1)).onCompleted(); - } - - } - } diff --git a/rxjava-core/src/main/java/rx/operators/OperationSum.java b/rxjava-core/src/main/java/rx/operators/OperationSum.java index 5892a00520..fef81a2625 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSum.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSum.java @@ -15,16 +15,12 @@ */ package rx.operators; -import static org.mockito.Mockito.*; - -import org.junit.Test; - import rx.Observable; -import rx.Observer; import rx.util.functions.Func2; /** * A few operators for implementing the sum operation. + * * @see MSDN: Observable.Sum */ public final class OperationSum { @@ -63,103 +59,4 @@ public Double call(Double accu, Double next) { } }); } - - public static class UnitTest { - @SuppressWarnings("unchecked") - Observer w = mock(Observer.class); - @SuppressWarnings("unchecked") - Observer wl = mock(Observer.class); - @SuppressWarnings("unchecked") - Observer wf = mock(Observer.class); - @SuppressWarnings("unchecked") - Observer wd = mock(Observer.class); - - @Test - public void testSumOfAFewInts() throws Throwable { - Observable src = Observable.from(1, 2, 3, 4, 5); - sum(src).subscribe(w); - - verify(w, times(1)).onNext(anyInt()); - verify(w).onNext(15); - verify(w, never()).onError(any(Throwable.class)); - verify(w, times(1)).onCompleted(); - } - - @Test - public void testEmptySum() throws Throwable { - Observable src = Observable.empty(); - sum(src).subscribe(w); - - verify(w, times(1)).onNext(anyInt()); - verify(w).onNext(0); - verify(w, never()).onError(any(Throwable.class)); - verify(w, times(1)).onCompleted(); - } - - @Test - public void testSumOfAFewLongs() throws Throwable { - Observable src = Observable.from(1L, 2L, 3L, 4L, 5L); - sumLongs(src).subscribe(wl); - - verify(wl, times(1)).onNext(anyLong()); - verify(wl).onNext(15L); - verify(wl, never()).onError(any(Throwable.class)); - verify(wl, times(1)).onCompleted(); - } - - @Test - public void testEmptySumLongs() throws Throwable { - Observable src = Observable.empty(); - sumLongs(src).subscribe(wl); - - verify(wl, times(1)).onNext(anyLong()); - verify(wl).onNext(0L); - verify(wl, never()).onError(any(Throwable.class)); - verify(wl, times(1)).onCompleted(); - } - - @Test - public void testSumOfAFewFloats() throws Throwable { - Observable src = Observable.from(1.0f); - sumFloats(src).subscribe(wf); - - verify(wf, times(1)).onNext(anyFloat()); - verify(wf).onNext(1.0f); - verify(wf, never()).onError(any(Throwable.class)); - verify(wf, times(1)).onCompleted(); - } - - @Test - public void testEmptySumFloats() throws Throwable { - Observable src = Observable.empty(); - sumFloats(src).subscribe(wf); - - verify(wf, times(1)).onNext(anyFloat()); - verify(wf).onNext(0.0f); - verify(wf, never()).onError(any(Throwable.class)); - verify(wf, times(1)).onCompleted(); - } - - @Test - public void testSumOfAFewDoubles() throws Throwable { - Observable src = Observable.from(0.0d, 1.0d, 0.5d); - sumDoubles(src).subscribe(wd); - - verify(wd, times(1)).onNext(anyDouble()); - verify(wd).onNext(1.5d); - verify(wd, never()).onError(any(Throwable.class)); - verify(wd, times(1)).onCompleted(); - } - - @Test - public void testEmptySumDoubles() throws Throwable { - Observable src = Observable.empty(); - sumDoubles(src).subscribe(wd); - - verify(wd, times(1)).onNext(anyDouble()); - verify(wd).onNext(0.0d); - verify(wd, never()).onError(any(Throwable.class)); - verify(wd, times(1)).onCompleted(); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationSwitch.java b/rxjava-core/src/main/java/rx/operators/OperationSwitch.java index e3bedebe2e..9094a2affd 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSwitch.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSwitch.java @@ -15,36 +15,26 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.concurrency.TestScheduler; -import rx.subscriptions.Subscriptions; -import rx.util.functions.Action0; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.MultipleAssignmentSubscription; import rx.util.functions.Func1; /** - * Transforms an Observable that emits Observables into a single Observable that emits the items - * emitted by the most recently published of those Observables. + * Transforms an Observable that emits Observables into a single Observable that + * emits the items emitted by the most recently published of those Observables. *

- * + * */ public final class OperationSwitch { /** - * This function transforms an {@link Observable} sequence of {@link Observable} sequences into a single {@link Observable} sequence which produces values from the most recently published - * {@link Observable}. + * This function transforms an {@link Observable} sequence of {@link Observable} sequences into a single {@link Observable} sequence + * which produces values from the most recently published {@link Observable} . * * @param sequences * The {@link Observable} sequence consisting of {@link Observable} sequences. @@ -69,337 +59,107 @@ public Switch(Observable> sequences) { @Override public Subscription onSubscribe(Observer observer) { - SafeObservableSubscription subscription = new SafeObservableSubscription(); - subscription.wrap(sequences.subscribe(new SwitchObserver(observer, subscription))); - return subscription; + SafeObservableSubscription parent; + parent = new SafeObservableSubscription(); + + MultipleAssignmentSubscription child; + child = new MultipleAssignmentSubscription(); + + parent.wrap(sequences.subscribe(new SwitchObserver(observer, parent, child))); + + return new CompositeSubscription(parent, child); } } private static class SwitchObserver implements Observer> { + private final Object gate; private final Observer observer; private final SafeObservableSubscription parent; - private final AtomicReference subsequence = new AtomicReference(); + private final MultipleAssignmentSubscription child; + private long latest; + private boolean stopped; + private boolean hasLatest; - public SwitchObserver(Observer observer, SafeObservableSubscription parent) { + public SwitchObserver(Observer observer, SafeObservableSubscription parent, + MultipleAssignmentSubscription child) { this.observer = observer; this.parent = parent; - } - - @Override - public void onCompleted() { - unsubscribeFromSubSequence(); - observer.onCompleted(); - } - - @Override - public void onError(Throwable e) { - unsubscribeFromSubSequence(); - observer.onError(e); + this.child = child; + this.gate = new Object(); } @Override public void onNext(Observable args) { - unsubscribeFromSubSequence(); - - subsequence.set(args.subscribe(new Observer() { - @Override - public void onCompleted() { - // Do nothing. - } - - @Override - public void onError(Throwable e) { - parent.unsubscribe(); - observer.onError(e); - } - - @Override - public void onNext(T args) { - observer.onNext(args); - } - })); - } - - private void unsubscribeFromSubSequence() { - Subscription previousSubscription = subsequence.get(); - if (previousSubscription != null) { - previousSubscription.unsubscribe(); + final long id; + synchronized (gate) { + id = ++latest; + this.hasLatest = true; } - } - } - - public static class UnitTest { - - private TestScheduler scheduler; - private Observer observer; - - @Before - @SuppressWarnings("unchecked") - public void before() { - scheduler = new TestScheduler(); - observer = mock(Observer.class); - } - - @Test - public void testSwitchWithComplete() { - Observable> source = Observable.create(new OnSubscribeFunc>() { - @Override - public Subscription onSubscribe(Observer> observer) { - publishNext(observer, 50, Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - publishNext(observer, 50, "one"); - publishNext(observer, 100, "two"); - return Subscriptions.empty(); - } - })); - - publishNext(observer, 200, Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - publishNext(observer, 0, "three"); - publishNext(observer, 100, "four"); - return Subscriptions.empty(); - } - })); - - publishCompleted(observer, 250); - - return Subscriptions.empty(); - } - }); - - Observable sampled = Observable.create(OperationSwitch.switchDo(source)); - sampled.subscribe(observer); - - InOrder inOrder = inOrder(observer); - - scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyString()); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("one"); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - scheduler.advanceTimeTo(175, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("two"); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - scheduler.advanceTimeTo(225, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("three"); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - scheduler.advanceTimeTo(350, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyString()); - verify(observer, times(1)).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - } - - @Test - public void testSwitchWithError() { - Observable> source = Observable.create(new OnSubscribeFunc>() { + final SafeObservableSubscription sub; + sub = new SafeObservableSubscription(); + sub.wrap(args.subscribe(new Observer() { @Override - public Subscription onSubscribe(Observer> observer) { - publishNext(observer, 50, Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - publishNext(observer, 50, "one"); - publishNext(observer, 100, "two"); - return Subscriptions.empty(); - } - })); - - publishNext(observer, 200, Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - publishNext(observer, 0, "three"); - publishNext(observer, 100, "four"); - return Subscriptions.empty(); + public void onNext(T args) { + synchronized (gate) { + if (latest == id) { + SwitchObserver.this.observer.onNext(args); } - })); - - publishError(observer, 250, new TestException()); - - return Subscriptions.empty(); + } } - }); - - Observable sampled = Observable.create(OperationSwitch.switchDo(source)); - sampled.subscribe(observer); - - InOrder inOrder = inOrder(observer); - - scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyString()); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("one"); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - scheduler.advanceTimeTo(175, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("two"); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - scheduler.advanceTimeTo(225, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("three"); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - scheduler.advanceTimeTo(350, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyString()); - verify(observer, never()).onCompleted(); - verify(observer, times(1)).onError(any(TestException.class)); - } - - @Test - public void testSwitchWithSubsequenceComplete() { - Observable> source = Observable.create(new OnSubscribeFunc>() { @Override - public Subscription onSubscribe(Observer> observer) { - publishNext(observer, 50, Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - publishNext(observer, 50, "one"); - publishNext(observer, 100, "two"); - return Subscriptions.empty(); - } - })); - - publishNext(observer, 130, Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - publishCompleted(observer, 0); - return Subscriptions.empty(); - } - })); - - publishNext(observer, 150, Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - publishNext(observer, 50, "three"); - return Subscriptions.empty(); + public void onError(Throwable e) { + synchronized (gate) { + sub.unsubscribe(); + if (latest == id) { + SwitchObserver.this.observer.onError(e); + SwitchObserver.this.parent.unsubscribe(); } - })); - - return Subscriptions.empty(); + } } - }); - - Observable sampled = Observable.create(OperationSwitch.switchDo(source)); - sampled.subscribe(observer); - - InOrder inOrder = inOrder(observer); - scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyString()); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("one"); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - scheduler.advanceTimeTo(250, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("three"); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - } - - @Test - public void testSwitchWithSubsequenceError() { - Observable> source = Observable.create(new OnSubscribeFunc>() { @Override - public Subscription onSubscribe(Observer> observer) { - publishNext(observer, 50, Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - publishNext(observer, 50, "one"); - publishNext(observer, 100, "two"); - return Subscriptions.empty(); - } - })); - - publishNext(observer, 130, Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - publishError(observer, 0, new TestException()); - return Subscriptions.empty(); + public void onCompleted() { + synchronized (gate) { + sub.unsubscribe(); + if (latest == id) { + SwitchObserver.this.hasLatest = false; } - })); - publishNext(observer, 150, Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - publishNext(observer, 50, "three"); - return Subscriptions.empty(); + if (stopped) { + SwitchObserver.this.observer.onCompleted(); + SwitchObserver.this.parent.unsubscribe(); } - })); - return Subscriptions.empty(); + } } - }); - - Observable sampled = Observable.create(OperationSwitch.switchDo(source)); - sampled.subscribe(observer); - - InOrder inOrder = inOrder(observer); - scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext(anyString()); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); - - scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("one"); - verify(observer, never()).onCompleted(); - verify(observer, never()).onError(any(Throwable.class)); + })); - scheduler.advanceTimeTo(250, TimeUnit.MILLISECONDS); - inOrder.verify(observer, never()).onNext("three"); - verify(observer, never()).onCompleted(); - verify(observer, times(1)).onError(any(TestException.class)); + this.child.setSubscription(sub); } - private void publishCompleted(final Observer observer, long delay) { - scheduler.schedule(new Action0() { - @Override - public void call() { - observer.onCompleted(); - } - }, delay, TimeUnit.MILLISECONDS); - } + @Override + public void onError(Throwable e) { + synchronized (gate) { + this.observer.onError(e); + } - private void publishError(final Observer observer, long delay, final Throwable error) { - scheduler.schedule(new Action0() { - @Override - public void call() { - observer.onError(error); - } - }, delay, TimeUnit.MILLISECONDS); + this.parent.unsubscribe(); } - private void publishNext(final Observer observer, long delay, final T value) { - scheduler.schedule(new Action0() { - @Override - public void call() { - observer.onNext(value); + @Override + public void onCompleted() { + synchronized (gate) { + this.stopped = true; + if (!this.hasLatest) { + this.observer.onCompleted(); + this.parent.unsubscribe(); } - }, delay, TimeUnit.MILLISECONDS); + } } - @SuppressWarnings("serial") - private class TestException extends Throwable { - } } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationSynchronize.java b/rxjava-core/src/main/java/rx/operators/OperationSynchronize.java index 129728c303..5ba8c0a98e 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSynchronize.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSynchronize.java @@ -15,12 +15,6 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import org.junit.Test; -import org.mockito.Mockito; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -58,216 +52,48 @@ public final class OperationSynchronize { * @return the wrapped synchronized observable sequence */ public static OnSubscribeFunc synchronize(Observable observable) { - return new Synchronize(observable); + return new Synchronize(observable, null); + } + + /** + * Accepts an observable and wraps it in another observable which ensures that the resulting observable is well-behaved. + * This is accomplished by acquiring a mutual-exclusion lock for the object provided as the lock parameter. + * + * A well-behaved observable ensures onNext, onCompleted, or onError calls to its subscribers are + * not interleaved, onCompleted and onError are only called once respectively, and no + * onNext calls follow onCompleted and onError calls. + * + * @param observable + * @param lock + * The lock object to synchronize each observer call on + * @param + * @return the wrapped synchronized observable sequence + */ + public static OnSubscribeFunc synchronize(Observable observable, Object lock) { + return new Synchronize(observable, lock); } private static class Synchronize implements OnSubscribeFunc { - public Synchronize(Observable innerObservable) { + public Synchronize(Observable innerObservable, Object lock) { this.innerObservable = innerObservable; + this.lock = lock; } private Observable innerObservable; private SynchronizedObserver atomicObserver; + private Object lock; public Subscription onSubscribe(Observer observer) { SafeObservableSubscription subscription = new SafeObservableSubscription(); - atomicObserver = new SynchronizedObserver(observer, subscription); - return subscription.wrap(innerObservable.subscribe(atomicObserver)); - } - - } - - public static class UnitTest { - - /** - * Ensure onCompleted can not be called after an Unsubscribe - */ - @Test - public void testOnCompletedAfterUnSubscribe() { - TestObservable t = new TestObservable(null); - Observable st = Observable.create(synchronize(Observable.create(t))); - - @SuppressWarnings("unchecked") - Observer w = mock(Observer.class); - Subscription ws = st.subscribe(w); - - t.sendOnNext("one"); - ws.unsubscribe(); - t.sendOnCompleted(); - - verify(w, times(1)).onNext("one"); - verify(w, Mockito.never()).onCompleted(); - } - - /** - * Ensure onNext can not be called after an Unsubscribe - */ - @Test - public void testOnNextAfterUnSubscribe() { - TestObservable t = new TestObservable(null); - Observable st = Observable.create(synchronize(Observable.create(t))); - - @SuppressWarnings("unchecked") - Observer w = mock(Observer.class); - Subscription ws = st.subscribe(w); - - t.sendOnNext("one"); - ws.unsubscribe(); - t.sendOnNext("two"); - - verify(w, times(1)).onNext("one"); - verify(w, Mockito.never()).onNext("two"); - } - - /** - * Ensure onError can not be called after an Unsubscribe - */ - @Test - public void testOnErrorAfterUnSubscribe() { - TestObservable t = new TestObservable(null); - Observable st = Observable.create(synchronize(Observable.create(t))); - - @SuppressWarnings("unchecked") - Observer w = mock(Observer.class); - Subscription ws = st.subscribe(w); - - t.sendOnNext("one"); - ws.unsubscribe(); - t.sendOnError(new RuntimeException("bad")); - - verify(w, times(1)).onNext("one"); - verify(w, Mockito.never()).onError(any(Throwable.class)); - } - - /** - * Ensure onNext can not be called after onError - */ - @Test - public void testOnNextAfterOnError() { - TestObservable t = new TestObservable(null); - Observable st = Observable.create(synchronize(Observable.create(t))); - - @SuppressWarnings("unchecked") - Observer w = mock(Observer.class); - @SuppressWarnings("unused") - Subscription ws = st.subscribe(w); - - t.sendOnNext("one"); - t.sendOnError(new RuntimeException("bad")); - t.sendOnNext("two"); - - verify(w, times(1)).onNext("one"); - verify(w, times(1)).onError(any(Throwable.class)); - verify(w, Mockito.never()).onNext("two"); - } - - /** - * Ensure onCompleted can not be called after onError - */ - @Test - public void testOnCompletedAfterOnError() { - TestObservable t = new TestObservable(null); - Observable st = Observable.create(synchronize(Observable.create(t))); - - @SuppressWarnings("unchecked") - Observer w = mock(Observer.class); - @SuppressWarnings("unused") - Subscription ws = st.subscribe(w); - - t.sendOnNext("one"); - t.sendOnError(new RuntimeException("bad")); - t.sendOnCompleted(); - - verify(w, times(1)).onNext("one"); - verify(w, times(1)).onError(any(Throwable.class)); - verify(w, Mockito.never()).onCompleted(); - } - - /** - * Ensure onNext can not be called after onCompleted - */ - @Test - public void testOnNextAfterOnCompleted() { - TestObservable t = new TestObservable(null); - Observable st = Observable.create(synchronize(Observable.create(t))); - - @SuppressWarnings("unchecked") - Observer w = mock(Observer.class); - @SuppressWarnings("unused") - Subscription ws = st.subscribe(w); - - t.sendOnNext("one"); - t.sendOnCompleted(); - t.sendOnNext("two"); - - verify(w, times(1)).onNext("one"); - verify(w, Mockito.never()).onNext("two"); - verify(w, times(1)).onCompleted(); - verify(w, Mockito.never()).onError(any(Throwable.class)); - } - - /** - * Ensure onError can not be called after onCompleted - */ - @Test - public void testOnErrorAfterOnCompleted() { - TestObservable t = new TestObservable(null); - Observable st = Observable.create(synchronize(Observable.create(t))); - - @SuppressWarnings("unchecked") - Observer w = mock(Observer.class); - @SuppressWarnings("unused") - Subscription ws = st.subscribe(w); - - t.sendOnNext("one"); - t.sendOnCompleted(); - t.sendOnError(new RuntimeException("bad")); - - verify(w, times(1)).onNext("one"); - verify(w, times(1)).onCompleted(); - verify(w, Mockito.never()).onError(any(Throwable.class)); - } - - /** - * A Observable that doesn't do the right thing on UnSubscribe/Error/etc in that it will keep sending events down the pipe regardless of what happens. - */ - private static class TestObservable implements OnSubscribeFunc { - - Observer observer = null; - - public TestObservable(Subscription s) { + if (lock == null) { + atomicObserver = new SynchronizedObserver(observer, subscription); } - - /* used to simulate subscription */ - public void sendOnCompleted() { - observer.onCompleted(); - } - - /* used to simulate subscription */ - public void sendOnNext(String value) { - observer.onNext(value); - } - - /* used to simulate subscription */ - public void sendOnError(Throwable e) { - observer.onError(e); + else { + atomicObserver = new SynchronizedObserver(observer, subscription, lock); } - - @Override - public Subscription onSubscribe(final Observer observer) { - this.observer = observer; - return new Subscription() { - - @Override - public void unsubscribe() { - // going to do nothing to pretend I'm a bad Observable that keeps allowing events to be sent - } - - }; - } - + return subscription.wrap(innerObservable.subscribe(atomicObserver)); } - } + } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationTake.java b/rxjava-core/src/main/java/rx/operators/OperationTake.java index 881071e5b6..877ba4d5f3 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationTake.java +++ b/rxjava-core/src/main/java/rx/operators/OperationTake.java @@ -15,15 +15,8 @@ */ package rx.operators; -import static org.junit.Assert.*; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Test; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -114,6 +107,7 @@ private class ItemObserver implements Observer { private final Observer observer; private final AtomicInteger counter = new AtomicInteger(); + private volatile boolean hasEmitedError = false; public ItemObserver(Observer observer) { this.observer = observer; @@ -121,6 +115,9 @@ public ItemObserver(Observer observer) { @Override public void onCompleted() { + if (hasEmitedError) { + return; + } if (counter.getAndSet(num) < num) { observer.onCompleted(); } @@ -128,6 +125,9 @@ public void onCompleted() { @Override public void onError(Throwable e) { + if (hasEmitedError) { + return; + } if (counter.getAndSet(num) < num) { observer.onError(e); } @@ -135,9 +135,19 @@ public void onError(Throwable e) { @Override public void onNext(T args) { + if (hasEmitedError) { + return; + } final int count = counter.incrementAndGet(); if (count <= num) { - observer.onNext(args); + try { + observer.onNext(args); + } catch (Throwable ex) { + hasEmitedError = true; + observer.onError(ex); + subscription.unsubscribe(); + return; + } if (count == num) { observer.onCompleted(); } @@ -151,167 +161,4 @@ public void onNext(T args) { } } - - public static class UnitTest { - - @Test - public void testTake1() { - Observable w = Observable.from("one", "two", "three"); - Observable take = Observable.create(take(w, 2)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - take.subscribe(aObserver); - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, times(1)).onNext("two"); - verify(aObserver, never()).onNext("three"); - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - @Test - public void testTake2() { - Observable w = Observable.from("one", "two", "three"); - Observable take = Observable.create(take(w, 1)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - take.subscribe(aObserver); - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, never()).onNext("two"); - verify(aObserver, never()).onNext("three"); - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - @Test - public void testTakeDoesntLeakErrors() { - Observable source = Observable.create(new OnSubscribeFunc() - { - @Override - public Subscription onSubscribe(Observer observer) - { - observer.onNext("one"); - observer.onError(new Throwable("test failed")); - return Subscriptions.empty(); - } - }); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - - Observable.create(take(source, 1)).subscribe(aObserver); - - verify(aObserver, times(1)).onNext("one"); - // even though onError is called we take(1) so shouldn't see it - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verifyNoMoreInteractions(aObserver); - } - - @Test - public void testTakeZeroDoesntLeakError() { - final AtomicBoolean subscribed = new AtomicBoolean(false); - final AtomicBoolean unSubscribed = new AtomicBoolean(false); - Observable source = Observable.create(new OnSubscribeFunc() - { - @Override - public Subscription onSubscribe(Observer observer) - { - subscribed.set(true); - observer.onError(new Throwable("test failed")); - return new Subscription() - { - @Override - public void unsubscribe() - { - unSubscribed.set(true); - } - }; - } - }); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - - Observable.create(take(source, 0)).subscribe(aObserver); - assertTrue("source subscribed", subscribed.get()); - assertTrue("source unsubscribed", unSubscribed.get()); - - verify(aObserver, never()).onNext(anyString()); - // even though onError is called we take(0) so shouldn't see it - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verifyNoMoreInteractions(aObserver); - } - - @Test - public void testUnsubscribeAfterTake() { - final Subscription s = mock(Subscription.class); - TestObservableFunc f = new TestObservableFunc(s, "one", "two", "three"); - Observable w = Observable.create(f); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - Observable take = Observable.create(take(w, 1)); - take.subscribe(aObserver); - - // wait for the Observable to complete - try { - f.t.join(); - } catch (Throwable e) { - e.printStackTrace(); - fail(e.getMessage()); - } - - System.out.println("TestObservable thread finished"); - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, never()).onNext("two"); - verify(aObserver, never()).onNext("three"); - verify(aObserver, times(1)).onCompleted(); - verify(s, times(1)).unsubscribe(); - verifyNoMoreInteractions(aObserver); - } - - private static class TestObservableFunc implements OnSubscribeFunc { - - final Subscription s; - final String[] values; - Thread t = null; - - public TestObservableFunc(Subscription s, String... values) { - this.s = s; - this.values = values; - } - - @Override - public Subscription onSubscribe(final Observer observer) { - System.out.println("TestObservable subscribed to ..."); - t = new Thread(new Runnable() { - - @Override - public void run() { - try { - System.out.println("running TestObservable thread"); - for (String s : values) { - System.out.println("TestObservable onNext: " + s); - observer.onNext(s); - } - observer.onCompleted(); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - }); - System.out.println("starting TestObservable thread"); - t.start(); - System.out.println("done starting TestObservable thread"); - return s; - } - - } - - } - } diff --git a/rxjava-core/src/main/java/rx/operators/OperationTakeLast.java b/rxjava-core/src/main/java/rx/operators/OperationTakeLast.java index 46ff21e9e2..d324f7ce42 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationTakeLast.java +++ b/rxjava-core/src/main/java/rx/operators/OperationTakeLast.java @@ -15,14 +15,9 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import java.util.Iterator; -import java.util.concurrent.LinkedBlockingDeque; - -import org.junit.Test; -import org.mockito.InOrder; +import java.util.Deque; +import java.util.LinkedList; +import java.util.concurrent.locks.ReentrantLock; import rx.Observable; import rx.Observable.OnSubscribeFunc; @@ -59,13 +54,21 @@ private static class TakeLast implements OnSubscribeFunc { } public Subscription onSubscribe(Observer observer) { + if (count < 0) { + throw new IndexOutOfBoundsException( + "count could not be negative"); + } return subscription.wrap(items.subscribe(new ItemObserver(observer))); } private class ItemObserver implements Observer { - private LinkedBlockingDeque deque = new LinkedBlockingDeque(count); + /** + * Store the last count elements until now. + */ + private Deque deque = new LinkedList(); private final Observer observer; + private final ReentrantLock lock = new ReentrantLock(); public ItemObserver(Observer observer) { this.observer = observer; @@ -73,11 +76,14 @@ public ItemObserver(Observer observer) { @Override public void onCompleted() { - Iterator reverse = deque.descendingIterator(); - while (reverse.hasNext()) { - observer.onNext(reverse.next()); + try { + for (T value : deque) { + observer.onNext(value); + } + observer.onCompleted(); + } catch (Throwable e) { + observer.onError(e); } - observer.onCompleted(); } @Override @@ -86,60 +92,31 @@ public void onError(Throwable e) { } @Override - public void onNext(T args) { - while (!deque.offerFirst(args)) { - deque.removeLast(); + public void onNext(T value) { + if (count == 0) { + // If count == 0, we do not need to put value into deque and + // remove it at once. We can ignore the value directly. + return; + } + lock.lock(); + try { + deque.offerLast(value); + if (deque.size() > count) { + // Now deque has count + 1 elements, so the first + // element in the deque definitely does not belong + // to the last count elements of the source + // sequence. We can drop it now. + deque.removeFirst(); + } + } catch (Throwable e) { + observer.onError(e); + subscription.unsubscribe(); + } finally { + lock.unlock(); } } } } - - public static class UnitTest { - - @Test - public void testTakeLastEmpty() { - Observable w = Observable.empty(); - Observable take = Observable.create(takeLast(w, 2)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - take.subscribe(aObserver); - verify(aObserver, never()).onNext(any(String.class)); - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - @Test - public void testTakeLast1() { - Observable w = Observable.from("one", "two", "three"); - Observable take = Observable.create(takeLast(w, 2)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - InOrder inOrder = inOrder(aObserver); - take.subscribe(aObserver); - inOrder.verify(aObserver, times(1)).onNext("two"); - inOrder.verify(aObserver, times(1)).onNext("three"); - verify(aObserver, never()).onNext("one"); - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - @Test - public void testTakeLast2() { - Observable w = Observable.from("one"); - Observable take = Observable.create(takeLast(w, 10)); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - take.subscribe(aObserver); - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - } - } diff --git a/rxjava-core/src/main/java/rx/operators/OperationTakeUntil.java b/rxjava-core/src/main/java/rx/operators/OperationTakeUntil.java index 841190f023..4f344d72a1 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationTakeUntil.java +++ b/rxjava-core/src/main/java/rx/operators/OperationTakeUntil.java @@ -15,10 +15,6 @@ */ package rx.operators; -import static org.mockito.Mockito.*; - -import org.junit.Test; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -139,160 +135,4 @@ public void onNext(E args) { }); } } - - public static class UnitTest { - - @Test - @SuppressWarnings("unchecked") - public void testTakeUntil() { - Subscription sSource = mock(Subscription.class); - Subscription sOther = mock(Subscription.class); - TestObservable source = new TestObservable(sSource); - TestObservable other = new TestObservable(sOther); - - Observer result = mock(Observer.class); - Observable stringObservable = takeUntil(Observable.create(source), Observable.create(other)); - stringObservable.subscribe(result); - source.sendOnNext("one"); - source.sendOnNext("two"); - other.sendOnNext("three"); - source.sendOnNext("four"); - source.sendOnCompleted(); - other.sendOnCompleted(); - - verify(result, times(1)).onNext("one"); - verify(result, times(1)).onNext("two"); - verify(result, times(0)).onNext("three"); - verify(result, times(0)).onNext("four"); - verify(sSource, times(1)).unsubscribe(); - verify(sOther, times(1)).unsubscribe(); - - } - - @Test - @SuppressWarnings("unchecked") - public void testTakeUntilSourceCompleted() { - Subscription sSource = mock(Subscription.class); - Subscription sOther = mock(Subscription.class); - TestObservable source = new TestObservable(sSource); - TestObservable other = new TestObservable(sOther); - - Observer result = mock(Observer.class); - Observable stringObservable = takeUntil(Observable.create(source), Observable.create(other)); - stringObservable.subscribe(result); - source.sendOnNext("one"); - source.sendOnNext("two"); - source.sendOnCompleted(); - - verify(result, times(1)).onNext("one"); - verify(result, times(1)).onNext("two"); - verify(sSource, times(1)).unsubscribe(); - verify(sOther, times(1)).unsubscribe(); - - } - - @Test - @SuppressWarnings("unchecked") - public void testTakeUntilSourceError() { - Subscription sSource = mock(Subscription.class); - Subscription sOther = mock(Subscription.class); - TestObservable source = new TestObservable(sSource); - TestObservable other = new TestObservable(sOther); - Throwable error = new Throwable(); - - Observer result = mock(Observer.class); - Observable stringObservable = takeUntil(Observable.create(source), Observable.create(other)); - stringObservable.subscribe(result); - source.sendOnNext("one"); - source.sendOnNext("two"); - source.sendOnError(error); - - verify(result, times(1)).onNext("one"); - verify(result, times(1)).onNext("two"); - verify(result, times(1)).onError(error); - verify(sSource, times(1)).unsubscribe(); - verify(sOther, times(1)).unsubscribe(); - - } - - @Test - @SuppressWarnings("unchecked") - public void testTakeUntilOtherError() { - Subscription sSource = mock(Subscription.class); - Subscription sOther = mock(Subscription.class); - TestObservable source = new TestObservable(sSource); - TestObservable other = new TestObservable(sOther); - Throwable error = new Throwable(); - - Observer result = mock(Observer.class); - Observable stringObservable = takeUntil(Observable.create(source), Observable.create(other)); - stringObservable.subscribe(result); - source.sendOnNext("one"); - source.sendOnNext("two"); - other.sendOnError(error); - - verify(result, times(1)).onNext("one"); - verify(result, times(1)).onNext("two"); - verify(result, times(1)).onError(error); - verify(result, times(0)).onCompleted(); - verify(sSource, times(1)).unsubscribe(); - verify(sOther, times(1)).unsubscribe(); - - } - - @Test - @SuppressWarnings("unchecked") - public void testTakeUntilOtherCompleted() { - Subscription sSource = mock(Subscription.class); - Subscription sOther = mock(Subscription.class); - TestObservable source = new TestObservable(sSource); - TestObservable other = new TestObservable(sOther); - - Observer result = mock(Observer.class); - Observable stringObservable = takeUntil(Observable.create(source), Observable.create(other)); - stringObservable.subscribe(result); - source.sendOnNext("one"); - source.sendOnNext("two"); - other.sendOnCompleted(); - - verify(result, times(1)).onNext("one"); - verify(result, times(1)).onNext("two"); - verify(result, times(0)).onCompleted(); - verify(sSource, times(0)).unsubscribe(); - verify(sOther, times(0)).unsubscribe(); - - } - - private static class TestObservable implements OnSubscribeFunc { - - Observer observer = null; - Subscription s; - - public TestObservable(Subscription s) { - this.s = s; - } - - /* used to simulate subscription */ - public void sendOnCompleted() { - observer.onCompleted(); - } - - /* used to simulate subscription */ - public void sendOnNext(String value) { - observer.onNext(value); - } - - /* used to simulate subscription */ - public void sendOnError(Throwable e) { - observer.onError(e); - } - - @Override - public Subscription onSubscribe(final Observer observer) { - this.observer = observer; - return s; - } - } - - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationTakeWhile.java b/rxjava-core/src/main/java/rx/operators/OperationTakeWhile.java index 5b833b22a1..479badf6a6 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationTakeWhile.java +++ b/rxjava-core/src/main/java/rx/operators/OperationTakeWhile.java @@ -15,21 +15,12 @@ */ package rx.operators; -import static org.junit.Assert.*; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - import java.util.concurrent.atomic.AtomicInteger; -import org.junit.Test; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.subjects.PublishSubject; -import rx.subjects.Subject; -import rx.subscriptions.Subscriptions; import rx.util.functions.Func1; import rx.util.functions.Func2; @@ -151,207 +142,4 @@ public void onNext(T args) { } } - - public static class UnitTest { - - @Test - public void testTakeWhile1() { - Observable w = Observable.from(1, 2, 3); - Observable take = Observable.create(takeWhile(w, new Func1() - { - @Override - public Boolean call(Integer input) - { - return input < 3; - } - })); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - take.subscribe(aObserver); - verify(aObserver, times(1)).onNext(1); - verify(aObserver, times(1)).onNext(2); - verify(aObserver, never()).onNext(3); - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - @Test - public void testTakeWhileOnSubject1() { - Subject s = PublishSubject.create(); - Observable take = Observable.create(takeWhile(s, new Func1() - { - @Override - public Boolean call(Integer input) - { - return input < 3; - } - })); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - take.subscribe(aObserver); - - s.onNext(1); - s.onNext(2); - s.onNext(3); - s.onNext(4); - s.onNext(5); - s.onCompleted(); - - verify(aObserver, times(1)).onNext(1); - verify(aObserver, times(1)).onNext(2); - verify(aObserver, never()).onNext(3); - verify(aObserver, never()).onNext(4); - verify(aObserver, never()).onNext(5); - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - @Test - public void testTakeWhile2() { - Observable w = Observable.from("one", "two", "three"); - Observable take = Observable.create(takeWhileWithIndex(w, new Func2() - { - @Override - public Boolean call(String input, Integer index) - { - return index < 2; - } - })); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - take.subscribe(aObserver); - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, times(1)).onNext("two"); - verify(aObserver, never()).onNext("three"); - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - @Test - public void testTakeWhileDoesntLeakErrors() { - Observable source = Observable.create(new OnSubscribeFunc() - { - @Override - public Subscription onSubscribe(Observer observer) - { - observer.onNext("one"); - observer.onError(new Throwable("test failed")); - return Subscriptions.empty(); - } - }); - - Observable.create(takeWhile(source, new Func1() - { - @Override - public Boolean call(String s) - { - return false; - } - })).toBlockingObservable().last(); - } - - @Test - public void testTakeWhileProtectsPredicateCall() { - TestObservable source = new TestObservable(mock(Subscription.class), "one"); - final RuntimeException testException = new RuntimeException("test exception"); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - Observable take = Observable.create(takeWhile(Observable.create(source), new Func1() - { - @Override - public Boolean call(String s) - { - throw testException; - } - })); - take.subscribe(aObserver); - - // wait for the Observable to complete - try { - source.t.join(); - } catch (Throwable e) { - e.printStackTrace(); - fail(e.getMessage()); - } - - verify(aObserver, never()).onNext(any(String.class)); - verify(aObserver, times(1)).onError(testException); - } - - @Test - public void testUnsubscribeAfterTake() { - Subscription s = mock(Subscription.class); - TestObservable w = new TestObservable(s, "one", "two", "three"); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - Observable take = Observable.create(takeWhileWithIndex(Observable.create(w), new Func2() - { - @Override - public Boolean call(String s, Integer index) - { - return index < 1; - } - })); - take.subscribe(aObserver); - - // wait for the Observable to complete - try { - w.t.join(); - } catch (Throwable e) { - e.printStackTrace(); - fail(e.getMessage()); - } - - System.out.println("TestObservable thread finished"); - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, never()).onNext("two"); - verify(aObserver, never()).onNext("three"); - verify(s, times(1)).unsubscribe(); - } - - private static class TestObservable implements OnSubscribeFunc { - - final Subscription s; - final String[] values; - Thread t = null; - - public TestObservable(Subscription s, String... values) { - this.s = s; - this.values = values; - } - - @Override - public Subscription onSubscribe(final Observer observer) { - System.out.println("TestObservable subscribed to ..."); - t = new Thread(new Runnable() { - - @Override - public void run() { - try { - System.out.println("running TestObservable thread"); - for (String s : values) { - System.out.println("TestObservable onNext: " + s); - observer.onNext(s); - } - observer.onCompleted(); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - }); - System.out.println("starting TestObservable thread"); - t.start(); - System.out.println("done starting TestObservable thread"); - return s; - } - - } - } - } diff --git a/rxjava-core/src/main/java/rx/operators/OperationThrottleFirst.java b/rxjava-core/src/main/java/rx/operators/OperationThrottleFirst.java index 4c3a1ea8d4..e21617a82c 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationThrottleFirst.java +++ b/rxjava-core/src/main/java/rx/operators/OperationThrottleFirst.java @@ -15,25 +15,15 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Scheduler; import rx.Subscription; import rx.concurrency.Schedulers; -import rx.concurrency.TestScheduler; -import rx.subscriptions.Subscriptions; -import rx.util.functions.Action0; import rx.util.functions.Func1; /** @@ -94,104 +84,4 @@ public Boolean call(T value) { } }; } - - public static class UnitTest { - - private TestScheduler scheduler; - private Observer observer; - - @Before - @SuppressWarnings("unchecked") - public void before() { - scheduler = new TestScheduler(); - observer = mock(Observer.class); - } - - @Test - public void testThrottlingWithCompleted() { - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - publishNext(observer, 100, "one"); // publish as it's first - publishNext(observer, 300, "two"); // skip as it's last within the first 400 - publishNext(observer, 900, "three"); // publish - publishNext(observer, 905, "four"); // skip - publishCompleted(observer, 1000); // Should be published as soon as the timeout expires. - - return Subscriptions.empty(); - } - }); - - Observable sampled = Observable.create(OperationThrottleFirst.throttleFirst(source, 400, TimeUnit.MILLISECONDS, scheduler)); - sampled.subscribe(observer); - - InOrder inOrder = inOrder(observer); - - scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); - inOrder.verify(observer, times(1)).onNext("one"); - inOrder.verify(observer, times(0)).onNext("two"); - inOrder.verify(observer, times(1)).onNext("three"); - inOrder.verify(observer, times(0)).onNext("four"); - inOrder.verify(observer, times(1)).onCompleted(); - inOrder.verifyNoMoreInteractions(); - } - - @Test - public void testThrottlingWithError() { - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - Exception error = new TestException(); - publishNext(observer, 100, "one"); // Should be published since it is first - publishNext(observer, 200, "two"); // Should be skipped since onError will arrive before the timeout expires - publishError(observer, 300, error); // Should be published as soon as the timeout expires. - - return Subscriptions.empty(); - } - }); - - Observable sampled = Observable.create(OperationThrottleFirst.throttleFirst(source, 400, TimeUnit.MILLISECONDS, scheduler)); - sampled.subscribe(observer); - - InOrder inOrder = inOrder(observer); - - scheduler.advanceTimeTo(400, TimeUnit.MILLISECONDS); - inOrder.verify(observer).onNext("one"); - inOrder.verify(observer).onError(any(TestException.class)); - inOrder.verifyNoMoreInteractions(); - } - - private void publishCompleted(final Observer observer, long delay) { - scheduler.schedule(new Action0() { - @Override - public void call() { - observer.onCompleted(); - } - }, delay, TimeUnit.MILLISECONDS); - } - - private void publishError(final Observer observer, long delay, final Exception error) { - scheduler.schedule(new Action0() { - @Override - public void call() { - observer.onError(error); - } - }, delay, TimeUnit.MILLISECONDS); - } - - private void publishNext(final Observer observer, long delay, final T value) { - scheduler.schedule(new Action0() { - @Override - public void call() { - observer.onNext(value); - } - }, delay, TimeUnit.MILLISECONDS); - } - - @SuppressWarnings("serial") - private class TestException extends Exception { - } - - } - } diff --git a/rxjava-core/src/main/java/rx/operators/OperationTimeInterval.java b/rxjava-core/src/main/java/rx/operators/OperationTimeInterval.java new file mode 100644 index 0000000000..2cd1860711 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationTimeInterval.java @@ -0,0 +1,83 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Scheduler; +import rx.Subscription; +import rx.concurrency.Schedulers; +import rx.util.TimeInterval; + +/** + * Records the time interval between consecutive elements in an observable sequence. + */ +public class OperationTimeInterval { + + public static OnSubscribeFunc> timeInterval( + Observable source) { + return timeInterval(source, Schedulers.immediate()); + } + + public static OnSubscribeFunc> timeInterval( + final Observable source, final Scheduler scheduler) { + return new OnSubscribeFunc>() { + @Override + public Subscription onSubscribe( + Observer> observer) { + return source.subscribe(new TimeIntervalObserver(observer, + scheduler)); + } + }; + } + + private static class TimeIntervalObserver implements Observer { + + private final Observer> observer; + /** + * Only used to compute time intervals. + */ + private final Scheduler scheduler; + private long lastTimestamp; + + public TimeIntervalObserver(Observer> observer, + Scheduler scheduler) { + this.observer = observer; + this.scheduler = scheduler; + // The beginning time is the time when the observer subscribes. + lastTimestamp = scheduler.now(); + } + + @Override + public void onNext(T args) { + long nowTimestamp = scheduler.now(); + observer.onNext(new TimeInterval(nowTimestamp - lastTimestamp, + args)); + lastTimestamp = nowTimestamp; + } + + @Override + public void onCompleted() { + observer.onCompleted(); + } + + @Override + public void onError(Throwable e) { + observer.onCompleted(); + } + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationTimeout.java b/rxjava-core/src/main/java/rx/operators/OperationTimeout.java new file mode 100644 index 0000000000..b52c7a5a43 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationTimeout.java @@ -0,0 +1,157 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Scheduler; +import rx.Subscription; +import rx.concurrency.Schedulers; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.SerialSubscription; +import rx.util.functions.Action0; +import rx.util.functions.Func0; + +/** + * Applies a timeout policy for each element in the observable sequence, using + * the specified scheduler to run timeout timers. If the next element isn't + * received within the specified timeout duration starting from its predecessor, + * the other observable sequence is used to produce future messages from that + * point on. + */ +public final class OperationTimeout { + + public static OnSubscribeFunc timeout(Observable source, long timeout, TimeUnit timeUnit) { + return new Timeout(source, timeout, timeUnit, null, Schedulers.threadPoolForComputation()); + } + + public static OnSubscribeFunc timeout(Observable sequence, long timeout, TimeUnit timeUnit, Observable other) { + return new Timeout(sequence, timeout, timeUnit, other, Schedulers.threadPoolForComputation()); + } + + public static OnSubscribeFunc timeout(Observable source, long timeout, TimeUnit timeUnit, Scheduler scheduler) { + return new Timeout(source, timeout, timeUnit, null, scheduler); + } + + public static OnSubscribeFunc timeout(Observable sequence, long timeout, TimeUnit timeUnit, Observable other, Scheduler scheduler) { + return new Timeout(sequence, timeout, timeUnit, other, scheduler); + } + + private static class Timeout implements Observable.OnSubscribeFunc { + private final Observable source; + private final long timeout; + private final TimeUnit timeUnit; + private final Scheduler scheduler; + private final Observable other; + + private Timeout(Observable source, long timeout, TimeUnit timeUnit, Observable other, Scheduler scheduler) { + this.source = source; + this.timeout = timeout; + this.timeUnit = timeUnit; + this.other = other; + this.scheduler = scheduler; + } + + @Override + public Subscription onSubscribe(final Observer observer) { + final AtomicBoolean terminated = new AtomicBoolean(false); + final AtomicLong actual = new AtomicLong(0L); // Required to handle race between onNext and timeout + final SerialSubscription serial = new SerialSubscription(); + final Object gate = new Object(); + CompositeSubscription composite = new CompositeSubscription(); + final Func0 schedule = new Func0() { + @Override + public Subscription call() { + final long expected = actual.get(); + return scheduler.schedule(new Action0() { + @Override + public void call() { + boolean timeoutWins = false; + synchronized (gate) { + if (expected == actual.get() && !terminated.getAndSet(true)) { + timeoutWins = true; + } + } + if (timeoutWins) { + if (other == null) { + observer.onError(new TimeoutException()); + } + else { + serial.setSubscription(other.subscribe(observer)); + } + } + + } + }, timeout, timeUnit); + } + }; + SafeObservableSubscription subscription = new SafeObservableSubscription(); + composite.add(subscription.wrap(source.subscribe(new Observer() { + @Override + public void onNext(T value) { + boolean onNextWins = false; + synchronized (gate) { + if (!terminated.get()) { + actual.incrementAndGet(); + onNextWins = true; + } + } + if (onNextWins) { + serial.setSubscription(schedule.call()); + observer.onNext(value); + } + } + + @Override + public void onError(Throwable error) { + boolean onErrorWins = false; + synchronized (gate) { + if (!terminated.getAndSet(true)) { + onErrorWins = true; + } + } + if (onErrorWins) { + serial.unsubscribe(); + observer.onError(error); + } + } + + @Override + public void onCompleted() { + boolean onCompletedWins = false; + synchronized (gate) { + if (!terminated.getAndSet(true)) { + onCompletedWins = true; + } + } + if (onCompletedWins) { + serial.unsubscribe(); + observer.onCompleted(); + } + } + }))); + composite.add(serial); + serial.setSubscription(schedule.call()); + return composite; + } + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationToFuture.java b/rxjava-core/src/main/java/rx/operators/OperationToFuture.java index a3ecc49efe..d4433da9d6 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationToFuture.java +++ b/rxjava-core/src/main/java/rx/operators/OperationToFuture.java @@ -15,9 +15,6 @@ */ package rx.operators; -import static org.junit.Assert.*; - -import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; @@ -25,13 +22,9 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; -import org.junit.Test; - import rx.Observable; -import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.subscriptions.Subscriptions; /** * Returns a Future representing the single value emitted by an Observable. @@ -136,52 +129,4 @@ private T getValue() throws ExecutionException { } - @Test - public void testToFuture() throws InterruptedException, ExecutionException { - Observable obs = Observable.from("one"); - Future f = toFuture(obs); - assertEquals("one", f.get()); - } - - @Test - public void testToFutureList() throws InterruptedException, ExecutionException { - Observable obs = Observable.from("one", "two", "three"); - Future> f = toFuture(obs.toList()); - assertEquals("one", f.get().get(0)); - assertEquals("two", f.get().get(1)); - assertEquals("three", f.get().get(2)); - } - - @Test(expected = ExecutionException.class) - public void testExceptionWithMoreThanOneElement() throws InterruptedException, ExecutionException { - Observable obs = Observable.from("one", "two"); - Future f = toFuture(obs); - assertEquals("one", f.get()); - // we expect an exception since there are more than 1 element - } - - @Test - public void testToFutureWithException() { - Observable obs = Observable.create(new OnSubscribeFunc() { - - @Override - public Subscription onSubscribe(Observer observer) { - observer.onNext("one"); - observer.onError(new TestException()); - return Subscriptions.empty(); - } - }); - - Future f = toFuture(obs); - try { - f.get(); - fail("expected exception"); - } catch (Throwable e) { - assertEquals(TestException.class, e.getCause().getClass()); - } - } - - private static class TestException extends RuntimeException { - private static final long serialVersionUID = 1L; - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationToIterator.java b/rxjava-core/src/main/java/rx/operators/OperationToIterator.java index e7dcc308eb..2fcd51872e 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationToIterator.java +++ b/rxjava-core/src/main/java/rx/operators/OperationToIterator.java @@ -15,20 +15,13 @@ */ package rx.operators; -import static org.junit.Assert.*; - import java.util.Iterator; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; -import org.junit.Test; - import rx.Notification; import rx.Observable; -import rx.Observable.OnSubscribeFunc; import rx.Observer; -import rx.Subscription; -import rx.subscriptions.Subscriptions; import rx.util.Exceptions; /** @@ -36,6 +29,7 @@ *

* *

+ * * @see Issue #50 */ public class OperationToIterator { @@ -107,47 +101,4 @@ public void remove() { }; } - @Test - public void testToIterator() { - Observable obs = Observable.from("one", "two", "three"); - - Iterator it = toIterator(obs); - - assertEquals(true, it.hasNext()); - assertEquals("one", it.next()); - - assertEquals(true, it.hasNext()); - assertEquals("two", it.next()); - - assertEquals(true, it.hasNext()); - assertEquals("three", it.next()); - - assertEquals(false, it.hasNext()); - - } - - @Test(expected = TestException.class) - public void testToIteratorWithException() { - Observable obs = Observable.create(new OnSubscribeFunc() { - - @Override - public Subscription onSubscribe(Observer observer) { - observer.onNext("one"); - observer.onError(new TestException()); - return Subscriptions.empty(); - } - }); - - Iterator it = toIterator(obs); - - assertEquals(true, it.hasNext()); - assertEquals("one", it.next()); - - assertEquals(true, it.hasNext()); - it.next(); - } - - private static class TestException extends RuntimeException { - private static final long serialVersionUID = 1L; - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationToMap.java b/rxjava-core/src/main/java/rx/operators/OperationToMap.java new file mode 100644 index 0000000000..c52b0244f2 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationToMap.java @@ -0,0 +1,159 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.operators; + +import java.util.HashMap; +import java.util.Map; +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Functions; + +/** + * Maps the elements of the source observable into a java.util.Map instance and + * emits that once the source observable completes. + * + * @see Issue #96 + */ +public class OperationToMap { + /** + * ToMap with key selector, identity value selector and default HashMap factory. + */ + public static OnSubscribeFunc> toMap(Observable source, + Func1 keySelector) { + return new ToMap(source, keySelector, + Functions.identity(), new DefaultToMapFactory()); + } + + /** + * ToMap with key selector, value selector and default HashMap factory. + */ + public static OnSubscribeFunc> toMap(Observable source, + Func1 keySelector, + Func1 valueSelector) { + return new ToMap(source, keySelector, + valueSelector, new DefaultToMapFactory()); + } + + /** + * ToMap with key selector, value selector and custom Map factory. + */ + public static OnSubscribeFunc> toMap(Observable source, + Func1 keySelector, + Func1 valueSelector, + Func0> mapFactory) { + return new ToMap(source, keySelector, + valueSelector, mapFactory); + } + + /** The default map factory. */ + public static class DefaultToMapFactory implements Func0> { + @Override + public Map call() { + return new HashMap(); + } + } + /** + * Maps the elements of the source observable into a java.util.Map instance + * returned by the mapFactory function by using the keySelector and + * valueSelector. + * @param the source's value type + * @param the key type + * @param the value type + */ + public static class ToMap implements OnSubscribeFunc> { + /** The source. */ + private final Observable source; + /** Key extractor. */ + private final Func1 keySelector; + /** Value extractor. */ + private final Func1 valueSelector; + /** Map factory. */ + private final Func0> mapFactory; + public ToMap( + Observable source, + Func1 keySelector, + Func1 valueSelector, + Func0> mapFactory + ) { + this.source = source; + this.keySelector = keySelector; + this.valueSelector = valueSelector; + this.mapFactory = mapFactory; + + } + @Override + public Subscription onSubscribe(Observer> t1) { + Map map; + try { + map = mapFactory.call(); + } catch (Throwable t) { + t1.onError(t); + return Subscriptions.empty(); + } + return source.subscribe(new ToMapObserver( + t1, keySelector, valueSelector, map)); + } + /** + * Observer that collects the source values of T into + * a map. + */ + public static class ToMapObserver implements Observer { + /** The map. */ + Map map; + /** Key extractor. */ + private final Func1 keySelector; + /** Value extractor. */ + private final Func1 valueSelector; + /** The observer who is receiving the completed map. */ + private final Observer> t1; + + public ToMapObserver( + Observer> t1, + Func1 keySelector, + Func1 valueSelector, + Map map) { + this.map = map; + this.t1 = t1; + this.keySelector = keySelector; + this.valueSelector = valueSelector; + } + @Override + public void onNext(T args) { + K key = keySelector.call(args); + V value = valueSelector.call(args); + map.put(key, value); + } + @Override + public void onError(Throwable e) { + map = null; + t1.onError(e); + } + @Override + public void onCompleted() { + Map map0 = map; + map = null; + t1.onNext(map0); + t1.onCompleted(); + } + } + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationToMultimap.java b/rxjava-core/src/main/java/rx/operators/OperationToMultimap.java new file mode 100644 index 0000000000..e9cf8d9413 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationToMultimap.java @@ -0,0 +1,206 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package rx.operators; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Functions; + +/** + * Maps the elements of the source observable into a multimap + * (Map<K, Collection<V>>) where each + * key entry has a collection of the source's values. + * + * @see Issue #97 + */ +public class OperationToMultimap { + /** + * ToMultimap with key selector, identitiy value selector, + * default HashMap factory and default ArrayList collection factory. + */ + public static OnSubscribeFunc>> toMultimap( + Observable source, + Func1 keySelector + ) { + return new ToMultimap( + source, keySelector, Functions.identity(), + new DefaultToMultimapFactory(), + new DefaultMultimapCollectionFactory() + ); + } + + /** + * ToMultimap with key selector, custom value selector, + * default HashMap factory and default ArrayList collection factory. + */ + public static OnSubscribeFunc>> toMultimap( + Observable source, + Func1 keySelector, + Func1 valueSelector + ) { + return new ToMultimap( + source, keySelector, valueSelector, + new DefaultToMultimapFactory(), + new DefaultMultimapCollectionFactory() + ); + } + /** + * ToMultimap with key selector, custom value selector, + * custom Map factory and default ArrayList collection factory. + */ + public static OnSubscribeFunc>> toMultimap( + Observable source, + Func1 keySelector, + Func1 valueSelector, + Func0>> mapFactory + ) { + return new ToMultimap( + source, keySelector, valueSelector, + mapFactory, + new DefaultMultimapCollectionFactory() + ); + } + /** + * ToMultimap with key selector, custom value selector, + * custom Map factory and custom collection factory. + */ + public static OnSubscribeFunc>> toMultimap( + Observable source, + Func1 keySelector, + Func1 valueSelector, + Func0>> mapFactory, + Func1> collectionFactory + ) { + return new ToMultimap( + source, keySelector, valueSelector, + mapFactory, + collectionFactory + ); + } + /** + * The default multimap factory returning a HashMap. + */ + public static class DefaultToMultimapFactory implements Func0>> { + @Override + public Map> call() { + return new HashMap>(); + } + } + /** + * The default collection factory for a key in the multimap returning + * an ArrayList independent of the key. + */ + public static class DefaultMultimapCollectionFactory + implements Func1> { + @Override + public Collection call(K t1) { + return new ArrayList(); + } + } + /** + * Maps the elements of the source observable int a multimap customized + * by various selectors and factories. + */ + public static class ToMultimap implements OnSubscribeFunc>> { + private final Observable source; + private final Func1 keySelector; + private final Func1 valueSelector; + private final Func0>> mapFactory; + private final Func1> collectionFactory; + public ToMultimap( + Observable source, + Func1 keySelector, + Func1 valueSelector, + Func0>> mapFactory, + Func1> collectionFactory + ) { + this.source = source; + this.keySelector = keySelector; + this.valueSelector = valueSelector; + this.mapFactory = mapFactory; + this.collectionFactory = collectionFactory; + } + @Override + public Subscription onSubscribe(Observer>> t1) { + Map> map; + try { + map = mapFactory.call(); + } catch (Throwable t) { + t1.onError(t); + return Subscriptions.empty(); + } + return source.subscribe(new ToMultimapObserver( + t1, keySelector, valueSelector, map, collectionFactory + )); + } + /** + * Observer that collects the source values of Ts into a multimap. + */ + public static class ToMultimapObserver implements Observer { + private final Func1 keySelector; + private final Func1 valueSelector; + private final Func1> collectionFactory; + private Map> map; + private Observer>> t1; + public ToMultimapObserver( + Observer>> t1, + Func1 keySelector, + Func1 valueSelector, + Map> map, + Func1> collectionFactory + ) { + this.t1 = t1; + this.keySelector = keySelector; + this.valueSelector = valueSelector; + this.collectionFactory = collectionFactory; + this.map = map; + } + @Override + public void onNext(T args) { + K key = keySelector.call(args); + V value = valueSelector.call(args); + Collection collection = map.get(key); + if (collection == null) { + collection = collectionFactory.call(key); + map.put(key, collection); + } + collection.add(value); + } + @Override + public void onError(Throwable e) { + map = null; + t1.onError(e); + } + @Override + public void onCompleted() { + Map> map0 = map; + map = null; + t1.onNext(map0); + t1.onCompleted(); + } + } + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationToObservableFuture.java b/rxjava-core/src/main/java/rx/operators/OperationToObservableFuture.java index 958aa9ad3f..875a0dc0d8 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationToObservableFuture.java +++ b/rxjava-core/src/main/java/rx/operators/OperationToObservableFuture.java @@ -15,13 +15,9 @@ */ package rx.operators; -import static org.mockito.Mockito.*; - import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import org.junit.Test; - import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; @@ -39,7 +35,7 @@ * Observable.subscribe(Observer) does nothing. */ public class OperationToObservableFuture { - private static class ToObservableFuture implements OnSubscribeFunc { + /* package accessible for unit tests */static class ToObservableFuture implements OnSubscribeFunc { private final Future that; private final Long time; private final TimeUnit unit; @@ -82,41 +78,4 @@ public static OnSubscribeFunc toObservableFuture(final Future OnSubscribeFunc toObservableFuture(final Future that, long time, TimeUnit unit) { return new ToObservableFuture(that, time, unit); } - - @SuppressWarnings("unchecked") - public static class UnitTest { - @Test - public void testSuccess() throws Exception { - Future future = mock(Future.class); - Object value = new Object(); - when(future.get()).thenReturn(value); - ToObservableFuture ob = new ToObservableFuture(future); - Observer o = mock(Observer.class); - - Subscription sub = ob.onSubscribe(o); - sub.unsubscribe(); - - verify(o, times(1)).onNext(value); - verify(o, times(1)).onCompleted(); - verify(o, never()).onError(null); - verify(future, never()).cancel(true); - } - - @Test - public void testFailure() throws Exception { - Future future = mock(Future.class); - RuntimeException e = new RuntimeException(); - when(future.get()).thenThrow(e); - ToObservableFuture ob = new ToObservableFuture(future); - Observer o = mock(Observer.class); - - Subscription sub = ob.onSubscribe(o); - sub.unsubscribe(); - - verify(o, never()).onNext(null); - verify(o, never()).onCompleted(); - verify(o, times(1)).onError(e); - verify(future, never()).cancel(true); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationToObservableIterable.java b/rxjava-core/src/main/java/rx/operators/OperationToObservableIterable.java index bebc31550a..a8a970bdd8 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationToObservableIterable.java +++ b/rxjava-core/src/main/java/rx/operators/OperationToObservableIterable.java @@ -15,15 +15,6 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import java.util.Arrays; - -import org.junit.Test; -import org.mockito.Mockito; - -import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; @@ -59,21 +50,4 @@ public Subscription onSubscribe(Observer observer) { return Subscriptions.empty(); } } - - public static class UnitTest { - - @Test - public void testIterable() { - Observable observable = Observable.create(toObservableIterable(Arrays. asList("one", "two", "three"))); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - observable.subscribe(aObserver); - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, times(1)).onNext("two"); - verify(aObserver, times(1)).onNext("three"); - verify(aObserver, Mockito.never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationToObservableList.java b/rxjava-core/src/main/java/rx/operators/OperationToObservableList.java index f6bc080211..b87a49fdd1 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationToObservableList.java +++ b/rxjava-core/src/main/java/rx/operators/OperationToObservableList.java @@ -15,17 +15,10 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; -import org.junit.Test; -import org.mockito.Mockito; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -63,6 +56,7 @@ public Subscription onSubscribe(final Observer> observer) { return that.subscribe(new Observer() { final ConcurrentLinkedQueue list = new ConcurrentLinkedQueue(); + public void onNext(T value) { // onNext can be concurrently executed so list must be thread-safe list.add(value); @@ -93,44 +87,4 @@ public void onCompleted() { }); } } - - public static class UnitTest { - - @Test - public void testList() { - Observable w = Observable.from("one", "two", "three"); - Observable> observable = Observable.create(toObservableList(w)); - - @SuppressWarnings("unchecked") - Observer> aObserver = mock(Observer.class); - observable.subscribe(aObserver); - verify(aObserver, times(1)).onNext(Arrays.asList("one", "two", "three")); - verify(aObserver, Mockito.never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - @Test - public void testListMultipleObservers() { - Observable w = Observable.from("one", "two", "three"); - Observable> observable = Observable.create(toObservableList(w)); - - @SuppressWarnings("unchecked") - Observer> o1 = mock(Observer.class); - observable.subscribe(o1); - - @SuppressWarnings("unchecked") - Observer> o2 = mock(Observer.class); - observable.subscribe(o2); - - List expected = Arrays.asList("one", "two", "three"); - - verify(o1, times(1)).onNext(expected); - verify(o1, Mockito.never()).onError(any(Throwable.class)); - verify(o1, times(1)).onCompleted(); - - verify(o2, times(1)).onNext(expected); - verify(o2, Mockito.never()).onError(any(Throwable.class)); - verify(o2, times(1)).onCompleted(); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationToObservableSortedList.java b/rxjava-core/src/main/java/rx/operators/OperationToObservableSortedList.java index 655f833a5d..cfc7ba35fa 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationToObservableSortedList.java +++ b/rxjava-core/src/main/java/rx/operators/OperationToObservableSortedList.java @@ -15,19 +15,12 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; -import org.junit.Test; -import org.mockito.Mockito; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -142,41 +135,4 @@ public Integer call(Object t1, Object t2) { } } - - public static class UnitTest { - - @Test - public void testSortedList() { - Observable w = Observable.from(1, 3, 2, 5, 4); - Observable> observable = Observable.create(toSortedList(w)); - - @SuppressWarnings("unchecked") - Observer> aObserver = mock(Observer.class); - observable.subscribe(aObserver); - verify(aObserver, times(1)).onNext(Arrays.asList(1, 2, 3, 4, 5)); - verify(aObserver, Mockito.never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - @Test - public void testSortedListWithCustomFunction() { - Observable w = Observable.from(1, 3, 2, 5, 4); - Observable> observable = Observable.create(toSortedList(w, new Func2() { - - @Override - public Integer call(Integer t1, Integer t2) { - return t2 - t1; - } - - })); - - @SuppressWarnings("unchecked") - Observer> aObserver = mock(Observer.class); - observable.subscribe(aObserver); - verify(aObserver, times(1)).onNext(Arrays.asList(5, 4, 3, 2, 1)); - verify(aObserver, Mockito.never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationUsing.java b/rxjava-core/src/main/java/rx/operators/OperationUsing.java new file mode 100644 index 0000000000..dd0cc38d65 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationUsing.java @@ -0,0 +1,59 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Func0; +import rx.util.functions.Func1; + +/** + * Constructs an observable sequence that depends on a resource object. + */ +public class OperationUsing { + + public static OnSubscribeFunc using( + final Func0 resourceFactory, + final Func1> observableFactory) { + return new OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + Subscription resourceSubscription = Subscriptions.empty(); + try { + RESOURCE resource = resourceFactory.call(); + if (resource != null) { + resourceSubscription = resource; + } + Observable observable = observableFactory.call(resource); + SafeObservableSubscription subscription = new SafeObservableSubscription(); + // Use SafeObserver to guarantee resourceSubscription will + // be unsubscribed. + return subscription.wrap(new CompositeSubscription( + observable.subscribe(new SafeObserver( + subscription, observer)), + resourceSubscription)); + } catch (Throwable e) { + resourceSubscription.unsubscribe(); + return Observable. error(e).subscribe(observer); + } + } + }; + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationWindow.java b/rxjava-core/src/main/java/rx/operators/OperationWindow.java index 5cffda9661..d7ff20e061 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationWindow.java +++ b/rxjava-core/src/main/java/rx/operators/OperationWindow.java @@ -15,29 +15,16 @@ */ package rx.operators; -import static org.junit.Assert.*; - -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.TimeUnit; -import org.junit.Before; -import org.junit.Test; - import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Scheduler; import rx.Subscription; import rx.concurrency.Schedulers; -import rx.concurrency.TestScheduler; -import rx.subscriptions.Subscriptions; import rx.util.Closing; -import rx.util.Closings; import rx.util.Opening; -import rx.util.Openings; -import rx.util.functions.Action0; -import rx.util.functions.Action1; import rx.util.functions.Func0; import rx.util.functions.Func1; @@ -50,7 +37,7 @@ public Window call() { return new Window(); } }; - } + } /** *

This method creates a {@link rx.util.functions.Func1} object which represents the window operation. This operation takes @@ -76,7 +63,7 @@ public static OnSubscribeFunc> window(final Observable>() { @Override public Subscription onSubscribe(final Observer> observer) { - NonOverlappingChunks> windows = new NonOverlappingChunks>(observer, OperationWindow.windowMaker()); + NonOverlappingChunks> windows = new NonOverlappingChunks>(observer, OperationWindow. windowMaker()); ChunkCreator creator = new ObservableBasedSingleChunkCreator>(windows, windowClosingSelector); return source.subscribe(new ChunkObserver>(windows, observer, creator)); } @@ -113,7 +100,7 @@ public static OnSubscribeFunc> window(final Observable>() { @Override public Subscription onSubscribe(final Observer> observer) { - OverlappingChunks> windows = new OverlappingChunks>(observer, OperationWindow.windowMaker()); + OverlappingChunks> windows = new OverlappingChunks>(observer, OperationWindow. windowMaker()); ChunkCreator creator = new ObservableBasedMultiChunkCreator>(windows, windowOpenings, windowClosingSelector); return source.subscribe(new ChunkObserver>(windows, observer, creator)); } @@ -168,7 +155,7 @@ public static OnSubscribeFunc> window(final Observable>() { @Override public Subscription onSubscribe(final Observer> observer) { - Chunks> chunks = new SizeBasedChunks>(observer, OperationWindow.windowMaker(), count); + Chunks> chunks = new SizeBasedChunks>(observer, OperationWindow. windowMaker(), count); ChunkCreator creator = new SkippingChunkCreator>(chunks, skip); return source.subscribe(new ChunkObserver>(chunks, observer, creator)); } @@ -223,7 +210,7 @@ public static OnSubscribeFunc> window(final Observable>() { @Override public Subscription onSubscribe(final Observer> observer) { - NonOverlappingChunks> windows = new NonOverlappingChunks>(observer, OperationWindow.windowMaker()); + NonOverlappingChunks> windows = new NonOverlappingChunks>(observer, OperationWindow. windowMaker()); ChunkCreator creator = new TimeBasedChunkCreator>(windows, timespan, unit, scheduler); return source.subscribe(new ChunkObserver>(windows, observer, creator)); } @@ -284,7 +271,7 @@ public static OnSubscribeFunc> window(final Observable>() { @Override public Subscription onSubscribe(final Observer> observer) { - Chunks> chunks = new TimeAndSizeBasedChunks>(observer, OperationWindow.windowMaker(), count, timespan, unit, scheduler); + Chunks> chunks = new TimeAndSizeBasedChunks>(observer, OperationWindow. windowMaker(), count, timespan, unit, scheduler); ChunkCreator creator = new SingleChunkCreator>(chunks); return source.subscribe(new ChunkObserver>(chunks, observer, creator)); } @@ -345,7 +332,7 @@ public static OnSubscribeFunc> window(final Observable>() { @Override public Subscription onSubscribe(final Observer> observer) { - OverlappingChunks> windows = new TimeBasedChunks>(observer, OperationWindow.windowMaker(), timespan, unit, scheduler); + OverlappingChunks> windows = new TimeBasedChunks>(observer, OperationWindow. windowMaker(), timespan, unit, scheduler); ChunkCreator creator = new TimeBasedChunkCreator>(windows, timeshift, unit, scheduler); return source.subscribe(new ChunkObserver>(windows, observer, creator)); } @@ -369,295 +356,4 @@ public Observable getContents() { return Observable.from(contents); } } - - public static class UnitTest { - - private TestScheduler scheduler; - - @Before - public void before() { - scheduler = new TestScheduler(); - } - - private static List> toLists(Observable> observable) { - final List list = new ArrayList(); - final List> lists = new ArrayList>(); - - observable.subscribe(new Action1>() { - @Override - public void call(Observable tObservable) { - tObservable.subscribe(new Action1() { - @Override - public void call(T t) { - list.add(t); - } - }); - lists.add(new ArrayList(list)); - list.clear(); - } - }); - return lists; - } - - @Test - public void testNonOverlappingWindows() { - Observable subject = Observable.from("one", "two", "three", "four", "five"); - Observable> windowed = Observable.create(window(subject, 3)); - - List> windows = toLists(windowed); - - assertEquals(2, windows.size()); - assertEquals(list("one", "two", "three"), windows.get(0)); - assertEquals(list("four", "five"), windows.get(1)); - } - - @Test - public void testSkipAndCountGaplessEindows() { - Observable subject = Observable.from("one", "two", "three", "four", "five"); - Observable> windowed = Observable.create(window(subject, 3, 3)); - - List> windows = toLists(windowed); - - assertEquals(2, windows.size()); - assertEquals(list("one", "two", "three"), windows.get(0)); - assertEquals(list("four", "five"), windows.get(1)); - } - - @Test - public void testOverlappingWindows() { - Observable subject = Observable.from("zero", "one", "two", "three", "four", "five"); - Observable> windowed = Observable.create(window(subject, 3, 1)); - - List> windows = toLists(windowed); - - assertEquals(6, windows.size()); - assertEquals(list("zero", "one", "two"), windows.get(0)); - assertEquals(list("one", "two", "three"), windows.get(1)); - assertEquals(list("two", "three", "four"), windows.get(2)); - assertEquals(list("three", "four", "five"), windows.get(3)); - assertEquals(list("four", "five"), windows.get(4)); - assertEquals(list("five"), windows.get(5)); - } - - @Test - public void testSkipAndCountWindowsWithGaps() { - Observable subject = Observable.from("one", "two", "three", "four", "five"); - Observable> windowed = Observable.create(window(subject, 2, 3)); - - List> windows = toLists(windowed); - - assertEquals(2, windows.size()); - assertEquals(list("one", "two"), windows.get(0)); - assertEquals(list("four", "five"), windows.get(1)); - } - - @Test - public void testTimedAndCount() { - final List list = new ArrayList(); - final List> lists = new ArrayList>(); - - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - push(observer, "one", 10); - push(observer, "two", 90); - push(observer, "three", 110); - push(observer, "four", 190); - push(observer, "five", 210); - complete(observer, 250); - return Subscriptions.empty(); - } - }); - - Observable> windowed = Observable.create(window(source, 100, TimeUnit.MILLISECONDS, 2, scheduler)); - windowed.subscribe(observeWindow(list, lists)); - - scheduler.advanceTimeTo(100, TimeUnit.MILLISECONDS); - assertEquals(1, lists.size()); - assertEquals(lists.get(0), list("one", "two")); - - scheduler.advanceTimeTo(200, TimeUnit.MILLISECONDS); - assertEquals(2, lists.size()); - assertEquals(lists.get(1), list("three", "four")); - - scheduler.advanceTimeTo(300, TimeUnit.MILLISECONDS); - assertEquals(3, lists.size()); - assertEquals(lists.get(2), list("five")); - } - - @Test - public void testTimed() { - final List list = new ArrayList(); - final List> lists = new ArrayList>(); - - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - push(observer, "one", 98); - push(observer, "two", 99); - push(observer, "three", 100); - push(observer, "four", 101); - push(observer, "five", 102); - complete(observer, 150); - return Subscriptions.empty(); - } - }); - - Observable> windowed = Observable.create(window(source, 100, TimeUnit.MILLISECONDS, scheduler)); - windowed.subscribe(observeWindow(list, lists)); - - scheduler.advanceTimeTo(101, TimeUnit.MILLISECONDS); - assertEquals(1, lists.size()); - assertEquals(lists.get(0), list("one", "two", "three")); - - scheduler.advanceTimeTo(201, TimeUnit.MILLISECONDS); - assertEquals(2, lists.size()); - assertEquals(lists.get(1), list("four", "five")); - } - - @Test - public void testObservableBasedOpenerAndCloser() { - final List list = new ArrayList(); - final List> lists = new ArrayList>(); - - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - push(observer, "one", 10); - push(observer, "two", 60); - push(observer, "three", 110); - push(observer, "four", 160); - push(observer, "five", 210); - complete(observer, 500); - return Subscriptions.empty(); - } - }); - - Observable openings = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - push(observer, Openings.create(), 50); - push(observer, Openings.create(), 200); - complete(observer, 250); - return Subscriptions.empty(); - } - }); - - Func1> closer = new Func1>() { - @Override - public Observable call(Opening opening) { - return Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - push(observer, Closings.create(), 100); - complete(observer, 101); - return Subscriptions.empty(); - } - }); - } - }; - - Observable> windowed = Observable.create(window(source, openings, closer)); - windowed.subscribe(observeWindow(list, lists)); - - scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); - assertEquals(2, lists.size()); - assertEquals(lists.get(0), list("two", "three")); - assertEquals(lists.get(1), list("five")); - } - - @Test - public void testObservableBasedCloser() { - final List list = new ArrayList(); - final List> lists = new ArrayList>(); - - Observable source = Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - push(observer, "one", 10); - push(observer, "two", 60); - push(observer, "three", 110); - push(observer, "four", 160); - push(observer, "five", 210); - complete(observer, 250); - return Subscriptions.empty(); - } - }); - - Func0> closer = new Func0>() { - @Override - public Observable call() { - return Observable.create(new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer observer) { - push(observer, Closings.create(), 100); - complete(observer, 101); - return Subscriptions.empty(); - } - }); - } - }; - - Observable> windowed = Observable.create(window(source, closer)); - windowed.subscribe(observeWindow(list, lists)); - - scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); - assertEquals(3, lists.size()); - assertEquals(lists.get(0), list("one", "two")); - assertEquals(lists.get(1), list("three", "four")); - assertEquals(lists.get(2), list("five")); - } - - private List list(String... args) { - List list = new ArrayList(); - for (String arg : args) { - list.add(arg); - } - return list; - } - - private void push(final Observer observer, final T value, int delay) { - scheduler.schedule(new Action0() { - @Override - public void call() { - observer.onNext(value); - } - }, delay, TimeUnit.MILLISECONDS); - } - - private void complete(final Observer observer, int delay) { - scheduler.schedule(new Action0() { - @Override - public void call() { - observer.onCompleted(); - } - }, delay, TimeUnit.MILLISECONDS); - } - - private Action1> observeWindow(final List list, final List> lists) { - return new Action1>() { - @Override - public void call(Observable stringObservable) { - stringObservable.subscribe(new Observer() { - @Override - public void onCompleted() { - lists.add(new ArrayList(list)); - list.clear(); - } - - @Override - public void onError(Throwable e) { - fail(e.getMessage()); - } - - @Override - public void onNext(String args) { - list.add(args); - } - }); - } - }; - } - - } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationZip.java b/rxjava-core/src/main/java/rx/operators/OperationZip.java index 517a1faecc..4c7c70851f 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationZip.java +++ b/rxjava-core/src/main/java/rx/operators/OperationZip.java @@ -1,12 +1,12 @@ /** * Copyright 2013 Netflix, Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,23 +15,23 @@ */ package rx.operators; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - +import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; - -import org.junit.Test; -import org.mockito.InOrder; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.subscriptions.Subscriptions; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.SerialSubscription; import rx.util.functions.Func2; import rx.util.functions.Func3; import rx.util.functions.Func4; @@ -58,135 +58,87 @@ * number of onNext invocations of the source Observable that emits the fewest items. */ public final class OperationZip { - - public static OnSubscribeFunc zip(Observable o1, Observable o2, Func2 zipFunction) { - Aggregator a = new Aggregator(Functions.fromFunc(zipFunction)); - a.addObserver(new ZipObserver(a, o1)); - a.addObserver(new ZipObserver(a, o2)); - return a; + + @SuppressWarnings("unchecked") + public static OnSubscribeFunc zip(Observable o1, Observable o2, final Func2 zipFunction) { + return zip(Arrays.asList(o1, o2), Functions.fromFunc(zipFunction)); } - - public static OnSubscribeFunc zip(Observable o1, Observable o2, Observable o3, Func3 zipFunction) { - Aggregator a = new Aggregator(Functions.fromFunc(zipFunction)); - a.addObserver(new ZipObserver(a, o1)); - a.addObserver(new ZipObserver(a, o2)); - a.addObserver(new ZipObserver(a, o3)); - return a; + + @SuppressWarnings("unchecked") + public static OnSubscribeFunc zip(Observable o1, Observable o2, Observable o3, final Func3 zipFunction) { + return zip(Arrays.asList(o1, o2, o3), Functions.fromFunc(zipFunction)); } - - public static OnSubscribeFunc zip(Observable o1, Observable o2, Observable o3, Observable o4, Func4 zipFunction) { - Aggregator a = new Aggregator(Functions.fromFunc(zipFunction)); - a.addObserver(new ZipObserver(a, o1)); - a.addObserver(new ZipObserver(a, o2)); - a.addObserver(new ZipObserver(a, o3)); - a.addObserver(new ZipObserver(a, o4)); - return a; + + @SuppressWarnings("unchecked") + public static OnSubscribeFunc zip(Observable o1, Observable o2, Observable o3, Observable o4, final Func4 zipFunction) { + return zip(Arrays.asList(o1, o2, o3, o4), Functions.fromFunc(zipFunction)); } - - public static OnSubscribeFunc zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Func5 zipFunction) { - Aggregator a = new Aggregator(Functions.fromFunc(zipFunction)); - a.addObserver(new ZipObserver(a, o1)); - a.addObserver(new ZipObserver(a, o2)); - a.addObserver(new ZipObserver(a, o3)); - a.addObserver(new ZipObserver(a, o4)); - a.addObserver(new ZipObserver(a, o5)); - return a; + + @SuppressWarnings("unchecked") + public static OnSubscribeFunc zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, final Func5 zipFunction) { + return zip(Arrays.asList(o1, o2, o3, o4, o5), Functions.fromFunc(zipFunction)); } - + + @SuppressWarnings("unchecked") public static OnSubscribeFunc zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, - Func6 zipFunction) { - Aggregator a = new Aggregator(Functions.fromFunc(zipFunction)); - a.addObserver(new ZipObserver(a, o1)); - a.addObserver(new ZipObserver(a, o2)); - a.addObserver(new ZipObserver(a, o3)); - a.addObserver(new ZipObserver(a, o4)); - a.addObserver(new ZipObserver(a, o5)); - a.addObserver(new ZipObserver(a, o6)); - return a; + final Func6 zipFunction) { + return zip(Arrays.asList(o1, o2, o3, o4, o5, o6), Functions.fromFunc(zipFunction)); } - + + @SuppressWarnings("unchecked") public static OnSubscribeFunc zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, - Func7 zipFunction) { - Aggregator a = new Aggregator(Functions.fromFunc(zipFunction)); - a.addObserver(new ZipObserver(a, o1)); - a.addObserver(new ZipObserver(a, o2)); - a.addObserver(new ZipObserver(a, o3)); - a.addObserver(new ZipObserver(a, o4)); - a.addObserver(new ZipObserver(a, o5)); - a.addObserver(new ZipObserver(a, o6)); - a.addObserver(new ZipObserver(a, o7)); - return a; + final Func7 zipFunction) { + return zip(Arrays.asList(o1, o2, o3, o4, o5, o6, o7), Functions.fromFunc(zipFunction)); } - + + @SuppressWarnings("unchecked") public static OnSubscribeFunc zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, - Func8 zipFunction) { - Aggregator a = new Aggregator(Functions.fromFunc(zipFunction)); - a.addObserver(new ZipObserver(a, o1)); - a.addObserver(new ZipObserver(a, o2)); - a.addObserver(new ZipObserver(a, o3)); - a.addObserver(new ZipObserver(a, o4)); - a.addObserver(new ZipObserver(a, o5)); - a.addObserver(new ZipObserver(a, o6)); - a.addObserver(new ZipObserver(a, o7)); - a.addObserver(new ZipObserver(a, o8)); - return a; + final Func8 zipFunction) { + return zip(Arrays.asList(o1, o2, o3, o4, o5, o6, o7, o8), Functions.fromFunc(zipFunction)); } - + + @SuppressWarnings("unchecked") public static OnSubscribeFunc zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, - Observable o9, Func9 zipFunction) { - Aggregator a = new Aggregator(Functions.fromFunc(zipFunction)); - a.addObserver(new ZipObserver(a, o1)); - a.addObserver(new ZipObserver(a, o2)); - a.addObserver(new ZipObserver(a, o3)); - a.addObserver(new ZipObserver(a, o4)); - a.addObserver(new ZipObserver(a, o5)); - a.addObserver(new ZipObserver(a, o6)); - a.addObserver(new ZipObserver(a, o7)); - a.addObserver(new ZipObserver(a, o8)); - a.addObserver(new ZipObserver(a, o9)); - return a; + Observable o9, final Func9 zipFunction) { + return zip(Arrays.asList(o1, o2, o3, o4, o5, o6, o7, o8, o9), Functions.fromFunc(zipFunction)); } - - public static OnSubscribeFunc zip(Iterable> ws, FuncN zipFunction) { - Aggregator a = new Aggregator(zipFunction); - for (Observable w : ws) { - ZipObserver zipObserver = new ZipObserver(a, w); - a.addObserver(zipObserver); - } + + public static OnSubscribeFunc zip(Iterable> ws, final FuncN zipFunction) { + ManyObservables a = new ManyObservables(ws, zipFunction); return a; } - + /* - * ThreadSafe - */ - private static class ZipObserver implements Observer { + * ThreadSafe + */ + /* package accessible for unit tests */static class ZipObserver implements Observer { final Observable w; final Aggregator a; private final SafeObservableSubscription subscription = new SafeObservableSubscription(); private final AtomicBoolean subscribed = new AtomicBoolean(false); - + public ZipObserver(Aggregator a, Observable w) { this.a = a; this.w = w; } - + public void startWatching() { if (subscribed.compareAndSet(false, true)) { // only subscribe once even if called more than once subscription.wrap(w.subscribe(this)); } } - + @Override public void onCompleted() { a.complete(this); } - + @Override public void onError(Throwable e) { a.error(this, e); } - + @Override public void onNext(T args) { try { @@ -196,47 +148,47 @@ public void onNext(T args) { } } } - + /** * Receive notifications from each of the Observables we are reducing and execute the zipFunction whenever we have received events from all Observables. - * + * * This class is thread-safe. - * + * * @param */ - private static class Aggregator implements OnSubscribeFunc { - + /* package accessible for unit tests */static class Aggregator implements OnSubscribeFunc { + private volatile SynchronizedObserver observer; private final FuncN zipFunction; private final AtomicBoolean started = new AtomicBoolean(false); private final AtomicBoolean running = new AtomicBoolean(true); private final ConcurrentHashMap, Boolean> completed = new ConcurrentHashMap, Boolean>(); - + /* we use ConcurrentHashMap despite synchronization of methods because stop() does NOT use synchronization and this map is used by it and can be called by other threads */ private ConcurrentHashMap, ConcurrentLinkedQueue> receivedValuesPerObserver = new ConcurrentHashMap, ConcurrentLinkedQueue>(); /* we use a ConcurrentLinkedQueue to retain ordering (I'd like to just use a ConcurrentLinkedHashMap for 'receivedValuesPerObserver' but that doesn't exist in standard java */ private ConcurrentLinkedQueue> observers = new ConcurrentLinkedQueue>(); - + public Aggregator(FuncN zipFunction) { this.zipFunction = zipFunction; } - + /** * Receive notification of a Observer starting (meaning we should require it for aggregation) - * + * * Thread Safety => Invoke ONLY from the static factory methods at top of this class which are always an atomic execution by a single thread. - * + * * @param w */ - private void addObserver(ZipObserver w) { + void addObserver(ZipObserver w) { // initialize this ZipObserver observers.add(w); receivedValuesPerObserver.put(w, new ConcurrentLinkedQueue()); } - + /** * Receive notification of a Observer completing its iterations. - * + * * @param w */ void complete(ZipObserver w) { @@ -251,10 +203,10 @@ void complete(ZipObserver w) { } } } - + /** * Receive error for a Observer. Throw the error up the chain and stop processing. - * + * * @param w */ void error(ZipObserver w, Throwable e) { @@ -265,12 +217,12 @@ void error(ZipObserver w, Throwable e) { stop(); } } - + /** * Receive the next value from a Observer. *

* If we have received values from all Observers, trigger the zip function, otherwise store the value and keep waiting. - * + * * @param w * @param arg */ @@ -278,18 +230,18 @@ void next(ZipObserver w, Object arg) { if (observer == null) { throw new RuntimeException("This shouldn't be running if a Observer isn't registered"); } - + /* if we've been 'unsubscribed' don't process anything further even if the things we're watching keep sending (likely because they are not responding to the unsubscribe call) */ if (!running.get()) { return; } - + // store the value we received and below we'll decide if we are to send it to the Observer receivedValuesPerObserver.get(w).add(arg); - + // define here so the variable is out of the synchronized scope Object[] argsToZip = new Object[observers.size()]; - + /* we have to synchronize here despite using concurrent data structures because the compound logic here must all be done atomically */ synchronized (this) { // if all ZipObservers in 'receivedValues' map have a value, invoke the zipFunction @@ -310,7 +262,7 @@ void next(ZipObserver w, Object arg) { // this 'next' method while another thread finishes calling this zipFunction observer.onNext(zipFunction.call(argsToZip)); } - + @Override public Subscription onSubscribe(Observer observer) { if (started.compareAndSet(false, true)) { @@ -320,34 +272,34 @@ public Subscription onSubscribe(Observer observer) { for (ZipObserver rw : observers) { rw.startWatching(); } - + return subscription.wrap(new Subscription() { - + @Override public void unsubscribe() { stop(); } - + }); } else { /* a Observer already has subscribed so blow up */ throw new IllegalStateException("Only one Observer can subscribe to this Observable."); } } - + /* - * Do NOT synchronize this because it gets called via unsubscribe which can occur on other threads - * and result in deadlocks. (http://jira/browse/API-4060) - * - * AtomicObservableSubscription uses compareAndSet instead of locking to avoid deadlocks but ensure single-execution. - * - * We do the same in the implementation of this method. - * - * ThreadSafety of this method is provided by: - * - AtomicBoolean[running].compareAndSet - * - ConcurrentLinkedQueue[Observers] - * - ZipObserver.subscription being an AtomicObservableSubscription - */ + * Do NOT synchronize this because it gets called via unsubscribe which can occur on other threads + * and result in deadlocks. (http://jira/browse/API-4060) + * + * AtomicObservableSubscription uses compareAndSet instead of locking to avoid deadlocks but ensure single-execution. + * + * We do the same in the implementation of this method. + * + * ThreadSafety of this method is provided by: + * - AtomicBoolean[running].compareAndSet + * - ConcurrentLinkedQueue[Observers] + * - ZipObserver.subscription being an AtomicObservableSubscription + */ private void stop() { /* tell ourselves to stop processing onNext events by setting running=false */ if (running.compareAndSet(true, false)) { @@ -359,568 +311,218 @@ private void stop() { } } } - + } - - public static class UnitTest { - - @SuppressWarnings("unchecked") - @Test - public void testCollectionSizeDifferentThanFunction() { - FuncN zipr = Functions.fromFunc(getConcatStringIntegerIntArrayZipr()); - //Func3 - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - - @SuppressWarnings("rawtypes") - Collection ws = java.util.Collections.singleton(Observable.from("one", "two")); - Observable w = Observable.create(zip(ws, zipr)); - w.subscribe(aObserver); - - verify(aObserver, times(1)).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - verify(aObserver, never()).onNext(any(String.class)); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testZippingDifferentLengthObservableSequences1() { - Observer w = mock(Observer.class); - - TestObservable w1 = new TestObservable(); - TestObservable w2 = new TestObservable(); - TestObservable w3 = new TestObservable(); - - Observable zipW = Observable.create(zip(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsZipr())); - zipW.subscribe(w); - - /* simulate sending data */ - // once for w1 - w1.observer.onNext("1a"); - w1.observer.onCompleted(); - // twice for w2 - w2.observer.onNext("2a"); - w2.observer.onNext("2b"); - w2.observer.onCompleted(); - // 4 times for w3 - w3.observer.onNext("3a"); - w3.observer.onNext("3b"); - w3.observer.onNext("3c"); - w3.observer.onNext("3d"); - w3.observer.onCompleted(); - - /* we should have been called 1 time on the Observer */ - InOrder inOrder = inOrder(w); - inOrder.verify(w).onNext("1a2a3a"); - - inOrder.verify(w, times(1)).onCompleted(); - } - - @Test - public void testZippingDifferentLengthObservableSequences2() { - @SuppressWarnings("unchecked") - Observer w = mock(Observer.class); - - TestObservable w1 = new TestObservable(); - TestObservable w2 = new TestObservable(); - TestObservable w3 = new TestObservable(); - - Observable zipW = Observable.create(zip(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsZipr())); - zipW.subscribe(w); - - /* simulate sending data */ - // 4 times for w1 - w1.observer.onNext("1a"); - w1.observer.onNext("1b"); - w1.observer.onNext("1c"); - w1.observer.onNext("1d"); - w1.observer.onCompleted(); - // twice for w2 - w2.observer.onNext("2a"); - w2.observer.onNext("2b"); - w2.observer.onCompleted(); - // 1 times for w3 - w3.observer.onNext("3a"); - w3.observer.onCompleted(); - - /* we should have been called 1 time on the Observer */ - InOrder inOrder = inOrder(w); - inOrder.verify(w).onNext("1a2a3a"); - - inOrder.verify(w, times(1)).onCompleted(); - - } - + /** + * Merges the values across multiple sources and applies the selector + * function. + *

The resulting sequence terminates if no more pairs can be + * established, i.e., streams of length 1 and 2 zipped will produce + * only 1 item.

+ *

Exception semantics: errors from the source observable are + * propagated as-is.

+ * @param the common element type + * @param the result element type + */ + public static class ManyObservables implements OnSubscribeFunc { + /** */ + protected final Iterable> sources; + /** */ + protected final FuncN selector; /** - * Testing internal private logic due to the complexity so I want to use TDD to test as a I build it rather than relying purely on the overall functionality expected by the public methods. + * Constructor. + * @param sources the sources + * @param selector the result selector */ - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregatorSimple() { - FuncN zipr = getConcatZipr(); - /* create the aggregator which will execute the zip function when all Observables provide values */ - Aggregator a = new Aggregator(zipr); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - a.onSubscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - ZipObserver r1 = mock(ZipObserver.class); - ZipObserver r2 = mock(ZipObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "hello"); - a.next(r2, "world"); - - InOrder inOrder = inOrder(aObserver); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - inOrder.verify(aObserver, times(1)).onNext("helloworld"); - - a.next(r1, "hello "); - a.next(r2, "again"); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - inOrder.verify(aObserver, times(1)).onNext("hello again"); - - a.complete(r1); - a.complete(r2); - - inOrder.verify(aObserver, never()).onNext(anyString()); - verify(aObserver, times(1)).onCompleted(); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregatorDifferentSizedResultsWithOnComplete() { - FuncN zipr = getConcatZipr(); - /* create the aggregator which will execute the zip function when all Observables provide values */ - Aggregator a = new Aggregator(zipr); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - a.onSubscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - ZipObserver r1 = mock(ZipObserver.class); - ZipObserver r2 = mock(ZipObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "hello"); - a.next(r2, "world"); - a.complete(r2); - - InOrder inOrder = inOrder(aObserver); - - inOrder.verify(aObserver, never()).onError(any(Throwable.class)); - inOrder.verify(aObserver, never()).onCompleted(); - inOrder.verify(aObserver, times(1)).onNext("helloworld"); - - a.next(r1, "hi"); - a.complete(r1); - - inOrder.verify(aObserver, never()).onError(any(Throwable.class)); - inOrder.verify(aObserver, times(1)).onCompleted(); - inOrder.verify(aObserver, never()).onNext(anyString()); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregateMultipleTypes() { - FuncN zipr = getConcatZipr(); - /* create the aggregator which will execute the zip function when all Observables provide values */ - Aggregator a = new Aggregator(zipr); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - a.onSubscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - ZipObserver r1 = mock(ZipObserver.class); - ZipObserver r2 = mock(ZipObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "hello"); - a.next(r2, "world"); - a.complete(r2); - - InOrder inOrder = inOrder(aObserver); - - inOrder.verify(aObserver, never()).onError(any(Throwable.class)); - inOrder.verify(aObserver, never()).onCompleted(); - inOrder.verify(aObserver, times(1)).onNext("helloworld"); - - a.next(r1, "hi"); - a.complete(r1); - - inOrder.verify(aObserver, never()).onError(any(Throwable.class)); - inOrder.verify(aObserver, times(1)).onCompleted(); - inOrder.verify(aObserver, never()).onNext(anyString()); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregate3Types() { - FuncN zipr = getConcatZipr(); - /* create the aggregator which will execute the zip function when all Observables provide values */ - Aggregator a = new Aggregator(zipr); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - a.onSubscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - ZipObserver r1 = mock(ZipObserver.class); - ZipObserver r2 = mock(ZipObserver.class); - ZipObserver r3 = mock(ZipObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - a.addObserver(r3); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "hello"); - a.next(r2, 2); - a.next(r3, new int[] { 5, 6, 7 }); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - verify(aObserver, times(1)).onNext("hello2[5, 6, 7]"); + public ManyObservables( + Iterable> sources, + FuncN selector) { + this.sources = sources; + this.selector = selector; } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregatorsWithDifferentSizesAndTiming() { - FuncN zipr = getConcatZipr(); - /* create the aggregator which will execute the zip function when all Observables provide values */ - Aggregator a = new Aggregator(zipr); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - a.onSubscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - ZipObserver r1 = mock(ZipObserver.class); - ZipObserver r2 = mock(ZipObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "one"); - a.next(r1, "two"); - a.next(r1, "three"); - a.next(r2, "A"); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - verify(aObserver, times(1)).onNext("oneA"); - - a.next(r1, "four"); - a.complete(r1); - a.next(r2, "B"); - verify(aObserver, times(1)).onNext("twoB"); - a.next(r2, "C"); - verify(aObserver, times(1)).onNext("threeC"); - a.next(r2, "D"); - verify(aObserver, times(1)).onNext("fourD"); - a.next(r2, "E"); - verify(aObserver, never()).onNext("E"); - a.complete(r2); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregatorError() { - FuncN zipr = getConcatZipr(); - /* create the aggregator which will execute the zip function when all Observables provide values */ - Aggregator a = new Aggregator(zipr); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - a.onSubscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - ZipObserver r1 = mock(ZipObserver.class); - ZipObserver r2 = mock(ZipObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "hello"); - a.next(r2, "world"); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - verify(aObserver, times(1)).onNext("helloworld"); - - a.error(r1, new RuntimeException("")); - a.next(r1, "hello"); - a.next(r2, "again"); - - verify(aObserver, times(1)).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - // we don't want to be called again after an error - verify(aObserver, times(0)).onNext("helloagain"); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregatorUnsubscribe() { - FuncN zipr = getConcatZipr(); - /* create the aggregator which will execute the zip function when all Observables provide values */ - Aggregator a = new Aggregator(zipr); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - Subscription subscription = a.onSubscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - ZipObserver r1 = mock(ZipObserver.class); - ZipObserver r2 = mock(ZipObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "hello"); - a.next(r2, "world"); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - verify(aObserver, times(1)).onNext("helloworld"); - - subscription.unsubscribe(); - a.next(r1, "hello"); - a.next(r2, "again"); - - verify(aObserver, times(0)).onError(any(Throwable.class)); - verify(aObserver, never()).onCompleted(); - // we don't want to be called again after an error - verify(aObserver, times(0)).onNext("helloagain"); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testAggregatorEarlyCompletion() { - FuncN zipr = getConcatZipr(); - /* create the aggregator which will execute the zip function when all Observables provide values */ - Aggregator a = new Aggregator(zipr); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - a.onSubscribe(aObserver); - - /* mock the Observable Observers that are 'pushing' data for us */ - ZipObserver r1 = mock(ZipObserver.class); - ZipObserver r2 = mock(ZipObserver.class); - - /* pretend we're starting up */ - a.addObserver(r1); - a.addObserver(r2); - - /* simulate the Observables pushing data into the aggregator */ - a.next(r1, "one"); - a.next(r1, "two"); - a.complete(r1); - a.next(r2, "A"); - - InOrder inOrder = inOrder(aObserver); - - inOrder.verify(aObserver, never()).onError(any(Throwable.class)); - inOrder.verify(aObserver, never()).onCompleted(); - inOrder.verify(aObserver, times(1)).onNext("oneA"); - - a.complete(r2); - - inOrder.verify(aObserver, never()).onError(any(Throwable.class)); - inOrder.verify(aObserver, times(1)).onCompleted(); - inOrder.verify(aObserver, never()).onNext(anyString()); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testZip2Types() { - Func2 zipr = getConcatStringIntegerZipr(); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - - Observable w = Observable.create(zip(Observable.from("one", "two"), Observable.from(2, 3, 4), zipr)); - w.subscribe(aObserver); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verify(aObserver, times(1)).onNext("one2"); - verify(aObserver, times(1)).onNext("two3"); - verify(aObserver, never()).onNext("4"); - } - - @SuppressWarnings("unchecked") - /* mock calls don't do generics */ - @Test - public void testZip3Types() { - Func3 zipr = getConcatStringIntegerIntArrayZipr(); - - /* define a Observer to receive aggregated events */ - Observer aObserver = mock(Observer.class); - - Observable w = Observable.create(zip(Observable.from("one", "two"), Observable.from(2), Observable.from(new int[] { 4, 5, 6 }), zipr)); - w.subscribe(aObserver); - - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - verify(aObserver, times(1)).onNext("one2[4, 5, 6]"); - verify(aObserver, never()).onNext("two"); - } - - @Test - public void testOnNextExceptionInvokesOnError() { - Func2 zipr = getDivideZipr(); - - @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - - Observable w = Observable.create(zip(Observable.from(10, 20, 30), Observable.from(0, 1, 2), zipr)); - w.subscribe(aObserver); - - verify(aObserver, times(1)).onError(any(Throwable.class)); - } - - private Func2 getDivideZipr() { - Func2 zipr = new Func2() { - + + @Override + public Subscription onSubscribe(final Observer observer) { + + final CompositeSubscription composite = new CompositeSubscription(); + + final ReadWriteLock rwLock = new ReentrantReadWriteLock(true); + + final List> all = new ArrayList>(); + + Observer> o2 = new Observer>() { @Override - public Integer call(Integer i1, Integer i2) { - return i1 / i2; + public void onCompleted() { + observer.onCompleted(); } - - }; - return zipr; - } - - private Func3 getConcat3StringsZipr() { - Func3 zipr = new Func3() { - @Override - public String call(String a1, String a2, String a3) { - if (a1 == null) { - a1 = ""; - } - if (a2 == null) { - a2 = ""; - } - if (a3 == null) { - a3 = ""; - } - return a1 + a2 + a3; + public void onError(Throwable t) { + observer.onError(t); + } + @Override + public void onNext(List value) { + observer.onNext(selector.call(value.toArray(new Object[value.size()]))); } - }; - return zipr; + + for (Observable o : sources) { + + ItemObserver io = new ItemObserver( + rwLock, all, o, o2, composite); + composite.add(io); + all.add(io); + } + + for (ItemObserver io : all) { + io.connect(); + } + + return composite; } - - private FuncN getConcatZipr() { - FuncN zipr = new FuncN() { - - @Override - public String call(Object... args) { - String returnValue = ""; - for (Object o : args) { - if (o != null) { - returnValue += getStringValue(o); + /** + * The individual line's observer. + * @author akarnokd, 2013.01.14. + * @param the element type + */ + public static class ItemObserver implements Observer, Subscription { + /** Reader-writer lock. */ + protected final ReadWriteLock rwLock; + /** The queue. */ + public final Queue queue = new LinkedList(); + /** The list of the other observers. */ + public final List> all; + /** The null sentinel value. */ + protected static final Object NULL_SENTINEL = new Object(); + /** The global cancel. */ + protected final Subscription cancel; + /** The subscription to the source. */ + protected final SerialSubscription toSource = new SerialSubscription(); + /** Indicate completion of this stream. */ + protected boolean done; + /** The source. */ + protected final Observable source; + /** The observer. */ + protected final Observer> observer; + /** + * Constructor. + * @param rwLock the reader-writer lock to use + * @param all all observers + * @param source the source sequence + * @param observer the output observer + * @param cancel the cancellation handler + */ + public ItemObserver( + ReadWriteLock rwLock, + List> all, + Observable source, + Observer> observer, + Subscription cancel) { + this.rwLock = rwLock; + this.all = all; + this.source = source; + this.observer = observer; + this.cancel = cancel; + } + @SuppressWarnings("unchecked") + @Override + public void onNext(T value) { + rwLock.readLock().lock(); + try { + if (done) { + return; + } + queue.add(value != null ? value : NULL_SENTINEL); + } finally { + rwLock.readLock().unlock(); + } + // run collector + if (rwLock.writeLock().tryLock()) { + try { + while (true) { + List values = new ArrayList(all.size()); + for (ItemObserver io : all) { + if (io.queue.isEmpty()) { + if (io.done) { + observer.onCompleted(); + cancel.unsubscribe(); + } + return; + } + Object v = io.queue.peek(); + if (v == NULL_SENTINEL) { + v = null; + } + values.add((T)v); + } + if (values.size() == all.size()) { + for (ItemObserver io : all) { + io.queue.poll(); + } + observer.onNext(values); + } } + } finally { + rwLock.writeLock().unlock(); } - System.out.println("returning: " + returnValue); - return returnValue; } - - }; - return zipr; - } - - private Func2 getConcatStringIntegerZipr() { - Func2 zipr = new Func2() { - - @Override - public String call(String s, Integer i) { - return getStringValue(s) + getStringValue(i); + } + + @Override + public void onError(Throwable ex) { + boolean c = false; + rwLock.writeLock().lock(); + try { + if (done) { + return; + } + done = true; + c = true; + observer.onError(ex); + cancel.unsubscribe(); + } finally { + rwLock.writeLock().unlock(); } - - }; - return zipr; - } - - private Func3 getConcatStringIntegerIntArrayZipr() { - Func3 zipr = new Func3() { - - @Override - public String call(String s, Integer i, int[] iArray) { - return getStringValue(s) + getStringValue(i) + getStringValue(iArray); + if (c) { + unsubscribe(); } - - }; - return zipr; - } - - private static String getStringValue(Object o) { - if (o == null) { - return ""; - } else { - if (o instanceof int[]) { - return Arrays.toString((int[]) o); - } else { - return String.valueOf(o); + } + + @Override + public void onCompleted() { + boolean c = false; + rwLock.readLock().lock(); + try { + done = true; + c = true; + } finally { + rwLock.readLock().unlock(); + } + if (rwLock.writeLock().tryLock()) { + try { + for (ItemObserver io : all) { + if (io.queue.isEmpty() && io.done) { + observer.onCompleted(); + cancel.unsubscribe(); + return; + } + } + } finally { + rwLock.writeLock().unlock(); + } + } + if (c) { + unsubscribe(); } } - } - - private static class TestObservable implements OnSubscribeFunc { - - Observer observer; - + /** Connect to the source observable. */ + public void connect() { + toSource.setSubscription(source.subscribe(this)); + } @Override - public Subscription onSubscribe(Observer Observer) { - // just store the variable where it can be accessed so we can manually trigger it - this.observer = Observer; - return Subscriptions.empty(); + public void unsubscribe() { + toSource.unsubscribe(); } - + } } - } diff --git a/rxjava-core/src/main/java/rx/operators/OperatorTester.java b/rxjava-core/src/main/java/rx/operators/OperatorTester.java deleted file mode 100644 index 62b41a0567..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperatorTester.java +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright 2013 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.operators; - -import java.util.concurrent.TimeUnit; - -import rx.Scheduler; -import rx.Subscription; -import rx.util.functions.Action0; -import rx.util.functions.Func2; - -/** - * Common utility functions for testing operator implementations. - */ -/* package */class OperatorTester { - /* - * This is purposefully package-only so it does not leak into the public API outside of this package. - * - * This package is implementation details and not part of the Javadocs and thus can change without breaking backwards compatibility. - * - * benjchristensen => I'm procrastinating the decision of where and how these types of classes (see rx.subjects.UnsubscribeTester) should exist. - * If they are only for internal implementations then I don't want them as part of the API. - * If they are truly useful for everyone to use then an "rx.testing" package may make sense. - */ - - private OperatorTester() { - } - - public static class UnitTest { - - /** - * Used for mocking of Schedulers since many Scheduler implementations are static/final. - * - * @param underlying - * @return - */ - public static Scheduler forwardingScheduler(Scheduler underlying) { - return new ForwardingScheduler(underlying); - } - - public static class ForwardingScheduler extends Scheduler { - private final Scheduler underlying; - - public ForwardingScheduler(Scheduler underlying) { - this.underlying = underlying; - } - - @Override - public Subscription schedule(Action0 action) { - return underlying.schedule(action); - } - - @Override - public Subscription schedule(T state, Func2 action) { - return underlying.schedule(state, action); - } - - @Override - public Subscription schedule(Action0 action, long dueTime, TimeUnit unit) { - return underlying.schedule(action, dueTime, unit); - } - - @Override - public Subscription schedule(T state, Func2 action, long dueTime, TimeUnit unit) { - return underlying.schedule(state, action, dueTime, unit); - } - - @Override - public Subscription schedulePeriodically(Action0 action, long initialDelay, long period, TimeUnit unit) { - return underlying.schedulePeriodically(action, initialDelay, period, unit); - } - - @Override - public Subscription schedulePeriodically(T state, Func2 action, long initialDelay, long period, TimeUnit unit) { - return underlying.schedulePeriodically(state, action, initialDelay, period, unit); - } - - @Override - public long now() { - return underlying.now(); - } - } - } -} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/operators/README.txt b/rxjava-core/src/main/java/rx/operators/README.txt index c2d441a10c..1be4baa962 100644 --- a/rxjava-core/src/main/java/rx/operators/README.txt +++ b/rxjava-core/src/main/java/rx/operators/README.txt @@ -1,3 +1,19 @@ +==== + Copyright 2013 Netflix, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==== + This package "rx.operators" is for internal implementation details and can change at any time. It is excluded from the public Javadocs (http://netflix.github.io/RxJava/javadoc/) and should not be relied upon by any code. diff --git a/rxjava-core/src/main/java/rx/operators/SafeObservableSubscription.java b/rxjava-core/src/main/java/rx/operators/SafeObservableSubscription.java index a94f658708..ef33ebd3d3 100644 --- a/rxjava-core/src/main/java/rx/operators/SafeObservableSubscription.java +++ b/rxjava-core/src/main/java/rx/operators/SafeObservableSubscription.java @@ -15,12 +15,8 @@ */ package rx.operators; -import static org.mockito.Mockito.*; - import java.util.concurrent.atomic.AtomicReference; -import org.junit.Test; - import rx.Subscription; /** @@ -54,7 +50,8 @@ public SafeObservableSubscription(Subscription actualSubscription) { /** * Wraps the actual subscription once it exists (if it wasn't available when constructed) * - * @param actualSubscription the wrapped subscription + * @param actualSubscription + * the wrapped subscription * @throws IllegalStateException * if trying to set more than once (or use this method after setting via constructor) */ @@ -82,15 +79,4 @@ public void unsubscribe() { public boolean isUnsubscribed() { return actualSubscription.get() == UNSUBSCRIBED; } - - public static class UnitTest { - @Test - public void testWrapAfterUnsubscribe() { - SafeObservableSubscription atomicObservableSubscription = new SafeObservableSubscription(); - atomicObservableSubscription.unsubscribe(); - Subscription innerSubscription = mock(Subscription.class); - atomicObservableSubscription.wrap(innerSubscription); - verify(innerSubscription, times(1)).unsubscribe(); - } - } } diff --git a/rxjava-core/src/main/java/rx/operators/ScheduledObserver.java b/rxjava-core/src/main/java/rx/operators/ScheduledObserver.java deleted file mode 100644 index a5eff1b852..0000000000 --- a/rxjava-core/src/main/java/rx/operators/ScheduledObserver.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Copyright 2013 Netflix, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package rx.operators; - -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.atomic.AtomicInteger; - -import rx.Notification; -import rx.Observer; -import rx.Scheduler; -import rx.util.functions.Action0; - -/* package */class ScheduledObserver implements Observer { - private final Observer underlying; - private final Scheduler scheduler; - - private final ConcurrentLinkedQueue> queue = new ConcurrentLinkedQueue>(); - private final AtomicInteger counter = new AtomicInteger(0); - - public ScheduledObserver(Observer underlying, Scheduler scheduler) { - this.underlying = underlying; - this.scheduler = scheduler; - } - - @Override - public void onCompleted() { - enqueue(new Notification()); - } - - @Override - public void onError(final Throwable e) { - enqueue(new Notification(e)); - } - - @Override - public void onNext(final T args) { - enqueue(new Notification(args)); - } - - private void enqueue(Notification notification) { - // this must happen before 'counter' is used to provide synchronization between threads - queue.offer(notification); - - // we now use counter to atomically determine if we need to start processing or not - // it will be 0 if it's the first notification or the scheduler has finished processing work - // and we need to start doing it again - if (counter.getAndIncrement() == 0) { - processQueue(); - } - } - - private void processQueue() { - scheduler.schedule(new Action0() { - @Override - public void call() { - Notification not = queue.poll(); - - switch (not.getKind()) { - case OnNext: - underlying.onNext(not.getValue()); - break; - case OnError: - underlying.onError(not.getThrowable()); - break; - case OnCompleted: - underlying.onCompleted(); - break; - default: - throw new IllegalStateException("Unknown kind of notification " + not); - - } - - // decrement count and if we still have work to do - // recursively schedule ourselves to process again - if (counter.decrementAndGet() > 0) { - scheduler.schedule(this); - } - - } - }); - } -} diff --git a/rxjava-core/src/main/java/rx/operators/SynchronizedObserver.java b/rxjava-core/src/main/java/rx/operators/SynchronizedObserver.java index fff3fccbf8..2daef3e280 100644 --- a/rxjava-core/src/main/java/rx/operators/SynchronizedObserver.java +++ b/rxjava-core/src/main/java/rx/operators/SynchronizedObserver.java @@ -15,26 +15,7 @@ */ package rx.operators; -import static org.junit.Assert.*; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import rx.Observable; -import rx.Observable.OnSubscribeFunc; import rx.Observer; -import rx.Subscription; /** * A thread-safe Observer for transitioning states in operators. @@ -68,20 +49,27 @@ public final class SynchronizedObserver implements Observer { private final SafeObservableSubscription subscription; private volatile boolean finishRequested = false; private volatile boolean finished = false; + private volatile Object lock; public SynchronizedObserver(Observer Observer, SafeObservableSubscription subscription) { this.observer = Observer; this.subscription = subscription; + this.lock = this; + } + + public SynchronizedObserver(Observer Observer, SafeObservableSubscription subscription, Object lock) { + this.observer = Observer; + this.subscription = subscription; + this.lock = lock; } - + /** * Used when synchronizing an Observer without access to the subscription. * * @param Observer */ public SynchronizedObserver(Observer Observer) { - this.observer = Observer; - this.subscription = new SafeObservableSubscription(); + this(Observer, new SafeObservableSubscription()); } public void onNext(T arg) { @@ -89,7 +77,7 @@ public void onNext(T arg) { // if we're already stopped, or a finish request has been received, we won't allow further onNext requests return; } - synchronized (this) { + synchronized (lock) { // check again since this could have changed while waiting if (finished || finishRequested || subscription.isUnsubscribed()) { // if we're already stopped, or a finish request has been received, we won't allow further onNext requests @@ -105,7 +93,7 @@ public void onError(Throwable e) { return; } finishRequested = true; - synchronized (this) { + synchronized (lock) { // check again since this could have changed while waiting if (finished || subscription.isUnsubscribed()) { return; @@ -121,7 +109,7 @@ public void onCompleted() { return; } finishRequested = true; - synchronized (this) { + synchronized (lock) { // check again since this could have changed while waiting if (finished || subscription.isUnsubscribed()) { return; @@ -130,528 +118,4 @@ public void onCompleted() { finished = true; } } - - public static class UnitTest { - @Mock - Observer aObserver; - - @Before - public void before() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testSingleThreadedBasic() { - Subscription s = mock(Subscription.class); - TestSingleThreadedObservable onSubscribe = new TestSingleThreadedObservable(s, "one", "two", "three"); - Observable w = Observable.create(onSubscribe); - - SafeObservableSubscription as = new SafeObservableSubscription(s); - SynchronizedObserver aw = new SynchronizedObserver(aObserver, as); - - w.subscribe(aw); - onSubscribe.waitToFinish(); - - verify(aObserver, times(1)).onNext("one"); - verify(aObserver, times(1)).onNext("two"); - verify(aObserver, times(1)).onNext("three"); - verify(aObserver, never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); - // non-deterministic because unsubscribe happens after 'waitToFinish' releases - // so commenting out for now as this is not a critical thing to test here - // verify(s, times(1)).unsubscribe(); - } - - @Test - public void testMultiThreadedBasic() { - Subscription s = mock(Subscription.class); - TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable(s, "one", "two", "three"); - Observable w = Observable.create(onSubscribe); - - SafeObservableSubscription as = new SafeObservableSubscription(s); - BusyObserver busyObserver = new BusyObserver(); - SynchronizedObserver aw = new SynchronizedObserver(busyObserver, as); - - w.subscribe(aw); - onSubscribe.waitToFinish(); - - assertEquals(3, busyObserver.onNextCount.get()); - assertFalse(busyObserver.onError); - assertTrue(busyObserver.onCompleted); - // non-deterministic because unsubscribe happens after 'waitToFinish' releases - // so commenting out for now as this is not a critical thing to test here - // verify(s, times(1)).unsubscribe(); - - // we can have concurrency ... - assertTrue(onSubscribe.maxConcurrentThreads.get() > 1); - // ... but the onNext execution should be single threaded - assertEquals(1, busyObserver.maxConcurrentThreads.get()); - } - - @Test - public void testMultiThreadedWithNPE() { - Subscription s = mock(Subscription.class); - TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable(s, "one", "two", "three", null); - Observable w = Observable.create(onSubscribe); - - SafeObservableSubscription as = new SafeObservableSubscription(s); - BusyObserver busyObserver = new BusyObserver(); - SynchronizedObserver aw = new SynchronizedObserver(busyObserver, as); - - w.subscribe(aw); - onSubscribe.waitToFinish(); - - System.out.println("maxConcurrentThreads: " + onSubscribe.maxConcurrentThreads.get()); - - // we can't know how many onNext calls will occur since they each run on a separate thread - // that depends on thread scheduling so 0, 1, 2 and 3 are all valid options - // assertEquals(3, busyObserver.onNextCount.get()); - assertTrue(busyObserver.onNextCount.get() < 4); - assertTrue(busyObserver.onError); - // no onCompleted because onError was invoked - assertFalse(busyObserver.onCompleted); - // non-deterministic because unsubscribe happens after 'waitToFinish' releases - // so commenting out for now as this is not a critical thing to test here - //verify(s, times(1)).unsubscribe(); - - // we can have concurrency ... - assertTrue(onSubscribe.maxConcurrentThreads.get() > 1); - // ... but the onNext execution should be single threaded - assertEquals(1, busyObserver.maxConcurrentThreads.get()); - } - - @Test - public void testMultiThreadedWithNPEinMiddle() { - Subscription s = mock(Subscription.class); - TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable(s, "one", "two", "three", null, "four", "five", "six", "seven", "eight", "nine"); - Observable w = Observable.create(onSubscribe); - - SafeObservableSubscription as = new SafeObservableSubscription(s); - BusyObserver busyObserver = new BusyObserver(); - SynchronizedObserver aw = new SynchronizedObserver(busyObserver, as); - - w.subscribe(aw); - onSubscribe.waitToFinish(); - - System.out.println("maxConcurrentThreads: " + onSubscribe.maxConcurrentThreads.get()); - // this should not be the full number of items since the error should stop it before it completes all 9 - System.out.println("onNext count: " + busyObserver.onNextCount.get()); - assertTrue(busyObserver.onNextCount.get() < 9); - assertTrue(busyObserver.onError); - // no onCompleted because onError was invoked - assertFalse(busyObserver.onCompleted); - // non-deterministic because unsubscribe happens after 'waitToFinish' releases - // so commenting out for now as this is not a critical thing to test here - // verify(s, times(1)).unsubscribe(); - - // we can have concurrency ... - assertTrue(onSubscribe.maxConcurrentThreads.get() > 1); - // ... but the onNext execution should be single threaded - assertEquals(1, busyObserver.maxConcurrentThreads.get()); - } - - /** - * A non-realistic use case that tries to expose thread-safety issues by throwing lots of out-of-order - * events on many threads. - * - * @param w - * @param tw - */ - @Test - public void runConcurrencyTest() { - ExecutorService tp = Executors.newFixedThreadPool(20); - try { - TestConcurrencyObserver tw = new TestConcurrencyObserver(); - SafeObservableSubscription s = new SafeObservableSubscription(); - SynchronizedObserver w = new SynchronizedObserver(tw, s); - - Future f1 = tp.submit(new OnNextThread(w, 12000)); - Future f2 = tp.submit(new OnNextThread(w, 5000)); - Future f3 = tp.submit(new OnNextThread(w, 75000)); - Future f4 = tp.submit(new OnNextThread(w, 13500)); - Future f5 = tp.submit(new OnNextThread(w, 22000)); - Future f6 = tp.submit(new OnNextThread(w, 15000)); - Future f7 = tp.submit(new OnNextThread(w, 7500)); - Future f8 = tp.submit(new OnNextThread(w, 23500)); - - Future f10 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onCompleted, f1, f2, f3, f4)); - try { - Thread.sleep(1); - } catch (InterruptedException e) { - // ignore - } - Future f11 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onCompleted, f4, f6, f7)); - Future f12 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onCompleted, f4, f6, f7)); - Future f13 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onCompleted, f4, f6, f7)); - Future f14 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onCompleted, f4, f6, f7)); - // // the next 4 onError events should wait on same as f10 - Future f15 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onError, f1, f2, f3, f4)); - Future f16 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onError, f1, f2, f3, f4)); - Future f17 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onError, f1, f2, f3, f4)); - Future f18 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onError, f1, f2, f3, f4)); - - waitOnThreads(f1, f2, f3, f4, f5, f6, f7, f8, f10, f11, f12, f13, f14, f15, f16, f17, f18); - @SuppressWarnings("unused") - int numNextEvents = tw.assertEvents(null); // no check of type since we don't want to test barging results here, just interleaving behavior - // System.out.println("Number of events executed: " + numNextEvents); - } catch (Throwable e) { - fail("Concurrency test failed: " + e.getMessage()); - e.printStackTrace(); - } finally { - tp.shutdown(); - try { - tp.awaitTermination(5000, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - - private static void waitOnThreads(Future... futures) { - for (Future f : futures) { - try { - f.get(10, TimeUnit.SECONDS); - } catch (Throwable e) { - System.err.println("Failed while waiting on future."); - e.printStackTrace(); - } - } - } - - /** - * A thread that will pass data to onNext - */ - public static class OnNextThread implements Runnable { - - private final Observer Observer; - private final int numStringsToSend; - - OnNextThread(Observer Observer, int numStringsToSend) { - this.Observer = Observer; - this.numStringsToSend = numStringsToSend; - } - - @Override - public void run() { - for (int i = 0; i < numStringsToSend; i++) { - Observer.onNext("aString"); - } - } - } - - /** - * A thread that will call onError or onNext - */ - public static class CompletionThread implements Runnable { - - private final Observer Observer; - private final TestConcurrencyObserverEvent event; - private final Future[] waitOnThese; - - CompletionThread(Observer Observer, TestConcurrencyObserverEvent event, Future... waitOnThese) { - this.Observer = Observer; - this.event = event; - this.waitOnThese = waitOnThese; - } - - @Override - public void run() { - /* if we have 'waitOnThese' futures, we'll wait on them before proceeding */ - if (waitOnThese != null) { - for (Future f : waitOnThese) { - try { - f.get(); - } catch (Throwable e) { - System.err.println("Error while waiting on future in CompletionThread"); - } - } - } - - /* send the event */ - if (event == TestConcurrencyObserverEvent.onError) { - Observer.onError(new RuntimeException("mocked exception")); - } else if (event == TestConcurrencyObserverEvent.onCompleted) { - Observer.onCompleted(); - - } else { - throw new IllegalArgumentException("Expecting either onError or onCompleted"); - } - } - } - - private static enum TestConcurrencyObserverEvent { - onCompleted, onError, onNext - } - - private static class TestConcurrencyObserver implements Observer { - - /** used to store the order and number of events received */ - private final LinkedBlockingQueue events = new LinkedBlockingQueue(); - private final int waitTime; - - @SuppressWarnings("unused") - public TestConcurrencyObserver(int waitTimeInNext) { - this.waitTime = waitTimeInNext; - } - - public TestConcurrencyObserver() { - this.waitTime = 0; - } - - @Override - public void onCompleted() { - events.add(TestConcurrencyObserverEvent.onCompleted); - } - - @Override - public void onError(Throwable e) { - events.add(TestConcurrencyObserverEvent.onError); - } - - @Override - public void onNext(String args) { - events.add(TestConcurrencyObserverEvent.onNext); - // do some artificial work to make the thread scheduling/timing vary - int s = 0; - for (int i = 0; i < 20; i++) { - s += s * i; - } - - if (waitTime > 0) { - try { - Thread.sleep(waitTime); - } catch (InterruptedException e) { - // ignore - } - } - } - - /** - * Assert the order of events is correct and return the number of onNext executions. - * - * @param expectedEndingEvent - * @return int count of onNext calls - * @throws IllegalStateException - * If order of events was invalid. - */ - public int assertEvents(TestConcurrencyObserverEvent expectedEndingEvent) throws IllegalStateException { - int nextCount = 0; - boolean finished = false; - for (TestConcurrencyObserverEvent e : events) { - if (e == TestConcurrencyObserverEvent.onNext) { - if (finished) { - // already finished, we shouldn't get this again - throw new IllegalStateException("Received onNext but we're already finished."); - } - nextCount++; - } else if (e == TestConcurrencyObserverEvent.onError) { - if (finished) { - // already finished, we shouldn't get this again - throw new IllegalStateException("Received onError but we're already finished."); - } - if (expectedEndingEvent != null && TestConcurrencyObserverEvent.onError != expectedEndingEvent) { - throw new IllegalStateException("Received onError ending event but expected " + expectedEndingEvent); - } - finished = true; - } else if (e == TestConcurrencyObserverEvent.onCompleted) { - if (finished) { - // already finished, we shouldn't get this again - throw new IllegalStateException("Received onCompleted but we're already finished."); - } - if (expectedEndingEvent != null && TestConcurrencyObserverEvent.onCompleted != expectedEndingEvent) { - throw new IllegalStateException("Received onCompleted ending event but expected " + expectedEndingEvent); - } - finished = true; - } - } - - return nextCount; - } - - } - - /** - * This spawns a single thread for the subscribe execution - * - */ - private static class TestSingleThreadedObservable implements OnSubscribeFunc { - - final Subscription s; - final String[] values; - private Thread t = null; - - public TestSingleThreadedObservable(final Subscription s, final String... values) { - this.s = s; - this.values = values; - - } - - public Subscription onSubscribe(final Observer observer) { - System.out.println("TestSingleThreadedObservable subscribed to ..."); - t = new Thread(new Runnable() { - - @Override - public void run() { - try { - System.out.println("running TestSingleThreadedObservable thread"); - for (String s : values) { - System.out.println("TestSingleThreadedObservable onNext: " + s); - observer.onNext(s); - } - observer.onCompleted(); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - }); - System.out.println("starting TestSingleThreadedObservable thread"); - t.start(); - System.out.println("done starting TestSingleThreadedObservable thread"); - return s; - } - - public void waitToFinish() { - try { - t.join(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - - } - - /** - * This spawns a thread for the subscription, then a separate thread for each onNext call. - * - */ - private static class TestMultiThreadedObservable implements OnSubscribeFunc { - - final Subscription s; - final String[] values; - Thread t = null; - AtomicInteger threadsRunning = new AtomicInteger(); - AtomicInteger maxConcurrentThreads = new AtomicInteger(); - ExecutorService threadPool; - - public TestMultiThreadedObservable(Subscription s, String... values) { - this.s = s; - this.values = values; - this.threadPool = Executors.newCachedThreadPool(); - } - - @Override - public Subscription onSubscribe(final Observer observer) { - System.out.println("TestMultiThreadedObservable subscribed to ..."); - t = new Thread(new Runnable() { - - @Override - public void run() { - try { - System.out.println("running TestMultiThreadedObservable thread"); - for (final String s : values) { - threadPool.execute(new Runnable() { - - @Override - public void run() { - threadsRunning.incrementAndGet(); - try { - // perform onNext call - System.out.println("TestMultiThreadedObservable onNext: " + s); - if (s == null) { - // force an error - throw new NullPointerException(); - } - observer.onNext(s); - // capture 'maxThreads' - int concurrentThreads = threadsRunning.get(); - int maxThreads = maxConcurrentThreads.get(); - if (concurrentThreads > maxThreads) { - maxConcurrentThreads.compareAndSet(maxThreads, concurrentThreads); - } - } catch (Throwable e) { - observer.onError(e); - } finally { - threadsRunning.decrementAndGet(); - } - } - }); - } - // we are done spawning threads - threadPool.shutdown(); - } catch (Throwable e) { - throw new RuntimeException(e); - } - - // wait until all threads are done, then mark it as COMPLETED - try { - // wait for all the threads to finish - threadPool.awaitTermination(2, TimeUnit.SECONDS); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - observer.onCompleted(); - } - }); - System.out.println("starting TestMultiThreadedObservable thread"); - t.start(); - System.out.println("done starting TestMultiThreadedObservable thread"); - return s; - } - - public void waitToFinish() { - try { - t.join(); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - } - } - - private static class BusyObserver implements Observer { - volatile boolean onCompleted = false; - volatile boolean onError = false; - AtomicInteger onNextCount = new AtomicInteger(); - AtomicInteger threadsRunning = new AtomicInteger(); - AtomicInteger maxConcurrentThreads = new AtomicInteger(); - - @Override - public void onCompleted() { - System.out.println(">>> BusyObserver received onCompleted"); - onCompleted = true; - } - - @Override - public void onError(Throwable e) { - System.out.println(">>> BusyObserver received onError: " + e.getMessage()); - onError = true; - } - - @Override - public void onNext(String args) { - threadsRunning.incrementAndGet(); - try { - onNextCount.incrementAndGet(); - System.out.println(">>> BusyObserver received onNext: " + args); - try { - // simulate doing something computational - Thread.sleep(200); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } finally { - // capture 'maxThreads' - int concurrentThreads = threadsRunning.get(); - int maxThreads = maxConcurrentThreads.get(); - if (concurrentThreads > maxThreads) { - maxConcurrentThreads.compareAndSet(maxThreads, concurrentThreads); - } - threadsRunning.decrementAndGet(); - } - } - - } - - } - } \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/package-info.java b/rxjava-core/src/main/java/rx/package-info.java index 8af9fb51c8..fdd5084e60 100644 --- a/rxjava-core/src/main/java/rx/package-info.java +++ b/rxjava-core/src/main/java/rx/package-info.java @@ -1,12 +1,12 @@ /** * Copyright 2013 Netflix, Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,7 +15,7 @@ */ /** *

Rx Observables

- * + * *

A library that enables subscribing to and composing asynchronous events and * callbacks.

*

The Observable/Observer interfaces and associated operators (in @@ -25,8 +25,8 @@ * More information can be found at http://msdn.microsoft.com/en-us/data/gg577609. *

- * - * + * + * *

Compared with the Microsoft implementation: *

    *
  • Observable == IObservable
  • @@ -36,9 +36,9 @@ *
*

*

Services which intend on exposing data asynchronously and wish - * to allow reactive processing and composition can implement the - * {@link rx.Observable} interface which then allows Observers to subscribe to them + * to allow reactive processing and composition can implement the {@link rx.Observable} interface which then allows Observers to subscribe to them * and receive events.

*

Usage examples can be found on the {@link rx.Observable} and {@link rx.Observer} classes.

*/ package rx; + diff --git a/rxjava-core/src/main/java/rx/plugins/RxJavaPlugins.java b/rxjava-core/src/main/java/rx/plugins/RxJavaPlugins.java index e27c343815..2eb806dd6b 100644 --- a/rxjava-core/src/main/java/rx/plugins/RxJavaPlugins.java +++ b/rxjava-core/src/main/java/rx/plugins/RxJavaPlugins.java @@ -15,12 +15,8 @@ */ package rx.plugins; -import static org.junit.Assert.*; - import java.util.concurrent.atomic.AtomicReference; -import org.junit.Test; - /** * Registry for plugin implementations that allows global override and handles the retrieval of correct implementation based on order of precedence: *
    @@ -36,7 +32,7 @@ public class RxJavaPlugins { private final AtomicReference errorHandler = new AtomicReference(); private final AtomicReference observableExecutionHook = new AtomicReference(); - private RxJavaPlugins() { + /* package accessible for unit tests */RxJavaPlugins() { } @@ -148,77 +144,4 @@ private static Object getPluginImplementationViaProperty(Class pluginClass) { return null; } } - - public static class UnitTest { - - @Test - public void testErrorHandlerDefaultImpl() { - RxJavaErrorHandler impl = new RxJavaPlugins().getErrorHandler(); - assertTrue(impl instanceof RxJavaErrorHandlerDefault); - } - - @Test - public void testErrorHandlerViaRegisterMethod() { - RxJavaPlugins p = new RxJavaPlugins(); - p.registerErrorHandler(new RxJavaErrorHandlerTestImpl()); - RxJavaErrorHandler impl = p.getErrorHandler(); - assertTrue(impl instanceof RxJavaErrorHandlerTestImpl); - } - - @Test - public void testErrorHandlerViaProperty() { - try { - RxJavaPlugins p = new RxJavaPlugins(); - String fullClass = getFullClassNameForTestClass(RxJavaErrorHandlerTestImpl.class); - System.setProperty("rxjava.plugin.RxJavaErrorHandler.implementation", fullClass); - RxJavaErrorHandler impl = p.getErrorHandler(); - assertTrue(impl instanceof RxJavaErrorHandlerTestImpl); - } finally { - System.clearProperty("rxjava.plugin.RxJavaErrorHandler.implementation"); - } - } - - // inside UnitTest so it is stripped from Javadocs - public static class RxJavaErrorHandlerTestImpl extends RxJavaErrorHandler { - // just use defaults - } - - @Test - public void testObservableExecutionHookDefaultImpl() { - RxJavaPlugins p = new RxJavaPlugins(); - RxJavaObservableExecutionHook impl = p.getObservableExecutionHook(); - assertTrue(impl instanceof RxJavaObservableExecutionHookDefault); - } - - @Test - public void testObservableExecutionHookViaRegisterMethod() { - RxJavaPlugins p = new RxJavaPlugins(); - p.registerObservableExecutionHook(new RxJavaObservableExecutionHookTestImpl()); - RxJavaObservableExecutionHook impl = p.getObservableExecutionHook(); - assertTrue(impl instanceof RxJavaObservableExecutionHookTestImpl); - } - - @Test - public void testObservableExecutionHookViaProperty() { - try { - RxJavaPlugins p = new RxJavaPlugins(); - String fullClass = getFullClassNameForTestClass(RxJavaObservableExecutionHookTestImpl.class); - System.setProperty("rxjava.plugin.RxJavaObservableExecutionHook.implementation", fullClass); - RxJavaObservableExecutionHook impl = p.getObservableExecutionHook(); - assertTrue(impl instanceof RxJavaObservableExecutionHookTestImpl); - } finally { - System.clearProperty("rxjava.plugin.RxJavaObservableExecutionHook.implementation"); - } - } - - // inside UnitTest so it is stripped from Javadocs - public static class RxJavaObservableExecutionHookTestImpl extends RxJavaObservableExecutionHook { - // just use defaults - } - - private static String getFullClassNameForTestClass(Class cls) { - return RxJavaPlugins.class.getPackage().getName() + "." + RxJavaPlugins.class.getSimpleName() + "$UnitTest$" + cls.getSimpleName(); - } - } - } diff --git a/rxjava-core/src/main/java/rx/subjects/AbstractSubject.java b/rxjava-core/src/main/java/rx/subjects/AbstractSubject.java new file mode 100644 index 0000000000..30db331ac2 --- /dev/null +++ b/rxjava-core/src/main/java/rx/subjects/AbstractSubject.java @@ -0,0 +1,167 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.subjects; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; + +import rx.Notification; +import rx.Observer; +import rx.Subscription; +import rx.operators.SafeObservableSubscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action2; + +public abstract class AbstractSubject extends Subject { + + protected AbstractSubject(rx.Observable.OnSubscribeFunc onSubscribe) { + super(onSubscribe); + } + + protected static class SubjectState { + protected final ConcurrentHashMap> observers = new ConcurrentHashMap>(); + protected final AtomicReference> currentValue = new AtomicReference>(); + protected final AtomicBoolean completed = new AtomicBoolean(); + protected final ReentrantLock SUBSCRIPTION_LOCK = new ReentrantLock(); + } + + protected static OnSubscribeFunc getOnSubscribeFunc(final SubjectState state, final Action2, Observer> onEach) { + return new OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + /* + * Subscription needs to be synchronized with terminal states to ensure + * race conditions are handled. When subscribing we must make sure + * onComplete/onError is correctly emitted to all observers, even if it + * comes in while the onComplete/onError is being propagated. + */ + state.SUBSCRIPTION_LOCK.lock(); + try { + if (state.completed.get()) { + emitNotification(state.currentValue.get(), observer); + if (onEach != null) { + onEach.call(state, observer); + } + return Subscriptions.empty(); + } else { + // the subject is not completed so we subscribe + final SafeObservableSubscription subscription = new SafeObservableSubscription(); + + subscription.wrap(new Subscription() { + @Override + public void unsubscribe() { + // on unsubscribe remove it from the map of outbound observers to notify + state.observers.remove(subscription); + } + }); + + // on subscribe add it to the map of outbound observers to notify + state.observers.put(subscription, observer); + + // invoke onSubscribe logic + if (onEach != null) { + onEach.call(state, observer); + } + + return subscription; + } + } finally { + state.SUBSCRIPTION_LOCK.unlock(); + } + + } + + }; + } + + protected static void emitNotification(Notification value, Observer observer) { + // if null that means onNext was never invoked (no Notification set) + if (value != null) { + if (value.isOnNext()) { + observer.onNext(value.getValue()); + } else if (value.isOnError()) { + observer.onError(value.getThrowable()); + } else if (value.isOnCompleted()) { + observer.onCompleted(); + } + } + } + + /** + * Emit the current value. + * + * @param state + */ + protected static void emitNotification(final SubjectState state, final Action2, Observer> onEach) { + for (Subscription s : snapshotOfObservers(state)) { + Observer o = state.observers.get(s); + // emit notifications to this observer + emitNotification(state.currentValue.get(), o); + // onEach action if applicable + if (onEach != null) { + onEach.call(state, o); + } + } + } + + /** + * Emit the current value to all observers and remove their subscription. + * + * @param state + */ + protected void emitNotificationAndTerminate(final SubjectState state, final Action2, Observer> onEach) { + /* + * We can not allow new subscribers to be added while we execute the terminal state. + */ + state.SUBSCRIPTION_LOCK.lock(); + try { + if (state.completed.compareAndSet(false, true)) { + for (Subscription s : snapshotOfObservers(state)) { + Observer o = state.observers.get(s); + // emit notifications to this observer + emitNotification(state.currentValue.get(), o); + // onEach action if applicable + if (onEach != null) { + onEach.call(state, o); + } + + // remove the subscription as it is completed + state.observers.remove(s); + } + } + } finally { + state.SUBSCRIPTION_LOCK.unlock(); + } + } + + /** + * Current snapshot of 'state.observers.keySet()' so that concurrent modifications aren't included. + * + * This makes it behave deterministically in a single-threaded execution when nesting subscribes. + * + * In multi-threaded execution it will cause new subscriptions to wait until the following onNext instead + * of possibly being included in the current onNext iteration. + * + * @return List> + */ + private static Collection snapshotOfObservers(final SubjectState state) { + return new ArrayList(state.observers.keySet()); + } +} diff --git a/rxjava-core/src/main/java/rx/subjects/AsyncSubject.java b/rxjava-core/src/main/java/rx/subjects/AsyncSubject.java index 4bd6e11106..a9c18be805 100644 --- a/rxjava-core/src/main/java/rx/subjects/AsyncSubject.java +++ b/rxjava-core/src/main/java/rx/subjects/AsyncSubject.java @@ -1,12 +1,12 @@ /** * Copyright 2013 Netflix, Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,31 +15,21 @@ */ package rx.subjects; -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicReference; - -import org.junit.Test; -import org.mockito.Mockito; - +import rx.Notification; import rx.Observer; -import rx.Subscription; -import rx.operators.SafeObservableSubscription; -import rx.util.functions.Action1; -import rx.util.functions.Func0; +import rx.util.functions.Action2; /** * Subject that publishes only the last event to each {@link Observer} that has subscribed when the * sequence completes. - * + *

    + * *

    * Example usage: *

    *

     {@code
     
    -  // observer will receive no onNext events because the subject.onCompleted() isn't called.
    + * / observer will receive no onNext events because the subject.onCompleted() isn't called.
       AsyncSubject subject = AsyncSubject.create();
       subject.subscribe(observer);
       subject.onNext("one");
    @@ -55,202 +45,71 @@
       subject.onCompleted();
     
       } 
    - *
    + * 
      * @param 
      */
    -public class AsyncSubject extends Subject {
    -
    +public class AsyncSubject extends AbstractSubject {
     
         /**
          * Create a new AsyncSubject
    -     *
    +     * 
          * @return a new AsyncSubject
          */
         public static  AsyncSubject create() {
    -        final ConcurrentHashMap> observers = new ConcurrentHashMap>();
    +        final SubjectState state = new SubjectState();
    +        OnSubscribeFunc onSubscribe = getOnSubscribeFunc(state, new Action2, Observer>() {
     
    -        OnSubscribeFunc onSubscribe = new OnSubscribeFunc() {
                 @Override
    -            public Subscription onSubscribe(Observer observer) {
    -                final SafeObservableSubscription subscription = new SafeObservableSubscription();
    -
    -                subscription.wrap(new Subscription() {
    -                    @Override
    -                    public void unsubscribe() {
    -                        // on unsubscribe remove it from the map of outbound observers to notify
    -                        observers.remove(subscription);
    +            public void call(SubjectState state, Observer o) {
    +                // we want the last value + completed so add this extra logic 
    +                // to send onCompleted if the last value is an onNext
    +                if (state.completed.get()) {
    +                    Notification value = state.currentValue.get();
    +                    if (value != null && value.isOnNext()) {
    +                        o.onCompleted();
                         }
    -                });
    -
    -                // on subscribe add it to the map of outbound observers to notify
    -                observers.put(subscription, observer);
    -                return subscription;
    +                }
                 }
    -        };
    -
    -        return new AsyncSubject(onSubscribe, observers);
    +        });
    +        return new AsyncSubject(onSubscribe, state);
         }
     
    -    private final ConcurrentHashMap> observers;
    -    private final AtomicReference currentValue;
    +    private final SubjectState state;
     
    -    protected AsyncSubject(OnSubscribeFunc onSubscribe, ConcurrentHashMap> observers) {
    +    protected AsyncSubject(OnSubscribeFunc onSubscribe, SubjectState state) {
             super(onSubscribe);
    -        this.observers = observers;
    -        this.currentValue = new AtomicReference();
    +        this.state = state;
         }
     
         @Override
         public void onCompleted() {
    -        T finalValue = currentValue.get();
    -        for (Observer observer : observers.values()) {
    -            observer.onNext(finalValue);
    -        }
    -        for (Observer observer : observers.values()) {
    -            observer.onCompleted();
    -        }
    +        /**
    +         * Mark this subject as completed and emit latest value + 'onCompleted' to all Observers
    +         */
    +        emitNotificationAndTerminate(state, new Action2, Observer>() {
    +
    +            @Override
    +            public void call(SubjectState state, Observer o) {
    +                o.onCompleted();
    +            }
    +        });
         }
     
         @Override
         public void onError(Throwable e) {
    -        for (Observer observer : observers.values()) {
    -            observer.onError(e);
    -        }
    +        /**
    +         * Mark this subject as completed with an error as the last value and emit 'onError' to all Observers
    +         */
    +        state.currentValue.set(new Notification(e));
    +        emitNotificationAndTerminate(state, null);
         }
     
         @Override
    -    public void onNext(T args) {
    -        currentValue.set(args);
    +    public void onNext(T v) {
    +        /**
    +         * Store the latest value but do not send it. It only gets sent when 'onCompleted' occurs.
    +         */
    +        state.currentValue.set(new Notification(v));
         }
     
    -    public static class UnitTest {
    -
    -        private final Throwable testException = new Throwable();
    -
    -        @Test
    -        public void testNeverCompleted() {
    -        	AsyncSubject subject = AsyncSubject.create();
    -
    -            @SuppressWarnings("unchecked")
    -            Observer aObserver = mock(Observer.class);
    -            subject.subscribe(aObserver);
    -
    -            subject.onNext("one");
    -            subject.onNext("two");
    -            subject.onNext("three");
    -
    -            assertNeverCompletedObserver(aObserver);
    -        }
    -
    -        private void assertNeverCompletedObserver(Observer aObserver)
    -        {
    -            verify(aObserver, Mockito.never()).onNext(anyString());
    -            verify(aObserver, Mockito.never()).onError(testException);
    -            verify(aObserver, Mockito.never()).onCompleted();
    -        }
    -
    -        @Test
    -        public void testCompleted() {
    -        	AsyncSubject subject = AsyncSubject.create();
    -
    -            @SuppressWarnings("unchecked")
    -            Observer aObserver = mock(Observer.class);
    -            subject.subscribe(aObserver);
    -
    -            subject.onNext("one");
    -            subject.onNext("two");
    -            subject.onNext("three");
    -            subject.onCompleted();
    -
    -            assertCompletedObserver(aObserver);
    -        }
    -
    -        private void assertCompletedObserver(Observer aObserver)
    -        {
    -            verify(aObserver, times(1)).onNext("three");
    -            verify(aObserver, Mockito.never()).onError(any(Throwable.class));
    -            verify(aObserver, times(1)).onCompleted();
    -        }
    -
    -        @Test
    -        public void testError() {
    -        	AsyncSubject subject = AsyncSubject.create();
    -
    -            @SuppressWarnings("unchecked")
    -            Observer aObserver = mock(Observer.class);
    -            subject.subscribe(aObserver);
    -
    -            subject.onNext("one");
    -            subject.onNext("two");
    -            subject.onNext("three");
    -            subject.onError(testException);
    -            subject.onNext("four");
    -            subject.onError(new Throwable());
    -            subject.onCompleted();
    -
    -            assertErrorObserver(aObserver);
    -        }
    -
    -        private void assertErrorObserver(Observer aObserver)
    -        {
    -            verify(aObserver, Mockito.never()).onNext(anyString());
    -            verify(aObserver, times(1)).onError(testException);
    -            verify(aObserver, Mockito.never()).onCompleted();
    -        }
    -
    -        @Test
    -        public void testUnsubscribeBeforeCompleted() {
    -        	AsyncSubject subject = AsyncSubject.create();
    -
    -            @SuppressWarnings("unchecked")
    -            Observer aObserver = mock(Observer.class);
    -            Subscription subscription = subject.subscribe(aObserver);
    -
    -            subject.onNext("one");
    -            subject.onNext("two");
    -
    -            subscription.unsubscribe();
    -            assertNoOnNextEventsReceived(aObserver);
    -
    -            subject.onNext("three");
    -            subject.onCompleted();
    -
    -            assertNoOnNextEventsReceived(aObserver);
    -        }
    -
    -        private void assertNoOnNextEventsReceived(Observer aObserver)
    -        {
    -            verify(aObserver, Mockito.never()).onNext(anyString());
    -            verify(aObserver, Mockito.never()).onError(any(Throwable.class));
    -            verify(aObserver, Mockito.never()).onCompleted();
    -        }
    -
    -        @Test
    -        public void testUnsubscribe()
    -        {
    -            UnsubscribeTester.test(new Func0>()
    -            {
    -                @Override
    -                public AsyncSubject call()
    -                {
    -                    return AsyncSubject.create();
    -                }
    -            }, new Action1>()
    -            {
    -                @Override
    -                public void call(AsyncSubject DefaultSubject)
    -                {
    -                    DefaultSubject.onCompleted();
    -                }
    -            }, new Action1>()
    -            {
    -                @Override
    -                public void call(AsyncSubject DefaultSubject)
    -                {
    -                    DefaultSubject.onError(new Throwable());
    -                }
    -            },
    -            null);
    -        }
    -    }
     }
    diff --git a/rxjava-core/src/main/java/rx/subjects/BehaviorSubject.java b/rxjava-core/src/main/java/rx/subjects/BehaviorSubject.java
    index 4a3729c24e..053a51f0cf 100644
    --- a/rxjava-core/src/main/java/rx/subjects/BehaviorSubject.java
    +++ b/rxjava-core/src/main/java/rx/subjects/BehaviorSubject.java
    @@ -1,12 +1,12 @@
     /**
      * Copyright 2013 Netflix, Inc.
    - *
    + * 
      * Licensed under the Apache License, Version 2.0 (the "License");
      * you may not use this file except in compliance with the License.
      * You may obtain a copy of the License at
    - *
    + * 
      * http://www.apache.org/licenses/LICENSE-2.0
    - *
    + * 
      * Unless required by applicable law or agreed to in writing, software
      * distributed under the License is distributed on an "AS IS" BASIS,
      * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    @@ -15,30 +15,20 @@
      */
     package rx.subjects;
     
    -import static org.mockito.Matchers.*;
    -import static org.mockito.Mockito.*;
    -
    -import java.util.concurrent.ConcurrentHashMap;
    -import java.util.concurrent.atomic.AtomicReference;
    -
    -import org.junit.Test;
    -import org.mockito.Mockito;
    -
    +import rx.Notification;
     import rx.Observer;
    -import rx.Subscription;
    -import rx.operators.SafeObservableSubscription;
    -import rx.util.functions.Action1;
    -import rx.util.functions.Func0;
    +import rx.util.functions.Action2;
     
     /**
      * Subject that publishes the most recent and all subsequent events to each subscribed {@link Observer}.
    - *
    + * 

    + * *

    * Example usage: *

    *

     {@code
     
    -  // observer will receive all events.
    + * / observer will receive all events.
       BehaviorSubject subject = BehaviorSubject.createWithDefaultValue("default");
       subject.subscribe(observer);
       subject.onNext("one");
    @@ -54,210 +44,79 @@
       subject.onNext("three");
     
       } 
    - *
    + * 
      * @param 
      */
    -public class BehaviorSubject extends Subject {
    +public class BehaviorSubject extends AbstractSubject {
     
         /**
    -     * Creates a {@link BehaviorSubject} which publishes the last and all subsequent events to each
    -     * {@link Observer} that subscribes to it.
    -     *
    +     * Creates a {@link BehaviorSubject} which publishes the last and all subsequent events to each {@link Observer} that subscribes to it.
    +     * 
          * @param defaultValue
    -     *            The value which will be published to any {@link Observer} as long as the
    -     *            {@link BehaviorSubject} has not yet received any events.
    +     *            The value which will be published to any {@link Observer} as long as the {@link BehaviorSubject} has not yet received any events.
          * @return the constructed {@link BehaviorSubject}.
    +     * @deprecated Use {@link create()} instead.
          */
         public static  BehaviorSubject createWithDefaultValue(T defaultValue) {
    -        final ConcurrentHashMap> observers = new ConcurrentHashMap>();
    +        return create(defaultValue);
    +    }
     
    -        final AtomicReference currentValue = new AtomicReference(defaultValue);
    +    /**
    +     * Creates a {@link BehaviorSubject} which publishes the last and all subsequent events to each {@link Observer} that subscribes to it.
    +     * 
    +     * @param defaultValue
    +     *            The value which will be published to any {@link Observer} as long as the {@link BehaviorSubject} has not yet received any events.
    +     * @return the constructed {@link BehaviorSubject}.
    +     */
    +    public static  BehaviorSubject create(T defaultValue) {
    +        final SubjectState state = new SubjectState();
    +        // set a default value so subscriptions will immediately receive this until a new notification is received
    +        state.currentValue.set(new Notification(defaultValue));
    +        OnSubscribeFunc onSubscribe = getOnSubscribeFunc(state, new Action2, Observer>() {
     
    -        OnSubscribeFunc onSubscribe = new OnSubscribeFunc() {
                 @Override
    -            public Subscription onSubscribe(Observer observer) {
    -                final SafeObservableSubscription subscription = new SafeObservableSubscription();
    -
    -                subscription.wrap(new Subscription() {
    -                    @Override
    -                    public void unsubscribe() {
    -                        // on unsubscribe remove it from the map of outbound observers to notify
    -                        observers.remove(subscription);
    -                    }
    -                });
    -
    -                observer.onNext(currentValue.get());
    -
    -                // on subscribe add it to the map of outbound observers to notify
    -                observers.put(subscription, observer);
    -                return subscription;
    +            public void call(SubjectState state, Observer o) {
    +                /**
    +                 * When we subscribe we always emit the latest value to the observer, including
    +                 * terminal states which are recorded as the last value.
    +                 */
    +                emitNotification(state.currentValue.get(), o);
                 }
    -        };
    -
    -        return new BehaviorSubject(currentValue, onSubscribe, observers);
    +        });
    +        return new BehaviorSubject(onSubscribe, state);
         }
     
    -    private final ConcurrentHashMap> observers;
    -    private final AtomicReference currentValue;
    +    private final SubjectState state;
     
    -    protected BehaviorSubject(AtomicReference currentValue, OnSubscribeFunc onSubscribe, ConcurrentHashMap> observers) {
    +    protected BehaviorSubject(OnSubscribeFunc onSubscribe, SubjectState state) {
             super(onSubscribe);
    -        this.currentValue = currentValue;
    -        this.observers = observers;
    +        this.state = state;
         }
     
         @Override
         public void onCompleted() {
    -        for (Observer observer : observers.values()) {
    -            observer.onCompleted();
    -        }
    +        /**
    +         * Mark this subject as completed and emit latest value + 'onCompleted' to all Observers
    +         */
    +        state.currentValue.set(new Notification());
    +        emitNotificationAndTerminate(state, null);
         }
     
         @Override
         public void onError(Throwable e) {
    -        for (Observer observer : observers.values()) {
    -            observer.onError(e);
    -        }
    +        /**
    +         * Mark this subject as completed with an error as the last value and emit 'onError' to all Observers
    +         */
    +        state.currentValue.set(new Notification(e));
    +        emitNotificationAndTerminate(state, null);
         }
     
         @Override
    -    public void onNext(T args) {
    -        currentValue.set(args);
    -        for (Observer observer : observers.values()) {
    -            observer.onNext(args);
    -        }
    -    }
    -
    -    public static class UnitTest {
    -
    -        private final Throwable testException = new Throwable();
    -
    -        @Test
    -        public void testThatObserverReceivesDefaultValueIfNothingWasPublished() {
    -            BehaviorSubject subject = BehaviorSubject.createWithDefaultValue("default");
    -
    -            @SuppressWarnings("unchecked")
    -            Observer aObserver = mock(Observer.class);
    -            subject.subscribe(aObserver);
    -
    -            subject.onNext("one");
    -            subject.onNext("two");
    -            subject.onNext("three");
    -
    -            assertReceivedAllEvents(aObserver);
    -        }
    -
    -        private void assertReceivedAllEvents(Observer aObserver) {
    -            verify(aObserver, times(1)).onNext("default");
    -            verify(aObserver, times(1)).onNext("one");
    -            verify(aObserver, times(1)).onNext("two");
    -            verify(aObserver, times(1)).onNext("three");
    -            verify(aObserver, Mockito.never()).onError(testException);
    -            verify(aObserver, Mockito.never()).onCompleted();
    -        }
    -
    -        @Test
    -        public void testThatObserverDoesNotReceiveDefaultValueIfSomethingWasPublished() {
    -            BehaviorSubject subject = BehaviorSubject.createWithDefaultValue("default");
    -
    -            subject.onNext("one");
    -
    -            @SuppressWarnings("unchecked")
    -            Observer aObserver = mock(Observer.class);
    -            subject.subscribe(aObserver);
    -
    -            subject.onNext("two");
    -            subject.onNext("three");
    -
    -            assertDidNotReceiveTheDefaultValue(aObserver);
    -        }
    -
    -        private void assertDidNotReceiveTheDefaultValue(Observer aObserver) {
    -            verify(aObserver, Mockito.never()).onNext("default");
    -            verify(aObserver, times(1)).onNext("one");
    -            verify(aObserver, times(1)).onNext("two");
    -            verify(aObserver, times(1)).onNext("three");
    -            verify(aObserver, Mockito.never()).onError(testException);
    -            verify(aObserver, Mockito.never()).onCompleted();
    -        }
    -
    -        @Test
    -        public void testCompleted() {
    -            BehaviorSubject subject = BehaviorSubject.createWithDefaultValue("default");
    -
    -            @SuppressWarnings("unchecked")
    -            Observer aObserver = mock(Observer.class);
    -            subject.subscribe(aObserver);
    -
    -            subject.onNext("one");
    -            subject.onCompleted();
    -
    -            assertCompletedObserver(aObserver);
    -        }
    -
    -        private void assertCompletedObserver(Observer aObserver)
    -        {
    -            verify(aObserver, times(1)).onNext("default");
    -            verify(aObserver, times(1)).onNext("one");
    -            verify(aObserver, Mockito.never()).onError(any(Throwable.class));
    -            verify(aObserver, times(1)).onCompleted();
    -        }
    -
    -        @Test
    -        public void testCompletedAfterError() {
    -            BehaviorSubject subject = BehaviorSubject.createWithDefaultValue("default");
    -
    -            @SuppressWarnings("unchecked")
    -            Observer aObserver = mock(Observer.class);
    -            subject.subscribe(aObserver);
    -
    -            subject.onNext("one");
    -            subject.onError(testException);
    -            subject.onNext("two");
    -            subject.onCompleted();
    -
    -            assertErrorObserver(aObserver);
    -        }
    -
    -        private void assertErrorObserver(Observer aObserver)
    -        {
    -            verify(aObserver, times(1)).onNext("default");
    -            verify(aObserver, times(1)).onNext("one");
    -            verify(aObserver, times(1)).onError(testException);
    -        }
    -
    -        @Test
    -        public void testUnsubscribe()
    -        {
    -            UnsubscribeTester.test(new Func0>()
    -            {
    -                @Override
    -                public BehaviorSubject call()
    -                {
    -                    return BehaviorSubject.createWithDefaultValue("default");
    -                }
    -            }, new Action1>()
    -            {
    -                @Override
    -                public void call(BehaviorSubject DefaultSubject)
    -                {
    -                    DefaultSubject.onCompleted();
    -                }
    -            }, new Action1>()
    -            {
    -                @Override
    -                public void call(BehaviorSubject DefaultSubject)
    -                {
    -                    DefaultSubject.onError(new Throwable());
    -                }
    -            }, new Action1>()
    -            {
    -                @Override
    -                public void call(BehaviorSubject DefaultSubject)
    -                {
    -                    DefaultSubject.onNext("one");
    -                }
    -            });
    -        }
    +    public void onNext(T v) {
    +        /**
    +         * Store the latest value and send it to all observers;
    +         */
    +        state.currentValue.set(new Notification(v));
    +        emitNotification(state, null);
         }
     }
    diff --git a/rxjava-core/src/main/java/rx/subjects/PublishSubject.java b/rxjava-core/src/main/java/rx/subjects/PublishSubject.java
    index c196d83cdd..8cf2d75747 100644
    --- a/rxjava-core/src/main/java/rx/subjects/PublishSubject.java
    +++ b/rxjava-core/src/main/java/rx/subjects/PublishSubject.java
    @@ -1,12 +1,12 @@
     /**
      * Copyright 2013 Netflix, Inc.
    - *
    + * 
      * Licensed under the Apache License, Version 2.0 (the "License");
      * you may not use this file except in compliance with the License.
      * You may obtain a copy of the License at
    - *
    + * 
      * http://www.apache.org/licenses/LICENSE-2.0
    - *
    + * 
      * Unless required by applicable law or agreed to in writing, software
      * distributed under the License is distributed on an "AS IS" BASIS,
      * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    @@ -15,43 +15,19 @@
      */
     package rx.subjects;
     
    -import static org.junit.Assert.*;
    -import static org.mockito.Matchers.*;
    -import static org.mockito.Mockito.*;
    -
    -import java.util.ArrayList;
    -import java.util.Collection;
    -import java.util.List;
    -import java.util.concurrent.ConcurrentHashMap;
    -import java.util.concurrent.atomic.AtomicBoolean;
    -import java.util.concurrent.atomic.AtomicInteger;
    -import java.util.concurrent.atomic.AtomicReference;
    -
    -import junit.framework.Assert;
    -
    -import org.junit.Test;
    -import org.mockito.InOrder;
    -import org.mockito.Mockito;
    -
     import rx.Notification;
    -import rx.Observable;
     import rx.Observer;
    -import rx.Subscription;
    -import rx.operators.SafeObservableSubscription;
    -import rx.subscriptions.Subscriptions;
    -import rx.util.functions.Action1;
    -import rx.util.functions.Func0;
    -import rx.util.functions.Func1;
     
     /**
      * Subject that, once and {@link Observer} has subscribed, publishes all subsequent events to the subscriber.
    - *
    + * 

    + * *

    * Example usage: *

    *

     {@code
     
    -  PublishSubject subject = PublishSubject.create();
    + * PublishSubject subject = PublishSubject.create();
       // observer1 will receive all onNext and onCompleted events
       subject.subscribe(observer1);
       subject.onNext("one");
    @@ -62,476 +38,47 @@
       subject.onCompleted();
     
       } 
    - *
    + * 
      * @param 
      */
    -public class PublishSubject extends Subject {
    +public class PublishSubject extends AbstractSubject {
         public static  PublishSubject create() {
    -        final ConcurrentHashMap> observers = new ConcurrentHashMap>();
    -        final AtomicReference> terminalState = new AtomicReference>();
    -
    -        OnSubscribeFunc onSubscribe = new OnSubscribeFunc() {
    -            @Override
    -            public Subscription onSubscribe(Observer observer) {
    -                // shortcut check if terminal state exists already
    -                Subscription s = checkTerminalState(observer);
    -                if(s != null) return s;
    -
    -                final SafeObservableSubscription subscription = new SafeObservableSubscription();
    -
    -                subscription.wrap(new Subscription() {
    -                    @Override
    -                    public void unsubscribe() {
    -                        // on unsubscribe remove it from the map of outbound observers to notify
    -                        observers.remove(subscription);
    -                    }
    -                });
    -
    -                /**
    -                 * NOTE: We are synchronizing to avoid a race condition between terminalState being set and
    -                 * a new observer being added to observers.
    -                 *
    -                 * The synchronization only occurs on subscription and terminal states, it does not affect onNext calls
    -                 * so a high-volume hot-observable will not pay this cost for emitting data.
    -                 *
    -                 * Due to the restricted impact of blocking synchronization here I have not pursued more complicated
    -                 * approaches to try and stay completely non-blocking.
    -                 */
    -                synchronized (terminalState) {
    -                    // check terminal state again
    -                    s = checkTerminalState(observer);
    -                    if (s != null)
    -                        return s;
    -
    -                    // on subscribe add it to the map of outbound observers to notify
    -                    observers.put(subscription, observer);
    -
    -                    return subscription;
    -                }
    -            }
    -
    -            private Subscription checkTerminalState(Observer observer) {
    -                Notification n = terminalState.get();
    -                if (n != null) {
    -                    // we are terminated to immediately emit and don't continue with subscription
    -                    if (n.isOnCompleted()) {
    -                        observer.onCompleted();
    -                    } else {
    -                        observer.onError(n.getThrowable());
    -                    }
    -                    return Subscriptions.empty();
    -                } else {
    -                    return null;
    -                }
    -            }
    -        };
    -
    -        return new PublishSubject(onSubscribe, observers, terminalState);
    +        final SubjectState state = new SubjectState();
    +        OnSubscribeFunc onSubscribe = getOnSubscribeFunc(state, null);
    +        return new PublishSubject(onSubscribe, state);
         }
     
    -    private final ConcurrentHashMap> observers;
    -    private final AtomicReference> terminalState;
    +    private final SubjectState state;
     
    -    protected PublishSubject(OnSubscribeFunc onSubscribe, ConcurrentHashMap> observers, AtomicReference> terminalState) {
    +    protected PublishSubject(OnSubscribeFunc onSubscribe, SubjectState state) {
             super(onSubscribe);
    -        this.observers = observers;
    -        this.terminalState = terminalState;
    +        this.state = state;
         }
     
         @Override
         public void onCompleted() {
             /**
    -         * Synchronizing despite terminalState being an AtomicReference because of multi-step logic in subscription.
    -         * Why use AtomicReference then? Convenient for passing around a mutable reference holder between the
    -         * onSubscribe function and PublishSubject instance... and it's a "better volatile" for the shortcut codepath.
    +         * Mark this subject as completed and emit latest value + 'onCompleted' to all Observers
              */
    -        synchronized (terminalState) {
    -            terminalState.set(new Notification());
    -        }
    -        for (Observer observer : snapshotOfValues()) {
    -            observer.onCompleted();
    -        }
    -        observers.clear();
    +        state.currentValue.set(new Notification());
    +        emitNotificationAndTerminate(state, null);
         }
     
         @Override
         public void onError(Throwable e) {
             /**
    -         * Synchronizing despite terminalState being an AtomicReference because of multi-step logic in subscription.
    -         * Why use AtomicReference then? Convenient for passing around a mutable reference holder between the
    -         * onSubscribe function and PublishSubject instance... and it's a "better volatile" for the shortcut codepath.
    +         * Mark this subject as completed with an error as the last value and emit 'onError' to all Observers
              */
    -        synchronized (terminalState) {
    -            terminalState.set(new Notification(e));
    -        }
    -        for (Observer observer : snapshotOfValues()) {
    -            observer.onError(e);
    -        }
    -        observers.clear();
    +        state.currentValue.set(new Notification(e));
    +        emitNotificationAndTerminate(state, null);
         }
     
         @Override
    -    public void onNext(T args) {
    -        for (Observer observer : snapshotOfValues()) {
    -            observer.onNext(args);
    -        }
    -    }
    -
    -    /**
    -     * Current snapshot of 'values()' so that concurrent modifications aren't included.
    -     *
    -     * This makes it behave deterministically in a single-threaded execution when nesting subscribes.
    -     *
    -     * In multi-threaded execution it will cause new subscriptions to wait until the following onNext instead
    -     * of possibly being included in the current onNext iteration.
    -     *
    -     * @return List>
    -     */
    -    private Collection> snapshotOfValues() {
    -        return new ArrayList>(observers.values());
    -    }
    -
    -    public static class UnitTest {
    -        @Test
    -        public void test() {
    -            PublishSubject subject = PublishSubject.create();
    -            final AtomicReference>> actualRef = new AtomicReference>>();
    -
    -            Observable>> wNotificationsList = subject.materialize().toList();
    -            wNotificationsList.subscribe(new Action1>>() {
    -                @Override
    -                public void call(List> actual) {
    -                    actualRef.set(actual);
    -                }
    -            });
    -
    -            Subscription sub = Observable.create(new OnSubscribeFunc() {
    -                @Override
    -                public Subscription onSubscribe(final Observer observer) {
    -                    final AtomicBoolean stop = new AtomicBoolean(false);
    -                    new Thread() {
    -                        @Override
    -                        public void run() {
    -                            int i = 1;
    -                            while (!stop.get()) {
    -                                observer.onNext(i++);
    -                            }
    -                            observer.onCompleted();
    -                        }
    -                    }.start();
    -                    return new Subscription() {
    -                        @Override
    -                        public void unsubscribe() {
    -                            stop.set(true);
    -                        }
    -                    };
    -                }
    -            }).subscribe(subject);
    -            // the subject has received an onComplete from the first subscribe because
    -            // it is synchronous and the next subscribe won't do anything.
    -            Observable.from(-1, -2, -3).subscribe(subject);
    -
    -            List> expected = new ArrayList>();
    -            expected.add(new Notification(-1));
    -            expected.add(new Notification(-2));
    -            expected.add(new Notification(-3));
    -            expected.add(new Notification());
    -            Assert.assertTrue(actualRef.get().containsAll(expected));
    -
    -            sub.unsubscribe();
    -        }
    -
    -        private final Throwable testException = new Throwable();
    -
    -        @Test
    -        public void testCompleted() {
    -            PublishSubject subject = PublishSubject.create();
    -
    -            @SuppressWarnings("unchecked")
    -            Observer aObserver = mock(Observer.class);
    -            subject.subscribe(aObserver);
    -
    -            subject.onNext("one");
    -            subject.onNext("two");
    -            subject.onNext("three");
    -            subject.onCompleted();
    -
    -            @SuppressWarnings("unchecked")
    -            Observer anotherObserver = mock(Observer.class);
    -            subject.subscribe(anotherObserver);
    -
    -            subject.onNext("four");
    -            subject.onCompleted();
    -            subject.onError(new Throwable());
    -
    -            assertCompletedObserver(aObserver);
    -            // todo bug?            assertNeverObserver(anotherObserver);
    -        }
    -
    -        private void assertCompletedObserver(Observer aObserver)
    -        {
    -            verify(aObserver, times(1)).onNext("one");
    -            verify(aObserver, times(1)).onNext("two");
    -            verify(aObserver, times(1)).onNext("three");
    -            verify(aObserver, Mockito.never()).onError(any(Throwable.class));
    -            verify(aObserver, times(1)).onCompleted();
    -        }
    -
    -        @Test
    -        public void testError() {
    -            PublishSubject subject = PublishSubject.create();
    -
    -            @SuppressWarnings("unchecked")
    -            Observer aObserver = mock(Observer.class);
    -            subject.subscribe(aObserver);
    -
    -            subject.onNext("one");
    -            subject.onNext("two");
    -            subject.onNext("three");
    -            subject.onError(testException);
    -
    -            @SuppressWarnings("unchecked")
    -            Observer anotherObserver = mock(Observer.class);
    -            subject.subscribe(anotherObserver);
    -
    -            subject.onNext("four");
    -            subject.onError(new Throwable());
    -            subject.onCompleted();
    -
    -            assertErrorObserver(aObserver);
    -            // todo bug?            assertNeverObserver(anotherObserver);
    -        }
    -
    -        private void assertErrorObserver(Observer aObserver)
    -        {
    -            verify(aObserver, times(1)).onNext("one");
    -            verify(aObserver, times(1)).onNext("two");
    -            verify(aObserver, times(1)).onNext("three");
    -            verify(aObserver, times(1)).onError(testException);
    -            verify(aObserver, Mockito.never()).onCompleted();
    -        }
    -
    -        @Test
    -        public void testSubscribeMidSequence() {
    -            PublishSubject subject = PublishSubject.create();
    -
    -            @SuppressWarnings("unchecked")
    -            Observer aObserver = mock(Observer.class);
    -            subject.subscribe(aObserver);
    -
    -            subject.onNext("one");
    -            subject.onNext("two");
    -
    -            assertObservedUntilTwo(aObserver);
    -
    -            @SuppressWarnings("unchecked")
    -            Observer anotherObserver = mock(Observer.class);
    -            subject.subscribe(anotherObserver);
    -
    -            subject.onNext("three");
    -            subject.onCompleted();
    -
    -            assertCompletedObserver(aObserver);
    -            assertCompletedStartingWithThreeObserver(anotherObserver);
    -        }
    -
    -        private void assertCompletedStartingWithThreeObserver(Observer aObserver)
    -        {
    -            verify(aObserver, Mockito.never()).onNext("one");
    -            verify(aObserver, Mockito.never()).onNext("two");
    -            verify(aObserver, times(1)).onNext("three");
    -            verify(aObserver, Mockito.never()).onError(any(Throwable.class));
    -            verify(aObserver, times(1)).onCompleted();
    -        }
    -
    -        @Test
    -        public void testUnsubscribeFirstObserver() {
    -            PublishSubject subject = PublishSubject.create();
    -
    -            @SuppressWarnings("unchecked")
    -            Observer aObserver = mock(Observer.class);
    -            Subscription subscription = subject.subscribe(aObserver);
    -
    -            subject.onNext("one");
    -            subject.onNext("two");
    -
    -            subscription.unsubscribe();
    -            assertObservedUntilTwo(aObserver);
    -
    -            @SuppressWarnings("unchecked")
    -            Observer anotherObserver = mock(Observer.class);
    -            subject.subscribe(anotherObserver);
    -
    -            subject.onNext("three");
    -            subject.onCompleted();
    -
    -            assertObservedUntilTwo(aObserver);
    -            assertCompletedStartingWithThreeObserver(anotherObserver);
    -        }
    -
    -        private void assertObservedUntilTwo(Observer aObserver)
    -        {
    -            verify(aObserver, times(1)).onNext("one");
    -            verify(aObserver, times(1)).onNext("two");
    -            verify(aObserver, Mockito.never()).onNext("three");
    -            verify(aObserver, Mockito.never()).onError(any(Throwable.class));
    -            verify(aObserver, Mockito.never()).onCompleted();
    -        }
    -
    +    public void onNext(T v) {
             /**
    -         * Test that subscribing after onError/onCompleted immediately terminates instead of causing it to hang.
    -         *
    -         * Nothing is mentioned in Rx Guidelines for what to do in this case so I'm doing what seems to make sense
    -         * which is:
    -         *
    -         * - cache terminal state (onError/onCompleted)
    -         * - any subsequent subscriptions will immediately receive the terminal state rather than start a new subscription
    -         *
    +         * Store the latest value and send it to all observers;
              */
    -        @Test
    -        public void testUnsubscribeAfterOnCompleted() {
    -            PublishSubject subject = PublishSubject.create();
    -
    -            @SuppressWarnings("unchecked")
    -            Observer anObserver = mock(Observer.class);
    -            subject.subscribe(anObserver);
    -
    -            subject.onNext("one");
    -            subject.onNext("two");
    -            subject.onCompleted();
    -
    -            InOrder inOrder = inOrder(anObserver);
    -            inOrder.verify(anObserver, times(1)).onNext("one");
    -            inOrder.verify(anObserver, times(1)).onNext("two");
    -            inOrder.verify(anObserver, times(1)).onCompleted();
    -            inOrder.verify(anObserver, Mockito.never()).onError(any(Throwable.class));
    -
    -            @SuppressWarnings("unchecked")
    -            Observer anotherObserver = mock(Observer.class);
    -            subject.subscribe(anotherObserver);
    -
    -            inOrder = inOrder(anotherObserver);
    -            inOrder.verify(anotherObserver, Mockito.never()).onNext("one");
    -            inOrder.verify(anotherObserver, Mockito.never()).onNext("two");
    -            inOrder.verify(anotherObserver, times(1)).onCompleted();
    -            inOrder.verify(anotherObserver, Mockito.never()).onError(any(Throwable.class));
    -        }
    -
    -        @Test
    -        public void testUnsubscribeAfterOnError() {
    -            PublishSubject subject = PublishSubject.create();
    -            RuntimeException exception = new RuntimeException("failure");
    -
    -            @SuppressWarnings("unchecked")
    -            Observer anObserver = mock(Observer.class);
    -            subject.subscribe(anObserver);
    -
    -            subject.onNext("one");
    -            subject.onNext("two");
    -            subject.onError(exception);
    -
    -            InOrder inOrder = inOrder(anObserver);
    -            inOrder.verify(anObserver, times(1)).onNext("one");
    -            inOrder.verify(anObserver, times(1)).onNext("two");
    -            inOrder.verify(anObserver, times(1)).onError(exception);
    -            inOrder.verify(anObserver, Mockito.never()).onCompleted();
    -
    -            @SuppressWarnings("unchecked")
    -            Observer anotherObserver = mock(Observer.class);
    -            subject.subscribe(anotherObserver);
    -
    -            inOrder = inOrder(anotherObserver);
    -            inOrder.verify(anotherObserver, Mockito.never()).onNext("one");
    -            inOrder.verify(anotherObserver, Mockito.never()).onNext("two");
    -            inOrder.verify(anotherObserver, times(1)).onError(exception);
    -            inOrder.verify(anotherObserver, Mockito.never()).onCompleted();
    -        }
    -
    -        @Test
    -        public void testUnsubscribe()
    -        {
    -            UnsubscribeTester.test(new Func0>()
    -            {
    -                @Override
    -                public PublishSubject call()
    -                {
    -                    return PublishSubject.create();
    -                }
    -            }, new Action1>()
    -            {
    -                @Override
    -                public void call(PublishSubject DefaultSubject)
    -                {
    -                    DefaultSubject.onCompleted();
    -                }
    -            }, new Action1>()
    -            {
    -                @Override
    -                public void call(PublishSubject DefaultSubject)
    -                {
    -                    DefaultSubject.onError(new Throwable());
    -                }
    -            }, new Action1>()
    -            {
    -                @Override
    -                public void call(PublishSubject DefaultSubject)
    -                {
    -                    DefaultSubject.onNext("one");
    -                }
    -            });
    -        }
    -
    -        @Test
    -        public void testNestedSubscribe() {
    -            final PublishSubject s = PublishSubject.create();
    -
    -            final AtomicInteger countParent = new AtomicInteger();
    -            final AtomicInteger countChildren = new AtomicInteger();
    -            final AtomicInteger countTotal = new AtomicInteger();
    -
    -            final ArrayList list = new ArrayList();
    -
    -            s.mapMany(new Func1>() {
    -
    -                @Override
    -                public Observable call(final Integer v) {
    -                    countParent.incrementAndGet();
    -
    -                    // then subscribe to subject again (it will not receive the previous value)
    -                    return s.map(new Func1() {
    -
    -                        @Override
    -                        public String call(Integer v2) {
    -                            countChildren.incrementAndGet();
    -                            return "Parent: " + v + " Child: " + v2;
    -                        }
    -
    -                    });
    -                }
    -
    -            }).subscribe(new Action1() {
    -
    -                @Override
    -                public void call(String v) {
    -                    countTotal.incrementAndGet();
    -                    list.add(v);
    -                }
    -
    -            });
    -
    -
    -            for(int i=0; i<10; i++) {
    -                s.onNext(i);
    -            }
    -            s.onCompleted();
    -
    -            //            System.out.println("countParent: " + countParent.get());
    -            //            System.out.println("countChildren: " + countChildren.get());
    -            //            System.out.println("countTotal: " + countTotal.get());
    -
    -            // 9+8+7+6+5+4+3+2+1+0 == 45
    -            assertEquals(45, list.size());
    -        }
    -
    +        state.currentValue.set(new Notification(v));
    +        emitNotification(state, null);
         }
     }
    diff --git a/rxjava-core/src/main/java/rx/subjects/ReplaySubject.java b/rxjava-core/src/main/java/rx/subjects/ReplaySubject.java
    index 51ac519357..13d0cc3b57 100644
    --- a/rxjava-core/src/main/java/rx/subjects/ReplaySubject.java
    +++ b/rxjava-core/src/main/java/rx/subjects/ReplaySubject.java
    @@ -1,12 +1,12 @@
     /**
      * Copyright 2013 Netflix, Inc.
    - *
    + * 
      * Licensed under the Apache License, Version 2.0 (the "License");
      * you may not use this file except in compliance with the License.
      * You may obtain a copy of the License at
    - *
    + * 
      * http://www.apache.org/licenses/LICENSE-2.0
    - *
    + * 
      * Unless required by applicable law or agreed to in writing, software
      * distributed under the License is distributed on an "AS IS" BASIS,
      * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    @@ -15,35 +15,27 @@
      */
     package rx.subjects;
     
    -import static org.mockito.Matchers.*;
    -import static org.mockito.Mockito.*;
    -
     import java.util.ArrayList;
     import java.util.Collections;
     import java.util.HashMap;
     import java.util.List;
     import java.util.Map;
     
    -import org.junit.Test;
    -import org.mockito.InOrder;
    -import org.mockito.Mockito;
    -
     import rx.Observer;
     import rx.Subscription;
     import rx.subscriptions.Subscriptions;
    -import rx.util.functions.Action1;
    -import rx.util.functions.Func0;
     import rx.util.functions.Func1;
     
     /**
      * Subject that retains all events and will replay them to an {@link Observer} that subscribes.
    - *
    + * 

    + * *

    * Example usage: *

    *

     {@code
     
    -  ReplaySubject subject = ReplaySubject.create();
    + * eplaySubject subject = ReplaySubject.create();
       subject.onNext("one");
       subject.onNext("two");
       subject.onNext("three");
    @@ -54,7 +46,7 @@
       subject.subscribe(observer2);
     
       } 
    - *
    + * 
      * @param 
      */
     public final class ReplaySubject extends Subject
    @@ -178,172 +170,4 @@ public void onNext(T args)
                 }
             }
         }
    -
    -    public static class UnitTest {
    -
    -        private final Throwable testException = new Throwable();
    -
    -        @SuppressWarnings("unchecked")
    -        @Test
    -        public void testCompleted() {
    -            ReplaySubject subject = ReplaySubject.create();
    -
    -            Observer o1 = mock(Observer.class);
    -            subject.subscribe(o1);
    -
    -            subject.onNext("one");
    -            subject.onNext("two");
    -            subject.onNext("three");
    -            subject.onCompleted();
    -
    -            subject.onNext("four");
    -            subject.onCompleted();
    -            subject.onError(new Throwable());
    -
    -            assertCompletedObserver(o1);
    -
    -            // assert that subscribing a 2nd time gets the same data
    -            Observer o2 = mock(Observer.class);
    -            subject.subscribe(o2);
    -            assertCompletedObserver(o2);
    -        }
    -
    -        private void assertCompletedObserver(Observer aObserver)
    -        {
    -            InOrder inOrder = inOrder(aObserver);
    -
    -            inOrder.verify(aObserver, times(1)).onNext("one");
    -            inOrder.verify(aObserver, times(1)).onNext("two");
    -            inOrder.verify(aObserver, times(1)).onNext("three");
    -            inOrder.verify(aObserver, Mockito.never()).onError(any(Throwable.class));
    -            inOrder.verify(aObserver, times(1)).onCompleted();
    -            inOrder.verifyNoMoreInteractions();
    -        }
    -
    -        @SuppressWarnings("unchecked")
    -        @Test
    -        public void testError() {
    -            ReplaySubject subject = ReplaySubject.create();
    -
    -            Observer aObserver = mock(Observer.class);
    -            subject.subscribe(aObserver);
    -
    -            subject.onNext("one");
    -            subject.onNext("two");
    -            subject.onNext("three");
    -            subject.onError(testException);
    -
    -            subject.onNext("four");
    -            subject.onError(new Throwable());
    -            subject.onCompleted();
    -
    -            assertErrorObserver(aObserver);
    -
    -            aObserver = mock(Observer.class);
    -            subject.subscribe(aObserver);
    -            assertErrorObserver(aObserver);
    -        }
    -
    -        private void assertErrorObserver(Observer aObserver)
    -        {
    -            verify(aObserver, times(1)).onNext("one");
    -            verify(aObserver, times(1)).onNext("two");
    -            verify(aObserver, times(1)).onNext("three");
    -            verify(aObserver, times(1)).onError(testException);
    -            verify(aObserver, Mockito.never()).onCompleted();
    -        }
    -
    -        @SuppressWarnings("unchecked")
    -        @Test
    -        public void testSubscribeMidSequence() {
    -            ReplaySubject subject = ReplaySubject.create();
    -
    -            Observer aObserver = mock(Observer.class);
    -            subject.subscribe(aObserver);
    -
    -            subject.onNext("one");
    -            subject.onNext("two");
    -
    -            assertObservedUntilTwo(aObserver);
    -
    -            Observer anotherObserver = mock(Observer.class);
    -            subject.subscribe(anotherObserver);
    -            assertObservedUntilTwo(anotherObserver);
    -
    -            subject.onNext("three");
    -            subject.onCompleted();
    -
    -            assertCompletedObserver(aObserver);
    -            assertCompletedObserver(anotherObserver);
    -        }
    -
    -        @SuppressWarnings("unchecked")
    -        @Test
    -        public void testUnsubscribeFirstObserver() {
    -            ReplaySubject subject = ReplaySubject.create();
    -
    -            Observer aObserver = mock(Observer.class);
    -            Subscription subscription = subject.subscribe(aObserver);
    -
    -            subject.onNext("one");
    -            subject.onNext("two");
    -
    -            subscription.unsubscribe();
    -            assertObservedUntilTwo(aObserver);
    -
    -            Observer anotherObserver = mock(Observer.class);
    -            subject.subscribe(anotherObserver);
    -            assertObservedUntilTwo(anotherObserver);
    -
    -            subject.onNext("three");
    -            subject.onCompleted();
    -
    -            assertObservedUntilTwo(aObserver);
    -            assertCompletedObserver(anotherObserver);
    -        }
    -
    -        private void assertObservedUntilTwo(Observer aObserver)
    -        {
    -            verify(aObserver, times(1)).onNext("one");
    -            verify(aObserver, times(1)).onNext("two");
    -            verify(aObserver, Mockito.never()).onNext("three");
    -            verify(aObserver, Mockito.never()).onError(any(Throwable.class));
    -            verify(aObserver, Mockito.never()).onCompleted();
    -        }
    -
    -        @Test
    -        public void testUnsubscribe()
    -        {
    -            UnsubscribeTester.test(new Func0>()
    -            {
    -                @Override
    -                public ReplaySubject call()
    -                {
    -                    return ReplaySubject.create();
    -                }
    -            }, new Action1>()
    -            {
    -                @Override
    -                public void call(ReplaySubject repeatSubject)
    -                {
    -                    repeatSubject.onCompleted();
    -                }
    -            }, new Action1>()
    -            {
    -                @Override
    -                public void call(ReplaySubject repeatSubject)
    -                {
    -                    repeatSubject.onError(new Throwable());
    -                }
    -            }, new Action1>()
    -            {
    -                @Override
    -                public void call(ReplaySubject repeatSubject)
    -                {
    -                    repeatSubject.onNext("one");
    -                }
    -            }
    -                    );
    -        }
    -    }
     }
    diff --git a/rxjava-core/src/main/java/rx/subjects/UnsubscribeTester.java b/rxjava-core/src/main/java/rx/subjects/UnsubscribeTester.java
    index 6ff50d39ee..0aaf498bc7 100644
    --- a/rxjava-core/src/main/java/rx/subjects/UnsubscribeTester.java
    +++ b/rxjava-core/src/main/java/rx/subjects/UnsubscribeTester.java
    @@ -1,12 +1,12 @@
     /**
      * Copyright 2013 Netflix, Inc.
    - *
    + * 
      * Licensed under the Apache License, Version 2.0 (the "License");
      * you may not use this file except in compliance with the License.
      * You may obtain a copy of the License at
    - *
    + * 
      * http://www.apache.org/licenses/LICENSE-2.0
    - *
    + * 
      * Unless required by applicable law or agreed to in writing, software
      * distributed under the License is distributed on an "AS IS" BASIS,
      * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    diff --git a/rxjava-core/src/main/java/rx/subscriptions/CompositeSubscription.java b/rxjava-core/src/main/java/rx/subscriptions/CompositeSubscription.java
    index 330657cd5d..a94fd047ae 100644
    --- a/rxjava-core/src/main/java/rx/subscriptions/CompositeSubscription.java
    +++ b/rxjava-core/src/main/java/rx/subscriptions/CompositeSubscription.java
    @@ -15,16 +15,11 @@
      */
     package rx.subscriptions;
     
    -import static org.junit.Assert.*;
    -
     import java.util.ArrayList;
     import java.util.Collection;
     import java.util.List;
    -import java.util.concurrent.LinkedBlockingDeque;
    +import java.util.concurrent.ConcurrentHashMap;
     import java.util.concurrent.atomic.AtomicBoolean;
    -import java.util.concurrent.atomic.AtomicInteger;
    -
    -import org.junit.Test;
     
     import rx.Subscription;
     import rx.util.CompositeException;
    @@ -42,18 +37,52 @@ public class CompositeSubscription implements Subscription {
          * TODO evaluate whether use of synchronized is a performance issue here and if it's worth using an atomic state machine or other non-locking approach
          */
         private AtomicBoolean unsubscribed = new AtomicBoolean(false);
    -    private final LinkedBlockingDeque subscriptions = new LinkedBlockingDeque();
    +    private final ConcurrentHashMap subscriptions = new ConcurrentHashMap();
     
         public CompositeSubscription(List subscriptions) {
    -        this.subscriptions.addAll(subscriptions);
    +        for (Subscription s : subscriptions) {
    +            this.subscriptions.put(s, Boolean.TRUE);
    +        }
         }
     
         public CompositeSubscription(Subscription... subscriptions) {
             for (Subscription s : subscriptions) {
    -            this.subscriptions.add(s);
    +            this.subscriptions.put(s, Boolean.TRUE);
             }
         }
     
    +    /**
    +     * Remove and unsubscribe all subscriptions but do not unsubscribe the outer CompositeSubscription.
    +     */
    +    public void clear() {
    +        Collection es = null;
    +        for (Subscription s : subscriptions.keySet()) {
    +            try {
    +                s.unsubscribe();
    +                this.subscriptions.remove(s);
    +            } catch (Throwable e) {
    +                if (es == null) {
    +                    es = new ArrayList();
    +                }
    +                es.add(e);
    +            }
    +        }
    +        if (es != null) {
    +            throw new CompositeException("Failed to unsubscribe to 1 or more subscriptions.", es);
    +        }
    +    }
    +
    +    /**
    +     * Remove the {@link Subscription} and unsubscribe it.
    +     * 
    +     * @param s
    +     */
    +    public void remove(Subscription s) {
    +        this.subscriptions.remove(s);
    +        // also unsubscribe from it: http://msdn.microsoft.com/en-us/library/system.reactive.disposables.compositedisposable.remove(v=vs.103).aspx
    +        s.unsubscribe();
    +    }
    +
         public boolean isUnsubscribed() {
             return unsubscribed.get();
         }
    @@ -62,24 +91,15 @@ public synchronized void add(Subscription s) {
             if (unsubscribed.get()) {
                 s.unsubscribe();
             } else {
    -            subscriptions.add(s);
    +            subscriptions.put(s, Boolean.TRUE);
             }
         }
     
    -    /**
    -     * Remove the last Subscription that was added.
    -     * 
    -     * @return Subscription or null if none exists
    -     */
    -    public synchronized Subscription removeLast() {
    -        return subscriptions.pollLast();
    -    }
    -
         @Override
         public synchronized void unsubscribe() {
             if (unsubscribed.compareAndSet(false, true)) {
                 Collection es = null;
    -            for (Subscription s : subscriptions) {
    +            for (Subscription s : subscriptions.keySet()) {
                     try {
                         s.unsubscribe();
                     } catch (Throwable e) {
    @@ -94,65 +114,4 @@ public synchronized void unsubscribe() {
                 }
             }
         }
    -
    -    public static class UnitTest {
    -
    -        @Test
    -        public void testSuccess() {
    -            final AtomicInteger counter = new AtomicInteger();
    -            CompositeSubscription s = new CompositeSubscription();
    -            s.add(new Subscription() {
    -
    -                @Override
    -                public void unsubscribe() {
    -                    counter.incrementAndGet();
    -                }
    -            });
    -
    -            s.add(new Subscription() {
    -
    -                @Override
    -                public void unsubscribe() {
    -                    counter.incrementAndGet();
    -                }
    -            });
    -
    -            s.unsubscribe();
    -
    -            assertEquals(2, counter.get());
    -        }
    -
    -        @Test
    -        public void testException() {
    -            final AtomicInteger counter = new AtomicInteger();
    -            CompositeSubscription s = new CompositeSubscription();
    -            s.add(new Subscription() {
    -
    -                @Override
    -                public void unsubscribe() {
    -                    throw new RuntimeException("failed on first one");
    -                }
    -            });
    -
    -            s.add(new Subscription() {
    -
    -                @Override
    -                public void unsubscribe() {
    -                    counter.incrementAndGet();
    -                }
    -            });
    -
    -            try {
    -                s.unsubscribe();
    -                fail("Expecting an exception");
    -            } catch (CompositeException e) {
    -                // we expect this
    -                assertEquals(1, e.getExceptions().size());
    -            }
    -
    -            // we should still have unsubscribed to the second one
    -            assertEquals(1, counter.get());
    -        }
    -    }
    -
     }
    diff --git a/rxjava-core/src/main/java/rx/subscriptions/MultipleAssignmentSubscription.java b/rxjava-core/src/main/java/rx/subscriptions/MultipleAssignmentSubscription.java
    new file mode 100644
    index 0000000000..74ed285f77
    --- /dev/null
    +++ b/rxjava-core/src/main/java/rx/subscriptions/MultipleAssignmentSubscription.java
    @@ -0,0 +1,60 @@
    +/**
    + * Copyright 2013 Netflix, Inc.
    + *
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + *
    + *     http://www.apache.org/licenses/LICENSE-2.0
    + *
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +package rx.subscriptions;
    +
    +import java.util.concurrent.atomic.AtomicBoolean;
    +import java.util.concurrent.atomic.AtomicReference;
    +
    +import rx.Observable;
    +import rx.Subscription;
    +
    +/**
    + * Subscription that can be checked for status such as in a loop inside an {@link Observable} to exit the loop if unsubscribed.
    + * 
    + * @see Rx.Net equivalent MultipleAssignmentDisposable
    + */
    +public class MultipleAssignmentSubscription implements Subscription {
    +
    +    private final AtomicBoolean unsubscribed = new AtomicBoolean(false);
    +    private AtomicReference subscription = new AtomicReference();
    +
    +    public boolean isUnsubscribed() {
    +        return unsubscribed.get();
    +    }
    +
    +    @Override
    +    public synchronized void unsubscribe() {
    +        unsubscribed.set(true);
    +        Subscription s = getSubscription();
    +        if (s != null) {
    +            s.unsubscribe();
    +        }
    +
    +    }
    +
    +    public synchronized void setSubscription(Subscription s) {
    +        if (unsubscribed.get()) {
    +            s.unsubscribe();
    +        } else {
    +            subscription.set(s);
    +        }
    +    }
    +
    +    public Subscription getSubscription() {
    +        return subscription.get();
    +    }
    +
    +}
    diff --git a/rxjava-core/src/main/java/rx/subscriptions/SerialSubscription.java b/rxjava-core/src/main/java/rx/subscriptions/SerialSubscription.java
    new file mode 100644
    index 0000000000..ebe084f11c
    --- /dev/null
    +++ b/rxjava-core/src/main/java/rx/subscriptions/SerialSubscription.java
    @@ -0,0 +1,70 @@
    +/**
    + * Copyright 2013 Netflix, Inc.
    + * 
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + * 
    + * http://www.apache.org/licenses/LICENSE-2.0
    + * 
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +package rx.subscriptions;
    +
    +import rx.Subscription;
    +
    +/**
    + * Represents a subscription whose underlying subscription can be swapped for another subscription
    + * which causes the previous underlying subscription to be unsubscribed.
    + * 
    + * @see Rx.Net equivalent SerialDisposable
    + */
    +public class SerialSubscription implements Subscription {
    +    private boolean unsubscribed;
    +    private Subscription subscription;
    +    private final Object gate = new Object();
    +
    +    @Override
    +    public void unsubscribe() {
    +        Subscription toUnsubscribe = null;
    +        synchronized (gate) {
    +            if (!unsubscribed) {
    +                if (subscription != null) {
    +                    toUnsubscribe = subscription;
    +                    subscription = null;
    +                }
    +                unsubscribed = true;
    +            }
    +        }
    +        if (toUnsubscribe != null) {
    +            toUnsubscribe.unsubscribe();
    +        }
    +    }
    +
    +    public Subscription getSubscription() {
    +        synchronized (gate) {
    +            return subscription;
    +        }
    +    }
    +
    +    public void setSubscription(Subscription subscription) {
    +        Subscription toUnsubscribe = null;
    +        synchronized (gate) {
    +            if (!unsubscribed) {
    +                if (this.subscription != null) {
    +                    toUnsubscribe = this.subscription;
    +                }
    +                this.subscription = subscription;
    +            } else {
    +                toUnsubscribe = subscription;
    +            }
    +        }
    +        if (toUnsubscribe != null) {
    +            toUnsubscribe.unsubscribe();
    +        }
    +    }
    +}
    diff --git a/rxjava-core/src/main/java/rx/subscriptions/SingleAssignmentSubscription.java b/rxjava-core/src/main/java/rx/subscriptions/SingleAssignmentSubscription.java
    new file mode 100644
    index 0000000000..492c4e6dbf
    --- /dev/null
    +++ b/rxjava-core/src/main/java/rx/subscriptions/SingleAssignmentSubscription.java
    @@ -0,0 +1,81 @@
    +/**
    + * Copyright 2013 Netflix, Inc.
    + * 
    + * Licensed under the Apache License, Version 2.0 (the "License");
    + * you may not use this file except in compliance with the License.
    + * You may obtain a copy of the License at
    + * 
    + * http://www.apache.org/licenses/LICENSE-2.0
    + * 
    + * Unless required by applicable law or agreed to in writing, software
    + * distributed under the License is distributed on an "AS IS" BASIS,
    + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    + * See the License for the specific language governing permissions and
    + * limitations under the License.
    + */
    +
    +package rx.subscriptions;
    +
    +import java.util.concurrent.atomic.AtomicReference;
    +import rx.Subscription;
    +
    +/**
    + * A subscription that allows only a single resource to be assigned.
    + * 

    + * If this subscription is live, no other subscription may be set() and + * yields an {@link IllegalStateException}. + *

    + * If the unsubscribe has been called, setting a new subscription will + * unsubscribe it immediately. + */ +public final class SingleAssignmentSubscription implements Subscription { + /** Holds the current resource. */ + private final AtomicReference current = new AtomicReference(); + /** Sentinel for the unsubscribed state. */ + private static final Subscription SENTINEL = new Subscription() { + @Override + public void unsubscribe() { + } + }; + /** + * Returns the current subscription or null if not yet set. + */ + public Subscription get() { + Subscription s = current.get(); + if (s == SENTINEL) { + return Subscriptions.empty(); + } + return s; + } + /** + * Sets a new subscription if not already set. + * @param s the new subscription + * @throws IllegalStateException if this subscription is live and contains + * another subscription. + */ + public void set(Subscription s) { + if (current.compareAndSet(null, s)) { + return; + } + if (current.get() != SENTINEL) { + throw new IllegalStateException("Subscription already set"); + } + if (s != null) { + s.unsubscribe(); + } + } + @Override + public void unsubscribe() { + Subscription old = current.getAndSet(SENTINEL); + if (old != null) { + old.unsubscribe(); + } + } + /** + * Test if this subscription is already unsubscribed. + */ + public boolean isUnsubscribed() { + return current.get() == SENTINEL; + } + +} diff --git a/rxjava-core/src/main/java/rx/subscriptions/Subscriptions.java b/rxjava-core/src/main/java/rx/subscriptions/Subscriptions.java index 5d52a69cb2..61fd6b3f6c 100644 --- a/rxjava-core/src/main/java/rx/subscriptions/Subscriptions.java +++ b/rxjava-core/src/main/java/rx/subscriptions/Subscriptions.java @@ -1,12 +1,12 @@ /** * Copyright 2013 Netflix, Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,6 +18,7 @@ import java.util.concurrent.Future; import rx.Subscription; +import rx.operators.SafeObservableSubscription; import rx.util.functions.Action0; /** @@ -26,7 +27,7 @@ public class Subscriptions { /** * A {@link Subscription} that does nothing. - * + * * @return {@link Subscription} */ public static Subscription empty() { @@ -35,28 +36,49 @@ public static Subscription empty() { /** * A {@link Subscription} which invokes the given {@link Action0} when unsubscribed. - * - * @param unsubscribe Action to invoke on unsubscribe. + * + * @param unsubscribe + * Action to invoke on unsubscribe. * @return {@link Subscription} */ public static Subscription create(final Action0 unsubscribe) { - return new Subscription() { + return new SafeObservableSubscription(new Subscription() { @Override public void unsubscribe() { unsubscribe.call(); } + }); + } + + /** + * A {@link Subscription} that wraps a {@link Future} and cancels it when unsubscribed. + * + * + * @param f + * {@link Future} + * @return {@link Subscription} + */ + public static Subscription from(final Future f) { + return new Subscription() { + + @Override + public void unsubscribe() { + f.cancel(true); + } + }; } /** * A {@link Subscription} that wraps a {@link Future} and cancels it when unsubscribed. - * - * + * + * * @param f * {@link Future} * @return {@link Subscription} + * @deprecated Use {@link #from(Future)} instead */ public static Subscription create(final Future f) { return new Subscription() { @@ -71,10 +93,23 @@ public void unsubscribe() { /** * A {@link Subscription} that groups multiple Subscriptions together and unsubscribes from all of them together. - * + * + * @param subscriptions + * Subscriptions to group together + * @return {@link Subscription} + */ + + public static CompositeSubscription from(Subscription... subscriptions) { + return new CompositeSubscription(subscriptions); + } + + /** + * A {@link Subscription} that groups multiple Subscriptions together and unsubscribes from all of them together. + * * @param subscriptions * Subscriptions to group together * @return {@link Subscription} + * @deprecated Use {@link #from(Subscription...)} instead */ public static CompositeSubscription create(Subscription... subscriptions) { diff --git a/rxjava-core/src/main/java/rx/util/Closings.java b/rxjava-core/src/main/java/rx/util/Closings.java index cc6c589283..0de43b97e9 100644 --- a/rxjava-core/src/main/java/rx/util/Closings.java +++ b/rxjava-core/src/main/java/rx/util/Closings.java @@ -18,7 +18,8 @@ public class Closings { public static Closing create() { - return new Closing() {}; + return new Closing() { + }; } private Closings() { diff --git a/rxjava-core/src/main/java/rx/util/Openings.java b/rxjava-core/src/main/java/rx/util/Openings.java index 3f962ec163..30e11f72ca 100644 --- a/rxjava-core/src/main/java/rx/util/Openings.java +++ b/rxjava-core/src/main/java/rx/util/Openings.java @@ -18,7 +18,8 @@ public class Openings { public static Opening create() { - return new Opening() {}; + return new Opening() { + }; } private Openings() { diff --git a/rxjava-core/src/main/java/rx/util/Range.java b/rxjava-core/src/main/java/rx/util/Range.java index c263c53471..5f7418b218 100644 --- a/rxjava-core/src/main/java/rx/util/Range.java +++ b/rxjava-core/src/main/java/rx/util/Range.java @@ -15,16 +15,9 @@ */ package rx.util; -import static org.junit.Assert.*; - -import java.util.ArrayList; -import java.util.Arrays; import java.util.Iterator; -import java.util.List; import java.util.NoSuchElementException; -import org.junit.Test; - public final class Range implements Iterable { private final int start; private final int end; @@ -79,46 +72,4 @@ public void remove() { public String toString() { return "Range (" + start + ", " + end + "), step " + step; } - - public static class UnitTest { - - @Test - public void testSimpleRange() { - assertEquals(Arrays.asList(1, 2, 3, 4), toList(Range.create(1, 5))); - } - - @Test - public void testRangeWithStep() { - assertEquals(Arrays.asList(1, 3, 5, 7, 9), toList(Range.createWithStep(1, 10, 2))); - } - - @Test - public void testRangeWithCount() { - assertEquals(Arrays.asList(1, 2, 3, 4, 5), toList(Range.createWithCount(1, 5))); - } - - @Test - public void testRangeWithCount2() { - assertEquals(Arrays.asList(2, 3, 4, 5), toList(Range.createWithCount(2, 4))); - } - - @Test - public void testRangeWithCount3() { - assertEquals(Arrays.asList(0, 1, 2, 3), toList(Range.createWithCount(0, 4))); - } - - @Test - public void testRangeWithCount4() { - assertEquals(Arrays.asList(10, 11, 12, 13, 14), toList(Range.createWithCount(10, 5))); - } - - private static List toList(Iterable iterable) { - List result = new ArrayList(); - for (T element : iterable) { - result.add(element); - } - return result; - } - - } } \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/util/TimeInterval.java b/rxjava-core/src/main/java/rx/util/TimeInterval.java new file mode 100644 index 0000000000..d585baf1ce --- /dev/null +++ b/rxjava-core/src/main/java/rx/util/TimeInterval.java @@ -0,0 +1,81 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.util; + +public class TimeInterval { + private final long intervalInMilliseconds; + private final T value; + + public TimeInterval(long intervalInMilliseconds, T value) { + this.value = value; + this.intervalInMilliseconds = intervalInMilliseconds; + } + + /** + * Returns the interval in milliseconds. + * + * @return interval in milliseconds + */ + public long getIntervalInMilliseconds() { + return intervalInMilliseconds; + } + + /** + * Returns the value. + * + * @return the value + */ + public T getValue() { + return value; + } + + // The following methods are generated by eclipse automatically. + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime + * result + + (int) (intervalInMilliseconds ^ (intervalInMilliseconds >>> 32)); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TimeInterval other = (TimeInterval) obj; + if (intervalInMilliseconds != other.intervalInMilliseconds) + return false; + if (value == null) { + if (other.value != null) + return false; + } else if (!value.equals(other.value)) + return false; + return true; + } + + @Override + public String toString() { + return "TimeInterval [intervalInMilliseconds=" + intervalInMilliseconds + + ", value=" + value + "]"; + } +} diff --git a/rxjava-core/src/main/java/rx/util/Timestamped.java b/rxjava-core/src/main/java/rx/util/Timestamped.java index 729d27ea99..d47bd2b57f 100644 --- a/rxjava-core/src/main/java/rx/util/Timestamped.java +++ b/rxjava-core/src/main/java/rx/util/Timestamped.java @@ -1,12 +1,12 @@ /** * Copyright 2013 Netflix, Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -29,7 +29,7 @@ public Timestamped(long timestampMillis, T value) { /** * Returns time timestamp, in milliseconds. - * + * * @return timestamp in milliseconds */ public long getTimestampMillis() { @@ -38,7 +38,7 @@ public long getTimestampMillis() { /** * Returns the value. - * + * * @return the value */ public T getValue() { diff --git a/rxjava-core/src/main/java/rx/util/functions/Actions.java b/rxjava-core/src/main/java/rx/util/functions/Actions.java new file mode 100644 index 0000000000..2bffbb9ec8 --- /dev/null +++ b/rxjava-core/src/main/java/rx/util/functions/Actions.java @@ -0,0 +1,72 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package rx.util.functions; + +import rx.Observer; +import rx.util.functions.Action0; +import rx.util.functions.Action1; + +/** + * Utility class for the Action interfaces. + */ +public final class Actions { + private Actions() { throw new IllegalStateException("No instances!"); } + /** + * Extracts a method reference to the observer's onNext method + * in the form of an Action1. + *

    Java 8: observer::onNext

    + * @param observer the observer to use + * @return an action which calls the observer's onNext method. + */ + public static Action1 onNextFrom(final Observer observer) { + return new Action1() { + @Override + public void call(T t1) { + observer.onNext(t1); + } + }; + } + /** + * Extracts a method reference to the observer's onError method + * in the form of an Action1. + *

    Java 8: observer::onError

    + * @param observer the observer to use + * @return an action which calls the observer's onError method. + */ + public static Action1 onErrorFrom(final Observer observer) { + return new Action1() { + @Override + public void call(Throwable t1) { + observer.onError(t1); + } + }; + } + /** + * Extracts a method reference to the observer's onCompleted method + * in the form of an Action0. + *

    Java 8: observer::onCompleted

    + * @param observer the observer to use + * @return an action which calls the observer's onCompleted method. + */ + public static Action0 onCompletedFrom(final Observer observer) { + return new Action0() { + @Override + public void call() { + observer.onCompleted(); + } + }; + } +} diff --git a/rxjava-core/src/main/java/rx/util/functions/Functions.java b/rxjava-core/src/main/java/rx/util/functions/Functions.java index 30728600a8..91449d95bd 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Functions.java +++ b/rxjava-core/src/main/java/rx/util/functions/Functions.java @@ -317,7 +317,8 @@ public Void call(Object... args) { * Constructs a predicate that returns true for each input that the source * predicate returns false for and vice versa. * - * @param predicate The source predicate to negate. + * @param predicate + * The source predicate to negate. */ public static Func1 not(Func1 predicate) { return new Not(predicate); @@ -327,6 +328,10 @@ public static Func1 alwaysTrue() { return AlwaysTrue.INSTANCE; } + public static Func1 alwaysFalse() { + return AlwaysFalse.INSTANCE; + } + public static Func1 identity() { return new Func1() { @Override @@ -344,5 +349,13 @@ public Boolean call(Object o) { return true; } } - + + private enum AlwaysFalse implements Func1 { + INSTANCE; + + @Override + public Boolean call(Object o) { + return false; + } + } } diff --git a/rxjava-core/src/main/java/rx/util/functions/Not.java b/rxjava-core/src/main/java/rx/util/functions/Not.java index d3928f7888..b63e1dcea4 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Not.java +++ b/rxjava-core/src/main/java/rx/util/functions/Not.java @@ -16,23 +16,25 @@ package rx.util.functions; /** - * Implements the negation of a predicate. + * Implements the negation of a predicate. * - * @param The type of the single input parameter. + * @param + * The type of the single input parameter. */ public class Not implements Func1 { private final Func1 predicate; - + /** * Constructs a predicate that returns true for each input that the source * predicate returns false for and vice versa. * - * @param predicate The source predicate to negate. + * @param predicate + * The source predicate to negate. */ public Not(Func1 predicate) { this.predicate = predicate; } - + @Override public Boolean call(T param) { return !predicate.call(param); diff --git a/rxjava-core/src/test/java/README.md b/rxjava-core/src/test/java/README.md deleted file mode 100644 index c6a9aea6af..0000000000 --- a/rxjava-core/src/test/java/README.md +++ /dev/null @@ -1,6 +0,0 @@ -Not all unit tests are here, many are also embedded as inner classes of the main code (such as here: [rxjava-core/src/main/java/rx/operators](https://github.com/Netflix/RxJava/tree/master/rxjava-core/src/main/java/rx/operators)). - -* For an explanation of this design choice see -Ben J. Christensen's [JUnit Tests as Inner Classes](http://benjchristensen.com/2011/10/23/junit-tests-as-inner-classes/). - -Also, each of the language adaptors has a /src/test/ folder which further testing (see Groovy for an example: [language-adaptors/rxjava-groovy/src/test](https://github.com/Netflix/RxJava/tree/master/language-adaptors/rxjava-groovy/src/test)). diff --git a/rxjava-core/src/test/java/rx/CombineLatestTests.java b/rxjava-core/src/test/java/rx/CombineLatestTests.java index 78dbd4c4e3..c52daa3870 100644 --- a/rxjava-core/src/test/java/rx/CombineLatestTests.java +++ b/rxjava-core/src/test/java/rx/CombineLatestTests.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx; import org.junit.Test; diff --git a/rxjava-core/src/test/java/rx/ConcatTests.java b/rxjava-core/src/test/java/rx/ConcatTests.java index 29a51d8dfe..80f7eb5ef7 100644 --- a/rxjava-core/src/test/java/rx/ConcatTests.java +++ b/rxjava-core/src/test/java/rx/ConcatTests.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx; import static org.junit.Assert.*; @@ -60,7 +75,7 @@ public void testConcatWithIterableOfObservable() { assertEquals("three", values.get(2)); assertEquals("four", values.get(3)); } - + @Test public void testConcatCovariance() { Observable o1 = Observable. from(new HorrorMovie(), new Movie()); @@ -80,14 +95,14 @@ public void testConcatCovariance2() { List values = Observable.concat(os).toList().toBlockingObservable().single(); } - + @Test public void testConcatCovariance3() { Observable o1 = Observable.from(new HorrorMovie(), new Movie()); Observable o2 = Observable.from(new Media(), new HorrorMovie()); List values = Observable.concat(o1, o2).toList().toBlockingObservable().single(); - + assertTrue(values.get(0) instanceof HorrorMovie); assertTrue(values.get(1) instanceof Movie); assertTrue(values.get(2) instanceof Media); @@ -112,7 +127,7 @@ public Subscription onSubscribe(Observer o) { Observable o2 = Observable.from(new Media(), new HorrorMovie()); List values = Observable.concat(o1, o2).toList().toBlockingObservable().single(); - + assertTrue(values.get(0) instanceof HorrorMovie); assertTrue(values.get(1) instanceof Movie); assertTrue(values.get(2) instanceof Media); diff --git a/rxjava-core/src/test/java/rx/CovarianceTest.java b/rxjava-core/src/test/java/rx/CovarianceTest.java index 01f2030c52..48ca64f61e 100644 --- a/rxjava-core/src/test/java/rx/CovarianceTest.java +++ b/rxjava-core/src/test/java/rx/CovarianceTest.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx; import java.util.ArrayList; diff --git a/rxjava-core/src/test/java/rx/EventStream.java b/rxjava-core/src/test/java/rx/EventStream.java index 5fbffba528..f32787bac1 100644 --- a/rxjava-core/src/test/java/rx/EventStream.java +++ b/rxjava-core/src/test/java/rx/EventStream.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx; import java.util.Collections; diff --git a/rxjava-core/src/test/java/rx/GroupByTests.java b/rxjava-core/src/test/java/rx/GroupByTests.java index 87448f8510..de3310b35c 100644 --- a/rxjava-core/src/test/java/rx/GroupByTests.java +++ b/rxjava-core/src/test/java/rx/GroupByTests.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx; import org.junit.Test; diff --git a/rxjava-core/src/test/java/rx/IntervalDemo.java b/rxjava-core/src/test/java/rx/IntervalDemo.java index ef2af97549..0222bc1b55 100644 --- a/rxjava-core/src/test/java/rx/IntervalDemo.java +++ b/rxjava-core/src/test/java/rx/IntervalDemo.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx; import java.util.ArrayList; @@ -10,33 +25,37 @@ import rx.util.functions.Action0; import rx.util.functions.Action1; -@Ignore // since this doesn't do any automatic testing +@Ignore +// since this doesn't do any automatic testing public class IntervalDemo { - - @Test public void demoInterval() throws Exception { - testLongObservable(Observable.interval(500, TimeUnit.MILLISECONDS).take(4), "demoInterval"); - } - - public void testLongObservable(Observable o, final String testname) throws Exception { - final List l = new ArrayList(); - Action1 onNext = new Action1() { - public void call(Long i) { - l.add(i); - System.out.println(testname + " got " + i); - } - }; - Action1 onError = new Action1() { - public void call(Throwable t) { t.printStackTrace(); } - }; - Action0 onComplete = new Action0() { - public void call() { - System.out.println(testname + " complete"); - } - }; - o.subscribe(onNext, onError, onComplete); - - // need to wait, otherwise JUnit kills the thread of interval() - Thread.sleep(2500); - } - + + @Test + public void demoInterval() throws Exception { + testLongObservable(Observable.interval(500, TimeUnit.MILLISECONDS).take(4), "demoInterval"); + } + + public void testLongObservable(Observable o, final String testname) throws Exception { + final List l = new ArrayList(); + Action1 onNext = new Action1() { + public void call(Long i) { + l.add(i); + System.out.println(testname + " got " + i); + } + }; + Action1 onError = new Action1() { + public void call(Throwable t) { + t.printStackTrace(); + } + }; + Action0 onComplete = new Action0() { + public void call() { + System.out.println(testname + " complete"); + } + }; + o.subscribe(onNext, onError, onComplete); + + // need to wait, otherwise JUnit kills the thread of interval() + Thread.sleep(2500); + } + } diff --git a/rxjava-core/src/test/java/rx/MergeTests.java b/rxjava-core/src/test/java/rx/MergeTests.java index 11f30c7908..1f697ce46a 100644 --- a/rxjava-core/src/test/java/rx/MergeTests.java +++ b/rxjava-core/src/test/java/rx/MergeTests.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx; import static org.junit.Assert.*; @@ -43,14 +58,14 @@ public void testMergeCovariance2() { List values = Observable.merge(os).toList().toBlockingObservable().single(); } - + @Test public void testMergeCovariance3() { Observable o1 = Observable.from(new HorrorMovie(), new Movie()); Observable o2 = Observable.from(new Media(), new HorrorMovie()); List values = Observable.merge(o1, o2).toList().toBlockingObservable().single(); - + assertTrue(values.get(0) instanceof HorrorMovie); assertTrue(values.get(1) instanceof Movie); assertTrue(values.get(2) instanceof Media); @@ -75,12 +90,11 @@ public Subscription onSubscribe(Observer o) { Observable o2 = Observable.from(new Media(), new HorrorMovie()); List values = Observable.merge(o1, o2).toList().toBlockingObservable().single(); - + assertTrue(values.get(0) instanceof HorrorMovie); assertTrue(values.get(1) instanceof Movie); assertTrue(values.get(2) instanceof Media); assertTrue(values.get(3) instanceof HorrorMovie); } - - + } diff --git a/rxjava-core/src/test/java/rx/ObservableDoOnTest.java b/rxjava-core/src/test/java/rx/ObservableDoOnTest.java new file mode 100644 index 0000000000..03aa8f53a4 --- /dev/null +++ b/rxjava-core/src/test/java/rx/ObservableDoOnTest.java @@ -0,0 +1,80 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx; + +import static org.junit.Assert.*; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; + +import rx.util.functions.Action0; +import rx.util.functions.Action1; + +public class ObservableDoOnTest { + + @Test + public void testDoOnEach() { + final AtomicReference r = new AtomicReference(); + String output = Observable.from("one").doOnEach(new Action1() { + + @Override + public void call(String v) { + r.set(v); + } + }).toBlockingObservable().single(); + + assertEquals("one", output); + assertEquals("one", r.get()); + } + + @Test + public void testDoOnError() { + final AtomicReference r = new AtomicReference(); + Throwable t = null; + try { + Observable. error(new RuntimeException("an error")).doOnError(new Action1() { + + @Override + public void call(Throwable v) { + r.set(v); + } + }).toBlockingObservable().single(); + fail("expected exception, not a return value"); + } catch (Throwable e) { + t = e; + } + + assertNotNull(t); + assertEquals(t, r.get()); + } + + @Test + public void testDoOnCompleted() { + final AtomicBoolean r = new AtomicBoolean(); + String output = Observable.from("one").doOnCompleted(new Action0() { + + @Override + public void call() { + r.set(true); + } + }).toBlockingObservable().single(); + + assertEquals("one", output); + assertTrue(r.get()); + } +} diff --git a/rxjava-core/src/test/java/rx/ObservableTests.java b/rxjava-core/src/test/java/rx/ObservableTests.java index 92af02b1dd..3d718210a1 100644 --- a/rxjava-core/src/test/java/rx/ObservableTests.java +++ b/rxjava-core/src/test/java/rx/ObservableTests.java @@ -20,6 +20,9 @@ import static org.mockito.Mockito.*; import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -27,13 +30,16 @@ import org.junit.Before; import org.junit.Test; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import rx.Observable.OnSubscribeFunc; +import rx.concurrency.TestScheduler; import rx.observables.ConnectableObservable; import rx.subscriptions.BooleanSubscription; import rx.subscriptions.Subscriptions; +import rx.util.functions.Action0; import rx.util.functions.Action1; import rx.util.functions.Func1; import rx.util.functions.Func2; @@ -208,6 +214,51 @@ public Integer call(Integer t1, Integer t2) { verify(w).onNext(10); } + + /** + * A reduce should fail with an IllegalArgumentException if done on an empty Observable. + */ + @Test(expected = IllegalArgumentException.class) + public void testReduceWithEmptyObservable() { + Observable observable = Observable.range(1, 0); + observable.reduce(new Func2() { + + @Override + public Integer call(Integer t1, Integer t2) { + return t1 + t2; + } + + }).toBlockingObservable().forEach(new Action1() { + + @Override + public void call(Integer t1) { + // do nothing ... we expect an exception instead + } + }); + + fail("Expected an exception to be thrown"); + } + + /** + * A reduce on an empty Observable and a seed should just pass the seed through. + * + * This is confirmed at https://github.com/Netflix/RxJava/issues/423#issuecomment-27642456 + */ + @Test + public void testReduceWithEmptyObservableAndSeed() { + Observable observable = Observable.range(1, 0); + int value = observable.reduce(1, new Func2() { + + @Override + public Integer call(Integer t1, Integer t2) { + return t1 + t2; + } + + }).toBlockingObservable().last(); + + assertEquals(1, value); + } + @Test public void testReduceWithInitialValue() { Observable observable = Observable.from(1, 2, 3, 4); @@ -487,6 +538,49 @@ public void call(String v) { } } + @Test + public void testPublishLast() throws InterruptedException { + final AtomicInteger count = new AtomicInteger(); + ConnectableObservable connectable = Observable.create(new OnSubscribeFunc() { + @Override + public Subscription onSubscribe(final Observer observer) { + count.incrementAndGet(); + final BooleanSubscription subscription = new BooleanSubscription(); + new Thread(new Runnable() { + @Override + public void run() { + observer.onNext("first"); + observer.onNext("last"); + observer.onCompleted(); + } + }).start(); + return subscription; + } + }).publishLast(); + + // subscribe once + final CountDownLatch latch = new CountDownLatch(1); + connectable.subscribe(new Action1() { + @Override + public void call(String value) { + assertEquals("last", value); + latch.countDown(); + } + }); + + // subscribe twice + connectable.subscribe(new Action1() { + @Override + public void call(String _) { + } + }); + + Subscription subscription = connectable.connect(); + assertTrue(latch.await(1000, TimeUnit.MILLISECONDS)); + assertEquals(1, count.get()); + subscription.unsubscribe(); + } + @Test public void testReplay() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); @@ -701,4 +795,167 @@ public void onNext(String v) { fail("It should be a NumberFormatException"); } } + + @Test + public void testOfType() { + Observable observable = Observable.from(1, "abc", false, 2L).ofType(String.class); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, never()).onNext(1); + verify(aObserver, times(1)).onNext("abc"); + verify(aObserver, never()).onNext(false); + verify(aObserver, never()).onNext(2L); + verify(aObserver, never()).onError( + org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testOfTypeWithPolymorphism() { + ArrayList l1 = new ArrayList(); + l1.add(1); + LinkedList l2 = new LinkedList(); + l2.add(2); + + @SuppressWarnings("rawtypes") + Observable observable = Observable. from(l1, l2, "123").ofType(List.class); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext(l1); + verify(aObserver, times(1)).onNext(l2); + verify(aObserver, never()).onNext("123"); + verify(aObserver, never()).onError( + org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testContains() { + Observable observable = Observable.from("a", "b", null).contains("b"); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext(true); + verify(aObserver, never()).onNext(false); + verify(aObserver, never()).onError( + org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testContainsWithInexistence() { + Observable observable = Observable.from("a", "b", null).contains("c"); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext(false); + verify(aObserver, never()).onNext(true); + verify(aObserver, never()).onError( + org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testContainsWithNull() { + Observable observable = Observable.from("a", "b", null).contains(null); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext(true); + verify(aObserver, never()).onNext(false); + verify(aObserver, never()).onError( + org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testContainsWithEmptyObservable() { + Observable observable = Observable. empty().contains("a"); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext(false); + verify(aObserver, never()).onNext(true); + verify(aObserver, never()).onError( + org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testIgnoreElements() { + Observable observable = Observable.from(1, 2, 3).ignoreElements(); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, never()).onNext(any(Integer.class)); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testFromWithScheduler() { + TestScheduler scheduler = new TestScheduler(); + Observable observable = Observable.from(Arrays.asList(1, 2), scheduler); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + + scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); + + InOrder inOrder = inOrder(aObserver); + inOrder.verify(aObserver, times(1)).onNext(1); + inOrder.verify(aObserver, times(1)).onNext(2); + inOrder.verify(aObserver, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testStartWithWithScheduler() { + TestScheduler scheduler = new TestScheduler(); + Observable observable = Observable.from(3, 4).startWith(Arrays.asList(1, 2), scheduler); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + + scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); + + InOrder inOrder = inOrder(aObserver); + inOrder.verify(aObserver, times(1)).onNext(1); + inOrder.verify(aObserver, times(1)).onNext(2); + inOrder.verify(aObserver, times(1)).onNext(3); + inOrder.verify(aObserver, times(1)).onNext(4); + inOrder.verify(aObserver, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testRangeWithScheduler() { + TestScheduler scheduler = new TestScheduler(); + Observable observable = Observable.range(3, 4, scheduler); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + + scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); + + InOrder inOrder = inOrder(aObserver); + inOrder.verify(aObserver, times(1)).onNext(3); + inOrder.verify(aObserver, times(1)).onNext(4); + inOrder.verify(aObserver, times(1)).onNext(5); + inOrder.verify(aObserver, times(1)).onNext(6); + inOrder.verify(aObserver, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } } \ No newline at end of file diff --git a/rxjava-core/src/test/java/rx/ObservableWindowTests.java b/rxjava-core/src/test/java/rx/ObservableWindowTests.java index 75e5cb565a..987ce255c9 100644 --- a/rxjava-core/src/test/java/rx/ObservableWindowTests.java +++ b/rxjava-core/src/test/java/rx/ObservableWindowTests.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx; import static org.junit.Assert.*; diff --git a/rxjava-core/src/test/java/rx/ObserveOnTests.java b/rxjava-core/src/test/java/rx/ObserveOnTests.java new file mode 100644 index 0000000000..57d13c3585 --- /dev/null +++ b/rxjava-core/src/test/java/rx/ObserveOnTests.java @@ -0,0 +1,133 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx; + +import static org.junit.Assert.*; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import rx.concurrency.Schedulers; +import rx.util.functions.Action1; +import rx.util.functions.Func1; + +public class ObserveOnTests { + + /** + * Confirm that running on a NewThreadScheduler uses the same thread for the entire stream + */ + @Test + public void testObserveOnWithNewThreadScheduler() { + final AtomicInteger count = new AtomicInteger(); + final int _multiple = 99; + + Observable.range(1, 100000).map(new Func1() { + + @Override + public Integer call(Integer t1) { + return t1 * _multiple; + } + + }).observeOn(Schedulers.newThread()) + .toBlockingObservable().forEach(new Action1() { + + @Override + public void call(Integer t1) { + assertEquals(count.incrementAndGet() * _multiple, t1.intValue()); + assertTrue(Thread.currentThread().getName().startsWith("RxNewThreadScheduler")); + } + + }); + } + + /** + * Confirm that running on a ThreadPoolScheduler allows multiple threads but is still ordered. + */ + @Test + public void testObserveOnWithThreadPoolScheduler() { + final AtomicInteger count = new AtomicInteger(); + final int _multiple = 99; + + Observable.range(1, 100000).map(new Func1() { + + @Override + public Integer call(Integer t1) { + return t1 * _multiple; + } + + }).observeOn(Schedulers.threadPoolForComputation()) + .toBlockingObservable().forEach(new Action1() { + + @Override + public void call(Integer t1) { + assertEquals(count.incrementAndGet() * _multiple, t1.intValue()); + assertTrue(Thread.currentThread().getName().startsWith("RxComputationThreadPool")); + } + + }); + } + + /** + * Attempts to confirm that when pauses exist between events, the ScheduledObserver + * does not lose or reorder any events since the scheduler will not block, but will + * be re-scheduled when it receives new events after each pause. + * + * + * This is non-deterministic in proving success, but if it ever fails (non-deterministically) + * it is a sign of potential issues as thread-races and scheduling should not affect output. + */ + @Test + public void testObserveOnOrderingConcurrency() { + final AtomicInteger count = new AtomicInteger(); + final int _multiple = 99; + + Observable.range(1, 10000).map(new Func1() { + + @Override + public Integer call(Integer t1) { + if (randomIntFrom0to100() > 98) { + try { + Thread.sleep(2); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + return t1 * _multiple; + } + + }).observeOn(Schedulers.threadPoolForComputation()) + .toBlockingObservable().forEach(new Action1() { + + @Override + public void call(Integer t1) { + assertEquals(count.incrementAndGet() * _multiple, t1.intValue()); + assertTrue(Thread.currentThread().getName().startsWith("RxComputationThreadPool")); + } + + }); + } + + private static int randomIntFrom0to100() { + // XORShift instead of Math.random http://javamex.com/tutorials/random_numbers/xorshift.shtml + long x = System.nanoTime(); + x ^= (x << 21); + x ^= (x >>> 35); + x ^= (x << 4); + return Math.abs((int) x % 100); + } + +} diff --git a/rxjava-core/src/test/java/rx/ReduceTests.java b/rxjava-core/src/test/java/rx/ReduceTests.java index b812ba3638..08ff57405b 100644 --- a/rxjava-core/src/test/java/rx/ReduceTests.java +++ b/rxjava-core/src/test/java/rx/ReduceTests.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx; import static org.junit.Assert.*; diff --git a/rxjava-core/src/test/java/rx/RefCountTests.java b/rxjava-core/src/test/java/rx/RefCountTests.java new file mode 100644 index 0000000000..b83e94fcae --- /dev/null +++ b/rxjava-core/src/test/java/rx/RefCountTests.java @@ -0,0 +1,154 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockitoAnnotations; + +import rx.concurrency.TestScheduler; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action0; +import rx.util.functions.Action1; + +public class RefCountTests { + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void onlyFirstShouldSubscribeAndLastUnsubscribe() { + final AtomicInteger subscriptionCount = new AtomicInteger(); + final AtomicInteger unsubscriptionCount = new AtomicInteger(); + Observable observable = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + subscriptionCount.incrementAndGet(); + return Subscriptions.create(new Action0() { + @Override + public void call() { + unsubscriptionCount.incrementAndGet(); + } + }); + } + }); + Observable refCounted = observable.publish().refCount(); + Observer observer = mock(Observer.class); + Subscription first = refCounted.subscribe(observer); + assertEquals(1, subscriptionCount.get()); + Subscription second = refCounted.subscribe(observer); + assertEquals(1, subscriptionCount.get()); + first.unsubscribe(); + assertEquals(0, unsubscriptionCount.get()); + second.unsubscribe(); + assertEquals(1, unsubscriptionCount.get()); + } + + @Test + public void testRefCount() { + TestScheduler s = new TestScheduler(); + Observable interval = Observable.interval(100, TimeUnit.MILLISECONDS, s).publish().refCount(); + + // subscribe list1 + final List list1 = new ArrayList(); + Subscription s1 = interval.subscribe(new Action1() { + + @Override + public void call(Long t1) { + list1.add(t1); + } + + }); + s.advanceTimeBy(200, TimeUnit.MILLISECONDS); + + assertEquals(2, list1.size()); + assertEquals(0L, list1.get(0).longValue()); + assertEquals(1L, list1.get(1).longValue()); + + // subscribe list2 + final List list2 = new ArrayList(); + Subscription s2 = interval.subscribe(new Action1() { + + @Override + public void call(Long t1) { + list2.add(t1); + } + + }); + s.advanceTimeBy(300, TimeUnit.MILLISECONDS); + + // list 1 should have 5 items + assertEquals(5, list1.size()); + assertEquals(2L, list1.get(2).longValue()); + assertEquals(3L, list1.get(3).longValue()); + assertEquals(4L, list1.get(4).longValue()); + + // list 2 should only have 3 items + assertEquals(3, list2.size()); + assertEquals(2L, list2.get(0).longValue()); + assertEquals(3L, list2.get(1).longValue()); + assertEquals(4L, list2.get(2).longValue()); + + // unsubscribe list1 + s1.unsubscribe(); + + // advance further + s.advanceTimeBy(300, TimeUnit.MILLISECONDS); + + // list 1 should still have 5 items + assertEquals(5, list1.size()); + + // list 2 should have 6 items + assertEquals(6, list2.size()); + assertEquals(5L, list2.get(3).longValue()); + assertEquals(6L, list2.get(4).longValue()); + assertEquals(7L, list2.get(5).longValue()); + + // unsubscribe list2 + s2.unsubscribe(); + + // advance further + s.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + + // subscribing a new one should start over because the source should have been unsubscribed + // subscribe list3 + final List list3 = new ArrayList(); + Subscription s3 = interval.subscribe(new Action1() { + + @Override + public void call(Long t1) { + list3.add(t1); + } + + }); + s.advanceTimeBy(200, TimeUnit.MILLISECONDS); + + assertEquals(2, list3.size()); + assertEquals(0L, list3.get(0).longValue()); + assertEquals(1L, list3.get(1).longValue()); + + } +} diff --git a/rxjava-core/src/test/java/rx/ScanTests.java b/rxjava-core/src/test/java/rx/ScanTests.java index bef93e471b..d7948963d7 100644 --- a/rxjava-core/src/test/java/rx/ScanTests.java +++ b/rxjava-core/src/test/java/rx/ScanTests.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx; import java.util.HashMap; diff --git a/rxjava-core/src/test/java/rx/concurrency/TestSchedulers.java b/rxjava-core/src/test/java/rx/SchedulersTest.java similarity index 89% rename from rxjava-core/src/test/java/rx/concurrency/TestSchedulers.java rename to rxjava-core/src/test/java/rx/SchedulersTest.java index 4241ee3f84..c9d97054d0 100644 --- a/rxjava-core/src/test/java/rx/concurrency/TestSchedulers.java +++ b/rxjava-core/src/test/java/rx/SchedulersTest.java @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package rx.concurrency; +package rx; import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; import java.util.Date; import java.util.concurrent.CountDownLatch; @@ -25,19 +27,60 @@ import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; -import rx.Observable; import rx.Observable.OnSubscribeFunc; -import rx.Observer; -import rx.Scheduler; -import rx.Subscription; +import rx.concurrency.Schedulers; +import rx.concurrency.TestScheduler; import rx.subscriptions.BooleanSubscription; import rx.subscriptions.Subscriptions; +import rx.util.functions.Action0; import rx.util.functions.Action1; import rx.util.functions.Func1; import rx.util.functions.Func2; -public class TestSchedulers { +public class SchedulersTest { + + @SuppressWarnings("unchecked") + // mocking is unchecked, unfortunately + @Test + public void testPeriodicScheduling() { + final Func1 calledOp = mock(Func1.class); + + final TestScheduler scheduler = new TestScheduler(); + Subscription subscription = scheduler.schedulePeriodically(new Action0() { + @Override + public void call() { + System.out.println(scheduler.now()); + calledOp.call(scheduler.now()); + } + }, 1, 2, TimeUnit.SECONDS); + + verify(calledOp, never()).call(anyLong()); + + InOrder inOrder = Mockito.inOrder(calledOp); + + scheduler.advanceTimeBy(999L, TimeUnit.MILLISECONDS); + inOrder.verify(calledOp, never()).call(anyLong()); + + scheduler.advanceTimeBy(1L, TimeUnit.MILLISECONDS); + inOrder.verify(calledOp, times(1)).call(1000L); + + scheduler.advanceTimeBy(1999L, TimeUnit.MILLISECONDS); + inOrder.verify(calledOp, never()).call(3000L); + + scheduler.advanceTimeBy(1L, TimeUnit.MILLISECONDS); + inOrder.verify(calledOp, times(1)).call(3000L); + + scheduler.advanceTimeBy(5L, TimeUnit.SECONDS); + inOrder.verify(calledOp, times(1)).call(5000L); + inOrder.verify(calledOp, times(1)).call(7000L); + + subscription.unsubscribe(); + scheduler.advanceTimeBy(11L, TimeUnit.SECONDS); + inOrder.verify(calledOp, never()).call(anyLong()); + } @Test public void testComputationThreadPool1() { @@ -361,8 +404,8 @@ public void testSchedulingWithDueTime() throws InterruptedException { @Override public Subscription call(Scheduler scheduler, String state) { System.out.println("doing work"); - latch.countDown(); counter.incrementAndGet(); + latch.countDown(); if (latch.getCount() == 0) { return Subscriptions.empty(); } else { @@ -474,6 +517,26 @@ public Subscription onSubscribe(final Observer observer) { } } + @Test + public void testRecursion() { + TestScheduler s = new TestScheduler(); + + final AtomicInteger counter = new AtomicInteger(0); + + Subscription subscription = s.schedule(new Action1() { + + @Override + public void call(Action0 self) { + counter.incrementAndGet(); + System.out.println("counter: " + counter.get()); + self.call(); + } + + }); + subscription.unsubscribe(); + assertEquals(0, counter.get()); + } + /** * Used to determine if onNext is being invoked concurrently. * diff --git a/rxjava-core/src/test/java/rx/StartWithTests.java b/rxjava-core/src/test/java/rx/StartWithTests.java index de7b03e6ef..5bf3c518c2 100644 --- a/rxjava-core/src/test/java/rx/StartWithTests.java +++ b/rxjava-core/src/test/java/rx/StartWithTests.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx; import static org.junit.Assert.*; @@ -16,7 +31,7 @@ public void startWith1() { assertEquals("zero", values.get(0)); assertEquals("two", values.get(2)); } - + @Test public void startWithIterable() { List li = new ArrayList(); diff --git a/rxjava-core/src/test/java/rx/ThrottleFirstTests.java b/rxjava-core/src/test/java/rx/ThrottleFirstTests.java index 5e8f3ef1cb..655754d398 100644 --- a/rxjava-core/src/test/java/rx/ThrottleFirstTests.java +++ b/rxjava-core/src/test/java/rx/ThrottleFirstTests.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx; import static org.mockito.Mockito.*; diff --git a/rxjava-core/src/test/java/rx/ThrottleLastTests.java b/rxjava-core/src/test/java/rx/ThrottleLastTests.java index 742bbc09ba..c3a037a78c 100644 --- a/rxjava-core/src/test/java/rx/ThrottleLastTests.java +++ b/rxjava-core/src/test/java/rx/ThrottleLastTests.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx; import static org.mockito.Mockito.*; diff --git a/rxjava-core/src/test/java/rx/ThrottleWithTimeoutTests.java b/rxjava-core/src/test/java/rx/ThrottleWithTimeoutTests.java index 3503fe7adb..ead4ddb24e 100644 --- a/rxjava-core/src/test/java/rx/ThrottleWithTimeoutTests.java +++ b/rxjava-core/src/test/java/rx/ThrottleWithTimeoutTests.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx; import static org.mockito.Mockito.*; diff --git a/rxjava-core/src/test/java/rx/TimeoutTests.java b/rxjava-core/src/test/java/rx/TimeoutTests.java new file mode 100644 index 0000000000..46a3620ce5 --- /dev/null +++ b/rxjava-core/src/test/java/rx/TimeoutTests.java @@ -0,0 +1,226 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.MockitoAnnotations; + +import rx.concurrency.TestScheduler; +import rx.subjects.PublishSubject; + +public class TimeoutTests { + private PublishSubject underlyingSubject; + private TestScheduler testScheduler; + private Observable withTimeout; + private static final long TIMEOUT = 3; + private static final TimeUnit TIME_UNIT = TimeUnit.SECONDS; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + underlyingSubject = PublishSubject.create(); + testScheduler = new TestScheduler(); + withTimeout = underlyingSubject.timeout(TIMEOUT, TIME_UNIT, testScheduler); + } + + @Test + public void shouldNotTimeoutIfOnNextWithinTimeout() { + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + Subscription subscription = withTimeout.subscribe(observer); + testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); + underlyingSubject.onNext("One"); + verify(observer).onNext("One"); + testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); + verify(observer, never()).onError(any(Throwable.class)); + subscription.unsubscribe(); + } + + @Test + public void shouldNotTimeoutIfSecondOnNextWithinTimeout() { + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + Subscription subscription = withTimeout.subscribe(observer); + testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); + underlyingSubject.onNext("One"); + testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); + underlyingSubject.onNext("Two"); + verify(observer).onNext("Two"); + testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); + verify(observer, never()).onError(any(Throwable.class)); + subscription.unsubscribe(); + } + + @Test + public void shouldTimeoutIfOnNextNotWithinTimeout() { + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + Subscription subscription = withTimeout.subscribe(observer); + testScheduler.advanceTimeBy(TIMEOUT + 1, TimeUnit.SECONDS); + verify(observer).onError(any(TimeoutException.class)); + subscription.unsubscribe(); + } + + @Test + public void shouldTimeoutIfSecondOnNextNotWithinTimeout() { + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + Subscription subscription = withTimeout.subscribe(observer); + testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); + underlyingSubject.onNext("One"); + verify(observer).onNext("One"); + testScheduler.advanceTimeBy(TIMEOUT + 1, TimeUnit.SECONDS); + verify(observer).onError(any(TimeoutException.class)); + subscription.unsubscribe(); + } + + @Test + public void shouldCompleteIfUnderlyingComletes() { + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + Subscription subscription = withTimeout.subscribe(observer); + testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); + underlyingSubject.onCompleted(); + testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); + verify(observer).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + subscription.unsubscribe(); + } + + @Test + public void shouldErrorIfUnderlyingErrors() { + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + Subscription subscription = withTimeout.subscribe(observer); + testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); + underlyingSubject.onError(new UnsupportedOperationException()); + testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); + verify(observer).onError(any(UnsupportedOperationException.class)); + subscription.unsubscribe(); + } + + @Test + public void shouldSwitchToOtherIfOnNextNotWithinTimeout() { + Observable other = Observable.from("a", "b", "c"); + Observable source = underlyingSubject.timeout(TIMEOUT, TIME_UNIT, other, testScheduler); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + Subscription subscription = source.subscribe(observer); + + testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); + underlyingSubject.onNext("One"); + testScheduler.advanceTimeBy(4, TimeUnit.SECONDS); + underlyingSubject.onNext("Two"); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext("One"); + inOrder.verify(observer, times(1)).onNext("a"); + inOrder.verify(observer, times(1)).onNext("b"); + inOrder.verify(observer, times(1)).onNext("c"); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + subscription.unsubscribe(); + } + + @Test + public void shouldSwitchToOtherIfOnErrorNotWithinTimeout() { + Observable other = Observable.from("a", "b", "c"); + Observable source = underlyingSubject.timeout(TIMEOUT, TIME_UNIT, other, testScheduler); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + Subscription subscription = source.subscribe(observer); + + testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); + underlyingSubject.onNext("One"); + testScheduler.advanceTimeBy(4, TimeUnit.SECONDS); + underlyingSubject.onError(new UnsupportedOperationException()); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext("One"); + inOrder.verify(observer, times(1)).onNext("a"); + inOrder.verify(observer, times(1)).onNext("b"); + inOrder.verify(observer, times(1)).onNext("c"); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + subscription.unsubscribe(); + } + + @Test + public void shouldSwitchToOtherIfOnCompletedNotWithinTimeout() { + Observable other = Observable.from("a", "b", "c"); + Observable source = underlyingSubject.timeout(TIMEOUT, TIME_UNIT, other, testScheduler); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + Subscription subscription = source.subscribe(observer); + + testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); + underlyingSubject.onNext("One"); + testScheduler.advanceTimeBy(4, TimeUnit.SECONDS); + underlyingSubject.onCompleted(); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext("One"); + inOrder.verify(observer, times(1)).onNext("a"); + inOrder.verify(observer, times(1)).onNext("b"); + inOrder.verify(observer, times(1)).onNext("c"); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + subscription.unsubscribe(); + } + + @Test + public void shouldSwitchToOtherAndCanBeUnsubscribedIfOnNextNotWithinTimeout() { + PublishSubject other = PublishSubject.create(); + Observable source = underlyingSubject.timeout(TIMEOUT, TIME_UNIT, other, testScheduler); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + Subscription subscription = source.subscribe(observer); + + testScheduler.advanceTimeBy(2, TimeUnit.SECONDS); + underlyingSubject.onNext("One"); + testScheduler.advanceTimeBy(4, TimeUnit.SECONDS); + underlyingSubject.onNext("Two"); + + other.onNext("a"); + other.onNext("b"); + subscription.unsubscribe(); + + // The following messages should not be delivered. + other.onNext("c"); + other.onNext("d"); + other.onCompleted(); + + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext("One"); + inOrder.verify(observer, times(1)).onNext("a"); + inOrder.verify(observer, times(1)).onNext("b"); + inOrder.verifyNoMoreInteractions(); + } +} diff --git a/rxjava-core/src/test/java/rx/ZipTests.java b/rxjava-core/src/test/java/rx/ZipTests.java index 64d96c904a..dd406ee2e0 100644 --- a/rxjava-core/src/test/java/rx/ZipTests.java +++ b/rxjava-core/src/test/java/rx/ZipTests.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx; import java.util.HashMap; diff --git a/rxjava-core/src/test/java/rx/concurrency/CurrentThreadSchedulerTest.java b/rxjava-core/src/test/java/rx/concurrency/CurrentThreadSchedulerTest.java new file mode 100644 index 0000000000..3613b7c592 --- /dev/null +++ b/rxjava-core/src/test/java/rx/concurrency/CurrentThreadSchedulerTest.java @@ -0,0 +1,143 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.concurrency; + +import static org.mockito.Mockito.*; + +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.mockito.InOrder; + +import rx.util.functions.Action0; + +public class CurrentThreadSchedulerTest { + + @Test + public void testNestedActions() { + final CurrentThreadScheduler scheduler = new CurrentThreadScheduler(); + + final Action0 firstStepStart = mock(Action0.class); + final Action0 firstStepEnd = mock(Action0.class); + + final Action0 secondStepStart = mock(Action0.class); + final Action0 secondStepEnd = mock(Action0.class); + + final Action0 thirdStepStart = mock(Action0.class); + final Action0 thirdStepEnd = mock(Action0.class); + + final Action0 firstAction = new Action0() { + @Override + public void call() { + firstStepStart.call(); + firstStepEnd.call(); + } + }; + final Action0 secondAction = new Action0() { + @Override + public void call() { + secondStepStart.call(); + scheduler.schedule(firstAction); + secondStepEnd.call(); + + } + }; + final Action0 thirdAction = new Action0() { + @Override + public void call() { + thirdStepStart.call(); + scheduler.schedule(secondAction); + thirdStepEnd.call(); + } + }; + + InOrder inOrder = inOrder(firstStepStart, firstStepEnd, secondStepStart, secondStepEnd, thirdStepStart, thirdStepEnd); + + scheduler.schedule(thirdAction); + + inOrder.verify(thirdStepStart, times(1)).call(); + inOrder.verify(thirdStepEnd, times(1)).call(); + inOrder.verify(secondStepStart, times(1)).call(); + inOrder.verify(secondStepEnd, times(1)).call(); + inOrder.verify(firstStepStart, times(1)).call(); + inOrder.verify(firstStepEnd, times(1)).call(); + } + + @Test + public void testSequenceOfActions() { + final CurrentThreadScheduler scheduler = new CurrentThreadScheduler(); + + final Action0 first = mock(Action0.class); + final Action0 second = mock(Action0.class); + + scheduler.schedule(first); + scheduler.schedule(second); + + verify(first, times(1)).call(); + verify(second, times(1)).call(); + + } + + @Test + public void testSequenceOfDelayedActions() { + final CurrentThreadScheduler scheduler = new CurrentThreadScheduler(); + + final Action0 first = mock(Action0.class); + final Action0 second = mock(Action0.class); + + scheduler.schedule(new Action0() { + @Override + public void call() { + scheduler.schedule(first, 30, TimeUnit.MILLISECONDS); + scheduler.schedule(second, 10, TimeUnit.MILLISECONDS); + } + }); + + InOrder inOrder = inOrder(first, second); + + inOrder.verify(second, times(1)).call(); + inOrder.verify(first, times(1)).call(); + + } + + @Test + public void testMixOfDelayedAndNonDelayedActions() { + final CurrentThreadScheduler scheduler = new CurrentThreadScheduler(); + + final Action0 first = mock(Action0.class); + final Action0 second = mock(Action0.class); + final Action0 third = mock(Action0.class); + final Action0 fourth = mock(Action0.class); + + scheduler.schedule(new Action0() { + @Override + public void call() { + scheduler.schedule(first); + scheduler.schedule(second, 300, TimeUnit.MILLISECONDS); + scheduler.schedule(third, 100, TimeUnit.MILLISECONDS); + scheduler.schedule(fourth); + } + }); + + InOrder inOrder = inOrder(first, second, third, fourth); + + inOrder.verify(first, times(1)).call(); + inOrder.verify(fourth, times(1)).call(); + inOrder.verify(third, times(1)).call(); + inOrder.verify(second, times(1)).call(); + + } +} diff --git a/rxjava-core/src/test/java/rx/concurrency/ImmediateSchedulerTest.java b/rxjava-core/src/test/java/rx/concurrency/ImmediateSchedulerTest.java new file mode 100644 index 0000000000..593f6b6a52 --- /dev/null +++ b/rxjava-core/src/test/java/rx/concurrency/ImmediateSchedulerTest.java @@ -0,0 +1,75 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.concurrency; + +import static org.mockito.Mockito.*; + +import org.junit.Test; +import org.mockito.InOrder; + +import rx.util.functions.Action0; + +public class ImmediateSchedulerTest { + @Test + public void testNestedActions() { + final ImmediateScheduler scheduler = new ImmediateScheduler(); + + final Action0 firstStepStart = mock(Action0.class); + final Action0 firstStepEnd = mock(Action0.class); + + final Action0 secondStepStart = mock(Action0.class); + final Action0 secondStepEnd = mock(Action0.class); + + final Action0 thirdStepStart = mock(Action0.class); + final Action0 thirdStepEnd = mock(Action0.class); + + final Action0 firstAction = new Action0() { + @Override + public void call() { + firstStepStart.call(); + firstStepEnd.call(); + } + }; + final Action0 secondAction = new Action0() { + @Override + public void call() { + secondStepStart.call(); + scheduler.schedule(firstAction); + secondStepEnd.call(); + + } + }; + final Action0 thirdAction = new Action0() { + @Override + public void call() { + thirdStepStart.call(); + scheduler.schedule(secondAction); + thirdStepEnd.call(); + } + }; + + InOrder inOrder = inOrder(firstStepStart, firstStepEnd, secondStepStart, secondStepEnd, thirdStepStart, thirdStepEnd); + + scheduler.schedule(thirdAction); + + inOrder.verify(thirdStepStart, times(1)).call(); + inOrder.verify(secondStepStart, times(1)).call(); + inOrder.verify(firstStepStart, times(1)).call(); + inOrder.verify(firstStepEnd, times(1)).call(); + inOrder.verify(secondStepEnd, times(1)).call(); + inOrder.verify(thirdStepEnd, times(1)).call(); + } +} diff --git a/rxjava-core/src/test/java/rx/concurrency/SchedulerUnsubscribeTest.java b/rxjava-core/src/test/java/rx/concurrency/SchedulerUnsubscribeTest.java new file mode 100644 index 0000000000..a89da84304 --- /dev/null +++ b/rxjava-core/src/test/java/rx/concurrency/SchedulerUnsubscribeTest.java @@ -0,0 +1,93 @@ +package rx.concurrency; + +import static org.junit.Assert.*; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; +import rx.Scheduler; +import rx.operators.SafeObservableSubscription; +import rx.util.functions.Func1; + +public class SchedulerUnsubscribeTest { + + /** + * Bug report: https://github.com/Netflix/RxJava/issues/431 + */ + @Test + public void testUnsubscribeOfNewThread() throws InterruptedException { + testUnSubscribeForScheduler(Schedulers.newThread()); + } + + @Test + public void testUnsubscribeOfThreadPoolForIO() throws InterruptedException { + testUnSubscribeForScheduler(Schedulers.threadPoolForIO()); + } + + @Test + public void testUnsubscribeOfThreadPoolForComputation() throws InterruptedException { + testUnSubscribeForScheduler(Schedulers.threadPoolForComputation()); + } + + @Test + public void testUnsubscribeOfImmediateThread() throws InterruptedException { + testUnSubscribeForScheduler(Schedulers.immediate()); + } + + @Test + public void testUnsubscribeOfCurrentThread() throws InterruptedException { + testUnSubscribeForScheduler(Schedulers.currentThread()); + } + + public void testUnSubscribeForScheduler(Scheduler scheduler) throws InterruptedException { + + final AtomicInteger countReceived = new AtomicInteger(); + final AtomicInteger countGenerated = new AtomicInteger(); + final SafeObservableSubscription s = new SafeObservableSubscription(); + final CountDownLatch latch = new CountDownLatch(1); + + s.wrap(Observable.interval(50, TimeUnit.MILLISECONDS) + .map(new Func1() { + @Override + public Long call(Long aLong) { + System.out.println("generated " + aLong); + countGenerated.incrementAndGet(); + return aLong; + } + }) + .subscribeOn(scheduler) + .observeOn(scheduler) + .subscribe(new Observer() { + @Override + public void onCompleted() { + System.out.println("--- completed"); + } + + @Override + public void onError(Throwable e) { + System.out.println("--- onError"); + } + + @Override + public void onNext(Long args) { + if (countReceived.incrementAndGet() == 2) { + s.unsubscribe(); + latch.countDown(); + } + System.out.println("==> Received " + args); + } + })); + + latch.await(1000, TimeUnit.MILLISECONDS); + + System.out.println("----------- it thinks it is finished ------------------ "); + Thread.sleep(100); + + assertEquals(2, countGenerated.get()); + } +} diff --git a/rxjava-core/src/test/java/rx/observables/BlockingObservableTest.java b/rxjava-core/src/test/java/rx/observables/BlockingObservableTest.java new file mode 100644 index 0000000000..f3e05189c1 --- /dev/null +++ b/rxjava-core/src/test/java/rx/observables/BlockingObservableTest.java @@ -0,0 +1,263 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.observables; + +import static org.junit.Assert.*; + +import java.util.Iterator; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.BooleanSubscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action1; +import rx.util.functions.Func1; + +public class BlockingObservableTest { + + @Mock + Observer w; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testLast() { + BlockingObservable obs = BlockingObservable.from(Observable.from("one", "two", "three")); + + assertEquals("three", obs.last()); + } + + @Test(expected = IllegalArgumentException.class) + public void testLastEmptyObservable() { + BlockingObservable obs = BlockingObservable.from(Observable.empty()); + obs.last(); + } + + @Test + public void testLastOrDefault() { + BlockingObservable observable = BlockingObservable.from(Observable.from(1, 0, -1)); + int last = observable.lastOrDefault(-100, new Func1() { + @Override + public Boolean call(Integer args) { + return args >= 0; + } + }); + assertEquals(0, last); + } + + @Test + public void testLastOrDefault1() { + BlockingObservable observable = BlockingObservable.from(Observable.from("one", "two", "three")); + assertEquals("three", observable.lastOrDefault("default")); + } + + @Test + public void testLastOrDefault2() { + BlockingObservable observable = BlockingObservable.from(Observable.empty()); + assertEquals("default", observable.lastOrDefault("default")); + } + + @Test + public void testLastOrDefaultWithPredicate() { + BlockingObservable observable = BlockingObservable.from(Observable.from(1, 0, -1)); + int last = observable.lastOrDefault(0, new Func1() { + @Override + public Boolean call(Integer args) { + return args < 0; + } + }); + + assertEquals(-1, last); + } + + @Test + public void testLastOrDefaultWrongPredicate() { + BlockingObservable observable = BlockingObservable.from(Observable.from(-1, -2, -3)); + int last = observable.lastOrDefault(0, new Func1() { + @Override + public Boolean call(Integer args) { + return args >= 0; + } + }); + assertEquals(0, last); + } + + @Test + public void testLastWithPredicate() { + BlockingObservable obs = BlockingObservable.from(Observable.from("one", "two", "three")); + + assertEquals("two", obs.last(new Func1() { + @Override + public Boolean call(String s) { + return s.length() == 3; + } + })); + } + + public void testSingle() { + BlockingObservable observable = BlockingObservable.from(Observable.from("one")); + assertEquals("one", observable.single()); + } + + @Test + public void testSingleDefault() { + BlockingObservable observable = BlockingObservable.from(Observable.empty()); + assertEquals("default", observable.singleOrDefault("default")); + } + + @Test(expected = IllegalStateException.class) + public void testSingleDefaultPredicateMatchesMoreThanOne() { + BlockingObservable.from(Observable.from("one", "two")).singleOrDefault("default", new Func1() { + @Override + public Boolean call(String args) { + return args.length() == 3; + } + }); + } + + @Test + public void testSingleDefaultPredicateMatchesNothing() { + BlockingObservable observable = BlockingObservable.from(Observable.from("one", "two")); + String result = observable.singleOrDefault("default", new Func1() { + @Override + public Boolean call(String args) { + return args.length() == 4; + } + }); + assertEquals("default", result); + } + + @Test(expected = IllegalStateException.class) + public void testSingleDefaultWithMoreThanOne() { + BlockingObservable observable = BlockingObservable.from(Observable.from("one", "two", "three")); + observable.singleOrDefault("default"); + } + + @Test + public void testSingleWithPredicateDefault() { + BlockingObservable observable = BlockingObservable.from(Observable.from("one", "two", "four")); + assertEquals("four", observable.single(new Func1() { + @Override + public Boolean call(String s) { + return s.length() == 4; + } + })); + } + + @Test(expected = IllegalStateException.class) + public void testSingleWrong() { + BlockingObservable observable = BlockingObservable.from(Observable.from(1, 2)); + observable.single(); + } + + @Test(expected = IllegalStateException.class) + public void testSingleWrongPredicate() { + BlockingObservable observable = BlockingObservable.from(Observable.from(-1)); + observable.single(new Func1() { + @Override + public Boolean call(Integer args) { + return args > 0; + } + }); + } + + @Test + public void testToIterable() { + BlockingObservable obs = BlockingObservable.from(Observable.from("one", "two", "three")); + + Iterator it = obs.toIterable().iterator(); + + assertEquals(true, it.hasNext()); + assertEquals("one", it.next()); + + assertEquals(true, it.hasNext()); + assertEquals("two", it.next()); + + assertEquals(true, it.hasNext()); + assertEquals("three", it.next()); + + assertEquals(false, it.hasNext()); + + } + + @Test(expected = TestException.class) + public void testToIterableWithException() { + BlockingObservable obs = BlockingObservable.from(Observable.create(new Observable.OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(Observer observer) { + observer.onNext("one"); + observer.onError(new TestException()); + return Subscriptions.empty(); + } + })); + + Iterator it = obs.toIterable().iterator(); + + assertEquals(true, it.hasNext()); + assertEquals("one", it.next()); + + assertEquals(true, it.hasNext()); + it.next(); + + } + + @Test + public void testForEachWithError() { + try { + BlockingObservable.from(Observable.create(new Observable.OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(final Observer observer) { + final BooleanSubscription subscription = new BooleanSubscription(); + new Thread(new Runnable() { + + @Override + public void run() { + observer.onNext("one"); + observer.onNext("two"); + observer.onNext("three"); + observer.onCompleted(); + } + }).start(); + return subscription; + } + })).forEach(new Action1() { + + @Override + public void call(String t1) { + throw new RuntimeException("fail"); + } + }); + fail("we expect an exception to be thrown"); + } catch (Throwable e) { + // do nothing as we expect this + } + } + + private static class TestException extends RuntimeException { + private static final long serialVersionUID = 1L; + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationAllTest.java b/rxjava-core/src/test/java/rx/operators/OperationAllTest.java new file mode 100644 index 0000000000..0b05ee1d4e --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationAllTest.java @@ -0,0 +1,100 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Mockito.*; +import static rx.operators.OperationAll.*; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; +import rx.util.functions.Func1; + +public class OperationAllTest { + + @Test + @SuppressWarnings("unchecked") + public void testAll() { + Observable obs = Observable.from("one", "two", "six"); + + Observer observer = mock(Observer.class); + Observable.create(all(obs, new Func1() { + @Override + public Boolean call(String s) { + return s.length() == 3; + } + })).subscribe(observer); + + verify(observer).onNext(true); + verify(observer).onCompleted(); + verifyNoMoreInteractions(observer); + } + + @Test + @SuppressWarnings("unchecked") + public void testNotAll() { + Observable obs = Observable.from("one", "two", "three", "six"); + + Observer observer = mock(Observer.class); + Observable.create(all(obs, new Func1() { + @Override + public Boolean call(String s) { + return s.length() == 3; + } + })).subscribe(observer); + + verify(observer).onNext(false); + verify(observer).onCompleted(); + verifyNoMoreInteractions(observer); + } + + @Test + @SuppressWarnings("unchecked") + public void testEmpty() { + Observable obs = Observable.empty(); + + Observer observer = mock(Observer.class); + Observable.create(all(obs, new Func1() { + @Override + public Boolean call(String s) { + return s.length() == 3; + } + })).subscribe(observer); + + verify(observer).onNext(true); + verify(observer).onCompleted(); + verifyNoMoreInteractions(observer); + } + + @Test + @SuppressWarnings("unchecked") + public void testError() { + Throwable error = new Throwable(); + Observable obs = Observable.error(error); + + Observer observer = mock(Observer.class); + Observable.create(all(obs, new Func1() { + @Override + public Boolean call(String s) { + return s.length() == 3; + } + })).subscribe(observer); + + verify(observer).onError(error); + verifyNoMoreInteractions(observer); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationAmbTest.java b/rxjava-core/src/test/java/rx/operators/OperationAmbTest.java new file mode 100644 index 0000000000..784d686198 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationAmbTest.java @@ -0,0 +1,157 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Mockito.*; +import static rx.operators.OperationAmb.*; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.concurrency.TestScheduler; +import rx.subscriptions.CompositeSubscription; +import rx.util.functions.Action0; + +public class OperationAmbTest { + + private TestScheduler scheduler; + + @Before + public void setUp() { + scheduler = new TestScheduler(); + } + + private Observable createObservable(final String[] values, + final long interval, final Throwable e) { + return Observable.create(new OnSubscribeFunc() { + + @Override + public Subscription onSubscribe( + final Observer observer) { + CompositeSubscription parentSubscription = new CompositeSubscription(); + long delay = interval; + for (final String value : values) { + parentSubscription.add(scheduler.schedule( + new Action0() { + @Override + public void call() { + observer.onNext(value); + } + }, delay, TimeUnit.MILLISECONDS)); + delay += interval; + } + parentSubscription.add(scheduler.schedule(new Action0() { + @Override + public void call() { + if (e == null) { + observer.onCompleted(); + } else { + observer.onError(e); + } + } + }, delay, TimeUnit.MILLISECONDS)); + return parentSubscription; + } + }); + } + + @Test + public void testAmb() { + Observable observable1 = createObservable(new String[] { + "1", "11", "111", "1111" }, 2000, null); + Observable observable2 = createObservable(new String[] { + "2", "22", "222", "2222" }, 1000, null); + Observable observable3 = createObservable(new String[] { + "3", "33", "333", "3333" }, 3000, null); + + Observable o = Observable.create(amb(observable1, + observable2, observable3)); + + @SuppressWarnings("unchecked") + Observer observer = (Observer) mock(Observer.class); + o.subscribe(observer); + + scheduler.advanceTimeBy(100000, TimeUnit.MILLISECONDS); + + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext("2"); + inOrder.verify(observer, times(1)).onNext("22"); + inOrder.verify(observer, times(1)).onNext("222"); + inOrder.verify(observer, times(1)).onNext("2222"); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testAmb2() { + IOException needHappenedException = new IOException( + "fake exception"); + Observable observable1 = createObservable(new String[] {}, + 2000, new IOException("fake exception")); + Observable observable2 = createObservable(new String[] { + "2", "22", "222", "2222" }, 1000, needHappenedException); + Observable observable3 = createObservable(new String[] {}, + 3000, new IOException("fake exception")); + + Observable o = Observable.create(amb(observable1, + observable2, observable3)); + + @SuppressWarnings("unchecked") + Observer observer = (Observer) mock(Observer.class); + o.subscribe(observer); + + scheduler.advanceTimeBy(100000, TimeUnit.MILLISECONDS); + + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext("2"); + inOrder.verify(observer, times(1)).onNext("22"); + inOrder.verify(observer, times(1)).onNext("222"); + inOrder.verify(observer, times(1)).onNext("2222"); + inOrder.verify(observer, times(1)).onError(needHappenedException); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testAmb3() { + Observable observable1 = createObservable(new String[] { + "1" }, 2000, null); + Observable observable2 = createObservable(new String[] {}, + 1000, null); + Observable observable3 = createObservable(new String[] { + "3" }, 3000, null); + + Observable o = Observable.create(amb(observable1, + observable2, observable3)); + + @SuppressWarnings("unchecked") + Observer observer = (Observer) mock(Observer.class); + o.subscribe(observer); + + scheduler.advanceTimeBy(100000, TimeUnit.MILLISECONDS); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationAnyTest.java b/rxjava-core/src/test/java/rx/operators/OperationAnyTest.java new file mode 100644 index 0000000000..f98149376a --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationAnyTest.java @@ -0,0 +1,197 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Mockito.*; +import static rx.operators.OperationAny.*; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; +import rx.util.functions.Func1; + +public class OperationAnyTest { + + @Test + public void testAnyWithTwoItems() { + Observable w = Observable.from(1, 2); + Observable observable = Observable.create(any(w)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, never()).onNext(false); + verify(aObserver, times(1)).onNext(true); + verify(aObserver, never()).onError(org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testIsEmptyWithTwoItems() { + Observable w = Observable.from(1, 2); + Observable observable = Observable.create(isEmpty(w)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, never()).onNext(true); + verify(aObserver, times(1)).onNext(false); + verify(aObserver, never()).onError(org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testAnyWithOneItem() { + Observable w = Observable.from(1); + Observable observable = Observable.create(any(w)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, never()).onNext(false); + verify(aObserver, times(1)).onNext(true); + verify(aObserver, never()).onError(org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testIsEmptyWithOneItem() { + Observable w = Observable.from(1); + Observable observable = Observable.create(isEmpty(w)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, never()).onNext(true); + verify(aObserver, times(1)).onNext(false); + verify(aObserver, never()).onError(org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testAnyWithEmpty() { + Observable w = Observable.empty(); + Observable observable = Observable.create(any(w)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext(false); + verify(aObserver, never()).onNext(true); + verify(aObserver, never()).onError(org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testIsEmptyWithEmpty() { + Observable w = Observable.empty(); + Observable observable = Observable.create(isEmpty(w)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext(true); + verify(aObserver, never()).onNext(false); + verify(aObserver, never()).onError(org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testAnyWithPredicate1() { + Observable w = Observable.from(1, 2, 3); + Observable observable = Observable.create(any(w, + new Func1() { + + @Override + public Boolean call(Integer t1) { + return t1 < 2; + } + })); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, never()).onNext(false); + verify(aObserver, times(1)).onNext(true); + verify(aObserver, never()).onError(org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testExists1() { + Observable w = Observable.from(1, 2, 3); + Observable observable = Observable.create(exists(w, + new Func1() { + + @Override + public Boolean call(Integer t1) { + return t1 < 2; + } + })); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, never()).onNext(false); + verify(aObserver, times(1)).onNext(true); + verify(aObserver, never()).onError(org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testAnyWithPredicate2() { + Observable w = Observable.from(1, 2, 3); + Observable observable = Observable.create(any(w, + new Func1() { + + @Override + public Boolean call(Integer t1) { + return t1 < 1; + } + })); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext(false); + verify(aObserver, never()).onNext(true); + verify(aObserver, never()).onError(org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testAnyWithEmptyAndPredicate() { + // If the source is empty, always output false. + Observable w = Observable.empty(); + Observable observable = Observable.create(any(w, + new Func1() { + + @Override + public Boolean call(Integer t1) { + return true; + } + })); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext(false); + verify(aObserver, never()).onNext(true); + verify(aObserver, never()).onError(org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationAverageTest.java b/rxjava-core/src/test/java/rx/operators/OperationAverageTest.java new file mode 100644 index 0000000000..357743da17 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationAverageTest.java @@ -0,0 +1,121 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationAverage.*; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; + +public class OperationAverageTest { + + @SuppressWarnings("unchecked") + Observer w = mock(Observer.class); + @SuppressWarnings("unchecked") + Observer wl = mock(Observer.class); + @SuppressWarnings("unchecked") + Observer wf = mock(Observer.class); + @SuppressWarnings("unchecked") + Observer wd = mock(Observer.class); + + @Test + public void testAverageOfAFewInts() throws Throwable { + Observable src = Observable.from(1, 2, 3, 4, 6); + average(src).subscribe(w); + + verify(w, times(1)).onNext(anyInt()); + verify(w).onNext(3); + verify(w, never()).onError(any(Throwable.class)); + verify(w, times(1)).onCompleted(); + } + + @Test + public void testEmptyAverage() throws Throwable { + Observable src = Observable.empty(); + average(src).subscribe(w); + + verify(w, never()).onNext(anyInt()); + verify(w, times(1)).onError(isA(IllegalArgumentException.class)); + verify(w, never()).onCompleted(); + } + + @Test + public void testAverageOfAFewLongs() throws Throwable { + Observable src = Observable.from(1L, 2L, 3L, 4L, 6L); + averageLongs(src).subscribe(wl); + + verify(wl, times(1)).onNext(anyLong()); + verify(wl).onNext(3L); + verify(wl, never()).onError(any(Throwable.class)); + verify(wl, times(1)).onCompleted(); + } + + @Test + public void testEmptyAverageLongs() throws Throwable { + Observable src = Observable.empty(); + averageLongs(src).subscribe(wl); + + verify(wl, never()).onNext(anyLong()); + verify(wl, times(1)).onError(isA(IllegalArgumentException.class)); + verify(wl, never()).onCompleted(); + } + + @Test + public void testAverageOfAFewFloats() throws Throwable { + Observable src = Observable.from(1.0f, 2.0f); + averageFloats(src).subscribe(wf); + + verify(wf, times(1)).onNext(anyFloat()); + verify(wf).onNext(1.5f); + verify(wf, never()).onError(any(Throwable.class)); + verify(wf, times(1)).onCompleted(); + } + + @Test + public void testEmptyAverageFloats() throws Throwable { + Observable src = Observable.empty(); + averageFloats(src).subscribe(wf); + + verify(wf, never()).onNext(anyFloat()); + verify(wf, times(1)).onError(isA(IllegalArgumentException.class)); + verify(wf, never()).onCompleted(); + } + + @Test + public void testAverageOfAFewDoubles() throws Throwable { + Observable src = Observable.from(1.0d, 2.0d); + averageDoubles(src).subscribe(wd); + + verify(wd, times(1)).onNext(anyDouble()); + verify(wd).onNext(1.5d); + verify(wd, never()).onError(any(Throwable.class)); + verify(wd, times(1)).onCompleted(); + } + + @Test + public void testEmptyAverageDoubles() throws Throwable { + Observable src = Observable.empty(); + averageDoubles(src).subscribe(wd); + + verify(wd, never()).onNext(anyDouble()); + verify(wd, times(1)).onError(isA(IllegalArgumentException.class)); + verify(wd, never()).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationBufferTest.java b/rxjava-core/src/test/java/rx/operators/OperationBufferTest.java new file mode 100644 index 0000000000..9318af8fd6 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationBufferTest.java @@ -0,0 +1,366 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static rx.operators.OperationBuffer.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.concurrency.TestScheduler; +import rx.subscriptions.Subscriptions; +import rx.util.Closing; +import rx.util.Closings; +import rx.util.Opening; +import rx.util.Openings; +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Func0; +import rx.util.functions.Func1; + +public class OperationBufferTest { + + private Observer> observer; + private TestScheduler scheduler; + + @Before + @SuppressWarnings("unchecked") + public void before() { + observer = Mockito.mock(Observer.class); + scheduler = new TestScheduler(); + } + + @Test + public void testComplete() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + observer.onCompleted(); + return Subscriptions.empty(); + } + }); + + Observable> buffered = Observable.create(buffer(source, 3, 3)); + buffered.subscribe(observer); + + Mockito.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); + Mockito.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); + Mockito.verify(observer, Mockito.times(1)).onCompleted(); + } + + @Test + public void testSkipAndCountOverlappingBuffers() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + observer.onNext("one"); + observer.onNext("two"); + observer.onNext("three"); + observer.onNext("four"); + observer.onNext("five"); + return Subscriptions.empty(); + } + }); + + Observable> buffered = Observable.create(buffer(source, 3, 1)); + buffered.subscribe(observer); + + InOrder inOrder = Mockito.inOrder(observer); + inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two", "three")); + inOrder.verify(observer, Mockito.times(1)).onNext(list("two", "three", "four")); + inOrder.verify(observer, Mockito.times(1)).onNext(list("three", "four", "five")); + inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); + inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); + inOrder.verify(observer, Mockito.never()).onCompleted(); + } + + @Test + public void testSkipAndCountGaplessBuffers() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + observer.onNext("one"); + observer.onNext("two"); + observer.onNext("three"); + observer.onNext("four"); + observer.onNext("five"); + observer.onCompleted(); + return Subscriptions.empty(); + } + }); + + Observable> buffered = Observable.create(buffer(source, 3, 3)); + buffered.subscribe(observer); + + InOrder inOrder = Mockito.inOrder(observer); + inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two", "three")); + inOrder.verify(observer, Mockito.times(1)).onNext(list("four", "five")); + inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); + inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); + inOrder.verify(observer, Mockito.times(1)).onCompleted(); + } + + @Test + public void testSkipAndCountBuffersWithGaps() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + observer.onNext("one"); + observer.onNext("two"); + observer.onNext("three"); + observer.onNext("four"); + observer.onNext("five"); + observer.onCompleted(); + return Subscriptions.empty(); + } + }); + + Observable> buffered = Observable.create(buffer(source, 2, 3)); + buffered.subscribe(observer); + + InOrder inOrder = Mockito.inOrder(observer); + inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two")); + inOrder.verify(observer, Mockito.times(1)).onNext(list("four", "five")); + inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); + inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); + inOrder.verify(observer, Mockito.times(1)).onCompleted(); + } + + @Test + public void testTimedAndCount() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + push(observer, "one", 10); + push(observer, "two", 90); + push(observer, "three", 110); + push(observer, "four", 190); + push(observer, "five", 210); + complete(observer, 250); + return Subscriptions.empty(); + } + }); + + Observable> buffered = Observable.create(buffer(source, 100, TimeUnit.MILLISECONDS, 2, scheduler)); + buffered.subscribe(observer); + + InOrder inOrder = Mockito.inOrder(observer); + scheduler.advanceTimeTo(100, TimeUnit.MILLISECONDS); + inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two")); + + scheduler.advanceTimeTo(200, TimeUnit.MILLISECONDS); + inOrder.verify(observer, Mockito.times(1)).onNext(list("three", "four")); + + scheduler.advanceTimeTo(300, TimeUnit.MILLISECONDS); + inOrder.verify(observer, Mockito.times(1)).onNext(list("five")); + inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); + inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); + inOrder.verify(observer, Mockito.times(1)).onCompleted(); + } + + @Test + public void testTimed() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + push(observer, "one", 98); + push(observer, "two", 99); + push(observer, "three", 100); + push(observer, "four", 101); + push(observer, "five", 102); + complete(observer, 150); + return Subscriptions.empty(); + } + }); + + Observable> buffered = Observable.create(buffer(source, 100, TimeUnit.MILLISECONDS, scheduler)); + buffered.subscribe(observer); + + InOrder inOrder = Mockito.inOrder(observer); + scheduler.advanceTimeTo(101, TimeUnit.MILLISECONDS); + inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two", "three")); + + scheduler.advanceTimeTo(201, TimeUnit.MILLISECONDS); + inOrder.verify(observer, Mockito.times(1)).onNext(list("four", "five")); + inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); + inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); + inOrder.verify(observer, Mockito.times(1)).onCompleted(); + } + + @Test + public void testObservableBasedOpenerAndCloser() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + push(observer, "one", 10); + push(observer, "two", 60); + push(observer, "three", 110); + push(observer, "four", 160); + push(observer, "five", 210); + complete(observer, 500); + return Subscriptions.empty(); + } + }); + + Observable openings = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + push(observer, Openings.create(), 50); + push(observer, Openings.create(), 200); + complete(observer, 250); + return Subscriptions.empty(); + } + }); + + Func1> closer = new Func1>() { + @Override + public Observable call(Opening opening) { + return Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + push(observer, Closings.create(), 100); + complete(observer, 101); + return Subscriptions.empty(); + } + }); + } + }; + + Observable> buffered = Observable.create(buffer(source, openings, closer)); + buffered.subscribe(observer); + + InOrder inOrder = Mockito.inOrder(observer); + scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); + inOrder.verify(observer, Mockito.times(1)).onNext(list("two", "three")); + inOrder.verify(observer, Mockito.times(1)).onNext(list("five")); + inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); + inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); + inOrder.verify(observer, Mockito.times(1)).onCompleted(); + } + + @Test + public void testObservableBasedCloser() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + push(observer, "one", 10); + push(observer, "two", 60); + push(observer, "three", 110); + push(observer, "four", 160); + push(observer, "five", 210); + complete(observer, 250); + return Subscriptions.empty(); + } + }); + + Func0> closer = new Func0>() { + @Override + public Observable call() { + return Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + push(observer, Closings.create(), 100); + complete(observer, 101); + return Subscriptions.empty(); + } + }); + } + }; + + Observable> buffered = Observable.create(buffer(source, closer)); + buffered.subscribe(observer); + + InOrder inOrder = Mockito.inOrder(observer); + scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); + inOrder.verify(observer, Mockito.times(1)).onNext(list("one", "two")); + inOrder.verify(observer, Mockito.times(1)).onNext(list("three", "four")); + inOrder.verify(observer, Mockito.times(1)).onNext(list("five")); + inOrder.verify(observer, Mockito.never()).onNext(Mockito.anyListOf(String.class)); + inOrder.verify(observer, Mockito.never()).onError(Mockito.any(Throwable.class)); + inOrder.verify(observer, Mockito.times(1)).onCompleted(); + } + + @Test + public void testLongTimeAction() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + LongTimeAction action = new LongTimeAction(latch); + Observable.from(1).buffer(10, TimeUnit.MILLISECONDS, 10) + .subscribe(action); + latch.await(); + assertFalse(action.fail); + } + + private static class LongTimeAction implements Action1> { + + CountDownLatch latch; + boolean fail = false; + + public LongTimeAction(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void call(List t1) { + try { + if (fail) { + return; + } + Thread.sleep(200); + } catch (InterruptedException e) { + fail = true; + } finally { + latch.countDown(); + } + } + } + + private List list(String... args) { + List list = new ArrayList(); + for (String arg : args) { + list.add(arg); + } + return list; + } + + private void push(final Observer observer, final T value, int delay) { + scheduler.schedule(new Action0() { + @Override + public void call() { + observer.onNext(value); + } + }, delay, TimeUnit.MILLISECONDS); + } + + private void complete(final Observer observer, int delay) { + scheduler.schedule(new Action0() { + @Override + public void call() { + observer.onCompleted(); + } + }, delay, TimeUnit.MILLISECONDS); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationCacheTest.java b/rxjava-core/src/test/java/rx/operators/OperationCacheTest.java new file mode 100644 index 0000000000..79a811b6de --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationCacheTest.java @@ -0,0 +1,87 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static rx.operators.OperationCache.*; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.BooleanSubscription; +import rx.util.functions.Action1; + +public class OperationCacheTest { + + @Test + public void testCache() throws InterruptedException { + final AtomicInteger counter = new AtomicInteger(); + Observable o = Observable.create(cache(Observable.create(new Observable.OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(final Observer observer) { + final BooleanSubscription subscription = new BooleanSubscription(); + new Thread(new Runnable() { + + @Override + public void run() { + counter.incrementAndGet(); + System.out.println("published observable being executed"); + observer.onNext("one"); + observer.onCompleted(); + } + }).start(); + return subscription; + } + }))); + + // we then expect the following 2 subscriptions to get that same value + final CountDownLatch latch = new CountDownLatch(2); + + // subscribe once + o.subscribe(new Action1() { + + @Override + public void call(String v) { + assertEquals("one", v); + System.out.println("v: " + v); + latch.countDown(); + } + }); + + // subscribe again + o.subscribe(new Action1() { + + @Override + public void call(String v) { + assertEquals("one", v); + System.out.println("v: " + v); + latch.countDown(); + } + }); + + if (!latch.await(1000, TimeUnit.MILLISECONDS)) { + fail("subscriptions did not receive values"); + } + assertEquals(1, counter.get()); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationCastTest.java b/rxjava-core/src/test/java/rx/operators/OperationCastTest.java new file mode 100644 index 0000000000..782fe6bc90 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationCastTest.java @@ -0,0 +1,56 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Mockito.*; +import static rx.operators.OperationCast.*; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; + +public class OperationCastTest { + + @Test + public void testCast() { + Observable source = Observable.from(1, 2); + Observable observable = Observable.create(cast(source, + Integer.class)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext(1); + verify(aObserver, times(1)).onNext(1); + verify(aObserver, never()).onError( + org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testCastWithWrongType() { + Observable source = Observable.from(1, 2); + Observable observable = Observable.create(cast(source, + Boolean.class)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onError( + org.mockito.Matchers.any(ClassCastException.class)); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationCombineLatestTest.java b/rxjava-core/src/test/java/rx/operators/OperationCombineLatestTest.java new file mode 100644 index 0000000000..c576337e1e --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationCombineLatestTest.java @@ -0,0 +1,636 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationCombineLatest.*; + +import java.util.Arrays; + +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Matchers; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.operators.OperationCombineLatest.Aggregator; +import rx.operators.OperationCombineLatest.CombineObserver; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.FuncN; + +public class OperationCombineLatestTest { + + @Test + public void testCombineLatestWithFunctionThatThrowsAnException() { + @SuppressWarnings("unchecked") + // mock calls don't do generics + Observer w = mock(Observer.class); + + TestObservable w1 = new TestObservable(); + TestObservable w2 = new TestObservable(); + + Observable combined = Observable.create(combineLatest(Observable.create(w1), Observable.create(w2), new Func2() { + @Override + public String call(String v1, String v2) { + throw new RuntimeException("I don't work."); + } + })); + combined.subscribe(w); + + w1.observer.onNext("first value of w1"); + w2.observer.onNext("first value of w2"); + + verify(w, never()).onNext(anyString()); + verify(w, never()).onCompleted(); + verify(w, times(1)).onError(Matchers. any()); + } + + @Test + public void testCombineLatestDifferentLengthObservableSequences1() { + @SuppressWarnings("unchecked") + // mock calls don't do generics + Observer w = mock(Observer.class); + + TestObservable w1 = new TestObservable(); + TestObservable w2 = new TestObservable(); + TestObservable w3 = new TestObservable(); + + Observable combineLatestW = Observable.create(combineLatest(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsCombineLatestFunction())); + combineLatestW.subscribe(w); + + /* simulate sending data */ + // once for w1 + w1.observer.onNext("1a"); + w2.observer.onNext("2a"); + w3.observer.onNext("3a"); + w1.observer.onCompleted(); + // twice for w2 + w2.observer.onNext("2b"); + w2.observer.onCompleted(); + // 4 times for w3 + w3.observer.onNext("3b"); + w3.observer.onNext("3c"); + w3.observer.onNext("3d"); + w3.observer.onCompleted(); + + /* we should have been called 4 times on the Observer */ + InOrder inOrder = inOrder(w); + inOrder.verify(w).onNext("1a2a3a"); + inOrder.verify(w).onNext("1a2b3a"); + inOrder.verify(w).onNext("1a2b3b"); + inOrder.verify(w).onNext("1a2b3c"); + inOrder.verify(w).onNext("1a2b3d"); + inOrder.verify(w, never()).onNext(anyString()); + inOrder.verify(w, times(1)).onCompleted(); + } + + @Test + public void testCombineLatestDifferentLengthObservableSequences2() { + @SuppressWarnings("unchecked") + Observer w = mock(Observer.class); + + TestObservable w1 = new TestObservable(); + TestObservable w2 = new TestObservable(); + TestObservable w3 = new TestObservable(); + + Observable combineLatestW = Observable.create(combineLatest(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsCombineLatestFunction())); + combineLatestW.subscribe(w); + + /* simulate sending data */ + // 4 times for w1 + w1.observer.onNext("1a"); + w1.observer.onNext("1b"); + w1.observer.onNext("1c"); + w1.observer.onNext("1d"); + w1.observer.onCompleted(); + // twice for w2 + w2.observer.onNext("2a"); + w2.observer.onNext("2b"); + w2.observer.onCompleted(); + // 1 times for w3 + w3.observer.onNext("3a"); + w3.observer.onCompleted(); + + /* we should have been called 1 time only on the Observer since we only combine the "latest" we don't go back and loop through others once completed */ + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext("1d2b3a"); + inOrder.verify(w, never()).onNext(anyString()); + + inOrder.verify(w, times(1)).onCompleted(); + + } + + @Test + public void testCombineLatestWithInterleavingSequences() { + @SuppressWarnings("unchecked") + Observer w = mock(Observer.class); + + TestObservable w1 = new TestObservable(); + TestObservable w2 = new TestObservable(); + TestObservable w3 = new TestObservable(); + + Observable combineLatestW = Observable.create(combineLatest(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsCombineLatestFunction())); + combineLatestW.subscribe(w); + + /* simulate sending data */ + w1.observer.onNext("1a"); + w2.observer.onNext("2a"); + w2.observer.onNext("2b"); + w3.observer.onNext("3a"); + + w1.observer.onNext("1b"); + w2.observer.onNext("2c"); + w2.observer.onNext("2d"); + w3.observer.onNext("3b"); + + w1.observer.onCompleted(); + w2.observer.onCompleted(); + w3.observer.onCompleted(); + + /* we should have been called 5 times on the Observer */ + InOrder inOrder = inOrder(w); + inOrder.verify(w).onNext("1a2b3a"); + inOrder.verify(w).onNext("1b2b3a"); + inOrder.verify(w).onNext("1b2c3a"); + inOrder.verify(w).onNext("1b2d3a"); + inOrder.verify(w).onNext("1b2d3b"); + + inOrder.verify(w, never()).onNext(anyString()); + inOrder.verify(w, times(1)).onCompleted(); + } + + /** + * Testing internal private logic due to the complexity so I want to use TDD to test as a I build it rather than relying purely on the overall functionality expected by the public methods. + */ + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregatorSimple() { + FuncN combineLatestFunction = getConcatCombineLatestFunction(); + /* create the aggregator which will execute the combineLatest function when all Observables provide values */ + Aggregator a = new Aggregator(combineLatestFunction); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + Observable.create(a).subscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + CombineObserver r1 = mock(CombineObserver.class); + CombineObserver r2 = mock(CombineObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "hello"); + a.next(r2, "world"); + + InOrder inOrder = inOrder(aObserver); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + inOrder.verify(aObserver, times(1)).onNext("helloworld"); + + a.next(r1, "hello "); + a.next(r2, "again"); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + inOrder.verify(aObserver, times(1)).onNext("hello again"); + + a.complete(r1); + a.complete(r2); + + inOrder.verify(aObserver, never()).onNext(anyString()); + verify(aObserver, times(1)).onCompleted(); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregatorDifferentSizedResultsWithOnComplete() { + FuncN combineLatestFunction = getConcatCombineLatestFunction(); + /* create the aggregator which will execute the combineLatest function when all Observables provide values */ + Aggregator a = new Aggregator(combineLatestFunction); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + Observable.create(a).subscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + CombineObserver r1 = mock(CombineObserver.class); + CombineObserver r2 = mock(CombineObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "hello"); + a.next(r2, "world"); + a.complete(r2); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + verify(aObserver, times(1)).onNext("helloworld"); + + a.next(r1, "hi"); + a.complete(r1); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verify(aObserver, times(1)).onNext("hiworld"); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregateMultipleTypes() { + FuncN combineLatestFunction = getConcatCombineLatestFunction(); + /* create the aggregator which will execute the combineLatest function when all Observables provide values */ + Aggregator a = new Aggregator(combineLatestFunction); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + Observable.create(a).subscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + CombineObserver r1 = mock(CombineObserver.class); + CombineObserver r2 = mock(CombineObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "hello"); + a.next(r2, "world"); + a.complete(r2); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + verify(aObserver, times(1)).onNext("helloworld"); + + a.next(r1, "hi"); + a.complete(r1); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verify(aObserver, times(1)).onNext("hiworld"); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregate3Types() { + FuncN combineLatestFunction = getConcatCombineLatestFunction(); + /* create the aggregator which will execute the combineLatest function when all Observables provide values */ + Aggregator a = new Aggregator(combineLatestFunction); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + Observable.create(a).subscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + CombineObserver r1 = mock(CombineObserver.class); + CombineObserver r2 = mock(CombineObserver.class); + CombineObserver r3 = mock(CombineObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + a.addObserver(r3); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "hello"); + a.next(r2, 2); + a.next(r3, new int[] { 5, 6, 7 }); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + verify(aObserver, times(1)).onNext("hello2[5, 6, 7]"); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregatorsWithDifferentSizesAndTiming() { + FuncN combineLatestFunction = getConcatCombineLatestFunction(); + /* create the aggregator which will execute the combineLatest function when all Observables provide values */ + Aggregator a = new Aggregator(combineLatestFunction); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + Observable.create(a).subscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + CombineObserver r1 = mock(CombineObserver.class); + CombineObserver r2 = mock(CombineObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "one"); + a.next(r1, "two"); + a.next(r1, "three"); + a.next(r2, "A"); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + verify(aObserver, times(1)).onNext("threeA"); + + a.next(r1, "four"); + a.complete(r1); + a.next(r2, "B"); + verify(aObserver, times(1)).onNext("fourB"); + a.next(r2, "C"); + verify(aObserver, times(1)).onNext("fourC"); + a.next(r2, "D"); + verify(aObserver, times(1)).onNext("fourD"); + a.next(r2, "E"); + verify(aObserver, times(1)).onNext("fourE"); + a.complete(r2); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregatorError() { + FuncN combineLatestFunction = getConcatCombineLatestFunction(); + /* create the aggregator which will execute the combineLatest function when all Observables provide values */ + Aggregator a = new Aggregator(combineLatestFunction); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + Observable.create(a).subscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + CombineObserver r1 = mock(CombineObserver.class); + CombineObserver r2 = mock(CombineObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "hello"); + a.next(r2, "world"); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + verify(aObserver, times(1)).onNext("helloworld"); + + a.error(new RuntimeException("")); + a.next(r1, "hello"); + a.next(r2, "again"); + + verify(aObserver, times(1)).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + // we don't want to be called again after an error + verify(aObserver, times(0)).onNext("helloagain"); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregatorUnsubscribe() { + FuncN combineLatestFunction = getConcatCombineLatestFunction(); + /* create the aggregator which will execute the combineLatest function when all Observables provide values */ + Aggregator a = new Aggregator(combineLatestFunction); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + Subscription subscription = Observable.create(a).subscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + CombineObserver r1 = mock(CombineObserver.class); + CombineObserver r2 = mock(CombineObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "hello"); + a.next(r2, "world"); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + verify(aObserver, times(1)).onNext("helloworld"); + + subscription.unsubscribe(); + a.next(r1, "hello"); + a.next(r2, "again"); + + verify(aObserver, times(0)).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + // we don't want to be called again after an error + verify(aObserver, times(0)).onNext("helloagain"); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregatorEarlyCompletion() { + FuncN combineLatestFunction = getConcatCombineLatestFunction(); + /* create the aggregator which will execute the combineLatest function when all Observables provide values */ + Aggregator a = new Aggregator(combineLatestFunction); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + Observable.create(a).subscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + CombineObserver r1 = mock(CombineObserver.class); + CombineObserver r2 = mock(CombineObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "one"); + a.next(r1, "two"); + a.complete(r1); + a.next(r2, "A"); + + InOrder inOrder = inOrder(aObserver); + + inOrder.verify(aObserver, never()).onError(any(Throwable.class)); + inOrder.verify(aObserver, never()).onCompleted(); + inOrder.verify(aObserver, times(1)).onNext("twoA"); + + a.complete(r2); + + inOrder.verify(aObserver, never()).onError(any(Throwable.class)); + inOrder.verify(aObserver, times(1)).onCompleted(); + // we shouldn't get this since completed is called before any other onNext calls could trigger this + inOrder.verify(aObserver, never()).onNext(anyString()); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testCombineLatest2Types() { + Func2 combineLatestFunction = getConcatStringIntegerCombineLatestFunction(); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + + Observable w = Observable.create(combineLatest(Observable.from("one", "two"), Observable.from(2, 3, 4), combineLatestFunction)); + w.subscribe(aObserver); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verify(aObserver, times(1)).onNext("two2"); + verify(aObserver, times(1)).onNext("two3"); + verify(aObserver, times(1)).onNext("two4"); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testCombineLatest3TypesA() { + Func3 combineLatestFunction = getConcatStringIntegerIntArrayCombineLatestFunction(); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + + Observable w = Observable.create(combineLatest(Observable.from("one", "two"), Observable.from(2), Observable.from(new int[] { 4, 5, 6 }), combineLatestFunction)); + w.subscribe(aObserver); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verify(aObserver, times(1)).onNext("two2[4, 5, 6]"); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testCombineLatest3TypesB() { + Func3 combineLatestFunction = getConcatStringIntegerIntArrayCombineLatestFunction(); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + + Observable w = Observable.create(combineLatest(Observable.from("one"), Observable.from(2), Observable.from(new int[] { 4, 5, 6 }, new int[] { 7, 8 }), combineLatestFunction)); + w.subscribe(aObserver); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verify(aObserver, times(1)).onNext("one2[4, 5, 6]"); + verify(aObserver, times(1)).onNext("one2[7, 8]"); + } + + private Func3 getConcat3StringsCombineLatestFunction() { + Func3 combineLatestFunction = new Func3() { + + @Override + public String call(String a1, String a2, String a3) { + if (a1 == null) { + a1 = ""; + } + if (a2 == null) { + a2 = ""; + } + if (a3 == null) { + a3 = ""; + } + return a1 + a2 + a3; + } + + }; + return combineLatestFunction; + } + + private FuncN getConcatCombineLatestFunction() { + FuncN combineLatestFunction = new FuncN() { + + @Override + public String call(Object... args) { + String returnValue = ""; + for (Object o : args) { + if (o != null) { + returnValue += getStringValue(o); + } + } + System.out.println("returning: " + returnValue); + return returnValue; + } + + }; + return combineLatestFunction; + } + + private Func2 getConcatStringIntegerCombineLatestFunction() { + Func2 combineLatestFunction = new Func2() { + + @Override + public String call(String s, Integer i) { + return getStringValue(s) + getStringValue(i); + } + + }; + return combineLatestFunction; + } + + private Func3 getConcatStringIntegerIntArrayCombineLatestFunction() { + Func3 combineLatestFunction = new Func3() { + + @Override + public String call(String s, Integer i, int[] iArray) { + return getStringValue(s) + getStringValue(i) + getStringValue(iArray); + } + + }; + return combineLatestFunction; + } + + private static String getStringValue(Object o) { + if (o == null) { + return ""; + } else { + if (o instanceof int[]) { + return Arrays.toString((int[]) o); + } else { + return String.valueOf(o); + } + } + } + + private static class TestObservable implements Observable.OnSubscribeFunc { + + Observer observer; + + @Override + public Subscription onSubscribe(Observer observer) { + // just store the variable where it can be accessed so we can manually trigger it + this.observer = observer; + return Subscriptions.empty(); + } + + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationConcatTest.java b/rxjava-core/src/test/java/rx/operators/OperationConcatTest.java new file mode 100644 index 0000000000..f42d8b81ad --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationConcatTest.java @@ -0,0 +1,559 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationConcat.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.BooleanSubscription; + +public class OperationConcatTest { + + @Test + public void testConcat() { + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + + final String[] o = { "1", "3", "5", "7" }; + final String[] e = { "2", "4", "6" }; + + final Observable odds = Observable.from(o); + final Observable even = Observable.from(e); + + @SuppressWarnings("unchecked") + Observable concat = Observable.create(concat(odds, even)); + concat.subscribe(observer); + + verify(observer, times(7)).onNext(anyString()); + } + + @Test + public void testConcatWithList() { + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + + final String[] o = { "1", "3", "5", "7" }; + final String[] e = { "2", "4", "6" }; + + final Observable odds = Observable.from(o); + final Observable even = Observable.from(e); + final List> list = new ArrayList>(); + list.add(odds); + list.add(even); + Observable concat = Observable.create(concat(list)); + concat.subscribe(observer); + + verify(observer, times(7)).onNext(anyString()); + } + + @Test + public void testConcatObservableOfObservables() { + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + + final String[] o = { "1", "3", "5", "7" }; + final String[] e = { "2", "4", "6" }; + + final Observable odds = Observable.from(o); + final Observable even = Observable.from(e); + + Observable> observableOfObservables = Observable.create(new Observable.OnSubscribeFunc>() { + + @Override + public Subscription onSubscribe(Observer> observer) { + // simulate what would happen in an observable + observer.onNext(odds); + observer.onNext(even); + observer.onCompleted(); + + return new Subscription() { + + @Override + public void unsubscribe() { + // unregister ... will never be called here since we are executing synchronously + } + + }; + } + + }); + Observable concat = Observable.create(concat(observableOfObservables)); + + concat.subscribe(observer); + + verify(observer, times(7)).onNext(anyString()); + } + + /** + * Simple concat of 2 asynchronous observables ensuring it emits in correct order. + */ + @SuppressWarnings("unchecked") + @Test + public void testSimpleAsyncConcat() { + Observer observer = mock(Observer.class); + + TestObservable o1 = new TestObservable("one", "two", "three"); + TestObservable o2 = new TestObservable("four", "five", "six"); + + Observable.concat(Observable.create(o1), Observable.create(o2)).subscribe(observer); + + try { + // wait for async observables to complete + o1.t.join(); + o2.t.join(); + } catch (Throwable e) { + throw new RuntimeException("failed waiting on threads"); + } + + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext("one"); + inOrder.verify(observer, times(1)).onNext("two"); + inOrder.verify(observer, times(1)).onNext("three"); + inOrder.verify(observer, times(1)).onNext("four"); + inOrder.verify(observer, times(1)).onNext("five"); + inOrder.verify(observer, times(1)).onNext("six"); + } + + /** + * Test an async Observable that emits more async Observables + */ + @SuppressWarnings("unchecked") + @Test + public void testNestedAsyncConcat() throws Throwable { + Observer observer = mock(Observer.class); + + final TestObservable o1 = new TestObservable("one", "two", "three"); + final TestObservable o2 = new TestObservable("four", "five", "six"); + final TestObservable o3 = new TestObservable("seven", "eight", "nine"); + final CountDownLatch allowThird = new CountDownLatch(1); + + final AtomicReference parent = new AtomicReference(); + Observable> observableOfObservables = Observable.create(new Observable.OnSubscribeFunc>() { + + @Override + public Subscription onSubscribe(final Observer> observer) { + final BooleanSubscription s = new BooleanSubscription(); + parent.set(new Thread(new Runnable() { + + @Override + public void run() { + try { + // emit first + if (!s.isUnsubscribed()) { + System.out.println("Emit o1"); + observer.onNext(Observable.create(o1)); + } + // emit second + if (!s.isUnsubscribed()) { + System.out.println("Emit o2"); + observer.onNext(Observable.create(o2)); + } + + // wait until sometime later and emit third + try { + allowThird.await(); + } catch (InterruptedException e) { + observer.onError(e); + } + if (!s.isUnsubscribed()) { + System.out.println("Emit o3"); + observer.onNext(Observable.create(o3)); + } + + } catch (Throwable e) { + observer.onError(e); + } finally { + System.out.println("Done parent Observable"); + observer.onCompleted(); + } + } + })); + parent.get().start(); + return s; + } + }); + + Observable.create(concat(observableOfObservables)).subscribe(observer); + + // wait for parent to start + while (parent.get() == null) { + Thread.sleep(1); + } + + try { + // wait for first 2 async observables to complete + while (o1.t == null) { + Thread.sleep(1); + } + System.out.println("Thread1 started ... waiting for it to complete ..."); + o1.t.join(); + while (o2.t == null) { + Thread.sleep(1); + } + System.out.println("Thread2 started ... waiting for it to complete ..."); + o2.t.join(); + } catch (Throwable e) { + throw new RuntimeException("failed waiting on threads", e); + } + + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext("one"); + inOrder.verify(observer, times(1)).onNext("two"); + inOrder.verify(observer, times(1)).onNext("three"); + inOrder.verify(observer, times(1)).onNext("four"); + inOrder.verify(observer, times(1)).onNext("five"); + inOrder.verify(observer, times(1)).onNext("six"); + // we shouldn't have the following 3 yet + inOrder.verify(observer, never()).onNext("seven"); + inOrder.verify(observer, never()).onNext("eight"); + inOrder.verify(observer, never()).onNext("nine"); + // we should not be completed yet + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + // now allow the third + allowThird.countDown(); + + try { + while (o3.t == null) { + Thread.sleep(1); + } + // wait for 3rd to complete + o3.t.join(); + } catch (Throwable e) { + throw new RuntimeException("failed waiting on threads", e); + } + + inOrder.verify(observer, times(1)).onNext("seven"); + inOrder.verify(observer, times(1)).onNext("eight"); + inOrder.verify(observer, times(1)).onNext("nine"); + + inOrder.verify(observer, times(1)).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @SuppressWarnings("unchecked") + @Test + public void testBlockedObservableOfObservables() { + Observer observer = mock(Observer.class); + + final String[] o = { "1", "3", "5", "7" }; + final String[] e = { "2", "4", "6" }; + final Observable odds = Observable.from(o); + final Observable even = Observable.from(e); + final CountDownLatch callOnce = new CountDownLatch(1); + final CountDownLatch okToContinue = new CountDownLatch(1); + TestObservable> observableOfObservables = new TestObservable>(callOnce, okToContinue, odds, even); + Observable.OnSubscribeFunc concatF = concat(Observable.create(observableOfObservables)); + Observable concat = Observable.create(concatF); + concat.subscribe(observer); + try { + //Block main thread to allow observables to serve up o1. + callOnce.await(); + } catch (Throwable ex) { + ex.printStackTrace(); + fail(ex.getMessage()); + } + // The concated observable should have served up all of the odds. + verify(observer, times(1)).onNext("1"); + verify(observer, times(1)).onNext("3"); + verify(observer, times(1)).onNext("5"); + verify(observer, times(1)).onNext("7"); + + try { + // unblock observables so it can serve up o2 and complete + okToContinue.countDown(); + observableOfObservables.t.join(); + } catch (Throwable ex) { + ex.printStackTrace(); + fail(ex.getMessage()); + } + // The concatenated observable should now have served up all the evens. + verify(observer, times(1)).onNext("2"); + verify(observer, times(1)).onNext("4"); + verify(observer, times(1)).onNext("6"); + } + + @Test + public void testConcatConcurrentWithInfinity() { + final TestObservable w1 = new TestObservable("one", "two", "three"); + //This observable will send "hello" MAX_VALUE time. + final TestObservable w2 = new TestObservable("hello", Integer.MAX_VALUE); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + @SuppressWarnings("unchecked") + TestObservable> observableOfObservables = new TestObservable>(Observable.create(w1), Observable.create(w2)); + Observable.OnSubscribeFunc concatF = concat(Observable.create(observableOfObservables)); + + Observable concat = Observable.create(concatF); + + concat.take(50).subscribe(aObserver); + + //Wait for the thread to start up. + try { + Thread.sleep(25); + w1.t.join(); + w2.t.join(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + InOrder inOrder = inOrder(aObserver); + inOrder.verify(aObserver, times(1)).onNext("one"); + inOrder.verify(aObserver, times(1)).onNext("two"); + inOrder.verify(aObserver, times(1)).onNext("three"); + inOrder.verify(aObserver, times(47)).onNext("hello"); + verify(aObserver, times(1)).onCompleted(); + verify(aObserver, never()).onError(any(Throwable.class)); + } + + @Test + public void testConcatNonBlockingObservables() { + + final CountDownLatch okToContinueW1 = new CountDownLatch(1); + final CountDownLatch okToContinueW2 = new CountDownLatch(1); + + final TestObservable w1 = new TestObservable(null, okToContinueW1, "one", "two", "three"); + final TestObservable w2 = new TestObservable(null, okToContinueW2, "four", "five", "six"); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + Observable> observableOfObservables = Observable.create(new Observable.OnSubscribeFunc>() { + + @Override + public Subscription onSubscribe(Observer> observer) { + // simulate what would happen in an observable + observer.onNext(Observable.create(w1)); + observer.onNext(Observable.create(w2)); + observer.onCompleted(); + + return new Subscription() { + + @Override + public void unsubscribe() { + } + + }; + } + + }); + Observable concat = Observable.create(concat(observableOfObservables)); + concat.subscribe(aObserver); + + verify(aObserver, times(0)).onCompleted(); + + try { + // release both threads + okToContinueW1.countDown(); + okToContinueW2.countDown(); + // wait for both to finish + w1.t.join(); + w2.t.join(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + InOrder inOrder = inOrder(aObserver); + inOrder.verify(aObserver, times(1)).onNext("one"); + inOrder.verify(aObserver, times(1)).onNext("two"); + inOrder.verify(aObserver, times(1)).onNext("three"); + inOrder.verify(aObserver, times(1)).onNext("four"); + inOrder.verify(aObserver, times(1)).onNext("five"); + inOrder.verify(aObserver, times(1)).onNext("six"); + verify(aObserver, times(1)).onCompleted(); + + } + + /** + * Test unsubscribing the concatenated Observable in a single thread. + */ + @Test + public void testConcatUnsubscribe() { + final CountDownLatch callOnce = new CountDownLatch(1); + final CountDownLatch okToContinue = new CountDownLatch(1); + final TestObservable w1 = new TestObservable("one", "two", "three"); + final TestObservable w2 = new TestObservable(callOnce, okToContinue, "four", "five", "six"); + + @SuppressWarnings("unchecked") + final Observer aObserver = mock(Observer.class); + @SuppressWarnings("unchecked") + final Observable concat = Observable.create(concat(Observable.create(w1), Observable.create(w2))); + final SafeObservableSubscription s1 = new SafeObservableSubscription(); + + try { + // Subscribe + s1.wrap(concat.subscribe(aObserver)); + //Block main thread to allow observable "w1" to complete and observable "w2" to call onNext once. + callOnce.await(); + // Unsubcribe + s1.unsubscribe(); + //Unblock the observable to continue. + okToContinue.countDown(); + w1.t.join(); + w2.t.join(); + } catch (Throwable e) { + e.printStackTrace(); + fail(e.getMessage()); + } + + InOrder inOrder = inOrder(aObserver); + inOrder.verify(aObserver, times(1)).onNext("one"); + inOrder.verify(aObserver, times(1)).onNext("two"); + inOrder.verify(aObserver, times(1)).onNext("three"); + inOrder.verify(aObserver, times(1)).onNext("four"); + inOrder.verify(aObserver, never()).onNext("five"); + inOrder.verify(aObserver, never()).onNext("six"); + inOrder.verify(aObserver, never()).onCompleted(); + + } + + /** + * All observables will be running in different threads so subscribe() is unblocked. CountDownLatch is only used in order to call unsubscribe() in a predictable manner. + */ + @Test + public void testConcatUnsubscribeConcurrent() { + final CountDownLatch callOnce = new CountDownLatch(1); + final CountDownLatch okToContinue = new CountDownLatch(1); + final TestObservable w1 = new TestObservable("one", "two", "three"); + final TestObservable w2 = new TestObservable(callOnce, okToContinue, "four", "five", "six"); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + @SuppressWarnings("unchecked") + TestObservable> observableOfObservables = new TestObservable>(Observable.create(w1), Observable.create(w2)); + Observable.OnSubscribeFunc concatF = concat(Observable.create(observableOfObservables)); + + Observable concat = Observable.create(concatF); + + Subscription s1 = concat.subscribe(aObserver); + + try { + //Block main thread to allow observable "w1" to complete and observable "w2" to call onNext exactly once. + callOnce.await(); + //"four" from w2 has been processed by onNext() + s1.unsubscribe(); + //"five" and "six" will NOT be processed by onNext() + //Unblock the observable to continue. + okToContinue.countDown(); + w1.t.join(); + w2.t.join(); + } catch (Throwable e) { + e.printStackTrace(); + fail(e.getMessage()); + } + + InOrder inOrder = inOrder(aObserver); + inOrder.verify(aObserver, times(1)).onNext("one"); + inOrder.verify(aObserver, times(1)).onNext("two"); + inOrder.verify(aObserver, times(1)).onNext("three"); + inOrder.verify(aObserver, times(1)).onNext("four"); + inOrder.verify(aObserver, never()).onNext("five"); + inOrder.verify(aObserver, never()).onNext("six"); + verify(aObserver, never()).onCompleted(); + verify(aObserver, never()).onError(any(Throwable.class)); + } + + private static class TestObservable implements Observable.OnSubscribeFunc { + + private final Subscription s = new Subscription() { + + @Override + public void unsubscribe() { + subscribed = false; + } + + }; + private final List values; + private Thread t = null; + private int count = 0; + private boolean subscribed = true; + private final CountDownLatch once; + private final CountDownLatch okToContinue; + private final T seed; + private final int size; + + public TestObservable(T... values) { + this(null, null, values); + } + + public TestObservable(CountDownLatch once, CountDownLatch okToContinue, T... values) { + this.values = Arrays.asList(values); + this.size = this.values.size(); + this.once = once; + this.okToContinue = okToContinue; + this.seed = null; + } + + public TestObservable(T seed, int size) { + values = null; + once = null; + okToContinue = null; + this.seed = seed; + this.size = size; + } + + @Override + public Subscription onSubscribe(final Observer observer) { + t = new Thread(new Runnable() { + + @Override + public void run() { + try { + while (count < size && subscribed) { + if (null != values) + observer.onNext(values.get(count)); + else + observer.onNext(seed); + count++; + //Unblock the main thread to call unsubscribe. + if (null != once) + once.countDown(); + //Block until the main thread has called unsubscribe. + if (null != okToContinue) + okToContinue.await(5, TimeUnit.SECONDS); + } + if (subscribed) + observer.onCompleted(); + } catch (InterruptedException e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + + }); + t.start(); + return s; + } + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationDebounceTest.java b/rxjava-core/src/test/java/rx/operators/OperationDebounceTest.java new file mode 100644 index 0000000000..e831c7b829 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationDebounceTest.java @@ -0,0 +1,161 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.concurrency.TestScheduler; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action0; + +public class OperationDebounceTest { + + private TestScheduler scheduler; + private Observer observer; + + @Before + @SuppressWarnings("unchecked") + public void before() { + scheduler = new TestScheduler(); + observer = mock(Observer.class); + } + + @Test + public void testDebounceWithCompleted() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + publishNext(observer, 100, "one"); // Should be skipped since "two" will arrive before the timeout expires. + publishNext(observer, 400, "two"); // Should be published since "three" will arrive after the timeout expires. + publishNext(observer, 900, "three"); // Should be skipped since onCompleted will arrive before the timeout expires. + publishCompleted(observer, 1000); // Should be published as soon as the timeout expires. + + return Subscriptions.empty(); + } + }); + + Observable sampled = Observable.create(OperationDebounce.debounce(source, 400, TimeUnit.MILLISECONDS, scheduler)); + sampled.subscribe(observer); + + scheduler.advanceTimeTo(0, TimeUnit.MILLISECONDS); + InOrder inOrder = inOrder(observer); + // must go to 800 since it must be 400 after when two is sent, which is at 400 + scheduler.advanceTimeTo(800, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext("two"); + scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testDebounceNeverEmits() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + // all should be skipped since they are happening faster than the 200ms timeout + publishNext(observer, 100, "a"); // Should be skipped + publishNext(observer, 200, "b"); // Should be skipped + publishNext(observer, 300, "c"); // Should be skipped + publishNext(observer, 400, "d"); // Should be skipped + publishNext(observer, 500, "e"); // Should be skipped + publishNext(observer, 600, "f"); // Should be skipped + publishNext(observer, 700, "g"); // Should be skipped + publishNext(observer, 800, "h"); // Should be skipped + publishCompleted(observer, 900); // Should be published as soon as the timeout expires. + + return Subscriptions.empty(); + } + }); + + Observable sampled = Observable.create(OperationDebounce.debounce(source, 200, TimeUnit.MILLISECONDS, scheduler)); + sampled.subscribe(observer); + + scheduler.advanceTimeTo(0, TimeUnit.MILLISECONDS); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(0)).onNext(anyString()); + scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testDebounceWithError() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + Exception error = new TestException(); + publishNext(observer, 100, "one"); // Should be published since "two" will arrive after the timeout expires. + publishNext(observer, 600, "two"); // Should be skipped since onError will arrive before the timeout expires. + publishError(observer, 700, error); // Should be published as soon as the timeout expires. + + return Subscriptions.empty(); + } + }); + + Observable sampled = Observable.create(OperationDebounce.debounce(source, 400, TimeUnit.MILLISECONDS, scheduler)); + sampled.subscribe(observer); + + scheduler.advanceTimeTo(0, TimeUnit.MILLISECONDS); + InOrder inOrder = inOrder(observer); + // 100 + 400 means it triggers at 500 + scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); + inOrder.verify(observer).onNext("one"); + scheduler.advanceTimeTo(701, TimeUnit.MILLISECONDS); + inOrder.verify(observer).onError(any(TestException.class)); + inOrder.verifyNoMoreInteractions(); + } + + private void publishCompleted(final Observer observer, long delay) { + scheduler.schedule(new Action0() { + @Override + public void call() { + observer.onCompleted(); + } + }, delay, TimeUnit.MILLISECONDS); + } + + private void publishError(final Observer observer, long delay, final Exception error) { + scheduler.schedule(new Action0() { + @Override + public void call() { + observer.onError(error); + } + }, delay, TimeUnit.MILLISECONDS); + } + + private void publishNext(final Observer observer, final long delay, final T value) { + scheduler.schedule(new Action0() { + @Override + public void call() { + observer.onNext(value); + } + }, delay, TimeUnit.MILLISECONDS); + } + + @SuppressWarnings("serial") + private class TestException extends Exception { + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationDefaultIfEmptyTest.java b/rxjava-core/src/test/java/rx/operators/OperationDefaultIfEmptyTest.java new file mode 100644 index 0000000000..68003ceb01 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationDefaultIfEmptyTest.java @@ -0,0 +1,60 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Mockito.*; +import static rx.operators.OperationDefaultIfEmpty.*; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; + +public class OperationDefaultIfEmptyTest { + + @Test + public void testDefaultIfEmpty() { + Observable source = Observable.from(1, 2, 3); + Observable observable = Observable.create(defaultIfEmpty( + source, 10)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, never()).onNext(10); + verify(aObserver, times(1)).onNext(1); + verify(aObserver, times(1)).onNext(2); + verify(aObserver, times(1)).onNext(3); + verify(aObserver, never()).onError( + org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testDefaultIfEmptyWithEmpty() { + Observable source = Observable.empty(); + Observable observable = Observable.create(defaultIfEmpty( + source, 10)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext(10); + verify(aObserver, never()).onError( + org.mockito.Matchers.any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationDeferTest.java b/rxjava-core/src/test/java/rx/operators/OperationDeferTest.java new file mode 100644 index 0000000000..550f428134 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationDeferTest.java @@ -0,0 +1,63 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Mockito.*; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; +import rx.util.functions.Func0; + +public class OperationDeferTest { + + @Test + @SuppressWarnings("unchecked") + public void testDefer() throws Throwable { + + Func0> factory = mock(Func0.class); + + Observable firstObservable = Observable.from("one", "two"); + Observable secondObservable = Observable.from("three", "four"); + when(factory.call()).thenReturn(firstObservable, secondObservable); + + Observable deferred = Observable.defer(factory); + + verifyZeroInteractions(factory); + + Observer firstObserver = mock(Observer.class); + deferred.subscribe(firstObserver); + + verify(factory, times(1)).call(); + verify(firstObserver, times(1)).onNext("one"); + verify(firstObserver, times(1)).onNext("two"); + verify(firstObserver, times(0)).onNext("three"); + verify(firstObserver, times(0)).onNext("four"); + verify(firstObserver, times(1)).onCompleted(); + + Observer secondObserver = mock(Observer.class); + deferred.subscribe(secondObserver); + + verify(factory, times(2)).call(); + verify(secondObserver, times(0)).onNext("one"); + verify(secondObserver, times(0)).onNext("two"); + verify(secondObserver, times(1)).onNext("three"); + verify(secondObserver, times(1)).onNext("four"); + verify(secondObserver, times(1)).onCompleted(); + + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationDematerializeTest.java b/rxjava-core/src/test/java/rx/operators/OperationDematerializeTest.java new file mode 100644 index 0000000000..1465b4f3d2 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationDematerializeTest.java @@ -0,0 +1,74 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationDematerialize.*; + +import org.junit.Test; + +import rx.Notification; +import rx.Observable; +import rx.Observer; + +public class OperationDematerializeTest { + + @Test + @SuppressWarnings("unchecked") + public void testDematerialize1() { + Observable> notifications = Observable.from(1, 2).materialize(); + Observable dematerialize = notifications.dematerialize(); + + Observer aObserver = mock(Observer.class); + dematerialize.subscribe(aObserver); + + verify(aObserver, times(1)).onNext(1); + verify(aObserver, times(1)).onNext(2); + verify(aObserver, times(1)).onCompleted(); + verify(aObserver, never()).onError(any(Throwable.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void testDematerialize2() { + Throwable exception = new Throwable("test"); + Observable observable = Observable.error(exception); + Observable dematerialize = Observable.create(dematerialize(observable.materialize())); + + Observer aObserver = mock(Observer.class); + dematerialize.subscribe(aObserver); + + verify(aObserver, times(1)).onError(exception); + verify(aObserver, times(0)).onCompleted(); + verify(aObserver, times(0)).onNext(any(Integer.class)); + } + + @Test + @SuppressWarnings("unchecked") + public void testDematerialize3() { + Exception exception = new Exception("test"); + Observable observable = Observable.error(exception); + Observable dematerialize = Observable.create(dematerialize(observable.materialize())); + + Observer aObserver = mock(Observer.class); + dematerialize.subscribe(aObserver); + + verify(aObserver, times(1)).onError(exception); + verify(aObserver, times(0)).onCompleted(); + verify(aObserver, times(0)).onNext(any(Integer.class)); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationDistinctTest.java b/rxjava-core/src/test/java/rx/operators/OperationDistinctTest.java new file mode 100644 index 0000000000..de57e8fc1e --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationDistinctTest.java @@ -0,0 +1,195 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static org.mockito.MockitoAnnotations.*; +import static rx.operators.OperationDistinct.*; + +import java.util.Comparator; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; + +import rx.Observable; +import rx.Observer; +import rx.util.functions.Func1; + +public class OperationDistinctTest { + + @Mock + Observer w; + @Mock + Observer w2; + + // nulls lead to exceptions + final Func1 TO_UPPER_WITH_EXCEPTION = new Func1() { + @Override + public String call(String s) { + if (s.equals("x")) { + return "XX"; + } + return s.toUpperCase(); + } + }; + + final Comparator COMPARE_LENGTH = new Comparator() { + @Override + public int compare(String s1, String s2) { + return s1.length() - s2.length(); + } + }; + + @Before + public void before() { + initMocks(this); + } + + @Test + public void testDistinctOfNone() { + Observable src = Observable.empty(); + Observable.create(distinct(src)).subscribe(w); + + verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + verify(w, times(1)).onCompleted(); + } + + @Test + public void testDistinctOfNoneWithKeySelector() { + Observable src = Observable.empty(); + Observable.create(distinct(src, TO_UPPER_WITH_EXCEPTION)).subscribe(w); + + verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + verify(w, times(1)).onCompleted(); + } + + @Test + public void testDistinctOfNormalSource() { + Observable src = Observable.from("a", "b", "c", "c", "c", "b", "b", "a", "e"); + Observable.create(distinct(src)).subscribe(w); + + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext("a"); + inOrder.verify(w, times(1)).onNext("b"); + inOrder.verify(w, times(1)).onNext("c"); + inOrder.verify(w, times(1)).onNext("e"); + inOrder.verify(w, times(1)).onCompleted(); + inOrder.verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + } + + @Test + public void testDistinctOfNormalSourceWithKeySelector() { + Observable src = Observable.from("a", "B", "c", "C", "c", "B", "b", "a", "E"); + Observable.create(distinct(src, TO_UPPER_WITH_EXCEPTION)).subscribe(w); + + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext("a"); + inOrder.verify(w, times(1)).onNext("B"); + inOrder.verify(w, times(1)).onNext("c"); + inOrder.verify(w, times(1)).onNext("E"); + inOrder.verify(w, times(1)).onCompleted(); + inOrder.verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + } + + @Test + public void testDistinctOfNormalSourceWithComparator() { + Observable src = Observable.from("1", "12", "123", "aaa", "321", "12", "21", "1", "12345"); + Observable.create(distinct(src, COMPARE_LENGTH)).subscribe(w); + + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext("1"); + inOrder.verify(w, times(1)).onNext("12"); + inOrder.verify(w, times(1)).onNext("123"); + inOrder.verify(w, times(1)).onNext("12345"); + inOrder.verify(w, times(1)).onCompleted(); + inOrder.verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + } + + @Test + public void testDistinctOfNormalSourceWithKeySelectorAndComparator() { + Observable src = Observable.from("a", "x", "ab", "abc", "cba", "de", "x", "a", "abcd"); + Observable.create(distinct(src, TO_UPPER_WITH_EXCEPTION, COMPARE_LENGTH)).subscribe(w); + + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext("a"); + inOrder.verify(w, times(1)).onNext("x"); + inOrder.verify(w, times(1)).onNext("abc"); + inOrder.verify(w, times(1)).onNext("abcd"); + inOrder.verify(w, times(1)).onCompleted(); + inOrder.verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + } + + @Test + public void testDistinctOfNormalSourceWithKeySelectorAndComparatorAndTwoSubscriptions() { + Observable src = Observable.from("a", "x", "ab", "abc", "cba", "de", "x", "a", "abcd"); + Observable.create(distinct(src, TO_UPPER_WITH_EXCEPTION, COMPARE_LENGTH)).subscribe(w); + + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext("a"); + inOrder.verify(w, times(1)).onNext("x"); + Observable.create(distinct(src, TO_UPPER_WITH_EXCEPTION, COMPARE_LENGTH)).subscribe(w2); + inOrder.verify(w, times(1)).onNext("abc"); + inOrder.verify(w, times(1)).onNext("abcd"); + inOrder.verify(w, times(1)).onCompleted(); + inOrder.verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + + InOrder inOrder2 = inOrder(w2); + inOrder2.verify(w2, times(1)).onNext("a"); + inOrder2.verify(w2, times(1)).onNext("x"); + inOrder2.verify(w2, times(1)).onNext("abc"); + inOrder2.verify(w2, times(1)).onNext("abcd"); + inOrder2.verify(w2, times(1)).onCompleted(); + inOrder2.verify(w2, never()).onNext(anyString()); + verify(w2, never()).onError(any(Throwable.class)); + } + + @Test + public void testDistinctOfSourceWithNulls() { + Observable src = Observable.from(null, "a", "a", null, null, "b", null); + Observable.create(distinct(src)).subscribe(w); + + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext(null); + inOrder.verify(w, times(1)).onNext("a"); + inOrder.verify(w, times(1)).onNext("b"); + inOrder.verify(w, times(1)).onCompleted(); + inOrder.verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + } + + @Test + public void testDistinctOfSourceWithExceptionsFromKeySelector() { + Observable src = Observable.from("a", "b", null, "c"); + Observable.create(distinct(src, TO_UPPER_WITH_EXCEPTION)).subscribe(w); + + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext("a"); + inOrder.verify(w, times(1)).onNext("b"); + inOrder.verify(w, times(1)).onError(any(NullPointerException.class)); + inOrder.verify(w, never()).onNext(anyString()); + inOrder.verify(w, never()).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationDistinctUntilChangedTest.java b/rxjava-core/src/test/java/rx/operators/OperationDistinctUntilChangedTest.java new file mode 100644 index 0000000000..b4568baaf7 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationDistinctUntilChangedTest.java @@ -0,0 +1,198 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static org.mockito.MockitoAnnotations.*; +import static rx.operators.OperationDistinctUntilChanged.*; + +import java.util.Comparator; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; + +import rx.Observable; +import rx.Observer; +import rx.util.functions.Func1; + +public class OperationDistinctUntilChangedTest { + + @Mock + Observer w; + @Mock + Observer w2; + + // nulls lead to exceptions + final Func1 TO_UPPER_WITH_EXCEPTION = new Func1() { + @Override + public String call(String s) { + if (s.equals("x")) { + return "xx"; + } + return s.toUpperCase(); + } + }; + + final Comparator COMPARE_LENGTH = new Comparator() { + @Override + public int compare(String s1, String s2) { + return s1.length() - s2.length(); + } + }; + + @Before + public void before() { + initMocks(this); + } + + @Test + public void testDistinctUntilChangedOfNone() { + Observable src = Observable.empty(); + Observable.create(distinctUntilChanged(src)).subscribe(w); + + verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + verify(w, times(1)).onCompleted(); + } + + @Test + public void testDistinctUntilChangedOfNoneWithKeySelector() { + Observable src = Observable.empty(); + Observable.create(distinctUntilChanged(src, TO_UPPER_WITH_EXCEPTION)).subscribe(w); + + verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + verify(w, times(1)).onCompleted(); + } + + @Test + public void testDistinctUntilChangedOfNormalSource() { + Observable src = Observable.from("a", "b", "c", "c", "c", "b", "b", "a", "e"); + Observable.create(distinctUntilChanged(src)).subscribe(w); + + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext("a"); + inOrder.verify(w, times(1)).onNext("b"); + inOrder.verify(w, times(1)).onNext("c"); + inOrder.verify(w, times(1)).onNext("b"); + inOrder.verify(w, times(1)).onNext("a"); + inOrder.verify(w, times(1)).onNext("e"); + inOrder.verify(w, times(1)).onCompleted(); + inOrder.verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + } + + @Test + public void testDistinctUntilChangedOfNormalSourceWithKeySelector() { + Observable src = Observable.from("a", "b", "c", "C", "c", "B", "b", "a", "e"); + Observable.create(distinctUntilChanged(src, TO_UPPER_WITH_EXCEPTION)).subscribe(w); + + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext("a"); + inOrder.verify(w, times(1)).onNext("b"); + inOrder.verify(w, times(1)).onNext("c"); + inOrder.verify(w, times(1)).onNext("B"); + inOrder.verify(w, times(1)).onNext("a"); + inOrder.verify(w, times(1)).onNext("e"); + inOrder.verify(w, times(1)).onCompleted(); + inOrder.verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + } + + @Test + public void testDistinctUntilChangedOfSourceWithNulls() { + Observable src = Observable.from(null, "a", "a", null, null, "b", null, null); + Observable.create(distinctUntilChanged(src)).subscribe(w); + + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext(null); + inOrder.verify(w, times(1)).onNext("a"); + inOrder.verify(w, times(1)).onNext(null); + inOrder.verify(w, times(1)).onNext("b"); + inOrder.verify(w, times(1)).onNext(null); + inOrder.verify(w, times(1)).onCompleted(); + inOrder.verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + } + + @Test + public void testDistinctUntilChangedOfSourceWithExceptionsFromKeySelector() { + Observable src = Observable.from("a", "b", null, "c"); + Observable.create(distinctUntilChanged(src, TO_UPPER_WITH_EXCEPTION)).subscribe(w); + + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext("a"); + inOrder.verify(w, times(1)).onNext("b"); + verify(w, times(1)).onError(any(NullPointerException.class)); + inOrder.verify(w, never()).onNext(anyString()); + inOrder.verify(w, never()).onCompleted(); + } + + @Test + public void testDistinctUntilChangedWithComparator() { + Observable src = Observable.from("a", "b", "c", "aa", "bb", "c", "ddd"); + Observable.create(distinctUntilChanged(src, COMPARE_LENGTH)).subscribe(w); + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext("a"); + inOrder.verify(w, times(1)).onNext("aa"); + inOrder.verify(w, times(1)).onNext("c"); + inOrder.verify(w, times(1)).onNext("ddd"); + inOrder.verify(w, times(1)).onCompleted(); + inOrder.verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + } + + @Test + public void testDistinctUntilChangedWithComparatorAndKeySelector() { + Observable src = Observable.from("a", "b", "x", "aa", "bb", "c", "ddd"); + Observable.create(distinctUntilChanged(src, TO_UPPER_WITH_EXCEPTION, COMPARE_LENGTH)).subscribe(w); + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext("a"); + inOrder.verify(w, times(1)).onNext("x"); + inOrder.verify(w, times(1)).onNext("c"); + inOrder.verify(w, times(1)).onNext("ddd"); + inOrder.verify(w, times(1)).onCompleted(); + inOrder.verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + } + + @Test + public void testDistinctUntilChangedWithComparatorAndKeySelectorandTwoSubscriptions() { + Observable src = Observable.from("a", "b", "x", "aa", "bb", "c", "ddd"); + Observable.create(distinctUntilChanged(src, TO_UPPER_WITH_EXCEPTION, COMPARE_LENGTH)).subscribe(w); + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext("a"); + inOrder.verify(w, times(1)).onNext("x"); + Observable.create(distinctUntilChanged(src, TO_UPPER_WITH_EXCEPTION, COMPARE_LENGTH)).subscribe(w2); + inOrder.verify(w, times(1)).onNext("c"); + inOrder.verify(w, times(1)).onNext("ddd"); + inOrder.verify(w, times(1)).onCompleted(); + inOrder.verify(w, never()).onNext(anyString()); + verify(w, never()).onError(any(Throwable.class)); + + InOrder inOrder2 = inOrder(w2); + inOrder2.verify(w2, times(1)).onNext("a"); + inOrder2.verify(w2, times(1)).onNext("x"); + inOrder2.verify(w2, times(1)).onNext("c"); + inOrder2.verify(w2, times(1)).onNext("ddd"); + inOrder2.verify(w2, times(1)).onCompleted(); + inOrder2.verify(w2, never()).onNext(anyString()); + verify(w2, never()).onError(any(Throwable.class)); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationDoOnEachTest.java b/rxjava-core/src/test/java/rx/operators/OperationDoOnEachTest.java new file mode 100644 index 0000000000..6c1407ebea --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationDoOnEachTest.java @@ -0,0 +1,129 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationMap.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import rx.Observable; +import rx.Observer; +import rx.concurrency.Schedulers; +import rx.util.functions.Func1; +import rx.util.functions.Func2; +import rx.util.functions.Action1; + +public class OperationDoOnEachTest { + + @Mock + Observer subscribedObserver; + @Mock + Observer sideEffectObserver; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testDoOnEach() { + Observable base = Observable.from("a", "b", "c"); + Observable doOnEach = base.doOnEach(sideEffectObserver); + + doOnEach.subscribe(subscribedObserver); + + // ensure the leaf observer is still getting called + verify(subscribedObserver, never()).onError(any(Throwable.class)); + verify(subscribedObserver, times(1)).onNext("a"); + verify(subscribedObserver, times(1)).onNext("b"); + verify(subscribedObserver, times(1)).onNext("c"); + verify(subscribedObserver, times(1)).onCompleted(); + + // ensure our injected observer is getting called + verify(sideEffectObserver, never()).onError(any(Throwable.class)); + verify(sideEffectObserver, times(1)).onNext("a"); + verify(sideEffectObserver, times(1)).onNext("b"); + verify(sideEffectObserver, times(1)).onNext("c"); + verify(sideEffectObserver, times(1)).onCompleted(); + } + + + + @Test + public void testDoOnEachWithError() { + Observable base = Observable.from("one", "fail", "two", "three", "fail"); + Observable errs = base.map(new Func1() { + @Override + public String call(String s) { + if ("fail".equals(s)) { + throw new RuntimeException("Forced Failure"); + } + return s; + } + }); + + Observable doOnEach = errs.doOnEach(sideEffectObserver); + + + doOnEach.subscribe(subscribedObserver); + verify(subscribedObserver, times(1)).onNext("one"); + verify(subscribedObserver, never()).onNext("two"); + verify(subscribedObserver, never()).onNext("three"); + verify(subscribedObserver, never()).onCompleted(); + verify(subscribedObserver, times(1)).onError(any(Throwable.class)); + + + verify(sideEffectObserver, times(1)).onNext("one"); + verify(sideEffectObserver, never()).onNext("two"); + verify(sideEffectObserver, never()).onNext("three"); + verify(sideEffectObserver, never()).onCompleted(); + verify(sideEffectObserver, times(1)).onError(any(Throwable.class)); + } + + @Test + public void testDoOnEachWithErrorInCallback() { + Observable base = Observable.from("one", "two", "fail", "three"); + Observable doOnEach = base.doOnEach(new Action1() { + @Override + public void call(String s) { + if ("fail".equals(s)) { + throw new RuntimeException("Forced Failure"); + } + } + }); + + doOnEach.subscribe(subscribedObserver); + verify(subscribedObserver, times(1)).onNext("one"); + verify(subscribedObserver, times(1)).onNext("two"); + verify(subscribedObserver, never()).onNext("three"); + verify(subscribedObserver, never()).onCompleted(); + verify(subscribedObserver, times(1)).onError(any(Throwable.class)); + + } + +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationElementAtTest.java b/rxjava-core/src/test/java/rx/operators/OperationElementAtTest.java new file mode 100644 index 0000000000..a28de23d08 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationElementAtTest.java @@ -0,0 +1,127 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationElementAt.*; + +import java.util.Iterator; +import java.util.concurrent.ExecutionException; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; + +public class OperationElementAtTest { + + @Test + public void testElementAt() { + Observable w = Observable.from(1, 2); + Observable observable = Observable.create(elementAt(w, 1)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, never()).onNext(1); + verify(aObserver, times(1)).onNext(2); + verify(aObserver, never()).onError( + any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testElementAtWithMinusIndex() { + Observable w = Observable.from(1, 2); + Observable observable = Observable + .create(elementAt(w, -1)); + + try { + Iterator iter = OperationToIterator + .toIterator(observable); + assertTrue(iter.hasNext()); + iter.next(); + fail("expect an IndexOutOfBoundsException when index is out of bounds"); + } catch (IndexOutOfBoundsException e) { + } + } + + @Test + public void testElementAtWithIndexOutOfBounds() + throws InterruptedException, ExecutionException { + Observable w = Observable.from(1, 2); + Observable observable = Observable.create(elementAt(w, 2)); + try { + Iterator iter = OperationToIterator + .toIterator(observable); + assertTrue(iter.hasNext()); + iter.next(); + fail("expect an IndexOutOfBoundsException when index is out of bounds"); + } catch (IndexOutOfBoundsException e) { + } + } + + @Test + public void testElementAtOrDefault() throws InterruptedException, + ExecutionException { + Observable w = Observable.from(1, 2); + Observable observable = Observable + .create(elementAtOrDefault(w, 1, 0)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, never()).onNext(1); + verify(aObserver, times(1)).onNext(2); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testElementAtOrDefaultWithIndexOutOfBounds() + throws InterruptedException, ExecutionException { + Observable w = Observable.from(1, 2); + Observable observable = Observable + .create(elementAtOrDefault(w, 2, 0)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, never()).onNext(1); + verify(aObserver, never()).onNext(2); + verify(aObserver, times(1)).onNext(0); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testElementAtOrDefaultWithMinusIndex() { + Observable w = Observable.from(1, 2); + Observable observable = Observable + .create(elementAtOrDefault(w, -1, 0)); + + try { + Iterator iter = OperationToIterator + .toIterator(observable); + assertTrue(iter.hasNext()); + iter.next(); + fail("expect an IndexOutOfBoundsException when index is out of bounds"); + } catch (IndexOutOfBoundsException e) { + } + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationFilterTest.java b/rxjava-core/src/test/java/rx/operators/OperationFilterTest.java new file mode 100644 index 0000000000..c22c3d46b8 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationFilterTest.java @@ -0,0 +1,51 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationFilter.*; + +import org.junit.Test; +import org.mockito.Mockito; + +import rx.Observable; +import rx.Observer; +import rx.util.functions.Func1; + +public class OperationFilterTest { + + @Test + public void testFilter() { + Observable w = Observable.from("one", "two", "three"); + Observable observable = Observable.create(filter(w, new Func1() { + + @Override + public Boolean call(String t1) { + return t1.equals("two"); + } + })); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, Mockito.never()).onNext("one"); + verify(aObserver, times(1)).onNext("two"); + verify(aObserver, Mockito.never()).onNext("three"); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationFinallyTest.java b/rxjava-core/src/test/java/rx/operators/OperationFinallyTest.java new file mode 100644 index 0000000000..1ce2111fe9 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationFinallyTest.java @@ -0,0 +1,55 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Mockito.*; +import static rx.operators.OperationFinally.*; + +import org.junit.Before; +import org.junit.Test; + +import rx.Observable; +import rx.Observer; +import rx.util.functions.Action0; + +public class OperationFinallyTest { + + private Action0 aAction0; + private Observer aObserver; + + @SuppressWarnings("unchecked") + // mocking has to be unchecked, unfortunately + @Before + public void before() { + aAction0 = mock(Action0.class); + aObserver = mock(Observer.class); + } + + private void checkActionCalled(Observable input) { + Observable.create(finallyDo(input, aAction0)).subscribe(aObserver); + verify(aAction0, times(1)).call(); + } + + @Test + public void testFinallyCalledOnComplete() { + checkActionCalled(Observable.from(new String[] { "1", "2", "3" })); + } + + @Test + public void testFinallyCalledOnError() { + checkActionCalled(Observable. error(new RuntimeException("expected"))); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationFirstOrDefaultTest.java b/rxjava-core/src/test/java/rx/operators/OperationFirstOrDefaultTest.java new file mode 100644 index 0000000000..026caa1319 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationFirstOrDefaultTest.java @@ -0,0 +1,91 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static org.mockito.MockitoAnnotations.*; +import static rx.operators.OperationFirstOrDefault.*; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import rx.Observable; +import rx.Observer; +import rx.util.functions.Func1; + +public class OperationFirstOrDefaultTest { + + @Mock + Observer w; + + private static final Func1 IS_D = new Func1() { + @Override + public Boolean call(String value) { + return "d".equals(value); + } + }; + + @Before + public void before() { + initMocks(this); + } + + @Test + public void testFirstOrElseOfNone() { + Observable src = Observable.empty(); + Observable.create(firstOrDefault(src, "default")).subscribe(w); + + verify(w, times(1)).onNext(anyString()); + verify(w, times(1)).onNext("default"); + verify(w, never()).onError(any(Throwable.class)); + verify(w, times(1)).onCompleted(); + } + + @Test + public void testFirstOrElseOfSome() { + Observable src = Observable.from("a", "b", "c"); + Observable.create(firstOrDefault(src, "default")).subscribe(w); + + verify(w, times(1)).onNext(anyString()); + verify(w, times(1)).onNext("a"); + verify(w, never()).onError(any(Throwable.class)); + verify(w, times(1)).onCompleted(); + } + + @Test + public void testFirstOrElseWithPredicateOfNoneMatchingThePredicate() { + Observable src = Observable.from("a", "b", "c"); + Observable.create(firstOrDefault(src, IS_D, "default")).subscribe(w); + + verify(w, times(1)).onNext(anyString()); + verify(w, times(1)).onNext("default"); + verify(w, never()).onError(any(Throwable.class)); + verify(w, times(1)).onCompleted(); + } + + @Test + public void testFirstOrElseWithPredicateOfSome() { + Observable src = Observable.from("a", "b", "c", "d", "e", "f"); + Observable.create(firstOrDefault(src, IS_D, "default")).subscribe(w); + + verify(w, times(1)).onNext(anyString()); + verify(w, times(1)).onNext("d"); + verify(w, never()).onError(any(Throwable.class)); + verify(w, times(1)).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationGroupByTest.java b/rxjava-core/src/test/java/rx/operators/OperationGroupByTest.java new file mode 100644 index 0000000000..cf47d9e683 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationGroupByTest.java @@ -0,0 +1,347 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static rx.operators.OperationGroupBy.*; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.observables.GroupedObservable; +import rx.subscriptions.BooleanSubscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action1; +import rx.util.functions.Func1; + +public class OperationGroupByTest { + + final Func1 length = new Func1() { + @Override + public Integer call(String s) { + return s.length(); + } + }; + + @Test + public void testGroupBy() { + Observable source = Observable.from("one", "two", "three", "four", "five", "six"); + Observable> grouped = Observable.create(groupBy(source, length)); + + Map> map = toMap(grouped); + + assertEquals(3, map.size()); + assertArrayEquals(Arrays.asList("one", "two", "six").toArray(), map.get(3).toArray()); + assertArrayEquals(Arrays.asList("four", "five").toArray(), map.get(4).toArray()); + assertArrayEquals(Arrays.asList("three").toArray(), map.get(5).toArray()); + } + + @Test + public void testEmpty() { + Observable source = Observable.empty(); + Observable> grouped = Observable.create(groupBy(source, length)); + + Map> map = toMap(grouped); + + assertTrue(map.isEmpty()); + } + + @Test + public void testError() { + Observable sourceStrings = Observable.from("one", "two", "three", "four", "five", "six"); + Observable errorSource = Observable.error(new RuntimeException("forced failure")); + Observable source = Observable.concat(sourceStrings, errorSource); + + Observable> grouped = Observable.create(groupBy(source, length)); + + final AtomicInteger groupCounter = new AtomicInteger(); + final AtomicInteger eventCounter = new AtomicInteger(); + final AtomicReference error = new AtomicReference(); + + grouped.mapMany(new Func1, Observable>() { + + @Override + public Observable call(final GroupedObservable o) { + groupCounter.incrementAndGet(); + return o.map(new Func1() { + + @Override + public String call(String v) { + return "Event => key: " + o.getKey() + " value: " + v; + } + }); + } + }).subscribe(new Observer() { + + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + error.set(e); + } + + @Override + public void onNext(String v) { + eventCounter.incrementAndGet(); + System.out.println(v); + + } + }); + + assertEquals(3, groupCounter.get()); + assertEquals(6, eventCounter.get()); + assertNotNull(error.get()); + } + + private static Map> toMap(Observable> observable) { + + final ConcurrentHashMap> result = new ConcurrentHashMap>(); + + observable.toBlockingObservable().forEach(new Action1>() { + + @Override + public void call(final GroupedObservable o) { + result.put(o.getKey(), new ConcurrentLinkedQueue()); + o.subscribe(new Action1() { + + @Override + public void call(V v) { + result.get(o.getKey()).add(v); + } + + }); + } + }); + + return result; + } + + /** + * Assert that only a single subscription to a stream occurs and that all events are received. + * + * @throws Throwable + */ + @Test + public void testGroupedEventStream() throws Throwable { + + final AtomicInteger eventCounter = new AtomicInteger(); + final AtomicInteger subscribeCounter = new AtomicInteger(); + final AtomicInteger groupCounter = new AtomicInteger(); + final CountDownLatch latch = new CountDownLatch(1); + final int count = 100; + final int groupCount = 2; + + Observable es = Observable.create(new Observable.OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(final Observer observer) { + System.out.println("*** Subscribing to EventStream ***"); + subscribeCounter.incrementAndGet(); + new Thread(new Runnable() { + + @Override + public void run() { + for (int i = 0; i < count; i++) { + Event e = new Event(); + e.source = i % groupCount; + e.message = "Event-" + i; + observer.onNext(e); + } + observer.onCompleted(); + } + + }).start(); + return Subscriptions.empty(); + } + + }); + + es.groupBy(new Func1() { + + @Override + public Integer call(Event e) { + return e.source; + } + }).mapMany(new Func1, Observable>() { + + @Override + public Observable call(GroupedObservable eventGroupedObservable) { + System.out.println("GroupedObservable Key: " + eventGroupedObservable.getKey()); + groupCounter.incrementAndGet(); + + return eventGroupedObservable.map(new Func1() { + + @Override + public String call(Event event) { + return "Source: " + event.source + " Message: " + event.message; + } + }); + + } + }).subscribe(new Observer() { + + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + latch.countDown(); + } + + @Override + public void onNext(String outputMessage) { + System.out.println(outputMessage); + eventCounter.incrementAndGet(); + } + }); + + latch.await(5000, TimeUnit.MILLISECONDS); + assertEquals(1, subscribeCounter.get()); + assertEquals(groupCount, groupCounter.get()); + assertEquals(count, eventCounter.get()); + + } + + /* + * We will only take 1 group with 20 events from it and then unsubscribe. + */ + @Test + public void testUnsubscribe() throws InterruptedException { + + final AtomicInteger eventCounter = new AtomicInteger(); + final AtomicInteger subscribeCounter = new AtomicInteger(); + final AtomicInteger groupCounter = new AtomicInteger(); + final AtomicInteger sentEventCounter = new AtomicInteger(); + final CountDownLatch latch = new CountDownLatch(1); + final int count = 100; + final int groupCount = 2; + + Observable es = Observable.create(new Observable.OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(final Observer observer) { + final BooleanSubscription s = new BooleanSubscription(); + System.out.println("testUnsubscribe => *** Subscribing to EventStream ***"); + subscribeCounter.incrementAndGet(); + new Thread(new Runnable() { + + @Override + public void run() { + for (int i = 0; i < count; i++) { + if (s.isUnsubscribed()) { + break; + } + Event e = new Event(); + e.source = i % groupCount; + e.message = "Event-" + i; + observer.onNext(e); + sentEventCounter.incrementAndGet(); + } + observer.onCompleted(); + } + + }).start(); + return s; + } + + }); + + es.groupBy(new Func1() { + + @Override + public Integer call(Event e) { + return e.source; + } + }) + .take(1) // we want only the first group + .mapMany(new Func1, Observable>() { + + @Override + public Observable call(GroupedObservable eventGroupedObservable) { + System.out.println("testUnsubscribe => GroupedObservable Key: " + eventGroupedObservable.getKey()); + groupCounter.incrementAndGet(); + + return eventGroupedObservable + .take(20) // limit to only 20 events on this group + .map(new Func1() { + + @Override + public String call(Event event) { + return "testUnsubscribe => Source: " + event.source + " Message: " + event.message; + } + }); + + } + }).subscribe(new Observer() { + + @Override + public void onCompleted() { + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + e.printStackTrace(); + latch.countDown(); + } + + @Override + public void onNext(String outputMessage) { + System.out.println(outputMessage); + eventCounter.incrementAndGet(); + } + }); + + latch.await(5000, TimeUnit.MILLISECONDS); + assertEquals(1, subscribeCounter.get()); + assertEquals(1, groupCounter.get()); + assertEquals(20, eventCounter.get()); + // sentEvents will go until 'eventCounter' hits 20 and then unsubscribes + // which means it will also send (but ignore) the 19/20 events for the other group + // It will not however send all 100 events. + assertEquals(39, sentEventCounter.get(), 10); + // gave it a delta of 10 to account for the threading/unsubscription race condition which can vary depending on a machines performance, thread-scheduler, etc + } + + private static class Event { + int source; + String message; + + @Override + public String toString() { + return "Event => source: " + source + " message: " + message; + } + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationIntervalTest.java b/rxjava-core/src/test/java/rx/operators/OperationIntervalTest.java new file mode 100644 index 0000000000..293ff236c9 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationIntervalTest.java @@ -0,0 +1,192 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.concurrency.TestScheduler; +import rx.observables.ConnectableObservable; + +public class OperationIntervalTest { + + private TestScheduler scheduler; + private Observer observer; + private Observer observer2; + + @Before + @SuppressWarnings("unchecked") + // due to mocking + public void before() { + scheduler = new TestScheduler(); + observer = mock(Observer.class); + observer2 = mock(Observer.class); + } + + @Test + public void testInterval() { + Observable w = Observable.create(OperationInterval.interval(1, TimeUnit.SECONDS, scheduler)); + Subscription sub = w.subscribe(observer); + + verify(observer, never()).onNext(0L); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(2, TimeUnit.SECONDS); + + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext(0L); + inOrder.verify(observer, times(1)).onNext(1L); + inOrder.verify(observer, never()).onNext(2L); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + sub.unsubscribe(); + scheduler.advanceTimeTo(4, TimeUnit.SECONDS); + verify(observer, never()).onNext(2L); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void testWithMultipleSubscribersStartingAtSameTime() { + Observable w = Observable.create(OperationInterval.interval(1, TimeUnit.SECONDS, scheduler)); + Subscription sub1 = w.subscribe(observer); + Subscription sub2 = w.subscribe(observer2); + + verify(observer, never()).onNext(anyLong()); + verify(observer2, never()).onNext(anyLong()); + + scheduler.advanceTimeTo(2, TimeUnit.SECONDS); + + InOrder inOrder1 = inOrder(observer); + InOrder inOrder2 = inOrder(observer2); + + inOrder1.verify(observer, times(1)).onNext(0L); + inOrder1.verify(observer, times(1)).onNext(1L); + inOrder1.verify(observer, never()).onNext(2L); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + inOrder2.verify(observer2, times(1)).onNext(0L); + inOrder2.verify(observer2, times(1)).onNext(1L); + inOrder2.verify(observer2, never()).onNext(2L); + verify(observer2, never()).onCompleted(); + verify(observer2, never()).onError(any(Throwable.class)); + + sub1.unsubscribe(); + sub2.unsubscribe(); + scheduler.advanceTimeTo(4, TimeUnit.SECONDS); + + verify(observer, never()).onNext(2L); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + verify(observer2, never()).onNext(2L); + verify(observer2, never()).onCompleted(); + verify(observer2, never()).onError(any(Throwable.class)); + } + + @Test + public void testWithMultipleStaggeredSubscribers() { + Observable w = Observable.create(OperationInterval.interval(1, TimeUnit.SECONDS, scheduler)); + Subscription sub1 = w.subscribe(observer); + + verify(observer, never()).onNext(anyLong()); + + scheduler.advanceTimeTo(2, TimeUnit.SECONDS); + Subscription sub2 = w.subscribe(observer2); + + InOrder inOrder1 = inOrder(observer); + inOrder1.verify(observer, times(1)).onNext(0L); + inOrder1.verify(observer, times(1)).onNext(1L); + inOrder1.verify(observer, never()).onNext(2L); + + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + verify(observer2, never()).onNext(anyLong()); + + scheduler.advanceTimeTo(4, TimeUnit.SECONDS); + + inOrder1.verify(observer, times(1)).onNext(2L); + inOrder1.verify(observer, times(1)).onNext(3L); + + InOrder inOrder2 = inOrder(observer2); + inOrder2.verify(observer2, times(1)).onNext(0L); + inOrder2.verify(observer2, times(1)).onNext(1L); + + sub1.unsubscribe(); + sub2.unsubscribe(); + + inOrder1.verify(observer, never()).onNext(anyLong()); + inOrder1.verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + inOrder2.verify(observer2, never()).onNext(anyLong()); + inOrder2.verify(observer2, never()).onCompleted(); + verify(observer2, never()).onError(any(Throwable.class)); + } + + @Test + public void testWithMultipleStaggeredSubscribersAndPublish() { + ConnectableObservable w = Observable.create(OperationInterval.interval(1, TimeUnit.SECONDS, scheduler)).publish(); + Subscription sub1 = w.subscribe(observer); + w.connect(); + + verify(observer, never()).onNext(anyLong()); + + scheduler.advanceTimeTo(2, TimeUnit.SECONDS); + Subscription sub2 = w.subscribe(observer2); + + InOrder inOrder1 = inOrder(observer); + inOrder1.verify(observer, times(1)).onNext(0L); + inOrder1.verify(observer, times(1)).onNext(1L); + inOrder1.verify(observer, never()).onNext(2L); + + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + verify(observer2, never()).onNext(anyLong()); + + scheduler.advanceTimeTo(4, TimeUnit.SECONDS); + + inOrder1.verify(observer, times(1)).onNext(2L); + inOrder1.verify(observer, times(1)).onNext(3L); + + InOrder inOrder2 = inOrder(observer2); + inOrder2.verify(observer2, times(1)).onNext(2L); + inOrder2.verify(observer2, times(1)).onNext(3L); + + sub1.unsubscribe(); + sub2.unsubscribe(); + + inOrder1.verify(observer, never()).onNext(anyLong()); + inOrder1.verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + inOrder2.verify(observer2, never()).onNext(anyLong()); + inOrder2.verify(observer2, never()).onCompleted(); + verify(observer2, never()).onError(any(Throwable.class)); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationJoinTest.java b/rxjava-core/src/test/java/rx/operators/OperationJoinTest.java new file mode 100644 index 0000000000..8f841ebb50 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationJoinTest.java @@ -0,0 +1,302 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import java.util.Collection; +import org.junit.Before; +import org.junit.Test; +import static org.mockito.Matchers.any; +import org.mockito.Mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import org.mockito.MockitoAnnotations; +import rx.Observable; +import rx.Observer; +import rx.subjects.PublishSubject; +import rx.util.functions.Action1; +import rx.util.functions.Func1; +import rx.util.functions.Func2; + +public class OperationJoinTest { + @Mock + Observer observer; + + Func2 add = new Func2() { + @Override + public Integer call(Integer t1, Integer t2) { + return t1 + t2; + } + }; + Func1> just(final Observable observable) { + return new Func1>() { + @Override + public Observable call(Integer t1) { + return observable; + } + }; + } + @Before + public void before() { + MockitoAnnotations.initMocks(this); + } + @Test + public void normal1() { + PublishSubject source1 = PublishSubject.create(); + PublishSubject source2 = PublishSubject.create(); + + Observable m = source1.join(source2, + just(Observable.never()), + just(Observable.never()), add); + + m.subscribe(observer); + + source1.onNext(1); + source1.onNext(2); + source1.onNext(4); + + source2.onNext(16); + source2.onNext(32); + source2.onNext(64); + + source1.onCompleted(); + source2.onCompleted(); + + verify(observer, times(1)).onNext(17); + verify(observer, times(1)).onNext(18); + verify(observer, times(1)).onNext(20); + verify(observer, times(1)).onNext(33); + verify(observer, times(1)).onNext(34); + verify(observer, times(1)).onNext(36); + verify(observer, times(1)).onNext(65); + verify(observer, times(1)).onNext(66); + verify(observer, times(1)).onNext(68); + + verify(observer, times(1)).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + @Test + public void normal1WithDuration() { + PublishSubject source1 = PublishSubject.create(); + PublishSubject source2 = PublishSubject.create(); + + PublishSubject duration1 = PublishSubject.create(); + + Observable m = source1.join(source2, + just(duration1), + just(Observable.never()), add); + m.subscribe(observer); + + source1.onNext(1); + source1.onNext(2); + source2.onNext(16); + + duration1.onNext(1); + + source1.onNext(4); + source1.onNext(8); + + source1.onCompleted(); + source2.onCompleted(); + + verify(observer, times(1)).onNext(17); + verify(observer, times(1)).onNext(18); + verify(observer, times(1)).onNext(20); + verify(observer, times(1)).onNext(24); + + verify(observer, times(1)).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + } + @Test + public void normal2() { + PublishSubject source1 = PublishSubject.create(); + PublishSubject source2 = PublishSubject.create(); + + Observable m = source1.join(source2, + just(Observable.never()), + just(Observable.never()), add); + + m.subscribe(observer); + + source1.onNext(1); + source1.onNext(2); + source1.onCompleted(); + + source2.onNext(16); + source2.onNext(32); + source2.onNext(64); + + source2.onCompleted(); + + verify(observer, times(1)).onNext(17); + verify(observer, times(1)).onNext(18); + verify(observer, times(1)).onNext(33); + verify(observer, times(1)).onNext(34); + verify(observer, times(1)).onNext(65); + verify(observer, times(1)).onNext(66); + + verify(observer, times(1)).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + @Test + public void leftThrows() { + PublishSubject source1 = PublishSubject.create(); + PublishSubject source2 = PublishSubject.create(); + + Observable m = source1.join(source2, + just(Observable.never()), + just(Observable.never()), add); + + m.subscribe(observer); + + source2.onNext(1); + source1.onError(new RuntimeException("Forced failure")); + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onCompleted(); + verify(observer, never()).onNext(any()); + } + @Test + public void rightThrows() { + PublishSubject source1 = PublishSubject.create(); + PublishSubject source2 = PublishSubject.create(); + + Observable m = source1.join(source2, + just(Observable.never()), + just(Observable.never()), add); + + m.subscribe(observer); + + source1.onNext(1); + source2.onError(new RuntimeException("Forced failure")); + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onCompleted(); + verify(observer, never()).onNext(any()); + } + @Test + public void leftDurationThrows() { + PublishSubject source1 = PublishSubject.create(); + PublishSubject source2 = PublishSubject.create(); + + Observable duration1 = Observable.error(new RuntimeException("Forced failure")); + + Observable m = source1.join(source2, + just(duration1), + just(Observable.never()), add); + m.subscribe(observer); + + source1.onNext(1); + + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onCompleted(); + verify(observer, never()).onNext(any()); + } + @Test + public void rightDurationThrows() { + PublishSubject source1 = PublishSubject.create(); + PublishSubject source2 = PublishSubject.create(); + + Observable duration1 = Observable.error(new RuntimeException("Forced failure")); + + Observable m = source1.join(source2, + just(Observable.never()), + just(duration1), add); + m.subscribe(observer); + + source2.onNext(1); + + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onCompleted(); + verify(observer, never()).onNext(any()); + } + @Test + public void leftDurationSelectorThrows() { + PublishSubject source1 = PublishSubject.create(); + PublishSubject source2 = PublishSubject.create(); + + Func1> fail = new Func1>() { + @Override + public Observable call(Integer t1) { + throw new RuntimeException("Forced failure"); + } + }; + + Observable m = source1.join(source2, + fail, + just(Observable.never()), add); + m.subscribe(observer); + + source1.onNext(1); + + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onCompleted(); + verify(observer, never()).onNext(any()); + } + @Test + public void rightDurationSelectorThrows() { + PublishSubject source1 = PublishSubject.create(); + PublishSubject source2 = PublishSubject.create(); + + Func1> fail = new Func1>() { + @Override + public Observable call(Integer t1) { + throw new RuntimeException("Forced failure"); + } + }; + + Observable m = source1.join(source2, + just(Observable.never()), + fail, add); + m.subscribe(observer); + + source2.onNext(1); + + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onCompleted(); + verify(observer, never()).onNext(any()); + } + @Test + public void resultSelectorThrows() { + PublishSubject source1 = PublishSubject.create(); + PublishSubject source2 = PublishSubject.create(); + + Func2 fail = new Func2() { + @Override + public Integer call(Integer t1, Integer t2) { + throw new RuntimeException("Forced failure"); + } + }; + + Observable m = source1.join(source2, + just(Observable.never()), + just(Observable.never()), fail); + m.subscribe(observer); + + source1.onNext(1); + source2.onNext(2); + + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onCompleted(); + verify(observer, never()).onNext(any()); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationJoinsTest.java b/rxjava-core/src/test/java/rx/operators/OperationJoinsTest.java new file mode 100644 index 0000000000..8a3b0c79c9 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationJoinsTest.java @@ -0,0 +1,382 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package rx.operators; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import static org.mockito.Matchers.any; +import org.mockito.Mock; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import org.mockito.MockitoAnnotations; +import rx.Observable; +import rx.Observer; +import rx.joins.Plan0; +import rx.subjects.PublishSubject; +import rx.util.functions.Func1; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.Functions; + +public class OperationJoinsTest { + @Mock + Observer observer; + + + Func2 add2 = new Func2() { + @Override + public Integer call(Integer t1, Integer t2) { + return t1 + t2; + } + }; + Func2 mul2 = new Func2() { + @Override + public Integer call(Integer t1, Integer t2) { + return t1 * t2; + } + }; + Func2 sub2 = new Func2() { + @Override + public Integer call(Integer t1, Integer t2) { + return t1 - t2; + } + }; + + Func3 add3 = new Func3() { + @Override + public Integer call(Integer t1, Integer t2, Integer t3) { + return t1 + t2 + t3; + } + }; + Func1 func1Throw = new Func1() { + @Override + public Integer call(Integer t1) { + throw new RuntimeException("Forced failure"); + } + }; + Func2 func2Throw = new Func2() { + @Override + public Integer call(Integer t1, Integer t2) { + throw new RuntimeException("Forced failure"); + } + }; + Func3 func3Throw = new Func3() { + @Override + public Integer call(Integer t1, Integer t2, Integer t3) { + throw new RuntimeException("Forced failure"); + } + }; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + } + + @Test(expected = NullPointerException.class) + public void and2ArgumentNull() { + Observable some = Observable.just(1); + some.and(null); + } + @Test(expected = NullPointerException.class) + public void and3argumentNull() { + Observable some = Observable.just(1); + some.and(some).and(null); + } + @Test + public void and2() { + Observable some = Observable.just(1); + + Observable m = Observable.when(some.and(some).then(add2)); + + m.subscribe(observer); + + verify(observer, never()).onError(any(Throwable.class)); + verify(observer, times(1)).onNext(2); + verify(observer, times(1)).onCompleted(); + } + @Test + public void and2Error1() { + Observable error = Observable.error(new RuntimeException("Forced failure")); + + Observable some = Observable.just(1); + + Observable m = Observable.when(error.and(some).then(add2)); + + m.subscribe(observer); + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + } + @Test + public void and2Error2() { + Observable error = Observable.error(new RuntimeException("Forced failure")); + + Observable some = Observable.just(1); + + Observable m = Observable.when(some.and(error).then(add2)); + + m.subscribe(observer); + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + } + @Test + public void and3() { + Observable some = Observable.just(1); + + Observable m = Observable.when(some.and(some).and(some).then(add3)); + + m.subscribe(observer); + + verify(observer, never()).onError(any(Throwable.class)); + verify(observer, times(1)).onNext(3); + verify(observer, times(1)).onCompleted(); + } + @Test + public void and3Error1() { + Observable error = Observable.error(new RuntimeException("Forced failure")); + + Observable some = Observable.just(1); + + Observable m = Observable.when(error.and(some).and(some).then(add3)); + + m.subscribe(observer); + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + } + @Test + public void and3Error2() { + Observable error = Observable.error(new RuntimeException("Forced failure")); + + Observable some = Observable.just(1); + + Observable m = Observable.when(some.and(error).and(some).then(add3)); + + m.subscribe(observer); + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + } + @Test + public void and3Error3() { + Observable error = Observable.error(new RuntimeException("Forced failure")); + + Observable some = Observable.just(1); + + Observable m = Observable.when(some.and(some).and(error).then(add3)); + + m.subscribe(observer); + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + } + @Test(expected = NullPointerException.class) + public void thenArgumentNull() { + Observable some = Observable.just(1); + + some.then(null); + } + @Test(expected = NullPointerException.class) + public void then2ArgumentNull() { + Observable some = Observable.just(1); + + some.and(some).then(null); + } + @Test(expected = NullPointerException.class) + public void then3ArgumentNull() { + Observable some = Observable.just(1); + + some.and(some).and(some).then(null); + } + @Test + public void then1() { + Observable some = Observable.just(1); + + Observable m = Observable.when(some.then(Functions.identity())); + m.subscribe(observer); + + verify(observer, never()).onError(any(Throwable.class)); + verify(observer, times(1)).onNext(1); + verify(observer, times(1)).onCompleted(); + } + @Test + public void then1Error() { + Observable some = Observable.error(new RuntimeException("Forced failure")); + + Observable m = Observable.when(some.then(Functions.identity())); + m.subscribe(observer); + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + } + @Test + public void then1Throws() { + Observable some = Observable.just(1); + + Observable m = Observable.when(some.then(func1Throw)); + m.subscribe(observer); + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + } + @Test + public void then2Throws() { + Observable some = Observable.just(1); + + Observable m = Observable.when(some.and(some).then(func2Throw)); + m.subscribe(observer); + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + } + @Test + public void then3Throws() { + Observable some = Observable.just(1); + + Observable m = Observable.when(some.and(some).and(some).then(func3Throw)); + m.subscribe(observer); + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + } + @Test(expected = NullPointerException.class) + public void whenArgumentNull1() { + Observable.when((Plan0[])null); + } + @Test(expected = NullPointerException.class) + public void whenArgumentNull2() { + Observable.when((Iterable>)null); + } + @Test + public void whenMultipleSymmetric() { + Observable source1 = Observable.from(1, 2, 3); + Observable source2 = Observable.from(4, 5, 6); + + Observable m = Observable.when(source1.and(source2).then(add2)); + m.subscribe(observer); + + verify(observer, never()).onError(any(Throwable.class)); + verify(observer, times(1)).onNext(1 + 4); + verify(observer, times(1)).onNext(2 + 5); + verify(observer, times(1)).onNext(3 + 6); + verify(observer, times(1)).onCompleted(); + } + + @Test + public void whenMultipleAsymSymmetric() { + Observable source1 = Observable.from(1, 2, 3); + Observable source2 = Observable.from(4, 5); + + Observable m = Observable.when(source1.and(source2).then(add2)); + m.subscribe(observer); + + verify(observer, never()).onError(any(Throwable.class)); + verify(observer, times(1)).onNext(1 + 4); + verify(observer, times(1)).onNext(2 + 5); + verify(observer, times(1)).onCompleted(); + } + @Test + public void whenEmptyEmpty() { + Observable source1 = Observable.empty(); + Observable source2 = Observable.empty(); + + Observable m = Observable.when(source1.and(source2).then(add2)); + m.subscribe(observer); + + verify(observer, never()).onError(any(Throwable.class)); + verify(observer, never()).onNext(any()); + verify(observer, times(1)).onCompleted(); + } + + @Test + public void whenNeverNever() { + Observable source1 = Observable.never(); + Observable source2 = Observable.never(); + + Observable m = Observable.when(source1.and(source2).then(add2)); + m.subscribe(observer); + + verify(observer, never()).onError(any(Throwable.class)); + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + } + @Test + public void whenThrowNonEmpty() { + Observable source1 = Observable.empty(); + Observable source2 = Observable.error(new RuntimeException("Forced failure")); + + Observable m = Observable.when(source1.and(source2).then(add2)); + m.subscribe(observer); + + verify(observer, times(1)).onError(any(Throwable.class)); + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + } + @Test + public void whenComplicated() { + PublishSubject xs = PublishSubject.create(); + PublishSubject ys = PublishSubject.create(); + PublishSubject zs = PublishSubject.create(); + + Observable m = Observable.when( + xs.and(ys).then(add2), + xs.and(zs).then(mul2), + ys.and(zs).then(sub2) + ); + m.subscribe(observer); + + xs.onNext(1); // t == 210 + + xs.onNext(2); // t == 220 + zs.onNext(7); // t == 220 + + xs.onNext(3); // t == 230 + zs.onNext(8); // t == 230 + + ys.onNext(4); // t == 240 + zs.onNext(9); // t == 240 + xs.onCompleted(); // t == 240 + + ys.onNext(5); // t == 250 + + ys.onNext(6); // t == 260 + + ys.onCompleted(); // t == 270 + + zs.onCompleted(); // t == 300 + + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext(1 * 7); + inOrder.verify(observer, times(1)).onNext(2 * 8); + inOrder.verify(observer, times(1)).onNext(3 + 4); + inOrder.verify(observer, times(1)).onNext(5 - 9); + inOrder.verify(observer, times(1)).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationLastTest.java b/rxjava-core/src/test/java/rx/operators/OperationLastTest.java new file mode 100644 index 0000000000..006de63060 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationLastTest.java @@ -0,0 +1,49 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import rx.Observable; + +public class OperationLastTest { + + @Test + public void testLastWithElements() { + Observable last = Observable.create(OperationLast.last(Observable.from(1, 2, 3))); + assertEquals(3, last.toBlockingObservable().single().intValue()); + } + + @Test(expected = IllegalArgumentException.class) + public void testLastWithNoElements() { + Observable last = Observable.create(OperationLast.last(Observable.empty())); + last.toBlockingObservable().single(); + } + + @Test + public void testLastMultiSubscribe() { + Observable last = Observable.create(OperationLast.last(Observable.from(1, 2, 3))); + assertEquals(3, last.toBlockingObservable().single().intValue()); + assertEquals(3, last.toBlockingObservable().single().intValue()); + } + + @Test + public void testLastViaObservable() { + Observable.from(1, 2, 3).last(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationMapTest.java b/rxjava-core/src/test/java/rx/operators/OperationMapTest.java new file mode 100644 index 0000000000..175cd66f75 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationMapTest.java @@ -0,0 +1,336 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationMap.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import rx.Observable; +import rx.Observer; +import rx.concurrency.Schedulers; +import rx.util.functions.Func1; +import rx.util.functions.Func2; + +public class OperationMapTest { + + @Mock + Observer stringObserver; + @Mock + Observer stringObserver2; + + final static Func2 APPEND_INDEX = new Func2() { + @Override + public String call(String value, Integer index) { + return value + index; + } + }; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testMap() { + Map m1 = getMap("One"); + Map m2 = getMap("Two"); + Observable> observable = Observable.from(m1, m2); + + Observable m = Observable.create(map(observable, new Func1, String>() { + + @Override + public String call(Map map) { + return map.get("firstName"); + } + + })); + m.subscribe(stringObserver); + + verify(stringObserver, never()).onError(any(Throwable.class)); + verify(stringObserver, times(1)).onNext("OneFirst"); + verify(stringObserver, times(1)).onNext("TwoFirst"); + verify(stringObserver, times(1)).onCompleted(); + } + + @Test + public void testMapWithIndex() { + Observable w = Observable.from("a", "b", "c"); + Observable m = Observable.create(mapWithIndex(w, APPEND_INDEX)); + m.subscribe(stringObserver); + InOrder inOrder = inOrder(stringObserver); + inOrder.verify(stringObserver, times(1)).onNext("a0"); + inOrder.verify(stringObserver, times(1)).onNext("b1"); + inOrder.verify(stringObserver, times(1)).onNext("c2"); + inOrder.verify(stringObserver, times(1)).onCompleted(); + verify(stringObserver, never()).onError(any(Throwable.class)); + } + + @Test + public void testMapWithIndexAndMultipleSubscribers() { + Observable w = Observable.from("a", "b", "c"); + Observable m = Observable.create(mapWithIndex(w, APPEND_INDEX)); + m.subscribe(stringObserver); + m.subscribe(stringObserver2); + InOrder inOrder = inOrder(stringObserver); + inOrder.verify(stringObserver, times(1)).onNext("a0"); + inOrder.verify(stringObserver, times(1)).onNext("b1"); + inOrder.verify(stringObserver, times(1)).onNext("c2"); + inOrder.verify(stringObserver, times(1)).onCompleted(); + verify(stringObserver, never()).onError(any(Throwable.class)); + + InOrder inOrder2 = inOrder(stringObserver2); + inOrder2.verify(stringObserver2, times(1)).onNext("a0"); + inOrder2.verify(stringObserver2, times(1)).onNext("b1"); + inOrder2.verify(stringObserver2, times(1)).onNext("c2"); + inOrder2.verify(stringObserver2, times(1)).onCompleted(); + verify(stringObserver2, never()).onError(any(Throwable.class)); + } + + @Test + public void testMapMany() { + /* simulate a top-level async call which returns IDs */ + Observable ids = Observable.from(1, 2); + + /* now simulate the behavior to take those IDs and perform nested async calls based on them */ + Observable m = Observable.create(mapMany(ids, new Func1>() { + + @Override + public Observable call(Integer id) { + /* simulate making a nested async call which creates another Observable */ + Observable> subObservable = null; + if (id == 1) { + Map m1 = getMap("One"); + Map m2 = getMap("Two"); + subObservable = Observable.from(m1, m2); + } else { + Map m3 = getMap("Three"); + Map m4 = getMap("Four"); + subObservable = Observable.from(m3, m4); + } + + /* simulate kicking off the async call and performing a select on it to transform the data */ + return Observable.create(map(subObservable, new Func1, String>() { + @Override + public String call(Map map) { + return map.get("firstName"); + } + })); + } + + })); + m.subscribe(stringObserver); + + verify(stringObserver, never()).onError(any(Throwable.class)); + verify(stringObserver, times(1)).onNext("OneFirst"); + verify(stringObserver, times(1)).onNext("TwoFirst"); + verify(stringObserver, times(1)).onNext("ThreeFirst"); + verify(stringObserver, times(1)).onNext("FourFirst"); + verify(stringObserver, times(1)).onCompleted(); + } + + @Test + public void testMapMany2() { + Map m1 = getMap("One"); + Map m2 = getMap("Two"); + Observable> observable1 = Observable.from(m1, m2); + + Map m3 = getMap("Three"); + Map m4 = getMap("Four"); + Observable> observable2 = Observable.from(m3, m4); + + Observable>> observable = Observable.from(observable1, observable2); + + Observable m = Observable.create(mapMany(observable, new Func1>, Observable>() { + + @Override + public Observable call(Observable> o) { + return Observable.create(map(o, new Func1, String>() { + + @Override + public String call(Map map) { + return map.get("firstName"); + } + })); + } + + })); + m.subscribe(stringObserver); + + verify(stringObserver, never()).onError(any(Throwable.class)); + verify(stringObserver, times(1)).onNext("OneFirst"); + verify(stringObserver, times(1)).onNext("TwoFirst"); + verify(stringObserver, times(1)).onNext("ThreeFirst"); + verify(stringObserver, times(1)).onNext("FourFirst"); + verify(stringObserver, times(1)).onCompleted(); + + } + + @Test + public void testMapWithError() { + Observable w = Observable.from("one", "fail", "two", "three", "fail"); + Observable m = Observable.create(map(w, new Func1() { + @Override + public String call(String s) { + if ("fail".equals(s)) { + throw new RuntimeException("Forced Failure"); + } + return s; + } + })); + + m.subscribe(stringObserver); + verify(stringObserver, times(1)).onNext("one"); + verify(stringObserver, never()).onNext("two"); + verify(stringObserver, never()).onNext("three"); + verify(stringObserver, never()).onCompleted(); + verify(stringObserver, times(1)).onError(any(Throwable.class)); + } + + /** + * This is testing how unsubscribe behavior is handled when an error occurs in a user provided function + * and the source is unsubscribed from ... but ignores or can't receive the unsubscribe as it is synchronous. + */ + @Test + public void testMapContainingErrorWithSequenceThatDoesntUnsubscribe() { + Observable w = Observable.from("one", "fail", "two", "three", "fail"); + final AtomicInteger c1 = new AtomicInteger(); + final AtomicInteger c2 = new AtomicInteger(); + Observable m = Observable.create(map(w, new Func1() { + @Override + public String call(String s) { + if ("fail".equals(s)) + throw new RuntimeException("Forced Failure"); + System.out.println("BadMapper:" + s); + c1.incrementAndGet(); + return s; + } + })).map(new Func1() { + @Override + public String call(String s) { + System.out.println("SecondMapper:" + s); + c2.incrementAndGet(); + return s; + } + }); + + m.subscribe(stringObserver); + + verify(stringObserver, times(1)).onNext("one"); + verify(stringObserver, never()).onNext("two"); + verify(stringObserver, never()).onNext("three"); + verify(stringObserver, never()).onCompleted(); + verify(stringObserver, times(1)).onError(any(Throwable.class)); + + // We should have only returned 1 value: "one" + // Since the unsubscribe doesn't propagate, we will actually be sent all events and need + // to ignore all after the first failure. + assertEquals(1, c1.get()); + assertEquals(1, c2.get()); + } + + @Test(expected = IllegalArgumentException.class) + public void testMapWithIssue417() { + Observable.from(1).observeOn(Schedulers.threadPoolForComputation()) + .map(new Func1() { + public Integer call(Integer arg0) { + throw new IllegalArgumentException("any error"); + } + }).toBlockingObservable().single(); + } + + @Test(expected = IllegalArgumentException.class) + public void testMapWithErrorInFuncAndThreadPoolScheduler() throws InterruptedException { + // The error will throw in one of threads in the thread pool. + // If map does not handle it, the error will disappear. + // so map needs to handle the error by itself. + Observable m = Observable.from("one") + .observeOn(Schedulers.threadPoolForComputation()) + .map(new Func1() { + public String call(String arg0) { + throw new IllegalArgumentException("any error"); + } + }); + + // block for response, expecting exception thrown + m.toBlockingObservable().last(); + } + + /** + * While mapping over range(1,1).last() we expect IllegalArgumentException since the sequence is empty. + */ + @Test(expected = IllegalArgumentException.class) + public void testErrorPassesThruMap() { + Observable.range(1,0).last().map(new Func1() { + + @Override + public Integer call(Integer i) { + return i; + } + + }).toBlockingObservable().single(); + } + + /** + * We expect IllegalStateException to pass thru map. + */ + @Test(expected = IllegalStateException.class) + public void testErrorPassesThruMap2() { + Observable.error(new IllegalStateException()).map(new Func1() { + + @Override + public Object call(Object i) { + return i; + } + + }).toBlockingObservable().single(); + } + + /** + * We expect an ArithmeticException exception here because last() emits a single value + * but then we divide by 0. + */ + @Test(expected = ArithmeticException.class) + public void testMapWithErrorInFunc() { + Observable.range(1,1).last().map(new Func1() { + + @Override + public Integer call(Integer i) { + return i/0; + } + + }).toBlockingObservable().single(); + } + + private static Map getMap(String prefix) { + Map m = new HashMap(); + m.put("firstName", prefix + "First"); + m.put("lastName", prefix + "Last"); + return m; + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationMaterializeTest.java b/rxjava-core/src/test/java/rx/operators/OperationMaterializeTest.java new file mode 100644 index 0000000000..54d1ab0474 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationMaterializeTest.java @@ -0,0 +1,166 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static rx.operators.OperationMaterialize.*; + +import java.util.List; +import java.util.Vector; +import java.util.concurrent.ExecutionException; + +import org.junit.Test; + +import rx.Notification; +import rx.Observable; +import rx.Observer; +import rx.Subscription; + +public class OperationMaterializeTest { + + @Test + public void testMaterialize1() { + // null will cause onError to be triggered before "three" can be returned + final TestAsyncErrorObservable o1 = new TestAsyncErrorObservable("one", "two", null, "three"); + + TestObserver Observer = new TestObserver(); + Observable> m = Observable.create(materialize(Observable.create(o1))); + m.subscribe(Observer); + + try { + o1.t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + assertFalse(Observer.onError); + assertTrue(Observer.onCompleted); + assertEquals(3, Observer.notifications.size()); + assertEquals("one", Observer.notifications.get(0).getValue()); + assertTrue(Observer.notifications.get(0).isOnNext()); + assertEquals("two", Observer.notifications.get(1).getValue()); + assertTrue(Observer.notifications.get(1).isOnNext()); + assertEquals(NullPointerException.class, Observer.notifications.get(2).getThrowable().getClass()); + assertTrue(Observer.notifications.get(2).isOnError()); + } + + @Test + public void testMaterialize2() { + final TestAsyncErrorObservable o1 = new TestAsyncErrorObservable("one", "two", "three"); + + TestObserver Observer = new TestObserver(); + Observable> m = Observable.create(materialize(Observable.create(o1))); + m.subscribe(Observer); + + try { + o1.t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + assertFalse(Observer.onError); + assertTrue(Observer.onCompleted); + assertEquals(4, Observer.notifications.size()); + assertEquals("one", Observer.notifications.get(0).getValue()); + assertTrue(Observer.notifications.get(0).isOnNext()); + assertEquals("two", Observer.notifications.get(1).getValue()); + assertTrue(Observer.notifications.get(1).isOnNext()); + assertEquals("three", Observer.notifications.get(2).getValue()); + assertTrue(Observer.notifications.get(2).isOnNext()); + assertTrue(Observer.notifications.get(3).isOnCompleted()); + } + + @Test + public void testMultipleSubscribes() throws InterruptedException, ExecutionException { + final TestAsyncErrorObservable o = new TestAsyncErrorObservable("one", "two", null, "three"); + + Observable> m = Observable.create(materialize(Observable.create(o))); + + assertEquals(3, m.toList().toBlockingObservable().toFuture().get().size()); + assertEquals(3, m.toList().toBlockingObservable().toFuture().get().size()); + } + + private static class TestObserver implements Observer> { + + boolean onCompleted = false; + boolean onError = false; + List> notifications = new Vector>(); + + @Override + public void onCompleted() { + this.onCompleted = true; + } + + @Override + public void onError(Throwable e) { + this.onError = true; + } + + @Override + public void onNext(Notification value) { + this.notifications.add(value); + } + + } + + private static class TestAsyncErrorObservable implements Observable.OnSubscribeFunc { + + String[] valuesToReturn; + + TestAsyncErrorObservable(String... values) { + valuesToReturn = values; + } + + volatile Thread t; + + @Override + public Subscription onSubscribe(final Observer observer) { + t = new Thread(new Runnable() { + + @Override + public void run() { + for (String s : valuesToReturn) { + if (s == null) { + System.out.println("throwing exception"); + try { + Thread.sleep(100); + } catch (Throwable e) { + + } + observer.onError(new NullPointerException()); + return; + } else { + observer.onNext(s); + } + } + System.out.println("subscription complete"); + observer.onCompleted(); + } + + }); + t.start(); + + return new Subscription() { + + @Override + public void unsubscribe() { + + } + + }; + } + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationMergeDelayErrorTest.java b/rxjava-core/src/test/java/rx/operators/OperationMergeDelayErrorTest.java new file mode 100644 index 0000000000..693b9d4801 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationMergeDelayErrorTest.java @@ -0,0 +1,515 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationMergeDelayError.*; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.util.CompositeException; + +public class OperationMergeDelayErrorTest { + + @Mock + Observer stringObserver; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testErrorDelayed1() { + final Observable o1 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called + final Observable o2 = Observable.create(new TestErrorObservable("one", "two", "three")); + + @SuppressWarnings("unchecked") + Observable m = Observable.create(mergeDelayError(o1, o2)); + m.subscribe(stringObserver); + + verify(stringObserver, times(1)).onError(any(NullPointerException.class)); + verify(stringObserver, never()).onCompleted(); + verify(stringObserver, times(1)).onNext("one"); + verify(stringObserver, times(1)).onNext("two"); + verify(stringObserver, times(1)).onNext("three"); + verify(stringObserver, times(1)).onNext("four"); + verify(stringObserver, times(0)).onNext("five"); + verify(stringObserver, times(0)).onNext("six"); + } + + @Test + public void testErrorDelayed2() { + final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); + final Observable o2 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called + final Observable o3 = Observable.create(new TestErrorObservable("seven", "eight", null)); + final Observable o4 = Observable.create(new TestErrorObservable("nine")); + + @SuppressWarnings("unchecked") + Observable m = Observable.create(mergeDelayError(o1, o2, o3, o4)); + m.subscribe(stringObserver); + + verify(stringObserver, times(1)).onError(any(NullPointerException.class)); + verify(stringObserver, never()).onCompleted(); + verify(stringObserver, times(1)).onNext("one"); + verify(stringObserver, times(1)).onNext("two"); + verify(stringObserver, times(1)).onNext("three"); + verify(stringObserver, times(1)).onNext("four"); + verify(stringObserver, times(0)).onNext("five"); + verify(stringObserver, times(0)).onNext("six"); + verify(stringObserver, times(1)).onNext("seven"); + verify(stringObserver, times(1)).onNext("eight"); + verify(stringObserver, times(1)).onNext("nine"); + } + + @Test + public void testErrorDelayed3() { + final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); + final Observable o2 = Observable.create(new TestErrorObservable("four", "five", "six")); + final Observable o3 = Observable.create(new TestErrorObservable("seven", "eight", null)); + final Observable o4 = Observable.create(new TestErrorObservable("nine")); + + @SuppressWarnings("unchecked") + Observable m = Observable.create(mergeDelayError(o1, o2, o3, o4)); + m.subscribe(stringObserver); + + verify(stringObserver, times(1)).onError(any(NullPointerException.class)); + verify(stringObserver, never()).onCompleted(); + verify(stringObserver, times(1)).onNext("one"); + verify(stringObserver, times(1)).onNext("two"); + verify(stringObserver, times(1)).onNext("three"); + verify(stringObserver, times(1)).onNext("four"); + verify(stringObserver, times(1)).onNext("five"); + verify(stringObserver, times(1)).onNext("six"); + verify(stringObserver, times(1)).onNext("seven"); + verify(stringObserver, times(1)).onNext("eight"); + verify(stringObserver, times(1)).onNext("nine"); + } + + @Test + public void testErrorDelayed4() { + final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); + final Observable o2 = Observable.create(new TestErrorObservable("four", "five", "six")); + final Observable o3 = Observable.create(new TestErrorObservable("seven", "eight")); + final Observable o4 = Observable.create(new TestErrorObservable("nine", null)); + + @SuppressWarnings("unchecked") + Observable m = Observable.create(mergeDelayError(o1, o2, o3, o4)); + m.subscribe(stringObserver); + + verify(stringObserver, times(1)).onError(any(NullPointerException.class)); + verify(stringObserver, never()).onCompleted(); + verify(stringObserver, times(1)).onNext("one"); + verify(stringObserver, times(1)).onNext("two"); + verify(stringObserver, times(1)).onNext("three"); + verify(stringObserver, times(1)).onNext("four"); + verify(stringObserver, times(1)).onNext("five"); + verify(stringObserver, times(1)).onNext("six"); + verify(stringObserver, times(1)).onNext("seven"); + verify(stringObserver, times(1)).onNext("eight"); + verify(stringObserver, times(1)).onNext("nine"); + } + + @Test + public void testErrorDelayed4WithThreading() { + final TestAsyncErrorObservable o1 = new TestAsyncErrorObservable("one", "two", "three"); + final TestAsyncErrorObservable o2 = new TestAsyncErrorObservable("four", "five", "six"); + final TestAsyncErrorObservable o3 = new TestAsyncErrorObservable("seven", "eight"); + // throw the error at the very end so no onComplete will be called after it + final TestAsyncErrorObservable o4 = new TestAsyncErrorObservable("nine", null); + + @SuppressWarnings("unchecked") + Observable m = Observable.create(mergeDelayError(Observable.create(o1), Observable.create(o2), Observable.create(o3), Observable.create(o4))); + m.subscribe(stringObserver); + + try { + o1.t.join(); + o2.t.join(); + o3.t.join(); + o4.t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + verify(stringObserver, times(1)).onError(any(NullPointerException.class)); + verify(stringObserver, never()).onCompleted(); + verify(stringObserver, times(1)).onNext("one"); + verify(stringObserver, times(1)).onNext("two"); + verify(stringObserver, times(1)).onNext("three"); + verify(stringObserver, times(1)).onNext("four"); + verify(stringObserver, times(1)).onNext("five"); + verify(stringObserver, times(1)).onNext("six"); + verify(stringObserver, times(1)).onNext("seven"); + verify(stringObserver, times(1)).onNext("eight"); + verify(stringObserver, times(1)).onNext("nine"); + } + + @Test + public void testCompositeErrorDelayed1() { + final Observable o1 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called + final Observable o2 = Observable.create(new TestErrorObservable("one", "two", null)); + + @SuppressWarnings("unchecked") + Observable m = Observable.create(mergeDelayError(o1, o2)); + m.subscribe(stringObserver); + + verify(stringObserver, times(1)).onError(any(CompositeException.class)); + verify(stringObserver, never()).onCompleted(); + verify(stringObserver, times(1)).onNext("one"); + verify(stringObserver, times(1)).onNext("two"); + verify(stringObserver, times(0)).onNext("three"); + verify(stringObserver, times(1)).onNext("four"); + verify(stringObserver, times(0)).onNext("five"); + verify(stringObserver, times(0)).onNext("six"); + } + + @Test + public void testCompositeErrorDelayed2() { + final Observable o1 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" from the source (and it should never be sent by the source since onError was called + final Observable o2 = Observable.create(new TestErrorObservable("one", "two", null)); + + @SuppressWarnings("unchecked") + Observable m = Observable.create(mergeDelayError(o1, o2)); + CaptureObserver w = new CaptureObserver(); + m.subscribe(w); + + assertNotNull(w.e); + if (w.e instanceof CompositeException) { + assertEquals(2, ((CompositeException) w.e).getExceptions().size()); + w.e.printStackTrace(); + } else { + fail("Expecting CompositeException"); + } + + } + + /** + * The unit tests below are from OperationMerge and should ensure the normal merge functionality is correct. + */ + + @Test + public void testMergeObservableOfObservables() { + final Observable o1 = Observable.create(new TestSynchronousObservable()); + final Observable o2 = Observable.create(new TestSynchronousObservable()); + + Observable> observableOfObservables = Observable.create(new Observable.OnSubscribeFunc>() { + + @Override + public Subscription onSubscribe(Observer> observer) { + // simulate what would happen in an observable + observer.onNext(o1); + observer.onNext(o2); + observer.onCompleted(); + + return new Subscription() { + + @Override + public void unsubscribe() { + // unregister ... will never be called here since we are executing synchronously + } + + }; + } + + }); + Observable m = Observable.create(mergeDelayError(observableOfObservables)); + m.subscribe(stringObserver); + + verify(stringObserver, never()).onError(any(Throwable.class)); + verify(stringObserver, times(1)).onCompleted(); + verify(stringObserver, times(2)).onNext("hello"); + } + + @Test + public void testMergeArray() { + final Observable o1 = Observable.create(new TestSynchronousObservable()); + final Observable o2 = Observable.create(new TestSynchronousObservable()); + + @SuppressWarnings("unchecked") + Observable m = Observable.create(mergeDelayError(o1, o2)); + m.subscribe(stringObserver); + + verify(stringObserver, never()).onError(any(Throwable.class)); + verify(stringObserver, times(2)).onNext("hello"); + verify(stringObserver, times(1)).onCompleted(); + } + + @Test + public void testMergeList() { + final Observable o1 = Observable.create(new TestSynchronousObservable()); + final Observable o2 = Observable.create(new TestSynchronousObservable()); + List> listOfObservables = new ArrayList>(); + listOfObservables.add(o1); + listOfObservables.add(o2); + + Observable m = Observable.create(mergeDelayError(listOfObservables)); + m.subscribe(stringObserver); + + verify(stringObserver, never()).onError(any(Throwable.class)); + verify(stringObserver, times(1)).onCompleted(); + verify(stringObserver, times(2)).onNext("hello"); + } + + @Test + public void testUnSubscribe() { + TestObservable tA = new TestObservable(); + TestObservable tB = new TestObservable(); + + @SuppressWarnings("unchecked") + Observable m = Observable.create(mergeDelayError(Observable.create(tA), Observable.create(tB))); + Subscription s = m.subscribe(stringObserver); + + tA.sendOnNext("Aone"); + tB.sendOnNext("Bone"); + s.unsubscribe(); + tA.sendOnNext("Atwo"); + tB.sendOnNext("Btwo"); + tA.sendOnCompleted(); + tB.sendOnCompleted(); + + verify(stringObserver, never()).onError(any(Throwable.class)); + verify(stringObserver, times(1)).onNext("Aone"); + verify(stringObserver, times(1)).onNext("Bone"); + assertTrue(tA.unsubscribed); + assertTrue(tB.unsubscribed); + verify(stringObserver, never()).onNext("Atwo"); + verify(stringObserver, never()).onNext("Btwo"); + verify(stringObserver, never()).onCompleted(); + } + + @Test + public void testMergeArrayWithThreading() { + final TestASynchronousObservable o1 = new TestASynchronousObservable(); + final TestASynchronousObservable o2 = new TestASynchronousObservable(); + + @SuppressWarnings("unchecked") + Observable m = Observable.create(mergeDelayError(Observable.create(o1), Observable.create(o2))); + m.subscribe(stringObserver); + + try { + o1.t.join(); + o2.t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + verify(stringObserver, never()).onError(any(Throwable.class)); + verify(stringObserver, times(2)).onNext("hello"); + verify(stringObserver, times(1)).onCompleted(); + } + + private static class TestSynchronousObservable implements Observable.OnSubscribeFunc { + + @Override + public Subscription onSubscribe(Observer observer) { + + observer.onNext("hello"); + observer.onCompleted(); + + return new Subscription() { + + @Override + public void unsubscribe() { + // unregister ... will never be called here since we are executing synchronously + } + + }; + } + } + + private static class TestASynchronousObservable implements Observable.OnSubscribeFunc { + Thread t; + + @Override + public Subscription onSubscribe(final Observer observer) { + t = new Thread(new Runnable() { + + @Override + public void run() { + observer.onNext("hello"); + observer.onCompleted(); + } + + }); + t.start(); + + return new Subscription() { + + @Override + public void unsubscribe() { + + } + + }; + } + } + + /** + * A Observable that doesn't do the right thing on UnSubscribe/Error/etc in that it will keep sending events down the pipe regardless of what happens. + */ + private static class TestObservable implements Observable.OnSubscribeFunc { + + Observer observer = null; + volatile boolean unsubscribed = false; + Subscription s = new Subscription() { + + @Override + public void unsubscribe() { + unsubscribed = true; + + } + + }; + + /* used to simulate subscription */ + public void sendOnCompleted() { + observer.onCompleted(); + } + + /* used to simulate subscription */ + public void sendOnNext(String value) { + observer.onNext(value); + } + + /* used to simulate subscription */ + @SuppressWarnings("unused") + public void sendOnError(Throwable e) { + observer.onError(e); + } + + @Override + public Subscription onSubscribe(final Observer observer) { + this.observer = observer; + return s; + } + } + + private static class TestErrorObservable implements Observable.OnSubscribeFunc { + + String[] valuesToReturn; + + TestErrorObservable(String... values) { + valuesToReturn = values; + } + + @Override + public Subscription onSubscribe(Observer observer) { + boolean errorThrown = false; + for (String s : valuesToReturn) { + if (s == null) { + System.out.println("throwing exception"); + observer.onError(new NullPointerException()); + errorThrown = true; + // purposefully not returning here so it will continue calling onNext + // so that we also test that we handle bad sequences like this + } else { + observer.onNext(s); + } + } + if (!errorThrown) { + observer.onCompleted(); + } + + return new Subscription() { + + @Override + public void unsubscribe() { + // unregister ... will never be called here since we are executing synchronously + } + + }; + } + } + + private static class TestAsyncErrorObservable implements Observable.OnSubscribeFunc { + + String[] valuesToReturn; + + TestAsyncErrorObservable(String... values) { + valuesToReturn = values; + } + + Thread t; + + @Override + public Subscription onSubscribe(final Observer observer) { + t = new Thread(new Runnable() { + + @Override + public void run() { + for (String s : valuesToReturn) { + if (s == null) { + System.out.println("throwing exception"); + try { + Thread.sleep(100); + } catch (Throwable e) { + + } + observer.onError(new NullPointerException()); + return; + } else { + observer.onNext(s); + } + } + System.out.println("subscription complete"); + observer.onCompleted(); + } + + }); + t.start(); + + return new Subscription() { + + @Override + public void unsubscribe() { + + } + + }; + } + } + + private static class CaptureObserver implements Observer { + volatile Throwable e; + + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + this.e = e; + } + + @Override + public void onNext(String args) { + + } + + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationMergeTest.java b/rxjava-core/src/test/java/rx/operators/OperationMergeTest.java new file mode 100644 index 0000000000..311a838691 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationMergeTest.java @@ -0,0 +1,474 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationMerge.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action0; +import rx.util.functions.Action1; + +public class OperationMergeTest { + + @Mock + Observer stringObserver; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testMergeObservableOfObservables() { + final Observable o1 = Observable.create(new TestSynchronousObservable()); + final Observable o2 = Observable.create(new TestSynchronousObservable()); + + Observable> observableOfObservables = Observable.create(new Observable.OnSubscribeFunc>() { + + @Override + public Subscription onSubscribe(Observer> observer) { + // simulate what would happen in an observable + observer.onNext(o1); + observer.onNext(o2); + observer.onCompleted(); + + return new Subscription() { + + @Override + public void unsubscribe() { + // unregister ... will never be called here since we are executing synchronously + } + + }; + } + + }); + Observable m = Observable.create(merge(observableOfObservables)); + m.subscribe(stringObserver); + + verify(stringObserver, never()).onError(any(Throwable.class)); + verify(stringObserver, times(1)).onCompleted(); + verify(stringObserver, times(2)).onNext("hello"); + } + + @Test + public void testMergeArray() { + final Observable o1 = Observable.create(new TestSynchronousObservable()); + final Observable o2 = Observable.create(new TestSynchronousObservable()); + + @SuppressWarnings("unchecked") + Observable m = Observable.create(merge(o1, o2)); + m.subscribe(stringObserver); + + verify(stringObserver, never()).onError(any(Throwable.class)); + verify(stringObserver, times(2)).onNext("hello"); + verify(stringObserver, times(1)).onCompleted(); + } + + @Test + public void testMergeList() { + final Observable o1 = Observable.create(new TestSynchronousObservable()); + final Observable o2 = Observable.create(new TestSynchronousObservable()); + List> listOfObservables = new ArrayList>(); + listOfObservables.add(o1); + listOfObservables.add(o2); + + Observable m = Observable.create(merge(listOfObservables)); + m.subscribe(stringObserver); + + verify(stringObserver, never()).onError(any(Throwable.class)); + verify(stringObserver, times(1)).onCompleted(); + verify(stringObserver, times(2)).onNext("hello"); + } + + @Test + public void testUnSubscribe() { + TestObservable tA = new TestObservable(); + TestObservable tB = new TestObservable(); + + @SuppressWarnings("unchecked") + Observable m = Observable.create(merge(Observable.create(tA), Observable.create(tB))); + Subscription s = m.subscribe(stringObserver); + + tA.sendOnNext("Aone"); + tB.sendOnNext("Bone"); + s.unsubscribe(); + tA.sendOnNext("Atwo"); + tB.sendOnNext("Btwo"); + tA.sendOnCompleted(); + tB.sendOnCompleted(); + + verify(stringObserver, never()).onError(any(Throwable.class)); + verify(stringObserver, times(1)).onNext("Aone"); + verify(stringObserver, times(1)).onNext("Bone"); + assertTrue(tA.unsubscribed); + assertTrue(tB.unsubscribed); + verify(stringObserver, never()).onNext("Atwo"); + verify(stringObserver, never()).onNext("Btwo"); + verify(stringObserver, never()).onCompleted(); + } + + @Test + public void testUnSubscribeObservableOfObservables() throws InterruptedException { + + final AtomicBoolean unsubscribed = new AtomicBoolean(); + final CountDownLatch latch = new CountDownLatch(1); + + Observable> source = Observable.create(new Observable.OnSubscribeFunc>() { + + @Override + public Subscription onSubscribe(final Observer> observer) { + // verbose on purpose so I can track the inside of it + final Subscription s = Subscriptions.create(new Action0() { + + @Override + public void call() { + System.out.println("*** unsubscribed"); + unsubscribed.set(true); + } + + }); + + new Thread(new Runnable() { + + @Override + public void run() { + + while (!unsubscribed.get()) { + observer.onNext(Observable.from(1L, 2L)); + } + System.out.println("Done looping after unsubscribe: " + unsubscribed.get()); + observer.onCompleted(); + + // mark that the thread is finished + latch.countDown(); + } + }).start(); + + return s; + } + + ; + + }); + + final AtomicInteger count = new AtomicInteger(); + Observable.create(merge(source)).take(6).toBlockingObservable().forEach(new Action1() { + + @Override + public void call(Long v) { + System.out.println("Value: " + v); + int c = count.incrementAndGet(); + if (c > 6) { + fail("Should be only 6"); + } + + } + }); + + latch.await(1000, TimeUnit.MILLISECONDS); + + System.out.println("unsubscribed: " + unsubscribed.get()); + + assertTrue(unsubscribed.get()); + + } + + @Test + public void testMergeArrayWithThreading() { + final TestASynchronousObservable o1 = new TestASynchronousObservable(); + final TestASynchronousObservable o2 = new TestASynchronousObservable(); + + @SuppressWarnings("unchecked") + Observable m = Observable.create(merge(Observable.create(o1), Observable.create(o2))); + m.subscribe(stringObserver); + + try { + o1.t.join(); + o2.t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + verify(stringObserver, never()).onError(any(Throwable.class)); + verify(stringObserver, times(2)).onNext("hello"); + verify(stringObserver, times(1)).onCompleted(); + } + + @Test + public void testSynchronizationOfMultipleSequences() throws Throwable { + final TestASynchronousObservable o1 = new TestASynchronousObservable(); + final TestASynchronousObservable o2 = new TestASynchronousObservable(); + + // use this latch to cause onNext to wait until we're ready to let it go + final CountDownLatch endLatch = new CountDownLatch(1); + + final AtomicInteger concurrentCounter = new AtomicInteger(); + final AtomicInteger totalCounter = new AtomicInteger(); + + @SuppressWarnings("unchecked") + Observable m = Observable.create(merge(Observable.create(o1), Observable.create(o2))); + m.subscribe(new Observer() { + + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + throw new RuntimeException("failed", e); + } + + @Override + public void onNext(String v) { + totalCounter.incrementAndGet(); + concurrentCounter.incrementAndGet(); + try { + // wait here until we're done asserting + endLatch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + throw new RuntimeException("failed", e); + } finally { + concurrentCounter.decrementAndGet(); + } + } + + }); + + // wait for both observables to send (one should be blocked) + o1.onNextBeingSent.await(); + o2.onNextBeingSent.await(); + + // I can't think of a way to know for sure that both threads have or are trying to send onNext + // since I can't use a CountDownLatch for "after" onNext since I want to catch during it + // but I can't know for sure onNext is invoked + // so I'm unfortunately reverting to using a Thread.sleep to allow the process scheduler time + // to make sure after o1.onNextBeingSent and o2.onNextBeingSent are hit that the following + // onNext is invoked. + + Thread.sleep(300); + + try { // in try/finally so threads are released via latch countDown even if assertion fails + assertEquals(1, concurrentCounter.get()); + } finally { + // release so it can finish + endLatch.countDown(); + } + + try { + o1.t.join(); + o2.t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + assertEquals(2, totalCounter.get()); + assertEquals(0, concurrentCounter.get()); + } + + /** + * unit test from OperationMergeDelayError backported here to show how these use cases work with normal merge + */ + @Test + public void testError1() { + // we are using synchronous execution to test this exactly rather than non-deterministic concurrent behavior + final Observable o1 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" + final Observable o2 = Observable.create(new TestErrorObservable("one", "two", "three")); // we expect to lose all of these since o1 is done first and fails + + @SuppressWarnings("unchecked") + Observable m = Observable.create(merge(o1, o2)); + m.subscribe(stringObserver); + + verify(stringObserver, times(1)).onError(any(NullPointerException.class)); + verify(stringObserver, never()).onCompleted(); + verify(stringObserver, times(0)).onNext("one"); + verify(stringObserver, times(0)).onNext("two"); + verify(stringObserver, times(0)).onNext("three"); + verify(stringObserver, times(1)).onNext("four"); + verify(stringObserver, times(0)).onNext("five"); + verify(stringObserver, times(0)).onNext("six"); + } + + /** + * unit test from OperationMergeDelayError backported here to show how these use cases work with normal merge + */ + @Test + public void testError2() { + // we are using synchronous execution to test this exactly rather than non-deterministic concurrent behavior + final Observable o1 = Observable.create(new TestErrorObservable("one", "two", "three")); + final Observable o2 = Observable.create(new TestErrorObservable("four", null, "six")); // we expect to lose "six" + final Observable o3 = Observable.create(new TestErrorObservable("seven", "eight", null));// we expect to lose all of these since o2 is done first and fails + final Observable o4 = Observable.create(new TestErrorObservable("nine"));// we expect to lose all of these since o2 is done first and fails + + @SuppressWarnings("unchecked") + Observable m = Observable.create(merge(o1, o2, o3, o4)); + m.subscribe(stringObserver); + + verify(stringObserver, times(1)).onError(any(NullPointerException.class)); + verify(stringObserver, never()).onCompleted(); + verify(stringObserver, times(1)).onNext("one"); + verify(stringObserver, times(1)).onNext("two"); + verify(stringObserver, times(1)).onNext("three"); + verify(stringObserver, times(1)).onNext("four"); + verify(stringObserver, times(0)).onNext("five"); + verify(stringObserver, times(0)).onNext("six"); + verify(stringObserver, times(0)).onNext("seven"); + verify(stringObserver, times(0)).onNext("eight"); + verify(stringObserver, times(0)).onNext("nine"); + } + + private static class TestSynchronousObservable implements Observable.OnSubscribeFunc { + + @Override + public Subscription onSubscribe(Observer observer) { + + observer.onNext("hello"); + observer.onCompleted(); + + return new Subscription() { + + @Override + public void unsubscribe() { + // unregister ... will never be called here since we are executing synchronously + } + + }; + } + } + + private static class TestASynchronousObservable implements Observable.OnSubscribeFunc { + Thread t; + final CountDownLatch onNextBeingSent = new CountDownLatch(1); + + @Override + public Subscription onSubscribe(final Observer observer) { + t = new Thread(new Runnable() { + + @Override + public void run() { + onNextBeingSent.countDown(); + observer.onNext("hello"); + // I can't use a countDownLatch to prove we are actually sending 'onNext' + // since it will block if synchronized and I'll deadlock + observer.onCompleted(); + } + + }); + t.start(); + + return new Subscription() { + + @Override + public void unsubscribe() { + + } + + }; + } + } + + /** + * A Observable that doesn't do the right thing on UnSubscribe/Error/etc in that it will keep sending events down the pipe regardless of what happens. + */ + private static class TestObservable implements Observable.OnSubscribeFunc { + + Observer observer = null; + volatile boolean unsubscribed = false; + Subscription s = new Subscription() { + + @Override + public void unsubscribe() { + unsubscribed = true; + + } + + }; + + /* used to simulate subscription */ + public void sendOnCompleted() { + observer.onCompleted(); + } + + /* used to simulate subscription */ + public void sendOnNext(String value) { + observer.onNext(value); + } + + /* used to simulate subscription */ + @SuppressWarnings("unused") + public void sendOnError(Throwable e) { + observer.onError(e); + } + + @Override + public Subscription onSubscribe(final Observer observer) { + this.observer = observer; + return s; + } + } + + private static class TestErrorObservable implements Observable.OnSubscribeFunc { + + String[] valuesToReturn; + + TestErrorObservable(String... values) { + valuesToReturn = values; + } + + @Override + public Subscription onSubscribe(Observer observer) { + + for (String s : valuesToReturn) { + if (s == null) { + System.out.println("throwing exception"); + observer.onError(new NullPointerException()); + } else { + observer.onNext(s); + } + } + observer.onCompleted(); + + return new Subscription() { + + @Override + public void unsubscribe() { + // unregister ... will never be called here since we are executing synchronously + } + + }; + } + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationMinMaxTest.java b/rxjava-core/src/test/java/rx/operators/OperationMinMaxTest.java new file mode 100644 index 0000000000..f2deff9e57 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationMinMaxTest.java @@ -0,0 +1,359 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.isA; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static rx.operators.OperationMinMax.max; +import static rx.operators.OperationMinMax.maxBy; +import static rx.operators.OperationMinMax.min; +import static rx.operators.OperationMinMax.minBy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; +import rx.util.functions.Func1; + +public class OperationMinMaxTest { + @Test + public void testMin() { + Observable observable = min(Observable.from(2, 3, 1, 4)); + + @SuppressWarnings("unchecked") + Observer observer = (Observer) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext(1); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMinWithEmpty() { + Observable observable = min(Observable. empty()); + + @SuppressWarnings("unchecked") + Observer observer = (Observer) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onError( + isA(IllegalArgumentException.class)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMinWithComparator() { + Observable observable = min(Observable.from(2, 3, 1, 4), + new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return o2 - o1; + } + }); + + @SuppressWarnings("unchecked") + Observer observer = (Observer) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext(4); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMinWithComparatorAndEmpty() { + Observable observable = min(Observable. empty(), + new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return o2 - o1; + } + }); + + @SuppressWarnings("unchecked") + Observer observer = (Observer) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onError( + isA(IllegalArgumentException.class)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMinBy() { + Observable> observable = minBy( + Observable.from("1", "2", "3", "4", "5", "6"), + new Func1() { + @Override + public Integer call(String t1) { + return Integer.parseInt(t1) % 2; + } + }); + + @SuppressWarnings("unchecked") + Observer> observer = (Observer>) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext(Arrays.asList("2", "4", "6")); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMinByWithEmpty() { + Observable> observable = minBy( + Observable. empty(), new Func1() { + @Override + public Integer call(String t1) { + return Integer.parseInt(t1) % 2; + } + }); + + @SuppressWarnings("unchecked") + Observer> observer = (Observer>) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext(new ArrayList()); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMinByWithComparator() { + Observable> observable = minBy( + Observable.from("1", "2", "3", "4", "5", "6"), + new Func1() { + @Override + public Integer call(String t1) { + return Integer.parseInt(t1) % 2; + } + }, new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return o2 - o1; + } + }); + + @SuppressWarnings("unchecked") + Observer> observer = (Observer>) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext(Arrays.asList("1", "3", "5")); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMinByWithComparatorAndEmpty() { + Observable> observable = minBy( + Observable. empty(), new Func1() { + @Override + public Integer call(String t1) { + return Integer.parseInt(t1) % 2; + } + }, new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return o2 - o1; + } + }); + + @SuppressWarnings("unchecked") + Observer> observer = (Observer>) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext(new ArrayList()); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMax() { + Observable observable = max(Observable.from(2, 3, 1, 4)); + + @SuppressWarnings("unchecked") + Observer observer = (Observer) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext(4); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMaxWithEmpty() { + Observable observable = max(Observable. empty()); + + @SuppressWarnings("unchecked") + Observer observer = (Observer) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onError( + isA(IllegalArgumentException.class)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMaxWithComparator() { + Observable observable = max(Observable.from(2, 3, 1, 4), + new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return o2 - o1; + } + }); + + @SuppressWarnings("unchecked") + Observer observer = (Observer) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext(1); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMaxWithComparatorAndEmpty() { + Observable observable = max(Observable. empty(), + new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return o2 - o1; + } + }); + + @SuppressWarnings("unchecked") + Observer observer = (Observer) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onError( + isA(IllegalArgumentException.class)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMaxBy() { + Observable> observable = maxBy( + Observable.from("1", "2", "3", "4", "5", "6"), + new Func1() { + @Override + public Integer call(String t1) { + return Integer.parseInt(t1) % 2; + } + }); + + @SuppressWarnings("unchecked") + Observer> observer = (Observer>) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext(Arrays.asList("1", "3", "5")); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMaxByWithEmpty() { + Observable> observable = maxBy( + Observable. empty(), new Func1() { + @Override + public Integer call(String t1) { + return Integer.parseInt(t1) % 2; + } + }); + + @SuppressWarnings("unchecked") + Observer> observer = (Observer>) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext(new ArrayList()); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMaxByWithComparator() { + Observable> observable = maxBy( + Observable.from("1", "2", "3", "4", "5", "6"), + new Func1() { + @Override + public Integer call(String t1) { + return Integer.parseInt(t1) % 2; + } + }, new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return o2 - o1; + } + }); + + @SuppressWarnings("unchecked") + Observer> observer = (Observer>) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext(Arrays.asList("2", "4", "6")); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMaxByWithComparatorAndEmpty() { + Observable> observable = maxBy( + Observable. empty(), new Func1() { + @Override + public Integer call(String t1) { + return Integer.parseInt(t1) % 2; + } + }, new Comparator() { + @Override + public int compare(Integer o1, Integer o2) { + return o2 - o1; + } + }); + + @SuppressWarnings("unchecked") + Observer> observer = (Observer>) mock(Observer.class); + + observable.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext(new ArrayList()); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationMostRecentTest.java b/rxjava-core/src/test/java/rx/operators/OperationMostRecentTest.java new file mode 100644 index 0000000000..d20f79e819 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationMostRecentTest.java @@ -0,0 +1,74 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static rx.operators.OperationMostRecent.*; + +import java.util.Iterator; + +import org.junit.Test; + +import rx.subjects.PublishSubject; +import rx.subjects.Subject; + +public class OperationMostRecentTest { + + @Test + public void testMostRecent() { + Subject observable = PublishSubject.create(); + + Iterator it = mostRecent(observable, "default").iterator(); + + assertTrue(it.hasNext()); + assertEquals("default", it.next()); + assertEquals("default", it.next()); + + observable.onNext("one"); + assertTrue(it.hasNext()); + assertEquals("one", it.next()); + assertEquals("one", it.next()); + + observable.onNext("two"); + assertTrue(it.hasNext()); + assertEquals("two", it.next()); + assertEquals("two", it.next()); + + observable.onCompleted(); + assertFalse(it.hasNext()); + + } + + @Test(expected = TestException.class) + public void testMostRecentWithException() { + Subject observable = PublishSubject.create(); + + Iterator it = mostRecent(observable, "default").iterator(); + + assertTrue(it.hasNext()); + assertEquals("default", it.next()); + assertEquals("default", it.next()); + + observable.onError(new TestException()); + assertTrue(it.hasNext()); + + it.next(); + } + + private static class TestException extends RuntimeException { + private static final long serialVersionUID = 1L; + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationMulticastTest.java b/rxjava-core/src/test/java/rx/operators/OperationMulticastTest.java new file mode 100644 index 0000000000..8f824f1484 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationMulticastTest.java @@ -0,0 +1,113 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Mockito.*; + +import org.junit.Test; + +import rx.Observer; +import rx.Subscription; +import rx.observables.ConnectableObservable; +import rx.subjects.PublishSubject; +import rx.subjects.Subject; + +public class OperationMulticastTest { + + @Test + public void testMulticast() { + Subject source = PublishSubject.create(); + + ConnectableObservable multicasted = OperationMulticast.multicast(source, + PublishSubject. create()); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + multicasted.subscribe(observer); + + source.onNext("one"); + source.onNext("two"); + + multicasted.connect(); + + source.onNext("three"); + source.onNext("four"); + source.onCompleted(); + + verify(observer, never()).onNext("one"); + verify(observer, never()).onNext("two"); + verify(observer, times(1)).onNext("three"); + verify(observer, times(1)).onNext("four"); + verify(observer, times(1)).onCompleted(); + + } + + @Test + public void testMulticastConnectTwice() { + Subject source = PublishSubject.create(); + + ConnectableObservable multicasted = OperationMulticast.multicast(source, + PublishSubject. create()); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + multicasted.subscribe(observer); + + source.onNext("one"); + + multicasted.connect(); + multicasted.connect(); + + source.onNext("two"); + source.onCompleted(); + + verify(observer, never()).onNext("one"); + verify(observer, times(1)).onNext("two"); + verify(observer, times(1)).onCompleted(); + + } + + @Test + public void testMulticastDisconnect() { + Subject source = PublishSubject.create(); + + ConnectableObservable multicasted = OperationMulticast.multicast(source, + PublishSubject. create()); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + multicasted.subscribe(observer); + + source.onNext("one"); + + Subscription connection = multicasted.connect(); + source.onNext("two"); + + connection.unsubscribe(); + source.onNext("three"); + + multicasted.connect(); + source.onNext("four"); + source.onCompleted(); + + verify(observer, never()).onNext("one"); + verify(observer, times(1)).onNext("two"); + verify(observer, never()).onNext("three"); + verify(observer, times(1)).onNext("four"); + verify(observer, times(1)).onCompleted(); + + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationNextTest.java b/rxjava-core/src/test/java/rx/operators/OperationNextTest.java new file mode 100644 index 0000000000..140cc0560c --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationNextTest.java @@ -0,0 +1,296 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static rx.operators.OperationNext.*; + +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.concurrency.Schedulers; +import rx.subjects.PublishSubject; +import rx.subjects.Subject; +import rx.subscriptions.Subscriptions; + +public class OperationNextTest { + + private void fireOnNextInNewThread(final Subject o, final String value) { + new Thread() { + @Override + public void run() { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // ignore + } + o.onNext(value); + } + }.start(); + } + + private void fireOnErrorInNewThread(final Subject o) { + new Thread() { + @Override + public void run() { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + // ignore + } + o.onError(new TestException()); + } + }.start(); + } + + @Test + public void testNext() { + Subject obs = PublishSubject.create(); + Iterator it = next(obs).iterator(); + fireOnNextInNewThread(obs, "one"); + assertTrue(it.hasNext()); + assertEquals("one", it.next()); + + fireOnNextInNewThread(obs, "two"); + assertTrue(it.hasNext()); + assertEquals("two", it.next()); + + obs.onCompleted(); + assertFalse(it.hasNext()); + try { + it.next(); + fail("At the end of an iterator should throw a NoSuchElementException"); + } catch (NoSuchElementException e) { + } + + // If the observable is completed, hasNext always returns false and next always throw a NoSuchElementException. + assertFalse(it.hasNext()); + try { + it.next(); + fail("At the end of an iterator should throw a NoSuchElementException"); + } catch (NoSuchElementException e) { + } + } + + @Test + public void testNextWithError() { + Subject obs = PublishSubject.create(); + Iterator it = next(obs).iterator(); + fireOnNextInNewThread(obs, "one"); + assertTrue(it.hasNext()); + assertEquals("one", it.next()); + + fireOnErrorInNewThread(obs); + try { + it.hasNext(); + fail("Expected an TestException"); + } catch (TestException e) { + } + + assertErrorAfterObservableFail(it); + } + + @Test + public void testNextWithEmpty() { + Observable obs = Observable. empty().observeOn(Schedulers.newThread()); + Iterator it = next(obs).iterator(); + + assertFalse(it.hasNext()); + try { + it.next(); + fail("At the end of an iterator should throw a NoSuchElementException"); + } catch (NoSuchElementException e) { + } + + // If the observable is completed, hasNext always returns false and next always throw a NoSuchElementException. + assertFalse(it.hasNext()); + try { + it.next(); + fail("At the end of an iterator should throw a NoSuchElementException"); + } catch (NoSuchElementException e) { + } + } + + @Test + public void testOnError() throws Throwable { + Subject obs = PublishSubject.create(); + Iterator it = next(obs).iterator(); + + obs.onError(new TestException()); + try { + it.hasNext(); + fail("Expected an TestException"); + } catch (TestException e) { + // successful + } + + assertErrorAfterObservableFail(it); + } + + @Test + public void testOnErrorInNewThread() { + Subject obs = PublishSubject.create(); + Iterator it = next(obs).iterator(); + + fireOnErrorInNewThread(obs); + + try { + it.hasNext(); + fail("Expected an TestException"); + } catch (TestException e) { + // successful + } + + assertErrorAfterObservableFail(it); + } + + private void assertErrorAfterObservableFail(Iterator it) { + // After the observable fails, hasNext and next always throw the exception. + try { + it.hasNext(); + fail("hasNext should throw a TestException"); + } catch (TestException e) { + } + try { + it.next(); + fail("next should throw a TestException"); + } catch (TestException e) { + } + } + + @Test + public void testNextWithOnlyUsingNextMethod() { + Subject obs = PublishSubject.create(); + Iterator it = next(obs).iterator(); + fireOnNextInNewThread(obs, "one"); + assertEquals("one", it.next()); + + fireOnNextInNewThread(obs, "two"); + assertEquals("two", it.next()); + + obs.onCompleted(); + try { + it.next(); + fail("At the end of an iterator should throw a NoSuchElementException"); + } catch (NoSuchElementException e) { + } + } + + @Test + public void testNextWithCallingHasNextMultipleTimes() { + Subject obs = PublishSubject.create(); + Iterator it = next(obs).iterator(); + fireOnNextInNewThread(obs, "one"); + assertTrue(it.hasNext()); + assertTrue(it.hasNext()); + assertTrue(it.hasNext()); + assertTrue(it.hasNext()); + assertEquals("one", it.next()); + + obs.onCompleted(); + try { + it.next(); + fail("At the end of an iterator should throw a NoSuchElementException"); + } catch (NoSuchElementException e) { + } + } + + @SuppressWarnings("serial") + private static class TestException extends RuntimeException { + + } + + /** + * Confirm that no buffering or blocking of the Observable onNext calls occurs and it just grabs the next emitted value. + *

    + * This results in output such as => a: 1 b: 2 c: 89 + * + * @throws Throwable + */ + @Test + public void testNoBufferingOrBlockingOfSequence() throws Throwable { + final CountDownLatch finished = new CountDownLatch(1); + final int COUNT = 30; + final CountDownLatch timeHasPassed = new CountDownLatch(COUNT); + final AtomicBoolean running = new AtomicBoolean(true); + final AtomicInteger count = new AtomicInteger(0); + final Observable obs = Observable.create(new Observable.OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(final Observer o) { + new Thread(new Runnable() { + + @Override + public void run() { + try { + while (running.get()) { + o.onNext(count.incrementAndGet()); + timeHasPassed.countDown(); + } + o.onCompleted(); + } catch (Throwable e) { + o.onError(e); + } finally { + finished.countDown(); + } + } + }).start(); + return Subscriptions.empty(); + } + + }); + + Iterator it = next(obs).iterator(); + + assertTrue(it.hasNext()); + int a = it.next(); + assertTrue(it.hasNext()); + int b = it.next(); + // we should have a different value + assertTrue("a and b should be different", a != b); + + // wait for some time (if times out we are blocked somewhere so fail ... set very high for very slow, constrained machines) + timeHasPassed.await(8000, TimeUnit.MILLISECONDS); + + assertTrue(it.hasNext()); + int c = it.next(); + + assertTrue("c should not just be the next in sequence", c != (b + 1)); + assertTrue("expected that c [" + c + "] is higher than or equal to " + COUNT, c >= COUNT); + + assertTrue(it.hasNext()); + int d = it.next(); + assertTrue(d > c); + + // shut down the thread + running.set(false); + + finished.await(); + + assertFalse(it.hasNext()); + + System.out.println("a: " + a + " b: " + b + " c: " + c); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationObserveOnTest.java b/rxjava-core/src/test/java/rx/operators/OperationObserveOnTest.java new file mode 100644 index 0000000000..15f1a44fb3 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationObserveOnTest.java @@ -0,0 +1,135 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationObserveOn.*; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import rx.Observable; +import rx.Observer; +import rx.concurrency.Schedulers; +import rx.util.functions.Action1; + +public class OperationObserveOnTest { + + /** + * This is testing a no-op path since it uses Schedulers.immediate() which will not do scheduling. + */ + @Test + @SuppressWarnings("unchecked") + public void testObserveOn() { + Observer observer = mock(Observer.class); + Observable.create(observeOn(Observable.from(1, 2, 3), Schedulers.immediate())).subscribe(observer); + + verify(observer, times(1)).onNext(1); + verify(observer, times(1)).onNext(2); + verify(observer, times(1)).onNext(3); + verify(observer, times(1)).onCompleted(); + } + + @Test + @SuppressWarnings("unchecked") + public void testOrdering() throws InterruptedException { + Observable obs = Observable.from("one", null, "two", "three", "four"); + + Observer observer = mock(Observer.class); + + InOrder inOrder = inOrder(observer); + + final CountDownLatch completedLatch = new CountDownLatch(1); + doAnswer(new Answer() { + + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + completedLatch.countDown(); + return null; + } + }).when(observer).onCompleted(); + + obs.observeOn(Schedulers.threadPoolForComputation()).subscribe(observer); + + if (!completedLatch.await(1000, TimeUnit.MILLISECONDS)) { + fail("timed out waiting"); + } + + inOrder.verify(observer, times(1)).onNext("one"); + inOrder.verify(observer, times(1)).onNext(null); + inOrder.verify(observer, times(1)).onNext("two"); + inOrder.verify(observer, times(1)).onNext("three"); + inOrder.verify(observer, times(1)).onNext("four"); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + @SuppressWarnings("unchecked") + public void testThreadName() throws InterruptedException { + Observable obs = Observable.from("one", null, "two", "three", "four"); + + Observer observer = mock(Observer.class); + + InOrder inOrder = inOrder(observer); + + final CountDownLatch completedLatch = new CountDownLatch(1); + doAnswer(new Answer() { + + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + completedLatch.countDown(); + + return null; + } + }).when(observer).onCompleted(); + + doAnswer(new Answer() { + + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + completedLatch.countDown(); + + return null; + } + }).when(observer).onError(any(Exception.class)); + + obs.observeOn(Schedulers.newThread()).doOnEach(new Action1() { + + @Override + public void call(String t1) { + String threadName = Thread.currentThread().getName(); + boolean correctThreadName = threadName.startsWith("RxNewThreadScheduler"); + System.out.println("ThreadName: " + threadName + " Correct => " + correctThreadName); + assertTrue(correctThreadName); + } + + }).subscribe(observer); + + if (!completedLatch.await(1000, TimeUnit.MILLISECONDS)) { + fail("timed out waiting"); + } + + inOrder.verify(observer, times(1)).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationOnErrorResumeNextViaFunctionTest.java b/rxjava-core/src/test/java/rx/operators/OperationOnErrorResumeNextViaFunctionTest.java new file mode 100644 index 0000000000..ab9293251e --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationOnErrorResumeNextViaFunctionTest.java @@ -0,0 +1,183 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationOnErrorResumeNextViaFunction.*; + +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.mockito.Mockito; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Func1; + +public class OperationOnErrorResumeNextViaFunctionTest { + + @Test + public void testResumeNextWithSynchronousExecution() { + final AtomicReference receivedException = new AtomicReference(); + Observable w = Observable.create(new Observable.OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(Observer observer) { + observer.onNext("one"); + observer.onError(new Throwable("injected failure")); + return Subscriptions.empty(); + } + }); + + Func1> resume = new Func1>() { + + @Override + public Observable call(Throwable t1) { + receivedException.set(t1); + return Observable.from("twoResume", "threeResume"); + } + + }; + Observable observable = Observable.create(onErrorResumeNextViaFunction(w, resume)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, Mockito.never()).onNext("two"); + verify(aObserver, Mockito.never()).onNext("three"); + verify(aObserver, times(1)).onNext("twoResume"); + verify(aObserver, times(1)).onNext("threeResume"); + assertNotNull(receivedException.get()); + } + + @Test + public void testResumeNextWithAsyncExecution() { + final AtomicReference receivedException = new AtomicReference(); + Subscription s = mock(Subscription.class); + TestObservable w = new TestObservable(s, "one"); + Func1> resume = new Func1>() { + + @Override + public Observable call(Throwable t1) { + receivedException.set(t1); + return Observable.from("twoResume", "threeResume"); + } + + }; + Observable observable = Observable.create(onErrorResumeNextViaFunction(Observable.create(w), resume)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + + try { + w.t.join(); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, Mockito.never()).onNext("two"); + verify(aObserver, Mockito.never()).onNext("three"); + verify(aObserver, times(1)).onNext("twoResume"); + verify(aObserver, times(1)).onNext("threeResume"); + assertNotNull(receivedException.get()); + } + + /** + * Test that when a function throws an exception this is propagated through onError + */ + @Test + public void testFunctionThrowsError() { + Subscription s = mock(Subscription.class); + TestObservable w = new TestObservable(s, "one"); + Func1> resume = new Func1>() { + + @Override + public Observable call(Throwable t1) { + throw new RuntimeException("exception from function"); + } + + }; + Observable observable = Observable.create(onErrorResumeNextViaFunction(Observable.create(w), resume)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + + try { + w.t.join(); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + + // we should get the "one" value before the error + verify(aObserver, times(1)).onNext("one"); + + // we should have received an onError call on the Observer since the resume function threw an exception + verify(aObserver, times(1)).onError(any(Throwable.class)); + verify(aObserver, times(0)).onCompleted(); + } + + private static class TestObservable implements Observable.OnSubscribeFunc { + + final Subscription s; + final String[] values; + Thread t = null; + + public TestObservable(Subscription s, String... values) { + this.s = s; + this.values = values; + } + + @Override + public Subscription onSubscribe(final Observer observer) { + System.out.println("TestObservable subscribed to ..."); + t = new Thread(new Runnable() { + + @Override + public void run() { + try { + System.out.println("running TestObservable thread"); + for (String s : values) { + System.out.println("TestObservable onNext: " + s); + observer.onNext(s); + } + throw new RuntimeException("Forced Failure"); + } catch (Throwable e) { + observer.onError(e); + } + } + + }); + System.out.println("starting TestObservable thread"); + t.start(); + System.out.println("done starting TestObservable thread"); + return s; + } + + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationOnErrorResumeNextViaObservableTest.java b/rxjava-core/src/test/java/rx/operators/OperationOnErrorResumeNextViaObservableTest.java new file mode 100644 index 0000000000..dcbbdc3fb4 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationOnErrorResumeNextViaObservableTest.java @@ -0,0 +1,143 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationOnErrorResumeNextViaObservable.*; + +import org.junit.Test; +import org.mockito.Mockito; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.util.functions.Func1; + +public class OperationOnErrorResumeNextViaObservableTest { + + @Test + public void testResumeNext() { + Subscription s = mock(Subscription.class); + // Trigger failure on second element + TestObservable f = new TestObservable(s, "one", "fail", "two", "three"); + Observable w = Observable.create(f); + Observable resume = Observable.from("twoResume", "threeResume"); + Observable observable = Observable.create(onErrorResumeNextViaObservable(w, resume)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + + try { + f.t.join(); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, Mockito.never()).onNext("two"); + verify(aObserver, Mockito.never()).onNext("three"); + verify(aObserver, times(1)).onNext("twoResume"); + verify(aObserver, times(1)).onNext("threeResume"); + } + + @Test + public void testMapResumeAsyncNext() { + Subscription sr = mock(Subscription.class); + // Trigger multiple failures + Observable w = Observable.from("one", "fail", "two", "three", "fail"); + // Resume Observable is async + TestObservable f = new TestObservable(sr, "twoResume", "threeResume"); + Observable resume = Observable.create(f); + + // Introduce map function that fails intermittently (Map does not prevent this when the observer is a + // rx.operator incl onErrorResumeNextViaObservable) + w = w.map(new Func1() { + public String call(String s) { + if ("fail".equals(s)) + throw new RuntimeException("Forced Failure"); + System.out.println("BadMapper:" + s); + return s; + } + }); + + Observable observable = Observable.create(onErrorResumeNextViaObservable(w, resume)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + + try { + f.t.join(); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, Mockito.never()).onNext("two"); + verify(aObserver, Mockito.never()).onNext("three"); + verify(aObserver, times(1)).onNext("twoResume"); + verify(aObserver, times(1)).onNext("threeResume"); + } + + private static class TestObservable implements Observable.OnSubscribeFunc { + + final Subscription s; + final String[] values; + Thread t = null; + + public TestObservable(Subscription s, String... values) { + this.s = s; + this.values = values; + } + + @Override + public Subscription onSubscribe(final Observer observer) { + System.out.println("TestObservable subscribed to ..."); + t = new Thread(new Runnable() { + + @Override + public void run() { + try { + System.out.println("running TestObservable thread"); + for (String s : values) { + if ("fail".equals(s)) + throw new RuntimeException("Forced Failure"); + System.out.println("TestObservable onNext: " + s); + observer.onNext(s); + } + System.out.println("TestObservable onCompleted"); + observer.onCompleted(); + } catch (Throwable e) { + System.out.println("TestObservable onError: " + e); + observer.onError(e); + } + } + + }); + System.out.println("starting TestObservable thread"); + t.start(); + System.out.println("done starting TestObservable thread"); + return s; + } + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationOnErrorReturnTest.java b/rxjava-core/src/test/java/rx/operators/OperationOnErrorReturnTest.java new file mode 100644 index 0000000000..e436dd29ee --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationOnErrorReturnTest.java @@ -0,0 +1,145 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationOnErrorReturn.*; + +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.mockito.Mockito; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.util.functions.Func1; + +public class OperationOnErrorReturnTest { + + @Test + public void testResumeNext() { + Subscription s = mock(Subscription.class); + TestObservable f = new TestObservable(s, "one"); + Observable w = Observable.create(f); + final AtomicReference capturedException = new AtomicReference(); + + Observable observable = Observable.create(onErrorReturn(w, new Func1() { + + @Override + public String call(Throwable e) { + capturedException.set(e); + return "failure"; + } + + })); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + + try { + f.t.join(); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, times(1)).onNext("failure"); + assertNotNull(capturedException.get()); + } + + /** + * Test that when a function throws an exception this is propagated through onError + */ + @Test + public void testFunctionThrowsError() { + Subscription s = mock(Subscription.class); + TestObservable f = new TestObservable(s, "one"); + Observable w = Observable.create(f); + final AtomicReference capturedException = new AtomicReference(); + + Observable observable = Observable.create(onErrorReturn(w, new Func1() { + + @Override + public String call(Throwable e) { + capturedException.set(e); + throw new RuntimeException("exception from function"); + } + + })); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + + try { + f.t.join(); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + + // we should get the "one" value before the error + verify(aObserver, times(1)).onNext("one"); + + // we should have received an onError call on the Observer since the resume function threw an exception + verify(aObserver, times(1)).onError(any(Throwable.class)); + verify(aObserver, times(0)).onCompleted(); + assertNotNull(capturedException.get()); + } + + private static class TestObservable implements Observable.OnSubscribeFunc { + + final Subscription s; + final String[] values; + Thread t = null; + + public TestObservable(Subscription s, String... values) { + this.s = s; + this.values = values; + } + + @Override + public Subscription onSubscribe(final Observer observer) { + System.out.println("TestObservable subscribed to ..."); + t = new Thread(new Runnable() { + + @Override + public void run() { + try { + System.out.println("running TestObservable thread"); + for (String s : values) { + System.out.println("TestObservable onNext: " + s); + observer.onNext(s); + } + throw new RuntimeException("Forced Failure"); + } catch (Throwable e) { + observer.onError(e); + } + } + + }); + System.out.println("starting TestObservable thread"); + t.start(); + System.out.println("done starting TestObservable thread"); + return s; + } + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationOnExceptionResumeNextViaObservableTest.java b/rxjava-core/src/test/java/rx/operators/OperationOnExceptionResumeNextViaObservableTest.java new file mode 100644 index 0000000000..8b5cc9271a --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationOnExceptionResumeNextViaObservableTest.java @@ -0,0 +1,240 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationOnExceptionResumeNextViaObservable.*; + +import org.junit.Test; +import org.mockito.Mockito; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.util.functions.Func1; + +public class OperationOnExceptionResumeNextViaObservableTest { + + @Test + public void testResumeNextWithException() { + Subscription s = mock(Subscription.class); + // Trigger failure on second element + TestObservable f = new TestObservable(s, "one", "EXCEPTION", "two", "three"); + Observable w = Observable.create(f); + Observable resume = Observable.from("twoResume", "threeResume"); + Observable observable = Observable.create(onExceptionResumeNextViaObservable(w, resume)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + + try { + f.t.join(); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, Mockito.never()).onNext("two"); + verify(aObserver, Mockito.never()).onNext("three"); + verify(aObserver, times(1)).onNext("twoResume"); + verify(aObserver, times(1)).onNext("threeResume"); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verifyNoMoreInteractions(aObserver); + } + + @Test + public void testResumeNextWithRuntimeException() { + Subscription s = mock(Subscription.class); + // Trigger failure on second element + TestObservable f = new TestObservable(s, "one", "RUNTIMEEXCEPTION", "two", "three"); + Observable w = Observable.create(f); + Observable resume = Observable.from("twoResume", "threeResume"); + Observable observable = Observable.create(onExceptionResumeNextViaObservable(w, resume)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + + try { + f.t.join(); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, Mockito.never()).onNext("two"); + verify(aObserver, Mockito.never()).onNext("three"); + verify(aObserver, times(1)).onNext("twoResume"); + verify(aObserver, times(1)).onNext("threeResume"); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verifyNoMoreInteractions(aObserver); + } + + @Test + public void testThrowablePassesThru() { + Subscription s = mock(Subscription.class); + // Trigger failure on second element + TestObservable f = new TestObservable(s, "one", "THROWABLE", "two", "three"); + Observable w = Observable.create(f); + Observable resume = Observable.from("twoResume", "threeResume"); + Observable observable = Observable.create(onExceptionResumeNextViaObservable(w, resume)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + + try { + f.t.join(); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, never()).onNext("two"); + verify(aObserver, never()).onNext("three"); + verify(aObserver, never()).onNext("twoResume"); + verify(aObserver, never()).onNext("threeResume"); + verify(aObserver, times(1)).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + verifyNoMoreInteractions(aObserver); + } + + @Test + public void testErrorPassesThru() { + Subscription s = mock(Subscription.class); + // Trigger failure on second element + TestObservable f = new TestObservable(s, "one", "ERROR", "two", "three"); + Observable w = Observable.create(f); + Observable resume = Observable.from("twoResume", "threeResume"); + Observable observable = Observable.create(onExceptionResumeNextViaObservable(w, resume)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + + try { + f.t.join(); + } catch (InterruptedException e) { + fail(e.getMessage()); + } + + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, never()).onNext("two"); + verify(aObserver, never()).onNext("three"); + verify(aObserver, never()).onNext("twoResume"); + verify(aObserver, never()).onNext("threeResume"); + verify(aObserver, times(1)).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + verifyNoMoreInteractions(aObserver); + } + + @Test + public void testMapResumeAsyncNext() { + Subscription sr = mock(Subscription.class); + // Trigger multiple failures + Observable w = Observable.from("one", "fail", "two", "three", "fail"); + // Resume Observable is async + TestObservable f = new TestObservable(sr, "twoResume", "threeResume"); + Observable resume = Observable.create(f); + + // Introduce map function that fails intermittently (Map does not prevent this when the observer is a + // rx.operator incl onErrorResumeNextViaObservable) + w = w.map(new Func1() { + public String call(String s) { + if ("fail".equals(s)) + throw new RuntimeException("Forced Failure"); + System.out.println("BadMapper:" + s); + return s; + } + }); + + Observable observable = Observable.create(onExceptionResumeNextViaObservable(w, resume)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + + try { + // if the thread gets started (which it shouldn't if it's working correctly) + if (f.t != null) { + f.t.join(); + } + } catch (InterruptedException e) { + fail(e.getMessage()); + } + + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, never()).onNext("two"); + verify(aObserver, never()).onNext("three"); + verify(aObserver, times(1)).onNext("twoResume"); + verify(aObserver, times(1)).onNext("threeResume"); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + private static class TestObservable implements Observable.OnSubscribeFunc { + + final Subscription s; + final String[] values; + Thread t = null; + + public TestObservable(Subscription s, String... values) { + this.s = s; + this.values = values; + } + + @Override + public Subscription onSubscribe(final Observer observer) { + System.out.println("TestObservable subscribed to ..."); + t = new Thread(new Runnable() { + + @Override + public void run() { + try { + System.out.println("running TestObservable thread"); + for (String s : values) { + if ("EXCEPTION".equals(s)) + throw new Exception("Forced Exception"); + else if ("RUNTIMEEXCEPTION".equals(s)) + throw new RuntimeException("Forced RuntimeException"); + else if ("ERROR".equals(s)) + throw new Error("Forced Error"); + else if ("THROWABLE".equals(s)) + throw new Throwable("Forced Throwable"); + System.out.println("TestObservable onNext: " + s); + observer.onNext(s); + } + System.out.println("TestObservable onCompleted"); + observer.onCompleted(); + } catch (Throwable e) { + System.out.println("TestObservable onError: " + e); + observer.onError(e); + } + } + + }); + System.out.println("starting TestObservable thread"); + t.start(); + System.out.println("done starting TestObservable thread"); + return s; + } + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationParallelTest.java b/rxjava-core/src/test/java/rx/operators/OperationParallelTest.java new file mode 100644 index 0000000000..d52c8bc0b9 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationParallelTest.java @@ -0,0 +1,61 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import rx.Observable; +import rx.util.functions.Action1; +import rx.util.functions.Func1; + +public class OperationParallelTest { + + @Test + public void testParallel() { + int NUM = 1000; + final AtomicInteger count = new AtomicInteger(); + Observable.range(1, NUM).parallel( + new Func1, Observable>() { + + @Override + public Observable call(Observable o) { + return o.map(new Func1() { + + @Override + public Integer[] call(Integer t) { + return new Integer[] { t, t * 99 }; + } + + }); + } + }).toBlockingObservable().forEach(new Action1() { + + @Override + public void call(Integer[] v) { + count.incrementAndGet(); + System.out.println("V: " + v[0] + " R: " + v[1] + " Thread: " + Thread.currentThread()); + } + + }); + + // just making sure we finish and get the number we expect + assertEquals(NUM, count.get()); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationRetryTest.java b/rxjava-core/src/test/java/rx/operators/OperationRetryTest.java new file mode 100644 index 0000000000..1b6006b923 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationRetryTest.java @@ -0,0 +1,130 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationRetry.*; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.Subscriptions; + +public class OperationRetryTest { + + @Test + public void testOriginFails() { + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + Observable origin = Observable.create(new FuncWithErrors(2)); + origin.subscribe(observer); + + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext("beginningEveryTime"); + inOrder.verify(observer, times(1)).onError(any(RuntimeException.class)); + inOrder.verify(observer, never()).onNext("onSuccessOnly"); + inOrder.verify(observer, never()).onCompleted(); + } + + @Test + public void testRetryFail() { + int NUM_RETRIES = 1; + int NUM_FAILURES = 2; + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + Observable origin = Observable.create(new FuncWithErrors(NUM_FAILURES)); + Observable.create(retry(origin, NUM_RETRIES)).subscribe(observer); + + InOrder inOrder = inOrder(observer); + // should show 2 attempts (first time fail, second time (1st retry) fail) + inOrder.verify(observer, times(1 + NUM_RETRIES)).onNext("beginningEveryTime"); + // should only retry once, fail again and emit onError + inOrder.verify(observer, times(1)).onError(any(RuntimeException.class)); + // no success + inOrder.verify(observer, never()).onNext("onSuccessOnly"); + inOrder.verify(observer, never()).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testRetrySuccess() { + int NUM_RETRIES = 3; + int NUM_FAILURES = 2; + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + Observable origin = Observable.create(new FuncWithErrors(NUM_FAILURES)); + Observable.create(retry(origin, NUM_RETRIES)).subscribe(observer); + + InOrder inOrder = inOrder(observer); + // should show 3 attempts + inOrder.verify(observer, times(1 + NUM_FAILURES)).onNext("beginningEveryTime"); + // should have no errors + inOrder.verify(observer, never()).onError(any(Throwable.class)); + // should have a single success + inOrder.verify(observer, times(1)).onNext("onSuccessOnly"); + // should have a single successful onCompleted + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testInfiniteRetry() { + int NUM_FAILURES = 20; + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + Observable origin = Observable.create(new FuncWithErrors(NUM_FAILURES)); + Observable.create(retry(origin)).subscribe(observer); + + InOrder inOrder = inOrder(observer); + // should show 3 attempts + inOrder.verify(observer, times(1 + NUM_FAILURES)).onNext("beginningEveryTime"); + // should have no errors + inOrder.verify(observer, never()).onError(any(Throwable.class)); + // should have a single success + inOrder.verify(observer, times(1)).onNext("onSuccessOnly"); + // should have a single successful onCompleted + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + public static class FuncWithErrors implements Observable.OnSubscribeFunc { + + private final int numFailures; + private final AtomicInteger count = new AtomicInteger(0); + + FuncWithErrors(int count) { + this.numFailures = count; + } + + @Override + public Subscription onSubscribe(Observer o) { + o.onNext("beginningEveryTime"); + if (count.incrementAndGet() <= numFailures) { + o.onError(new RuntimeException("forced failure: " + count.get())); + } else { + o.onNext("onSuccessOnly"); + o.onCompleted(); + } + return Subscriptions.empty(); + } + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationSampleTest.java b/rxjava-core/src/test/java/rx/operators/OperationSampleTest.java new file mode 100644 index 0000000000..d868697f9f --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationSampleTest.java @@ -0,0 +1,108 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.concurrency.TestScheduler; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action0; + +public class OperationSampleTest { + private TestScheduler scheduler; + private Observer observer; + + @Before + @SuppressWarnings("unchecked") + // due to mocking + public void before() { + scheduler = new TestScheduler(); + observer = mock(Observer.class); + } + + @Test + public void testSample() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(final Observer observer1) { + scheduler.schedule(new Action0() { + @Override + public void call() { + observer1.onNext(1L); + } + }, 1, TimeUnit.SECONDS); + scheduler.schedule(new Action0() { + @Override + public void call() { + observer1.onNext(2L); + } + }, 2, TimeUnit.SECONDS); + scheduler.schedule(new Action0() { + @Override + public void call() { + observer1.onCompleted(); + } + }, 3, TimeUnit.SECONDS); + + return Subscriptions.empty(); + } + }); + + Observable sampled = Observable.create(OperationSample.sample(source, 400L, TimeUnit.MILLISECONDS, scheduler)); + sampled.subscribe(observer); + + InOrder inOrder = inOrder(observer); + + scheduler.advanceTimeTo(800L, TimeUnit.MILLISECONDS); + verify(observer, never()).onNext(any(Long.class)); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(1200L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext(1L); + verify(observer, never()).onNext(2L); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(1600L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext(1L); + verify(observer, never()).onNext(2L); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(2000L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onNext(1L); + inOrder.verify(observer, times(1)).onNext(2L); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(3000L, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onNext(1L); + inOrder.verify(observer, times(2)).onNext(2L); + verify(observer, times(1)).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationScanTest.java b/rxjava-core/src/test/java/rx/operators/OperationScanTest.java new file mode 100644 index 0000000000..0dedef787c --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationScanTest.java @@ -0,0 +1,115 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationScan.*; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockitoAnnotations; + +import rx.Observable; +import rx.Observer; +import rx.util.functions.Func2; + +public class OperationScanTest { + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testScanIntegersWithInitialValue() { + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + + Observable observable = Observable.from(1, 2, 3); + + Observable m = Observable.create(scan(observable, "", new Func2() { + + @Override + public String call(String s, Integer n) { + return s + n.toString(); + } + + })); + m.subscribe(observer); + + verify(observer, never()).onError(any(Throwable.class)); + verify(observer, times(1)).onNext(""); + verify(observer, times(1)).onNext("1"); + verify(observer, times(1)).onNext("12"); + verify(observer, times(1)).onNext("123"); + verify(observer, times(4)).onNext(anyString()); + verify(observer, times(1)).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void testScanIntegersWithoutInitialValue() { + @SuppressWarnings("unchecked") + Observer Observer = mock(Observer.class); + + Observable observable = Observable.from(1, 2, 3); + + Observable m = Observable.create(scan(observable, new Func2() { + + @Override + public Integer call(Integer t1, Integer t2) { + return t1 + t2; + } + + })); + m.subscribe(Observer); + + verify(Observer, never()).onError(any(Throwable.class)); + verify(Observer, never()).onNext(0); + verify(Observer, times(1)).onNext(1); + verify(Observer, times(1)).onNext(3); + verify(Observer, times(1)).onNext(6); + verify(Observer, times(3)).onNext(anyInt()); + verify(Observer, times(1)).onCompleted(); + verify(Observer, never()).onError(any(Throwable.class)); + } + + @Test + public void testScanIntegersWithoutInitialValueAndOnlyOneValue() { + @SuppressWarnings("unchecked") + Observer Observer = mock(Observer.class); + + Observable observable = Observable.from(1); + + Observable m = Observable.create(scan(observable, new Func2() { + + @Override + public Integer call(Integer t1, Integer t2) { + return t1 + t2; + } + + })); + m.subscribe(Observer); + + verify(Observer, never()).onError(any(Throwable.class)); + verify(Observer, never()).onNext(0); + verify(Observer, times(1)).onNext(1); + verify(Observer, times(1)).onNext(anyInt()); + verify(Observer, times(1)).onCompleted(); + verify(Observer, never()).onError(any(Throwable.class)); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationSkipLastTest.java b/rxjava-core/src/test/java/rx/operators/OperationSkipLastTest.java new file mode 100644 index 0000000000..9391424e07 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationSkipLastTest.java @@ -0,0 +1,114 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationSkipLast.*; + +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; + +public class OperationSkipLastTest { + + @Test + public void testSkipLastEmpty() { + Observable w = Observable.empty(); + Observable observable = Observable.create(skipLast(w, 2)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, never()).onNext(any(String.class)); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testSkipLast1() { + Observable w = Observable.from("one", "two", "three"); + Observable observable = Observable.create(skipLast(w, 2)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + InOrder inOrder = inOrder(aObserver); + observable.subscribe(aObserver); + inOrder.verify(aObserver, never()).onNext("two"); + inOrder.verify(aObserver, never()).onNext("three"); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testSkipLast2() { + Observable w = Observable.from("one", "two"); + Observable observable = Observable.create(skipLast(w, 2)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, never()).onNext(any(String.class)); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testSkipLastWithZeroCount() { + Observable w = Observable.from("one", "two"); + Observable observable = Observable.create(skipLast(w, 0)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, times(1)).onNext("two"); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testSkipLastWithNull() { + Observable w = Observable.from("one", null, "two"); + Observable observable = Observable.create(skipLast(w, 1)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, times(1)).onNext(null); + verify(aObserver, never()).onNext("two"); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testSkipLastWithNegativeCount() { + Observable w = Observable.from("one"); + Observable observable = Observable.create(skipLast(w, -1)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, never()).onNext(any(String.class)); + verify(aObserver, times(1)).onError( + any(IndexOutOfBoundsException.class)); + verify(aObserver, never()).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationSkipTest.java b/rxjava-core/src/test/java/rx/operators/OperationSkipTest.java new file mode 100644 index 0000000000..16bc76820f --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationSkipTest.java @@ -0,0 +1,58 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationSkip.*; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; + +public class OperationSkipTest { + + @Test + public void testSkip1() { + Observable w = Observable.from("one", "two", "three"); + Observable skip = Observable.create(skip(w, 2)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + skip.subscribe(aObserver); + verify(aObserver, never()).onNext("one"); + verify(aObserver, never()).onNext("two"); + verify(aObserver, times(1)).onNext("three"); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testSkip2() { + Observable w = Observable.from("one", "two", "three"); + Observable skip = Observable.create(skip(w, 1)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + skip.subscribe(aObserver); + verify(aObserver, never()).onNext("one"); + verify(aObserver, times(1)).onNext("two"); + verify(aObserver, times(1)).onNext("three"); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationSkipWhileTest.java b/rxjava-core/src/test/java/rx/operators/OperationSkipWhileTest.java new file mode 100644 index 0000000000..153c9fb14e --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationSkipWhileTest.java @@ -0,0 +1,118 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationSkipWhile.*; + +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; +import rx.util.functions.Func1; +import rx.util.functions.Func2; + +public class OperationSkipWhileTest { + + @SuppressWarnings("unchecked") + Observer w = mock(Observer.class); + + private static final Func1 LESS_THAN_FIVE = new Func1() { + @Override + public Boolean call(Integer v) { + if (v == 42) + throw new RuntimeException("that's not the answer to everything!"); + return v < 5; + } + }; + + private static final Func2 INDEX_LESS_THAN_THREE = new Func2() { + @Override + public Boolean call(Integer value, Integer index) { + return index < 3; + } + }; + + @Test + public void testSkipWithIndex() { + Observable src = Observable.from(1, 2, 3, 4, 5); + Observable.create(skipWhileWithIndex(src, INDEX_LESS_THAN_THREE)).subscribe(w); + + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext(4); + inOrder.verify(w, times(1)).onNext(5); + inOrder.verify(w, times(1)).onCompleted(); + inOrder.verify(w, never()).onError(any(Throwable.class)); + } + + @Test + public void testSkipEmpty() { + Observable src = Observable.empty(); + Observable.create(skipWhile(src, LESS_THAN_FIVE)).subscribe(w); + verify(w, never()).onNext(anyInt()); + verify(w, never()).onError(any(Throwable.class)); + verify(w, times(1)).onCompleted(); + } + + @Test + public void testSkipEverything() { + Observable src = Observable.from(1, 2, 3, 4, 3, 2, 1); + Observable.create(skipWhile(src, LESS_THAN_FIVE)).subscribe(w); + verify(w, never()).onNext(anyInt()); + verify(w, never()).onError(any(Throwable.class)); + verify(w, times(1)).onCompleted(); + } + + @Test + public void testSkipNothing() { + Observable src = Observable.from(5, 3, 1); + Observable.create(skipWhile(src, LESS_THAN_FIVE)).subscribe(w); + + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext(5); + inOrder.verify(w, times(1)).onNext(3); + inOrder.verify(w, times(1)).onNext(1); + inOrder.verify(w, times(1)).onCompleted(); + inOrder.verify(w, never()).onError(any(Throwable.class)); + } + + @Test + public void testSkipSome() { + Observable src = Observable.from(1, 2, 3, 4, 5, 3, 1, 5); + Observable.create(skipWhile(src, LESS_THAN_FIVE)).subscribe(w); + + InOrder inOrder = inOrder(w); + inOrder.verify(w, times(1)).onNext(5); + inOrder.verify(w, times(1)).onNext(3); + inOrder.verify(w, times(1)).onNext(1); + inOrder.verify(w, times(1)).onNext(5); + inOrder.verify(w, times(1)).onCompleted(); + inOrder.verify(w, never()).onError(any(Throwable.class)); + } + + @Test + public void testSkipError() { + Observable src = Observable.from(1, 2, 42, 5, 3, 1); + Observable.create(skipWhile(src, LESS_THAN_FIVE)).subscribe(w); + + InOrder inOrder = inOrder(w); + inOrder.verify(w, never()).onNext(anyInt()); + inOrder.verify(w, never()).onCompleted(); + inOrder.verify(w, times(1)).onError(any(RuntimeException.class)); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationSubscribeOnTest.java b/rxjava-core/src/test/java/rx/operators/OperationSubscribeOnTest.java new file mode 100644 index 0000000000..635d4d8755 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationSubscribeOnTest.java @@ -0,0 +1,55 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationSubscribeOn.*; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; +import rx.Scheduler; +import rx.Subscription; +import rx.concurrency.Schedulers; +import rx.test.OperatorTester; +import rx.util.functions.Action0; +import rx.util.functions.Func2; + +public class OperationSubscribeOnTest { + + @Test + @SuppressWarnings("unchecked") + public void testSubscribeOn() { + Observable w = Observable.from(1, 2, 3); + + Scheduler scheduler = spy(OperatorTester.forwardingScheduler(Schedulers.immediate())); + + Observer observer = mock(Observer.class); + Subscription subscription = Observable.create(subscribeOn(w, scheduler)).subscribe(observer); + + verify(scheduler, times(1)).schedule(isNull(), any(Func2.class)); + subscription.unsubscribe(); + verify(scheduler, times(1)).schedule(any(Action0.class)); + verifyNoMoreInteractions(scheduler); + + verify(observer, times(1)).onNext(1); + verify(observer, times(1)).onNext(2); + verify(observer, times(1)).onNext(3); + verify(observer, times(1)).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationSumTest.java b/rxjava-core/src/test/java/rx/operators/OperationSumTest.java new file mode 100644 index 0000000000..e124ad13d5 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationSumTest.java @@ -0,0 +1,125 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationSum.*; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; + +public class OperationSumTest { + + @SuppressWarnings("unchecked") + Observer w = mock(Observer.class); + @SuppressWarnings("unchecked") + Observer wl = mock(Observer.class); + @SuppressWarnings("unchecked") + Observer wf = mock(Observer.class); + @SuppressWarnings("unchecked") + Observer wd = mock(Observer.class); + + @Test + public void testSumOfAFewInts() throws Throwable { + Observable src = Observable.from(1, 2, 3, 4, 5); + sum(src).subscribe(w); + + verify(w, times(1)).onNext(anyInt()); + verify(w).onNext(15); + verify(w, never()).onError(any(Throwable.class)); + verify(w, times(1)).onCompleted(); + } + + @Test + public void testEmptySum() throws Throwable { + Observable src = Observable.empty(); + sum(src).subscribe(w); + + verify(w, times(1)).onNext(anyInt()); + verify(w).onNext(0); + verify(w, never()).onError(any(Throwable.class)); + verify(w, times(1)).onCompleted(); + } + + @Test + public void testSumOfAFewLongs() throws Throwable { + Observable src = Observable.from(1L, 2L, 3L, 4L, 5L); + sumLongs(src).subscribe(wl); + + verify(wl, times(1)).onNext(anyLong()); + verify(wl).onNext(15L); + verify(wl, never()).onError(any(Throwable.class)); + verify(wl, times(1)).onCompleted(); + } + + @Test + public void testEmptySumLongs() throws Throwable { + Observable src = Observable.empty(); + sumLongs(src).subscribe(wl); + + verify(wl, times(1)).onNext(anyLong()); + verify(wl).onNext(0L); + verify(wl, never()).onError(any(Throwable.class)); + verify(wl, times(1)).onCompleted(); + } + + @Test + public void testSumOfAFewFloats() throws Throwable { + Observable src = Observable.from(1.0f); + sumFloats(src).subscribe(wf); + + verify(wf, times(1)).onNext(anyFloat()); + verify(wf).onNext(1.0f); + verify(wf, never()).onError(any(Throwable.class)); + verify(wf, times(1)).onCompleted(); + } + + @Test + public void testEmptySumFloats() throws Throwable { + Observable src = Observable.empty(); + sumFloats(src).subscribe(wf); + + verify(wf, times(1)).onNext(anyFloat()); + verify(wf).onNext(0.0f); + verify(wf, never()).onError(any(Throwable.class)); + verify(wf, times(1)).onCompleted(); + } + + @Test + public void testSumOfAFewDoubles() throws Throwable { + Observable src = Observable.from(0.0d, 1.0d, 0.5d); + sumDoubles(src).subscribe(wd); + + verify(wd, times(1)).onNext(anyDouble()); + verify(wd).onNext(1.5d); + verify(wd, never()).onError(any(Throwable.class)); + verify(wd, times(1)).onCompleted(); + } + + @Test + public void testEmptySumDoubles() throws Throwable { + Observable src = Observable.empty(); + sumDoubles(src).subscribe(wd); + + verify(wd, times(1)).onNext(anyDouble()); + verify(wd).onNext(0.0d); + verify(wd, never()).onError(any(Throwable.class)); + verify(wd, times(1)).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationSwitchTest.java b/rxjava-core/src/test/java/rx/operators/OperationSwitchTest.java new file mode 100644 index 0000000000..fa38f02ff8 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationSwitchTest.java @@ -0,0 +1,383 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.concurrency.TestScheduler; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action0; + +public class OperationSwitchTest { + + private TestScheduler scheduler; + private Observer observer; + + @Before + @SuppressWarnings("unchecked") + public void before() { + scheduler = new TestScheduler(); + observer = mock(Observer.class); + } + + @Test + public void testSwitchWhenOuterCompleteBeforeInner() { + Observable> source = Observable.create(new Observable.OnSubscribeFunc>() { + @Override + public Subscription onSubscribe(Observer> observer) { + publishNext(observer, 50, Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + publishNext(observer, 70, "one"); + publishNext(observer, 100, "two"); + publishCompleted(observer, 200); + return Subscriptions.empty(); + } + })); + publishCompleted(observer, 60); + + return Subscriptions.empty(); + } + }); + + Observable sampled = Observable.create(OperationSwitch.switchDo(source)); + sampled.subscribe(observer); + + InOrder inOrder = inOrder(observer); + + scheduler.advanceTimeTo(350, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(2)).onNext(anyString()); + inOrder.verify(observer, times(1)).onCompleted(); + } + + @Test + public void testSwitchWhenInnerCompleteBeforeOuter() { + Observable> source = Observable.create(new Observable.OnSubscribeFunc>() { + @Override + public Subscription onSubscribe(Observer> observer) { + publishNext(observer, 10, Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + publishNext(observer, 0, "one"); + publishNext(observer, 10, "two"); + publishCompleted(observer, 20); + return Subscriptions.empty(); + } + })); + + publishNext(observer, 100, Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + publishNext(observer, 0, "three"); + publishNext(observer, 10, "four"); + publishCompleted(observer, 20); + return Subscriptions.empty(); + } + })); + publishCompleted(observer, 200); + + return Subscriptions.empty(); + } + }); + + Observable sampled = Observable.create(OperationSwitch.switchDo(source)); + sampled.subscribe(observer); + + InOrder inOrder = inOrder(observer); + + scheduler.advanceTimeTo(150, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onCompleted(); + inOrder.verify(observer, times(1)).onNext("one"); + inOrder.verify(observer, times(1)).onNext("two"); + inOrder.verify(observer, times(1)).onNext("three"); + inOrder.verify(observer, times(1)).onNext("four"); + + scheduler.advanceTimeTo(250, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onNext(anyString()); + inOrder.verify(observer, times(1)).onCompleted(); + } + + @Test + public void testSwitchWithComplete() { + Observable> source = Observable.create(new Observable.OnSubscribeFunc>() { + @Override + public Subscription onSubscribe(Observer> observer) { + publishNext(observer, 50, Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + publishNext(observer, 60, "one"); + publishNext(observer, 100, "two"); + return Subscriptions.empty(); + } + })); + + publishNext(observer, 200, Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + publishNext(observer, 0, "three"); + publishNext(observer, 100, "four"); + return Subscriptions.empty(); + } + })); + + publishCompleted(observer, 250); + + return Subscriptions.empty(); + } + }); + + Observable sampled = Observable.create(OperationSwitch.switchDo(source)); + sampled.subscribe(observer); + + InOrder inOrder = inOrder(observer); + + scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onNext(anyString()); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext("one"); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(175, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext("two"); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(225, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext("three"); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(350, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext("four"); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void testSwitchWithError() { + Observable> source = Observable.create(new Observable.OnSubscribeFunc>() { + @Override + public Subscription onSubscribe(Observer> observer) { + publishNext(observer, 50, Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + publishNext(observer, 50, "one"); + publishNext(observer, 100, "two"); + return Subscriptions.empty(); + } + })); + + publishNext(observer, 200, Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + publishNext(observer, 0, "three"); + publishNext(observer, 100, "four"); + return Subscriptions.empty(); + } + })); + + publishError(observer, 250, new TestException()); + + return Subscriptions.empty(); + } + }); + + Observable sampled = Observable.create(OperationSwitch.switchDo(source)); + sampled.subscribe(observer); + + InOrder inOrder = inOrder(observer); + + scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onNext(anyString()); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext("one"); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(175, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext("two"); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(225, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext("three"); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(350, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onNext(anyString()); + verify(observer, never()).onCompleted(); + verify(observer, times(1)).onError(any(TestException.class)); + } + + @Test + public void testSwitchWithSubsequenceComplete() { + Observable> source = Observable.create(new Observable.OnSubscribeFunc>() { + @Override + public Subscription onSubscribe(Observer> observer) { + publishNext(observer, 50, Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + publishNext(observer, 50, "one"); + publishNext(observer, 100, "two"); + return Subscriptions.empty(); + } + })); + + publishNext(observer, 130, Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + publishCompleted(observer, 0); + return Subscriptions.empty(); + } + })); + + publishNext(observer, 150, Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + publishNext(observer, 50, "three"); + return Subscriptions.empty(); + } + })); + + return Subscriptions.empty(); + } + }); + + Observable sampled = Observable.create(OperationSwitch.switchDo(source)); + sampled.subscribe(observer); + + InOrder inOrder = inOrder(observer); + + scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onNext(anyString()); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext("one"); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(250, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext("three"); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void testSwitchWithSubsequenceError() { + Observable> source = Observable.create(new Observable.OnSubscribeFunc>() { + @Override + public Subscription onSubscribe(Observer> observer) { + publishNext(observer, 50, Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + publishNext(observer, 50, "one"); + publishNext(observer, 100, "two"); + return Subscriptions.empty(); + } + })); + + publishNext(observer, 130, Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + publishError(observer, 0, new TestException()); + return Subscriptions.empty(); + } + })); + + publishNext(observer, 150, Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + publishNext(observer, 50, "three"); + return Subscriptions.empty(); + } + })); + + return Subscriptions.empty(); + } + }); + + Observable sampled = Observable.create(OperationSwitch.switchDo(source)); + sampled.subscribe(observer); + + InOrder inOrder = inOrder(observer); + + scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onNext(anyString()); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext("one"); + verify(observer, never()).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeTo(250, TimeUnit.MILLISECONDS); + inOrder.verify(observer, never()).onNext("three"); + verify(observer, never()).onCompleted(); + verify(observer, times(1)).onError(any(TestException.class)); + } + + private void publishCompleted(final Observer observer, long delay) { + scheduler.schedule(new Action0() { + @Override + public void call() { + observer.onCompleted(); + } + }, delay, TimeUnit.MILLISECONDS); + } + + private void publishError(final Observer observer, long delay, final Throwable error) { + scheduler.schedule(new Action0() { + @Override + public void call() { + observer.onError(error); + } + }, delay, TimeUnit.MILLISECONDS); + } + + private void publishNext(final Observer observer, long delay, final T value) { + scheduler.schedule(new Action0() { + @Override + public void call() { + observer.onNext(value); + } + }, delay, TimeUnit.MILLISECONDS); + } + + @SuppressWarnings("serial") + private class TestException extends Throwable { + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationSynchronizeTest.java b/rxjava-core/src/test/java/rx/operators/OperationSynchronizeTest.java new file mode 100644 index 0000000000..73db076000 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationSynchronizeTest.java @@ -0,0 +1,219 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationSynchronize.*; + +import org.junit.Test; +import org.mockito.Mockito; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; + +public class OperationSynchronizeTest { + + /** + * Ensure onCompleted can not be called after an Unsubscribe + */ + @Test + public void testOnCompletedAfterUnSubscribe() { + TestObservable t = new TestObservable(null); + Observable st = Observable.create(synchronize(Observable.create(t))); + + @SuppressWarnings("unchecked") + Observer w = mock(Observer.class); + Subscription ws = st.subscribe(w); + + t.sendOnNext("one"); + ws.unsubscribe(); + t.sendOnCompleted(); + + verify(w, times(1)).onNext("one"); + verify(w, Mockito.never()).onCompleted(); + } + + /** + * Ensure onNext can not be called after an Unsubscribe + */ + @Test + public void testOnNextAfterUnSubscribe() { + TestObservable t = new TestObservable(null); + Observable st = Observable.create(synchronize(Observable.create(t))); + + @SuppressWarnings("unchecked") + Observer w = mock(Observer.class); + Subscription ws = st.subscribe(w); + + t.sendOnNext("one"); + ws.unsubscribe(); + t.sendOnNext("two"); + + verify(w, times(1)).onNext("one"); + verify(w, Mockito.never()).onNext("two"); + } + + /** + * Ensure onError can not be called after an Unsubscribe + */ + @Test + public void testOnErrorAfterUnSubscribe() { + TestObservable t = new TestObservable(null); + Observable st = Observable.create(synchronize(Observable.create(t))); + + @SuppressWarnings("unchecked") + Observer w = mock(Observer.class); + Subscription ws = st.subscribe(w); + + t.sendOnNext("one"); + ws.unsubscribe(); + t.sendOnError(new RuntimeException("bad")); + + verify(w, times(1)).onNext("one"); + verify(w, Mockito.never()).onError(any(Throwable.class)); + } + + /** + * Ensure onNext can not be called after onError + */ + @Test + public void testOnNextAfterOnError() { + TestObservable t = new TestObservable(null); + Observable st = Observable.create(synchronize(Observable.create(t))); + + @SuppressWarnings("unchecked") + Observer w = mock(Observer.class); + @SuppressWarnings("unused") + Subscription ws = st.subscribe(w); + + t.sendOnNext("one"); + t.sendOnError(new RuntimeException("bad")); + t.sendOnNext("two"); + + verify(w, times(1)).onNext("one"); + verify(w, times(1)).onError(any(Throwable.class)); + verify(w, Mockito.never()).onNext("two"); + } + + /** + * Ensure onCompleted can not be called after onError + */ + @Test + public void testOnCompletedAfterOnError() { + TestObservable t = new TestObservable(null); + Observable st = Observable.create(synchronize(Observable.create(t))); + + @SuppressWarnings("unchecked") + Observer w = mock(Observer.class); + @SuppressWarnings("unused") + Subscription ws = st.subscribe(w); + + t.sendOnNext("one"); + t.sendOnError(new RuntimeException("bad")); + t.sendOnCompleted(); + + verify(w, times(1)).onNext("one"); + verify(w, times(1)).onError(any(Throwable.class)); + verify(w, Mockito.never()).onCompleted(); + } + + /** + * Ensure onNext can not be called after onCompleted + */ + @Test + public void testOnNextAfterOnCompleted() { + TestObservable t = new TestObservable(null); + Observable st = Observable.create(synchronize(Observable.create(t))); + + @SuppressWarnings("unchecked") + Observer w = mock(Observer.class); + @SuppressWarnings("unused") + Subscription ws = st.subscribe(w); + + t.sendOnNext("one"); + t.sendOnCompleted(); + t.sendOnNext("two"); + + verify(w, times(1)).onNext("one"); + verify(w, Mockito.never()).onNext("two"); + verify(w, times(1)).onCompleted(); + verify(w, Mockito.never()).onError(any(Throwable.class)); + } + + /** + * Ensure onError can not be called after onCompleted + */ + @Test + public void testOnErrorAfterOnCompleted() { + TestObservable t = new TestObservable(null); + Observable st = Observable.create(synchronize(Observable.create(t))); + + @SuppressWarnings("unchecked") + Observer w = mock(Observer.class); + @SuppressWarnings("unused") + Subscription ws = st.subscribe(w); + + t.sendOnNext("one"); + t.sendOnCompleted(); + t.sendOnError(new RuntimeException("bad")); + + verify(w, times(1)).onNext("one"); + verify(w, times(1)).onCompleted(); + verify(w, Mockito.never()).onError(any(Throwable.class)); + } + + /** + * A Observable that doesn't do the right thing on UnSubscribe/Error/etc in that it will keep sending events down the pipe regardless of what happens. + */ + private static class TestObservable implements Observable.OnSubscribeFunc { + + Observer observer = null; + + public TestObservable(Subscription s) { + } + + /* used to simulate subscription */ + public void sendOnCompleted() { + observer.onCompleted(); + } + + /* used to simulate subscription */ + public void sendOnNext(String value) { + observer.onNext(value); + } + + /* used to simulate subscription */ + public void sendOnError(Throwable e) { + observer.onError(e); + } + + @Override + public Subscription onSubscribe(final Observer observer) { + this.observer = observer; + return new Subscription() { + + @Override + public void unsubscribe() { + // going to do nothing to pretend I'm a bad Observable that keeps allowing events to be sent + } + + }; + } + + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationTakeLastTest.java b/rxjava-core/src/test/java/rx/operators/OperationTakeLastTest.java new file mode 100644 index 0000000000..5ec876368b --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationTakeLastTest.java @@ -0,0 +1,113 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationTakeLast.*; + +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; + +public class OperationTakeLastTest { + + @Test + public void testTakeLastEmpty() { + Observable w = Observable.empty(); + Observable take = Observable.create(takeLast(w, 2)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + take.subscribe(aObserver); + verify(aObserver, never()).onNext(any(String.class)); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testTakeLast1() { + Observable w = Observable.from("one", "two", "three"); + Observable take = Observable.create(takeLast(w, 2)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + InOrder inOrder = inOrder(aObserver); + take.subscribe(aObserver); + inOrder.verify(aObserver, times(1)).onNext("two"); + inOrder.verify(aObserver, times(1)).onNext("three"); + verify(aObserver, never()).onNext("one"); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testTakeLast2() { + Observable w = Observable.from("one"); + Observable take = Observable.create(takeLast(w, 10)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + take.subscribe(aObserver); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testTakeLastWithZeroCount() { + Observable w = Observable.from("one"); + Observable take = Observable.create(takeLast(w, 0)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + take.subscribe(aObserver); + verify(aObserver, never()).onNext("one"); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testTakeLastWithNull() { + Observable w = Observable.from("one", null, "three"); + Observable take = Observable.create(takeLast(w, 2)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + take.subscribe(aObserver); + verify(aObserver, never()).onNext("one"); + verify(aObserver, times(1)).onNext(null); + verify(aObserver, times(1)).onNext("three"); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testTakeLastWithNegativeCount() { + Observable w = Observable.from("one"); + Observable take = Observable.create(takeLast(w, -1)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + take.subscribe(aObserver); + verify(aObserver, never()).onNext("one"); + verify(aObserver, times(1)).onError( + any(IndexOutOfBoundsException.class)); + verify(aObserver, never()).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationTakeTest.java b/rxjava-core/src/test/java/rx/operators/OperationTakeTest.java new file mode 100644 index 0000000000..773154eda0 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationTakeTest.java @@ -0,0 +1,227 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationTake.*; + +import java.util.concurrent.atomic.AtomicBoolean; + +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Func1; + +public class OperationTakeTest { + + @Test + public void testTake1() { + Observable w = Observable.from("one", "two", "three"); + Observable take = Observable.create(take(w, 2)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + take.subscribe(aObserver); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, times(1)).onNext("two"); + verify(aObserver, never()).onNext("three"); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testTake2() { + Observable w = Observable.from("one", "two", "three"); + Observable take = Observable.create(take(w, 1)); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + take.subscribe(aObserver); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, never()).onNext("two"); + verify(aObserver, never()).onNext("three"); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test(expected = IllegalArgumentException.class) + public void testTakeWithError() { + Observable.from(1, 2, 3).take(1).map(new Func1() { + public Integer call(Integer t1) { + throw new IllegalArgumentException("some error"); + } + }).toBlockingObservable().single(); + } + + @Test + public void testTakeWithErrorHappeningInOnNext() { + Observable w = Observable.from(1, 2, 3).take(2).map(new Func1() { + public Integer call(Integer t1) { + throw new IllegalArgumentException("some error"); + } + }); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + w.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onError(any(IllegalArgumentException.class)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testTakeWithErrorHappeningInTheLastOnNext() { + Observable w = Observable.from(1, 2, 3).take(1).map(new Func1() { + public Integer call(Integer t1) { + throw new IllegalArgumentException("some error"); + } + }); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + w.subscribe(observer); + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onError(any(IllegalArgumentException.class)); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testTakeDoesntLeakErrors() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + observer.onNext("one"); + observer.onError(new Throwable("test failed")); + return Subscriptions.empty(); + } + }); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + + Observable.create(take(source, 1)).subscribe(aObserver); + + verify(aObserver, times(1)).onNext("one"); + // even though onError is called we take(1) so shouldn't see it + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verifyNoMoreInteractions(aObserver); + } + + @Test + public void testTakeZeroDoesntLeakError() { + final AtomicBoolean subscribed = new AtomicBoolean(false); + final AtomicBoolean unSubscribed = new AtomicBoolean(false); + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + subscribed.set(true); + observer.onError(new Throwable("test failed")); + return new Subscription() { + @Override + public void unsubscribe() { + unSubscribed.set(true); + } + }; + } + }); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + + Observable.create(take(source, 0)).subscribe(aObserver); + assertTrue("source subscribed", subscribed.get()); + assertTrue("source unsubscribed", unSubscribed.get()); + + verify(aObserver, never()).onNext(anyString()); + // even though onError is called we take(0) so shouldn't see it + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verifyNoMoreInteractions(aObserver); + } + + @Test + public void testUnsubscribeAfterTake() { + final Subscription s = mock(Subscription.class); + TestObservableFunc f = new TestObservableFunc(s, "one", "two", "three"); + Observable w = Observable.create(f); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + Observable take = Observable.create(take(w, 1)); + take.subscribe(aObserver); + + // wait for the Observable to complete + try { + f.t.join(); + } catch (Throwable e) { + e.printStackTrace(); + fail(e.getMessage()); + } + + System.out.println("TestObservable thread finished"); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, never()).onNext("two"); + verify(aObserver, never()).onNext("three"); + verify(aObserver, times(1)).onCompleted(); + verify(s, times(1)).unsubscribe(); + verifyNoMoreInteractions(aObserver); + } + + private static class TestObservableFunc implements Observable.OnSubscribeFunc { + + final Subscription s; + final String[] values; + Thread t = null; + + public TestObservableFunc(Subscription s, String... values) { + this.s = s; + this.values = values; + } + + @Override + public Subscription onSubscribe(final Observer observer) { + System.out.println("TestObservable subscribed to ..."); + t = new Thread(new Runnable() { + + @Override + public void run() { + try { + System.out.println("running TestObservable thread"); + for (String s : values) { + System.out.println("TestObservable onNext: " + s); + observer.onNext(s); + } + observer.onCompleted(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + }); + System.out.println("starting TestObservable thread"); + t.start(); + System.out.println("done starting TestObservable thread"); + return s; + } + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationTakeUntilTest.java b/rxjava-core/src/test/java/rx/operators/OperationTakeUntilTest.java new file mode 100644 index 0000000000..c5182a71ba --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationTakeUntilTest.java @@ -0,0 +1,180 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Mockito.*; +import static rx.operators.OperationTakeUntil.*; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; + +public class OperationTakeUntilTest { + + @Test + @SuppressWarnings("unchecked") + public void testTakeUntil() { + Subscription sSource = mock(Subscription.class); + Subscription sOther = mock(Subscription.class); + TestObservable source = new TestObservable(sSource); + TestObservable other = new TestObservable(sOther); + + Observer result = mock(Observer.class); + Observable stringObservable = takeUntil(Observable.create(source), Observable.create(other)); + stringObservable.subscribe(result); + source.sendOnNext("one"); + source.sendOnNext("two"); + other.sendOnNext("three"); + source.sendOnNext("four"); + source.sendOnCompleted(); + other.sendOnCompleted(); + + verify(result, times(1)).onNext("one"); + verify(result, times(1)).onNext("two"); + verify(result, times(0)).onNext("three"); + verify(result, times(0)).onNext("four"); + verify(sSource, times(1)).unsubscribe(); + verify(sOther, times(1)).unsubscribe(); + + } + + @Test + @SuppressWarnings("unchecked") + public void testTakeUntilSourceCompleted() { + Subscription sSource = mock(Subscription.class); + Subscription sOther = mock(Subscription.class); + TestObservable source = new TestObservable(sSource); + TestObservable other = new TestObservable(sOther); + + Observer result = mock(Observer.class); + Observable stringObservable = takeUntil(Observable.create(source), Observable.create(other)); + stringObservable.subscribe(result); + source.sendOnNext("one"); + source.sendOnNext("two"); + source.sendOnCompleted(); + + verify(result, times(1)).onNext("one"); + verify(result, times(1)).onNext("two"); + verify(sSource, times(1)).unsubscribe(); + verify(sOther, times(1)).unsubscribe(); + + } + + @Test + @SuppressWarnings("unchecked") + public void testTakeUntilSourceError() { + Subscription sSource = mock(Subscription.class); + Subscription sOther = mock(Subscription.class); + TestObservable source = new TestObservable(sSource); + TestObservable other = new TestObservable(sOther); + Throwable error = new Throwable(); + + Observer result = mock(Observer.class); + Observable stringObservable = takeUntil(Observable.create(source), Observable.create(other)); + stringObservable.subscribe(result); + source.sendOnNext("one"); + source.sendOnNext("two"); + source.sendOnError(error); + + verify(result, times(1)).onNext("one"); + verify(result, times(1)).onNext("two"); + verify(result, times(1)).onError(error); + verify(sSource, times(1)).unsubscribe(); + verify(sOther, times(1)).unsubscribe(); + + } + + @Test + @SuppressWarnings("unchecked") + public void testTakeUntilOtherError() { + Subscription sSource = mock(Subscription.class); + Subscription sOther = mock(Subscription.class); + TestObservable source = new TestObservable(sSource); + TestObservable other = new TestObservable(sOther); + Throwable error = new Throwable(); + + Observer result = mock(Observer.class); + Observable stringObservable = takeUntil(Observable.create(source), Observable.create(other)); + stringObservable.subscribe(result); + source.sendOnNext("one"); + source.sendOnNext("two"); + other.sendOnError(error); + + verify(result, times(1)).onNext("one"); + verify(result, times(1)).onNext("two"); + verify(result, times(1)).onError(error); + verify(result, times(0)).onCompleted(); + verify(sSource, times(1)).unsubscribe(); + verify(sOther, times(1)).unsubscribe(); + + } + + @Test + @SuppressWarnings("unchecked") + public void testTakeUntilOtherCompleted() { + Subscription sSource = mock(Subscription.class); + Subscription sOther = mock(Subscription.class); + TestObservable source = new TestObservable(sSource); + TestObservable other = new TestObservable(sOther); + + Observer result = mock(Observer.class); + Observable stringObservable = takeUntil(Observable.create(source), Observable.create(other)); + stringObservable.subscribe(result); + source.sendOnNext("one"); + source.sendOnNext("two"); + other.sendOnCompleted(); + + verify(result, times(1)).onNext("one"); + verify(result, times(1)).onNext("two"); + verify(result, times(0)).onCompleted(); + verify(sSource, times(0)).unsubscribe(); + verify(sOther, times(0)).unsubscribe(); + + } + + private static class TestObservable implements Observable.OnSubscribeFunc { + + Observer observer = null; + Subscription s; + + public TestObservable(Subscription s) { + this.s = s; + } + + /* used to simulate subscription */ + public void sendOnCompleted() { + observer.onCompleted(); + } + + /* used to simulate subscription */ + public void sendOnNext(String value) { + observer.onNext(value); + } + + /* used to simulate subscription */ + public void sendOnError(Throwable e) { + observer.onError(e); + } + + @Override + public Subscription onSubscribe(final Observer observer) { + this.observer = observer; + return s; + } + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationTakeWhileTest.java b/rxjava-core/src/test/java/rx/operators/OperationTakeWhileTest.java new file mode 100644 index 0000000000..830dd59b33 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationTakeWhileTest.java @@ -0,0 +1,219 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationTakeWhile.*; + +import org.junit.Test; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.subjects.PublishSubject; +import rx.subjects.Subject; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Func1; +import rx.util.functions.Func2; + +public class OperationTakeWhileTest { + + @Test + public void testTakeWhile1() { + Observable w = Observable.from(1, 2, 3); + Observable take = Observable.create(takeWhile(w, new Func1() { + @Override + public Boolean call(Integer input) { + return input < 3; + } + })); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + take.subscribe(aObserver); + verify(aObserver, times(1)).onNext(1); + verify(aObserver, times(1)).onNext(2); + verify(aObserver, never()).onNext(3); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testTakeWhileOnSubject1() { + Subject s = PublishSubject.create(); + Observable take = Observable.create(takeWhile(s, new Func1() { + @Override + public Boolean call(Integer input) { + return input < 3; + } + })); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + take.subscribe(aObserver); + + s.onNext(1); + s.onNext(2); + s.onNext(3); + s.onNext(4); + s.onNext(5); + s.onCompleted(); + + verify(aObserver, times(1)).onNext(1); + verify(aObserver, times(1)).onNext(2); + verify(aObserver, never()).onNext(3); + verify(aObserver, never()).onNext(4); + verify(aObserver, never()).onNext(5); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testTakeWhile2() { + Observable w = Observable.from("one", "two", "three"); + Observable take = Observable.create(takeWhileWithIndex(w, new Func2() { + @Override + public Boolean call(String input, Integer index) { + return index < 2; + } + })); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + take.subscribe(aObserver); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, times(1)).onNext("two"); + verify(aObserver, never()).onNext("three"); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testTakeWhileDoesntLeakErrors() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + observer.onNext("one"); + observer.onError(new Throwable("test failed")); + return Subscriptions.empty(); + } + }); + + Observable.create(takeWhile(source, new Func1() { + @Override + public Boolean call(String s) { + return false; + } + })).toBlockingObservable().lastOrDefault(""); + } + + @Test + public void testTakeWhileProtectsPredicateCall() { + TestObservable source = new TestObservable(mock(Subscription.class), "one"); + final RuntimeException testException = new RuntimeException("test exception"); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + Observable take = Observable.create(takeWhile(Observable.create(source), new Func1() { + @Override + public Boolean call(String s) { + throw testException; + } + })); + take.subscribe(aObserver); + + // wait for the Observable to complete + try { + source.t.join(); + } catch (Throwable e) { + e.printStackTrace(); + fail(e.getMessage()); + } + + verify(aObserver, never()).onNext(any(String.class)); + verify(aObserver, times(1)).onError(testException); + } + + @Test + public void testUnsubscribeAfterTake() { + Subscription s = mock(Subscription.class); + TestObservable w = new TestObservable(s, "one", "two", "three"); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + Observable take = Observable.create(takeWhileWithIndex(Observable.create(w), new Func2() { + @Override + public Boolean call(String s, Integer index) { + return index < 1; + } + })); + take.subscribe(aObserver); + + // wait for the Observable to complete + try { + w.t.join(); + } catch (Throwable e) { + e.printStackTrace(); + fail(e.getMessage()); + } + + System.out.println("TestObservable thread finished"); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, never()).onNext("two"); + verify(aObserver, never()).onNext("three"); + verify(s, times(1)).unsubscribe(); + } + + private static class TestObservable implements Observable.OnSubscribeFunc { + + final Subscription s; + final String[] values; + Thread t = null; + + public TestObservable(Subscription s, String... values) { + this.s = s; + this.values = values; + } + + @Override + public Subscription onSubscribe(final Observer observer) { + System.out.println("TestObservable subscribed to ..."); + t = new Thread(new Runnable() { + + @Override + public void run() { + try { + System.out.println("running TestObservable thread"); + for (String s : values) { + System.out.println("TestObservable onNext: " + s); + observer.onNext(s); + } + observer.onCompleted(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + }); + System.out.println("starting TestObservable thread"); + t.start(); + System.out.println("done starting TestObservable thread"); + return s; + } + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationThrottleFirstTest.java b/rxjava-core/src/test/java/rx/operators/OperationThrottleFirstTest.java new file mode 100644 index 0000000000..ad58e13604 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationThrottleFirstTest.java @@ -0,0 +1,130 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.concurrency.TestScheduler; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action0; + +public class OperationThrottleFirstTest { + + private TestScheduler scheduler; + private Observer observer; + + @Before + @SuppressWarnings("unchecked") + public void before() { + scheduler = new TestScheduler(); + observer = mock(Observer.class); + } + + @Test + public void testThrottlingWithCompleted() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + publishNext(observer, 100, "one"); // publish as it's first + publishNext(observer, 300, "two"); // skip as it's last within the first 400 + publishNext(observer, 900, "three"); // publish + publishNext(observer, 905, "four"); // skip + publishCompleted(observer, 1000); // Should be published as soon as the timeout expires. + + return Subscriptions.empty(); + } + }); + + Observable sampled = Observable.create(OperationThrottleFirst.throttleFirst(source, 400, TimeUnit.MILLISECONDS, scheduler)); + sampled.subscribe(observer); + + InOrder inOrder = inOrder(observer); + + scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); + inOrder.verify(observer, times(1)).onNext("one"); + inOrder.verify(observer, times(0)).onNext("two"); + inOrder.verify(observer, times(1)).onNext("three"); + inOrder.verify(observer, times(0)).onNext("four"); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testThrottlingWithError() { + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + Exception error = new TestException(); + publishNext(observer, 100, "one"); // Should be published since it is first + publishNext(observer, 200, "two"); // Should be skipped since onError will arrive before the timeout expires + publishError(observer, 300, error); // Should be published as soon as the timeout expires. + + return Subscriptions.empty(); + } + }); + + Observable sampled = Observable.create(OperationThrottleFirst.throttleFirst(source, 400, TimeUnit.MILLISECONDS, scheduler)); + sampled.subscribe(observer); + + InOrder inOrder = inOrder(observer); + + scheduler.advanceTimeTo(400, TimeUnit.MILLISECONDS); + inOrder.verify(observer).onNext("one"); + inOrder.verify(observer).onError(any(TestException.class)); + inOrder.verifyNoMoreInteractions(); + } + + private void publishCompleted(final Observer observer, long delay) { + scheduler.schedule(new Action0() { + @Override + public void call() { + observer.onCompleted(); + } + }, delay, TimeUnit.MILLISECONDS); + } + + private void publishError(final Observer observer, long delay, final Exception error) { + scheduler.schedule(new Action0() { + @Override + public void call() { + observer.onError(error); + } + }, delay, TimeUnit.MILLISECONDS); + } + + private void publishNext(final Observer observer, long delay, final T value) { + scheduler.schedule(new Action0() { + @Override + public void call() { + observer.onNext(value); + } + }, delay, TimeUnit.MILLISECONDS); + } + + @SuppressWarnings("serial") + private class TestException extends Exception { + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationTimeIntervalTest.java b/rxjava-core/src/test/java/rx/operators/OperationTimeIntervalTest.java new file mode 100644 index 0000000000..056b97bf11 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationTimeIntervalTest.java @@ -0,0 +1,75 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Mockito.*; + +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import rx.Observable; +import rx.Observer; +import rx.concurrency.TestScheduler; +import rx.subjects.PublishSubject; +import rx.util.TimeInterval; + +public class OperationTimeIntervalTest { + + private static final TimeUnit TIME_UNIT = TimeUnit.MILLISECONDS; + + @Mock + private Observer> observer; + + private TestScheduler testScheduler; + private PublishSubject subject; + private Observable> observable; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + testScheduler = new TestScheduler(); + subject = PublishSubject.create(); + observable = subject.timeInterval(testScheduler); + } + + @Test + public void testTimeInterval() { + InOrder inOrder = inOrder(observer); + observable.subscribe(observer); + + testScheduler.advanceTimeBy(1000, TIME_UNIT); + subject.onNext(1); + testScheduler.advanceTimeBy(2000, TIME_UNIT); + subject.onNext(2); + testScheduler.advanceTimeBy(3000, TIME_UNIT); + subject.onNext(3); + subject.onCompleted(); + + inOrder.verify(observer, times(1)).onNext( + new TimeInterval(1000, 1)); + inOrder.verify(observer, times(1)).onNext( + new TimeInterval(2000, 2)); + inOrder.verify(observer, times(1)).onNext( + new TimeInterval(3000, 3)); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationToFutureTest.java b/rxjava-core/src/test/java/rx/operators/OperationToFutureTest.java new file mode 100644 index 0000000000..ffaf775f8b --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationToFutureTest.java @@ -0,0 +1,83 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static rx.operators.OperationToFuture.*; + +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import org.junit.Test; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.Subscriptions; + +public class OperationToFutureTest { + + @Test + public void testToFuture() throws InterruptedException, ExecutionException { + Observable obs = Observable.from("one"); + Future f = toFuture(obs); + assertEquals("one", f.get()); + } + + @Test + public void testToFutureList() throws InterruptedException, ExecutionException { + Observable obs = Observable.from("one", "two", "three"); + Future> f = toFuture(obs.toList()); + assertEquals("one", f.get().get(0)); + assertEquals("two", f.get().get(1)); + assertEquals("three", f.get().get(2)); + } + + @Test(expected = ExecutionException.class) + public void testExceptionWithMoreThanOneElement() throws InterruptedException, ExecutionException { + Observable obs = Observable.from("one", "two"); + Future f = toFuture(obs); + assertEquals("one", f.get()); + // we expect an exception since there are more than 1 element + } + + @Test + public void testToFutureWithException() { + Observable obs = Observable.create(new OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(Observer observer) { + observer.onNext("one"); + observer.onError(new TestException()); + return Subscriptions.empty(); + } + }); + + Future f = toFuture(obs); + try { + f.get(); + fail("expected exception"); + } catch (Throwable e) { + assertEquals(TestException.class, e.getCause().getClass()); + } + } + + private static class TestException extends RuntimeException { + private static final long serialVersionUID = 1L; + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationToIteratorTest.java b/rxjava-core/src/test/java/rx/operators/OperationToIteratorTest.java new file mode 100644 index 0000000000..b40dd9b62b --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationToIteratorTest.java @@ -0,0 +1,77 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static rx.operators.OperationToIterator.*; + +import java.util.Iterator; + +import org.junit.Test; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.Subscriptions; + +public class OperationToIteratorTest { + + @Test + public void testToIterator() { + Observable obs = Observable.from("one", "two", "three"); + + Iterator it = toIterator(obs); + + assertEquals(true, it.hasNext()); + assertEquals("one", it.next()); + + assertEquals(true, it.hasNext()); + assertEquals("two", it.next()); + + assertEquals(true, it.hasNext()); + assertEquals("three", it.next()); + + assertEquals(false, it.hasNext()); + + } + + @Test(expected = TestException.class) + public void testToIteratorWithException() { + Observable obs = Observable.create(new OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(Observer observer) { + observer.onNext("one"); + observer.onError(new TestException()); + return Subscriptions.empty(); + } + }); + + Iterator it = toIterator(obs); + + assertEquals(true, it.hasNext()); + assertEquals("one", it.next()); + + assertEquals(true, it.hasNext()); + it.next(); + } + + private static class TestException extends RuntimeException { + private static final long serialVersionUID = 1L; + } + +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationToMapTest.java b/rxjava-core/src/test/java/rx/operators/OperationToMapTest.java new file mode 100644 index 0000000000..ab1a9fb9de --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationToMapTest.java @@ -0,0 +1,215 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.Test; +import org.junit.Before; +import org.mockito.*; +import static org.mockito.Mockito.*; +import rx.Observable; +import rx.Observer; +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Functions; + +public class OperationToMapTest { + @Mock + Observer objectObserver; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + } + Func1 lengthFunc = new Func1() { + @Override + public Integer call(String t1) { + return t1.length(); + } + }; + Func1 duplicate = new Func1() { + @Override + public String call(String t1) { + return t1 + t1; + } + }; + @Test + public void testToMap() { + Observable source = Observable.from("a", "bb", "ccc", "dddd"); + + + Observable> mapped = Observable.create(OperationToMap.toMap(source, lengthFunc)); + + Map expected = new HashMap(); + expected.put(1, "a"); + expected.put(2, "bb"); + expected.put(3, "ccc"); + expected.put(4, "dddd"); + + mapped.subscribe(objectObserver); + + verify(objectObserver, never()).onError(any(Throwable.class)); + verify(objectObserver, times(1)).onNext(expected); + verify(objectObserver, times(1)).onCompleted(); + } + + @Test + public void testToMapWithValueSelector() { + Observable source = Observable.from("a", "bb", "ccc", "dddd"); + + Observable> mapped = Observable.create(OperationToMap.toMap(source, lengthFunc, duplicate)); + + Map expected = new HashMap(); + expected.put(1, "aa"); + expected.put(2, "bbbb"); + expected.put(3, "cccccc"); + expected.put(4, "dddddddd"); + + mapped.subscribe(objectObserver); + + verify(objectObserver, never()).onError(any(Throwable.class)); + verify(objectObserver, times(1)).onNext(expected); + verify(objectObserver, times(1)).onCompleted(); + } + + @Test + public void testToMapWithError() { + Observable source = Observable.from("a", "bb", "ccc", "dddd"); + + Func1 lengthFuncErr = new Func1() { + @Override + public Integer call(String t1) { + if ("bb".equals(t1)) { + throw new RuntimeException("Forced Failure"); + } + return t1.length(); + } + }; + Observable> mapped = Observable.create(OperationToMap.toMap(source, lengthFuncErr)); + + Map expected = new HashMap(); + expected.put(1, "a"); + expected.put(2, "bb"); + expected.put(3, "ccc"); + expected.put(4, "dddd"); + + mapped.subscribe(objectObserver); + + verify(objectObserver, never()).onNext(expected); + verify(objectObserver, never()).onCompleted(); + verify(objectObserver, times(1)).onError(any(Throwable.class)); + + } + + @Test + public void testToMapWithErrorInValueSelector() { + Observable source = Observable.from("a", "bb", "ccc", "dddd"); + + Func1 duplicateErr = new Func1() { + @Override + public String call(String t1) { + if ("bb".equals(t1)) { + throw new RuntimeException("Forced failure"); + } + return t1 + t1; + } + }; + + Observable> mapped = Observable.create(OperationToMap.toMap(source, lengthFunc, duplicateErr)); + + Map expected = new HashMap(); + expected.put(1, "aa"); + expected.put(2, "bbbb"); + expected.put(3, "cccccc"); + expected.put(4, "dddddddd"); + + mapped.subscribe(objectObserver); + + verify(objectObserver, never()).onNext(expected); + verify(objectObserver, never()).onCompleted(); + verify(objectObserver, times(1)).onError(any(Throwable.class)); + + } + + @Test + public void testToMapWithFactory() { + Observable source = Observable.from("a", "bb", "ccc", "dddd"); + + Func0> mapFactory = new Func0>() { + @Override + public Map call() { + return new LinkedHashMap() { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > 3; + } + }; + } + }; + + Func1 lengthFunc = new Func1() { + @Override + public Integer call(String t1) { + return t1.length(); + } + }; + Observable> mapped = Observable.create(OperationToMap.toMap(source, lengthFunc, Functions.identity(), mapFactory)); + + Map expected = new LinkedHashMap(); + expected.put(2, "bb"); + expected.put(3, "ccc"); + expected.put(4, "dddd"); + + mapped.subscribe(objectObserver); + + verify(objectObserver, never()).onError(any(Throwable.class)); + verify(objectObserver, times(1)).onNext(expected); + verify(objectObserver, times(1)).onCompleted(); + } + @Test + public void testToMapWithErrorThrowingFactory() { + Observable source = Observable.from("a", "bb", "ccc", "dddd"); + + Func0> mapFactory = new Func0>() { + @Override + public Map call() { + throw new RuntimeException("Forced failure"); + } + }; + + Func1 lengthFunc = new Func1() { + @Override + public Integer call(String t1) { + return t1.length(); + } + }; + Observable> mapped = Observable.create(OperationToMap.toMap(source, lengthFunc, Functions.identity(), mapFactory)); + + Map expected = new LinkedHashMap(); + expected.put(2, "bb"); + expected.put(3, "ccc"); + expected.put(4, "dddd"); + + mapped.subscribe(objectObserver); + + verify(objectObserver, never()).onNext(expected); + verify(objectObserver, never()).onCompleted(); + verify(objectObserver, times(1)).onError(any(Throwable.class)); + } + +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationToMultimapTest.java b/rxjava-core/src/test/java/rx/operators/OperationToMultimapTest.java new file mode 100644 index 0000000000..f715e69c61 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationToMultimapTest.java @@ -0,0 +1,250 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ + +package rx.operators; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import org.junit.Test; +import org.junit.Before; +import org.mockito.*; +import static org.mockito.Mockito.*; +import rx.Observable; +import rx.Observer; +import rx.operators.OperationToMultimap.DefaultMultimapCollectionFactory; +import rx.operators.OperationToMultimap.DefaultToMultimapFactory; +import rx.util.functions.Func0; +import rx.util.functions.Func1; +import rx.util.functions.Functions; +public class OperationToMultimapTest { + @Mock + Observer objectObserver; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + } + Func1 lengthFunc = new Func1() { + @Override + public Integer call(String t1) { + return t1.length(); + } + }; + Func1 duplicate = new Func1() { + @Override + public String call(String t1) { + return t1 + t1; + } + }; + @Test + public void testToMultimap() { + Observable source = Observable.from("a", "b", "cc", "dd"); + + + Observable>> mapped = Observable.create(OperationToMultimap.toMultimap(source, lengthFunc)); + + Map> expected = new HashMap>(); + expected.put(1, Arrays.asList("a", "b")); + expected.put(2, Arrays.asList("cc", "dd")); + + mapped.subscribe(objectObserver); + + verify(objectObserver, never()).onError(any(Throwable.class)); + verify(objectObserver, times(1)).onNext(expected); + verify(objectObserver, times(1)).onCompleted(); + } + @Test + public void testToMultimapWithValueSelector() { + Observable source = Observable.from("a", "b", "cc", "dd"); + + + Observable>> mapped = Observable.create(OperationToMultimap.toMultimap(source, lengthFunc, duplicate)); + + Map> expected = new HashMap>(); + expected.put(1, Arrays.asList("aa", "bb")); + expected.put(2, Arrays.asList("cccc", "dddd")); + + mapped.subscribe(objectObserver); + + verify(objectObserver, never()).onError(any(Throwable.class)); + verify(objectObserver, times(1)).onNext(expected); + verify(objectObserver, times(1)).onCompleted(); + } + @Test + public void testToMultimapWithMapFactory() { + Observable source = Observable.from("a", "b", "cc", "dd", "eee", "fff"); + + Func0>> mapFactory = new Func0>>() { + @Override + public Map> call() { + return new LinkedHashMap>() { + @Override + protected boolean removeEldestEntry(Map.Entry> eldest) { + return size() > 2; + } + }; + } + }; + + Observable>> mapped = Observable.create( + OperationToMultimap.toMultimap(source, + lengthFunc, Functions.identity(), + mapFactory, new DefaultMultimapCollectionFactory())); + + Map> expected = new HashMap>(); + expected.put(2, Arrays.asList("cc", "dd")); + expected.put(3, Arrays.asList("eee", "fff")); + + mapped.subscribe(objectObserver); + + verify(objectObserver, never()).onError(any(Throwable.class)); + verify(objectObserver, times(1)).onNext(expected); + verify(objectObserver, times(1)).onCompleted(); + } + @Test + public void testToMultimapWithCollectionFactory() { + Observable source = Observable.from("cc", "dd", "eee", "eee"); + + Func1> collectionFactory = new Func1>() { + + @Override + public Collection call(Integer t1) { + if (t1 == 2) { + return new ArrayList(); + } else { + return new HashSet(); + } + } + }; + + Observable>> mapped = Observable.create( + OperationToMultimap.toMultimap( + source, lengthFunc, Functions.identity(), + new DefaultToMultimapFactory(), collectionFactory)); + + Map> expected = new HashMap>(); + expected.put(2, Arrays.asList("cc", "dd")); + expected.put(3, new HashSet(Arrays.asList("eee"))); + + mapped.subscribe(objectObserver); + + verify(objectObserver, never()).onError(any(Throwable.class)); + verify(objectObserver, times(1)).onNext(expected); + verify(objectObserver, times(1)).onCompleted(); + } + @Test + public void testToMultimapWithError() { + Observable source = Observable.from("a", "b", "cc", "dd"); + + Func1 lengthFuncErr = new Func1() { + @Override + public Integer call(String t1) { + if ("b".equals(t1)) { + throw new RuntimeException("Forced Failure"); + } + return t1.length(); + } + }; + + Observable>> mapped = Observable.create(OperationToMultimap.toMultimap(source, lengthFuncErr)); + + Map> expected = new HashMap>(); + expected.put(1, Arrays.asList("a", "b")); + expected.put(2, Arrays.asList("cc", "dd")); + + mapped.subscribe(objectObserver); + + verify(objectObserver, times(1)).onError(any(Throwable.class)); + verify(objectObserver, never()).onNext(expected); + verify(objectObserver, never()).onCompleted(); + } + @Test + public void testToMultimapWithErrorInValueSelector() { + Observable source = Observable.from("a", "b", "cc", "dd"); + + Func1 duplicateErr = new Func1() { + @Override + public String call(String t1) { + if ("b".equals(t1)) { + throw new RuntimeException("Forced failure"); + } + return t1 + t1; + } + }; + + Observable>> mapped = Observable.create(OperationToMultimap.toMultimap(source, lengthFunc, duplicateErr)); + + Map> expected = new HashMap>(); + expected.put(1, Arrays.asList("aa", "bb")); + expected.put(2, Arrays.asList("cccc", "dddd")); + + mapped.subscribe(objectObserver); + + verify(objectObserver, times(1)).onError(any(Throwable.class)); + verify(objectObserver, never()).onNext(expected); + verify(objectObserver, never()).onCompleted(); + } + + @Test + public void testToMultimapWithMapThrowingFactory() { + Observable source = Observable.from("a", "b", "cc", "dd", "eee", "fff"); + + Func0>> mapFactory = new Func0>>() { + @Override + public Map> call() { + throw new RuntimeException("Forced failure"); + } + }; + + Observable>> mapped = Observable.create( + OperationToMultimap.toMultimap(source, lengthFunc, Functions.identity(), mapFactory)); + + Map> expected = new HashMap>(); + expected.put(2, Arrays.asList("cc", "dd")); + expected.put(3, Arrays.asList("eee", "fff")); + + mapped.subscribe(objectObserver); + + verify(objectObserver, times(1)).onError(any(Throwable.class)); + verify(objectObserver, never()).onNext(expected); + verify(objectObserver, never()).onCompleted(); + } + @Test + public void testToMultimapWithThrowingCollectionFactory() { + Observable source = Observable.from("cc", "cc", "eee", "eee"); + + Func1> collectionFactory = new Func1>() { + + @Override + public Collection call(Integer t1) { + if (t1 == 2) { + throw new RuntimeException("Forced failure"); + } else { + return new HashSet(); + } + } + }; + + Observable>> mapped = Observable.create( + OperationToMultimap.toMultimap( + source, lengthFunc, Functions.identity(), new DefaultToMultimapFactory(), collectionFactory)); + + Map> expected = new HashMap>(); + expected.put(2, Arrays.asList("cc", "dd")); + expected.put(3, Collections.singleton("eee")); + + mapped.subscribe(objectObserver); + + verify(objectObserver, times(1)).onError(any(Throwable.class)); + verify(objectObserver, never()).onNext(expected); + verify(objectObserver, never()).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationToObservableFutureTest.java b/rxjava-core/src/test/java/rx/operators/OperationToObservableFutureTest.java new file mode 100644 index 0000000000..dcc6efcc6a --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationToObservableFutureTest.java @@ -0,0 +1,63 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Mockito.*; + +import java.util.concurrent.Future; + +import org.junit.Test; + +import rx.Observer; +import rx.Subscription; +import rx.operators.OperationToObservableFuture.ToObservableFuture; + +public class OperationToObservableFutureTest { + + @Test + public void testSuccess() throws Exception { + Future future = mock(Future.class); + Object value = new Object(); + when(future.get()).thenReturn(value); + ToObservableFuture ob = new ToObservableFuture(future); + Observer o = mock(Observer.class); + + Subscription sub = ob.onSubscribe(o); + sub.unsubscribe(); + + verify(o, times(1)).onNext(value); + verify(o, times(1)).onCompleted(); + verify(o, never()).onError(null); + verify(future, never()).cancel(true); + } + + @Test + public void testFailure() throws Exception { + Future future = mock(Future.class); + RuntimeException e = new RuntimeException(); + when(future.get()).thenThrow(e); + ToObservableFuture ob = new ToObservableFuture(future); + Observer o = mock(Observer.class); + + Subscription sub = ob.onSubscribe(o); + sub.unsubscribe(); + + verify(o, never()).onNext(null); + verify(o, never()).onCompleted(); + verify(o, times(1)).onError(e); + verify(future, never()).cancel(true); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationToObservableIterableTest.java b/rxjava-core/src/test/java/rx/operators/OperationToObservableIterableTest.java new file mode 100644 index 0000000000..8d8be93dcb --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationToObservableIterableTest.java @@ -0,0 +1,45 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationToObservableIterable.*; + +import java.util.Arrays; + +import org.junit.Test; +import org.mockito.Mockito; + +import rx.Observable; +import rx.Observer; + +public class OperationToObservableIterableTest { + + @Test + public void testIterable() { + Observable observable = Observable.create(toObservableIterable(Arrays. asList("one", "two", "three"))); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, times(1)).onNext("two"); + verify(aObserver, times(1)).onNext("three"); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationToObservableListTest.java b/rxjava-core/src/test/java/rx/operators/OperationToObservableListTest.java new file mode 100644 index 0000000000..1124ea6211 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationToObservableListTest.java @@ -0,0 +1,69 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationToObservableList.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; +import org.mockito.Mockito; + +import rx.Observable; +import rx.Observer; + +public class OperationToObservableListTest { + + @Test + public void testList() { + Observable w = Observable.from("one", "two", "three"); + Observable> observable = Observable.create(toObservableList(w)); + + @SuppressWarnings("unchecked") + Observer> aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext(Arrays.asList("one", "two", "three")); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testListMultipleObservers() { + Observable w = Observable.from("one", "two", "three"); + Observable> observable = Observable.create(toObservableList(w)); + + @SuppressWarnings("unchecked") + Observer> o1 = mock(Observer.class); + observable.subscribe(o1); + + @SuppressWarnings("unchecked") + Observer> o2 = mock(Observer.class); + observable.subscribe(o2); + + List expected = Arrays.asList("one", "two", "three"); + + verify(o1, times(1)).onNext(expected); + verify(o1, Mockito.never()).onError(any(Throwable.class)); + verify(o1, times(1)).onCompleted(); + + verify(o2, times(1)).onNext(expected); + verify(o2, Mockito.never()).onError(any(Throwable.class)); + verify(o2, times(1)).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationToObservableSortedListTest.java b/rxjava-core/src/test/java/rx/operators/OperationToObservableSortedListTest.java new file mode 100644 index 0000000000..8c572a31b6 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationToObservableSortedListTest.java @@ -0,0 +1,66 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationToObservableSortedList.*; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; +import org.mockito.Mockito; + +import rx.Observable; +import rx.Observer; +import rx.util.functions.Func2; + +public class OperationToObservableSortedListTest { + + @Test + public void testSortedList() { + Observable w = Observable.from(1, 3, 2, 5, 4); + Observable> observable = Observable.create(toSortedList(w)); + + @SuppressWarnings("unchecked") + Observer> aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext(Arrays.asList(1, 2, 3, 4, 5)); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testSortedListWithCustomFunction() { + Observable w = Observable.from(1, 3, 2, 5, 4); + Observable> observable = Observable.create(toSortedList(w, new Func2() { + + @Override + public Integer call(Integer t1, Integer t2) { + return t2 - t1; + } + + })); + + @SuppressWarnings("unchecked") + Observer> aObserver = mock(Observer.class); + observable.subscribe(aObserver); + verify(aObserver, times(1)).onNext(Arrays.asList(5, 4, 3, 2, 1)); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationUsingTest.java b/rxjava-core/src/test/java/rx/operators/OperationUsingTest.java new file mode 100644 index 0000000000..42d31cea9a --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationUsingTest.java @@ -0,0 +1,219 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.fail; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static rx.operators.OperationUsing.using; + +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action0; +import rx.util.functions.Func0; +import rx.util.functions.Func1; + +public class OperationUsingTest { + + @SuppressWarnings("serial") + private static class TestException extends RuntimeException { + } + + private static interface Resource extends Subscription { + public String getTextFromWeb(); + + @Override + public void unsubscribe(); + } + + @Test + public void testUsing() { + final Resource resource = mock(Resource.class); + when(resource.getTextFromWeb()).thenReturn("Hello world!"); + + Func0 resourceFactory = new Func0() { + @Override + public Resource call() { + return resource; + } + }; + + Func1> observableFactory = new Func1>() { + @Override + public Observable call(Resource resource) { + return Observable.from(resource.getTextFromWeb().split(" ")); + } + }; + + @SuppressWarnings("unchecked") + Observer observer = (Observer) mock(Observer.class); + Observable observable = Observable.create(using( + resourceFactory, observableFactory)); + observable.subscribe(observer); + + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext("Hello"); + inOrder.verify(observer, times(1)).onNext("world!"); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + + // The resouce should be closed + verify(resource, times(1)).unsubscribe(); + } + + @Test + public void testUsingWithSubscribingTwice() { + // When subscribe is called, a new resource should be created. + Func0 resourceFactory = new Func0() { + @Override + public Resource call() { + return new Resource() { + + boolean first = true; + + @Override + public String getTextFromWeb() { + if (first) { + first = false; + return "Hello world!"; + } + return "Nothing"; + } + + @Override + public void unsubscribe() { + } + + }; + } + }; + + Func1> observableFactory = new Func1>() { + @Override + public Observable call(Resource resource) { + return Observable.from(resource.getTextFromWeb().split(" ")); + } + }; + + @SuppressWarnings("unchecked") + Observer observer = (Observer) mock(Observer.class); + Observable observable = Observable.create(using( + resourceFactory, observableFactory)); + observable.subscribe(observer); + observable.subscribe(observer); + + InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, times(1)).onNext("Hello"); + inOrder.verify(observer, times(1)).onNext("world!"); + inOrder.verify(observer, times(1)).onCompleted(); + + inOrder.verify(observer, times(1)).onNext("Hello"); + inOrder.verify(observer, times(1)).onNext("world!"); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test(expected = TestException.class) + public void testUsingWithResourceFactoryError() { + Func0 resourceFactory = new Func0() { + @Override + public Subscription call() { + throw new TestException(); + } + }; + + Func1> observableFactory = new Func1>() { + @Override + public Observable call(Subscription subscription) { + return Observable.empty(); + } + }; + + Observable.create(using(resourceFactory, observableFactory)) + .toBlockingObservable().last(); + } + + @Test + public void testUsingWithObservableFactoryError() { + final Action0 unsubscribe = mock(Action0.class); + Func0 resourceFactory = new Func0() { + @Override + public Subscription call() { + return Subscriptions.create(unsubscribe); + } + }; + + Func1> observableFactory = new Func1>() { + @Override + public Observable call(Subscription subscription) { + throw new TestException(); + } + }; + + try { + Observable.create(using(resourceFactory, observableFactory)) + .toBlockingObservable().last(); + fail("Should throw a TestException when the observableFactory throws it"); + } catch (TestException e) { + // Make sure that unsubscribe is called so that users can close + // the resource if some error happens. + verify(unsubscribe, times(1)).call(); + } + } + + @Test + public void testUsingWithObservableFactoryErrorInOnSubscribe() { + final Action0 unsubscribe = mock(Action0.class); + Func0 resourceFactory = new Func0() { + @Override + public Subscription call() { + return Subscriptions.create(unsubscribe); + } + }; + + Func1> observableFactory = new Func1>() { + @Override + public Observable call(Subscription subscription) { + return Observable.create(new OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer t1) { + throw new TestException(); + } + }); + } + }; + + try { + Observable.create(using(resourceFactory, observableFactory)) + .toBlockingObservable().last(); + fail("Should throw a TestException when the observableFactory throws it"); + } catch (TestException e) { + // Make sure that unsubscribe is called so that users can close + // the resource if some error happens. + verify(unsubscribe, times(1)).call(); + } + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationWindowTest.java b/rxjava-core/src/test/java/rx/operators/OperationWindowTest.java new file mode 100644 index 0000000000..b26cf42cae --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationWindowTest.java @@ -0,0 +1,330 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static rx.operators.OperationWindow.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.concurrency.TestScheduler; +import rx.subscriptions.Subscriptions; +import rx.util.Closing; +import rx.util.Closings; +import rx.util.Opening; +import rx.util.Openings; +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Func0; +import rx.util.functions.Func1; + +public class OperationWindowTest { + + private TestScheduler scheduler; + + @Before + public void before() { + scheduler = new TestScheduler(); + } + + private static List> toLists(Observable> observable) { + final List list = new ArrayList(); + final List> lists = new ArrayList>(); + + observable.subscribe(new Action1>() { + @Override + public void call(Observable tObservable) { + tObservable.subscribe(new Action1() { + @Override + public void call(T t) { + list.add(t); + } + }); + lists.add(new ArrayList(list)); + list.clear(); + } + }); + return lists; + } + + @Test + public void testNonOverlappingWindows() { + Observable subject = Observable.from("one", "two", "three", "four", "five"); + Observable> windowed = Observable.create(window(subject, 3)); + + List> windows = toLists(windowed); + + assertEquals(2, windows.size()); + assertEquals(list("one", "two", "three"), windows.get(0)); + assertEquals(list("four", "five"), windows.get(1)); + } + + @Test + public void testSkipAndCountGaplessEindows() { + Observable subject = Observable.from("one", "two", "three", "four", "five"); + Observable> windowed = Observable.create(window(subject, 3, 3)); + + List> windows = toLists(windowed); + + assertEquals(2, windows.size()); + assertEquals(list("one", "two", "three"), windows.get(0)); + assertEquals(list("four", "five"), windows.get(1)); + } + + @Test + public void testOverlappingWindows() { + Observable subject = Observable.from("zero", "one", "two", "three", "four", "five"); + Observable> windowed = Observable.create(window(subject, 3, 1)); + + List> windows = toLists(windowed); + + assertEquals(6, windows.size()); + assertEquals(list("zero", "one", "two"), windows.get(0)); + assertEquals(list("one", "two", "three"), windows.get(1)); + assertEquals(list("two", "three", "four"), windows.get(2)); + assertEquals(list("three", "four", "five"), windows.get(3)); + assertEquals(list("four", "five"), windows.get(4)); + assertEquals(list("five"), windows.get(5)); + } + + @Test + public void testSkipAndCountWindowsWithGaps() { + Observable subject = Observable.from("one", "two", "three", "four", "five"); + Observable> windowed = Observable.create(window(subject, 2, 3)); + + List> windows = toLists(windowed); + + assertEquals(2, windows.size()); + assertEquals(list("one", "two"), windows.get(0)); + assertEquals(list("four", "five"), windows.get(1)); + } + + @Test + public void testTimedAndCount() { + final List list = new ArrayList(); + final List> lists = new ArrayList>(); + + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + push(observer, "one", 10); + push(observer, "two", 90); + push(observer, "three", 110); + push(observer, "four", 190); + push(observer, "five", 210); + complete(observer, 250); + return Subscriptions.empty(); + } + }); + + Observable> windowed = Observable.create(window(source, 100, TimeUnit.MILLISECONDS, 2, scheduler)); + windowed.subscribe(observeWindow(list, lists)); + + scheduler.advanceTimeTo(100, TimeUnit.MILLISECONDS); + assertEquals(1, lists.size()); + assertEquals(lists.get(0), list("one", "two")); + + scheduler.advanceTimeTo(200, TimeUnit.MILLISECONDS); + assertEquals(2, lists.size()); + assertEquals(lists.get(1), list("three", "four")); + + scheduler.advanceTimeTo(300, TimeUnit.MILLISECONDS); + assertEquals(3, lists.size()); + assertEquals(lists.get(2), list("five")); + } + + @Test + public void testTimed() { + final List list = new ArrayList(); + final List> lists = new ArrayList>(); + + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + push(observer, "one", 98); + push(observer, "two", 99); + push(observer, "three", 100); + push(observer, "four", 101); + push(observer, "five", 102); + complete(observer, 150); + return Subscriptions.empty(); + } + }); + + Observable> windowed = Observable.create(window(source, 100, TimeUnit.MILLISECONDS, scheduler)); + windowed.subscribe(observeWindow(list, lists)); + + scheduler.advanceTimeTo(101, TimeUnit.MILLISECONDS); + assertEquals(1, lists.size()); + assertEquals(lists.get(0), list("one", "two", "three")); + + scheduler.advanceTimeTo(201, TimeUnit.MILLISECONDS); + assertEquals(2, lists.size()); + assertEquals(lists.get(1), list("four", "five")); + } + + @Test + public void testObservableBasedOpenerAndCloser() { + final List list = new ArrayList(); + final List> lists = new ArrayList>(); + + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + push(observer, "one", 10); + push(observer, "two", 60); + push(observer, "three", 110); + push(observer, "four", 160); + push(observer, "five", 210); + complete(observer, 500); + return Subscriptions.empty(); + } + }); + + Observable openings = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + push(observer, Openings.create(), 50); + push(observer, Openings.create(), 200); + complete(observer, 250); + return Subscriptions.empty(); + } + }); + + Func1> closer = new Func1>() { + @Override + public Observable call(Opening opening) { + return Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + push(observer, Closings.create(), 100); + complete(observer, 101); + return Subscriptions.empty(); + } + }); + } + }; + + Observable> windowed = Observable.create(window(source, openings, closer)); + windowed.subscribe(observeWindow(list, lists)); + + scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); + assertEquals(2, lists.size()); + assertEquals(lists.get(0), list("two", "three")); + assertEquals(lists.get(1), list("five")); + } + + @Test + public void testObservableBasedCloser() { + final List list = new ArrayList(); + final List> lists = new ArrayList>(); + + Observable source = Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + push(observer, "one", 10); + push(observer, "two", 60); + push(observer, "three", 110); + push(observer, "four", 160); + push(observer, "five", 210); + complete(observer, 250); + return Subscriptions.empty(); + } + }); + + Func0> closer = new Func0>() { + @Override + public Observable call() { + return Observable.create(new Observable.OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer observer) { + push(observer, Closings.create(), 100); + complete(observer, 101); + return Subscriptions.empty(); + } + }); + } + }; + + Observable> windowed = Observable.create(window(source, closer)); + windowed.subscribe(observeWindow(list, lists)); + + scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); + assertEquals(3, lists.size()); + assertEquals(lists.get(0), list("one", "two")); + assertEquals(lists.get(1), list("three", "four")); + assertEquals(lists.get(2), list("five")); + } + + private List list(String... args) { + List list = new ArrayList(); + for (String arg : args) { + list.add(arg); + } + return list; + } + + private void push(final Observer observer, final T value, int delay) { + scheduler.schedule(new Action0() { + @Override + public void call() { + observer.onNext(value); + } + }, delay, TimeUnit.MILLISECONDS); + } + + private void complete(final Observer observer, int delay) { + scheduler.schedule(new Action0() { + @Override + public void call() { + observer.onCompleted(); + } + }, delay, TimeUnit.MILLISECONDS); + } + + private Action1> observeWindow(final List list, final List> lists) { + return new Action1>() { + @Override + public void call(Observable stringObservable) { + stringObservable.subscribe(new Observer() { + @Override + public void onCompleted() { + lists.add(new ArrayList(list)); + list.clear(); + } + + @Override + public void onError(Throwable e) { + fail(e.getMessage()); + } + + @Override + public void onNext(String args) { + list.add(args); + } + }); + } + }; + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperationZipTest.java b/rxjava-core/src/test/java/rx/operators/OperationZipTest.java new file mode 100644 index 0000000000..f3549a6fbd --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperationZipTest.java @@ -0,0 +1,705 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import static rx.operators.OperationZip.*; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.operators.OperationZip.Aggregator; +import rx.operators.OperationZip.ZipObserver; +import rx.subjects.PublishSubject; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Func2; +import rx.util.functions.Func3; +import rx.util.functions.FuncN; +import rx.util.functions.Functions; + +public class OperationZipTest { + + @SuppressWarnings("unchecked") + @Test + public void testCollectionSizeDifferentThanFunction() { + FuncN zipr = Functions.fromFunc(getConcatStringIntegerIntArrayZipr()); + //Func3 + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + + @SuppressWarnings("rawtypes") + Collection ws = java.util.Collections.singleton(Observable.from("one", "two")); + Observable w = Observable.create(zip(ws, zipr)); + w.subscribe(aObserver); + + verify(aObserver, times(1)).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + verify(aObserver, never()).onNext(any(String.class)); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testZippingDifferentLengthObservableSequences1() { + Observer w = mock(Observer.class); + + TestObservable w1 = new TestObservable(); + TestObservable w2 = new TestObservable(); + TestObservable w3 = new TestObservable(); + + Observable zipW = Observable.create(zip(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsZipr())); + zipW.subscribe(w); + + /* simulate sending data */ + // once for w1 + w1.observer.onNext("1a"); + w1.observer.onCompleted(); + // twice for w2 + w2.observer.onNext("2a"); + w2.observer.onNext("2b"); + w2.observer.onCompleted(); + // 4 times for w3 + w3.observer.onNext("3a"); + w3.observer.onNext("3b"); + w3.observer.onNext("3c"); + w3.observer.onNext("3d"); + w3.observer.onCompleted(); + + /* we should have been called 1 time on the Observer */ + InOrder inOrder = inOrder(w); + inOrder.verify(w).onNext("1a2a3a"); + + inOrder.verify(w, times(1)).onCompleted(); + } + + @Test + public void testZippingDifferentLengthObservableSequences2() { + @SuppressWarnings("unchecked") + Observer w = mock(Observer.class); + + TestObservable w1 = new TestObservable(); + TestObservable w2 = new TestObservable(); + TestObservable w3 = new TestObservable(); + + Observable zipW = Observable.create(zip(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsZipr())); + zipW.subscribe(w); + + /* simulate sending data */ + // 4 times for w1 + w1.observer.onNext("1a"); + w1.observer.onNext("1b"); + w1.observer.onNext("1c"); + w1.observer.onNext("1d"); + w1.observer.onCompleted(); + // twice for w2 + w2.observer.onNext("2a"); + w2.observer.onNext("2b"); + w2.observer.onCompleted(); + // 1 times for w3 + w3.observer.onNext("3a"); + w3.observer.onCompleted(); + + /* we should have been called 1 time on the Observer */ + InOrder inOrder = inOrder(w); + inOrder.verify(w).onNext("1a2a3a"); + + inOrder.verify(w, times(1)).onCompleted(); + + } + + /** + * Testing internal private logic due to the complexity so I want to use TDD to test as a I build it rather than relying purely on the overall functionality expected by the public methods. + */ + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregatorSimple() { + FuncN zipr = getConcatZipr(); + /* create the aggregator which will execute the zip function when all Observables provide values */ + Aggregator a = new Aggregator(zipr); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + a.onSubscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + ZipObserver r1 = mock(ZipObserver.class); + ZipObserver r2 = mock(ZipObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "hello"); + a.next(r2, "world"); + + InOrder inOrder = inOrder(aObserver); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + inOrder.verify(aObserver, times(1)).onNext("helloworld"); + + a.next(r1, "hello "); + a.next(r2, "again"); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + inOrder.verify(aObserver, times(1)).onNext("hello again"); + + a.complete(r1); + a.complete(r2); + + inOrder.verify(aObserver, never()).onNext(anyString()); + verify(aObserver, times(1)).onCompleted(); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregatorDifferentSizedResultsWithOnComplete() { + FuncN zipr = getConcatZipr(); + /* create the aggregator which will execute the zip function when all Observables provide values */ + Aggregator a = new Aggregator(zipr); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + a.onSubscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + ZipObserver r1 = mock(ZipObserver.class); + ZipObserver r2 = mock(ZipObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "hello"); + a.next(r2, "world"); + a.complete(r2); + + InOrder inOrder = inOrder(aObserver); + + inOrder.verify(aObserver, never()).onError(any(Throwable.class)); + inOrder.verify(aObserver, never()).onCompleted(); + inOrder.verify(aObserver, times(1)).onNext("helloworld"); + + a.next(r1, "hi"); + a.complete(r1); + + inOrder.verify(aObserver, never()).onError(any(Throwable.class)); + inOrder.verify(aObserver, times(1)).onCompleted(); + inOrder.verify(aObserver, never()).onNext(anyString()); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregateMultipleTypes() { + FuncN zipr = getConcatZipr(); + /* create the aggregator which will execute the zip function when all Observables provide values */ + Aggregator a = new Aggregator(zipr); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + a.onSubscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + ZipObserver r1 = mock(ZipObserver.class); + ZipObserver r2 = mock(ZipObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "hello"); + a.next(r2, "world"); + a.complete(r2); + + InOrder inOrder = inOrder(aObserver); + + inOrder.verify(aObserver, never()).onError(any(Throwable.class)); + inOrder.verify(aObserver, never()).onCompleted(); + inOrder.verify(aObserver, times(1)).onNext("helloworld"); + + a.next(r1, "hi"); + a.complete(r1); + + inOrder.verify(aObserver, never()).onError(any(Throwable.class)); + inOrder.verify(aObserver, times(1)).onCompleted(); + inOrder.verify(aObserver, never()).onNext(anyString()); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregate3Types() { + FuncN zipr = getConcatZipr(); + /* create the aggregator which will execute the zip function when all Observables provide values */ + Aggregator a = new Aggregator(zipr); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + a.onSubscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + ZipObserver r1 = mock(ZipObserver.class); + ZipObserver r2 = mock(ZipObserver.class); + ZipObserver r3 = mock(ZipObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + a.addObserver(r3); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "hello"); + a.next(r2, 2); + a.next(r3, new int[] { 5, 6, 7 }); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + verify(aObserver, times(1)).onNext("hello2[5, 6, 7]"); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregatorsWithDifferentSizesAndTiming() { + FuncN zipr = getConcatZipr(); + /* create the aggregator which will execute the zip function when all Observables provide values */ + Aggregator a = new Aggregator(zipr); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + a.onSubscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + ZipObserver r1 = mock(ZipObserver.class); + ZipObserver r2 = mock(ZipObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "one"); + a.next(r1, "two"); + a.next(r1, "three"); + a.next(r2, "A"); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + verify(aObserver, times(1)).onNext("oneA"); + + a.next(r1, "four"); + a.complete(r1); + a.next(r2, "B"); + verify(aObserver, times(1)).onNext("twoB"); + a.next(r2, "C"); + verify(aObserver, times(1)).onNext("threeC"); + a.next(r2, "D"); + verify(aObserver, times(1)).onNext("fourD"); + a.next(r2, "E"); + verify(aObserver, never()).onNext("E"); + a.complete(r2); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregatorError() { + FuncN zipr = getConcatZipr(); + /* create the aggregator which will execute the zip function when all Observables provide values */ + Aggregator a = new Aggregator(zipr); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + a.onSubscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + ZipObserver r1 = mock(ZipObserver.class); + ZipObserver r2 = mock(ZipObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "hello"); + a.next(r2, "world"); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + verify(aObserver, times(1)).onNext("helloworld"); + + a.error(r1, new RuntimeException("")); + a.next(r1, "hello"); + a.next(r2, "again"); + + verify(aObserver, times(1)).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + // we don't want to be called again after an error + verify(aObserver, times(0)).onNext("helloagain"); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregatorUnsubscribe() { + FuncN zipr = getConcatZipr(); + /* create the aggregator which will execute the zip function when all Observables provide values */ + Aggregator a = new Aggregator(zipr); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + Subscription subscription = a.onSubscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + ZipObserver r1 = mock(ZipObserver.class); + ZipObserver r2 = mock(ZipObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "hello"); + a.next(r2, "world"); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + verify(aObserver, times(1)).onNext("helloworld"); + + subscription.unsubscribe(); + a.next(r1, "hello"); + a.next(r2, "again"); + + verify(aObserver, times(0)).onError(any(Throwable.class)); + verify(aObserver, never()).onCompleted(); + // we don't want to be called again after an error + verify(aObserver, times(0)).onNext("helloagain"); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testAggregatorEarlyCompletion() { + FuncN zipr = getConcatZipr(); + /* create the aggregator which will execute the zip function when all Observables provide values */ + Aggregator a = new Aggregator(zipr); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + a.onSubscribe(aObserver); + + /* mock the Observable Observers that are 'pushing' data for us */ + ZipObserver r1 = mock(ZipObserver.class); + ZipObserver r2 = mock(ZipObserver.class); + + /* pretend we're starting up */ + a.addObserver(r1); + a.addObserver(r2); + + /* simulate the Observables pushing data into the aggregator */ + a.next(r1, "one"); + a.next(r1, "two"); + a.complete(r1); + a.next(r2, "A"); + + InOrder inOrder = inOrder(aObserver); + + inOrder.verify(aObserver, never()).onError(any(Throwable.class)); + inOrder.verify(aObserver, never()).onCompleted(); + inOrder.verify(aObserver, times(1)).onNext("oneA"); + + a.complete(r2); + + inOrder.verify(aObserver, never()).onError(any(Throwable.class)); + inOrder.verify(aObserver, times(1)).onCompleted(); + inOrder.verify(aObserver, never()).onNext(anyString()); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testZip2Types() { + Func2 zipr = getConcatStringIntegerZipr(); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + + Observable w = Observable.create(zip(Observable.from("one", "two"), Observable.from(2, 3, 4), zipr)); + w.subscribe(aObserver); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verify(aObserver, times(1)).onNext("one2"); + verify(aObserver, times(1)).onNext("two3"); + verify(aObserver, never()).onNext("4"); + } + + @SuppressWarnings("unchecked") + /* mock calls don't do generics */ + @Test + public void testZip3Types() { + Func3 zipr = getConcatStringIntegerIntArrayZipr(); + + /* define a Observer to receive aggregated events */ + Observer aObserver = mock(Observer.class); + + Observable w = Observable.create(zip(Observable.from("one", "two"), Observable.from(2), Observable.from(new int[] { 4, 5, 6 }), zipr)); + w.subscribe(aObserver); + + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + verify(aObserver, times(1)).onNext("one2[4, 5, 6]"); + verify(aObserver, never()).onNext("two"); + } + + @Test + public void testOnNextExceptionInvokesOnError() { + Func2 zipr = getDivideZipr(); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + + Observable w = Observable.create(zip(Observable.from(10, 20, 30), Observable.from(0, 1, 2), zipr)); + w.subscribe(aObserver); + + verify(aObserver, times(1)).onError(any(Throwable.class)); + } + + @Test + public void testOnFirstCompletion() { + PublishSubject oA = PublishSubject.create(); + PublishSubject oB = PublishSubject.create(); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + + Observable o = Observable.create(zip(oA, oB, getConcat2Strings())); + o.subscribe(observer); + + InOrder inOrder = inOrder(observer); + + oA.onNext("a1"); + inOrder.verify(observer, never()).onNext(anyString()); + oB.onNext("b1"); + inOrder.verify(observer, times(1)).onNext("a1-b1"); + oB.onNext("b2"); + inOrder.verify(observer, never()).onNext(anyString()); + oA.onNext("a2"); + inOrder.verify(observer, times(1)).onNext("a2-b2"); + + oA.onNext("a3"); + oA.onNext("a4"); + oA.onNext("a5"); + oA.onCompleted(); + + // SHOULD ONCOMPLETE BE EMITTED HERE INSTEAD OF WAITING + // FOR B3, B4, B5 TO BE EMITTED? + + oB.onNext("b3"); + oB.onNext("b4"); + oB.onNext("b5"); + + inOrder.verify(observer, times(1)).onNext("a3-b3"); + inOrder.verify(observer, times(1)).onNext("a4-b4"); + inOrder.verify(observer, times(1)).onNext("a5-b5"); + + // WE RECEIVE THE ONCOMPLETE HERE + inOrder.verify(observer, times(1)).onCompleted(); + + oB.onNext("b6"); + oB.onNext("b7"); + oB.onNext("b8"); + oB.onNext("b9"); + // never completes (infinite stream for example) + + // we should receive nothing else despite oB continuing after oA completed + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testOnErrorTermination() { + PublishSubject oA = PublishSubject.create(); + PublishSubject oB = PublishSubject.create(); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + + Observable o = Observable.create(zip(oA, oB, getConcat2Strings())); + o.subscribe(observer); + + InOrder inOrder = inOrder(observer); + + oA.onNext("a1"); + inOrder.verify(observer, never()).onNext(anyString()); + oB.onNext("b1"); + inOrder.verify(observer, times(1)).onNext("a1-b1"); + oB.onNext("b2"); + inOrder.verify(observer, never()).onNext(anyString()); + oA.onNext("a2"); + inOrder.verify(observer, times(1)).onNext("a2-b2"); + + oA.onNext("a3"); + oA.onNext("a4"); + oA.onNext("a5"); + oA.onError(new RuntimeException("forced failure")); + + // it should emit failure immediately + inOrder.verify(observer, times(1)).onError(any(RuntimeException.class)); + + oB.onNext("b3"); + oB.onNext("b4"); + oB.onNext("b5"); + oB.onNext("b6"); + oB.onNext("b7"); + oB.onNext("b8"); + oB.onNext("b9"); + // never completes (infinite stream for example) + + // we should receive nothing else despite oB continuing after oA completed + inOrder.verifyNoMoreInteractions(); + } + + private Func2 getConcat2Strings() { + return new Func2() { + + @Override + public String call(String t1, String t2) { + return t1 + "-" + t2; + } + }; + } + + + + private Func2 getDivideZipr() { + Func2 zipr = new Func2() { + + @Override + public Integer call(Integer i1, Integer i2) { + return i1 / i2; + } + + }; + return zipr; + } + + private Func3 getConcat3StringsZipr() { + Func3 zipr = new Func3() { + + @Override + public String call(String a1, String a2, String a3) { + if (a1 == null) { + a1 = ""; + } + if (a2 == null) { + a2 = ""; + } + if (a3 == null) { + a3 = ""; + } + return a1 + a2 + a3; + } + + }; + return zipr; + } + + private FuncN getConcatZipr() { + FuncN zipr = new FuncN() { + + @Override + public String call(Object... args) { + String returnValue = ""; + for (Object o : args) { + if (o != null) { + returnValue += getStringValue(o); + } + } + System.out.println("returning: " + returnValue); + return returnValue; + } + + }; + return zipr; + } + + private Func2 getConcatStringIntegerZipr() { + Func2 zipr = new Func2() { + + @Override + public String call(String s, Integer i) { + return getStringValue(s) + getStringValue(i); + } + + }; + return zipr; + } + + private Func3 getConcatStringIntegerIntArrayZipr() { + Func3 zipr = new Func3() { + + @Override + public String call(String s, Integer i, int[] iArray) { + return getStringValue(s) + getStringValue(i) + getStringValue(iArray); + } + + }; + return zipr; + } + + private static String getStringValue(Object o) { + if (o == null) { + return ""; + } else { + if (o instanceof int[]) { + return Arrays.toString((int[]) o); + } else { + return String.valueOf(o); + } + } + } + + private static class TestObservable implements Observable.OnSubscribeFunc { + + Observer observer; + + @Override + public Subscription onSubscribe(Observer Observer) { + // just store the variable where it can be accessed so we can manually trigger it + this.observer = Observer; + return Subscriptions.empty(); + } + + } +} diff --git a/rxjava-core/src/test/java/rx/operators/OperatorTesterTest.java b/rxjava-core/src/test/java/rx/operators/OperatorTesterTest.java new file mode 100644 index 0000000000..645cea8e3b --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/OperatorTesterTest.java @@ -0,0 +1,22 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import org.junit.Ignore; + +@Ignore("WIP") +public class OperatorTesterTest { +} diff --git a/rxjava-core/src/test/java/rx/operators/SafeObservableSubscriptionTest.java b/rxjava-core/src/test/java/rx/operators/SafeObservableSubscriptionTest.java new file mode 100644 index 0000000000..bb317884bc --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/SafeObservableSubscriptionTest.java @@ -0,0 +1,34 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.mockito.Mockito.*; + +import org.junit.Test; + +import rx.Subscription; + +public class SafeObservableSubscriptionTest { + + @Test + public void testWrapAfterUnsubscribe() { + SafeObservableSubscription atomicObservableSubscription = new SafeObservableSubscription(); + atomicObservableSubscription.unsubscribe(); + Subscription innerSubscription = mock(Subscription.class); + atomicObservableSubscription.wrap(innerSubscription); + verify(innerSubscription, times(1)).unsubscribe(); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/SynchronizedObserverTest.java b/rxjava-core/src/test/java/rx/operators/SynchronizedObserverTest.java new file mode 100644 index 0000000000..f1c40c3674 --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/SynchronizedObserverTest.java @@ -0,0 +1,772 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; + +public class SynchronizedObserverTest { + + @Mock + Observer aObserver; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testSingleThreadedBasic() { + Subscription s = mock(Subscription.class); + TestSingleThreadedObservable onSubscribe = new TestSingleThreadedObservable(s, "one", "two", "three"); + Observable w = Observable.create(onSubscribe); + + SafeObservableSubscription as = new SafeObservableSubscription(s); + SynchronizedObserver aw = new SynchronizedObserver(aObserver, as); + + w.subscribe(aw); + onSubscribe.waitToFinish(); + + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, times(1)).onNext("two"); + verify(aObserver, times(1)).onNext("three"); + verify(aObserver, never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + // non-deterministic because unsubscribe happens after 'waitToFinish' releases + // so commenting out for now as this is not a critical thing to test here + // verify(s, times(1)).unsubscribe(); + } + + @Test + public void testMultiThreadedBasic() { + Subscription s = mock(Subscription.class); + TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable(s, "one", "two", "three"); + Observable w = Observable.create(onSubscribe); + + SafeObservableSubscription as = new SafeObservableSubscription(s); + BusyObserver busyObserver = new BusyObserver(); + SynchronizedObserver aw = new SynchronizedObserver(busyObserver, as); + + w.subscribe(aw); + onSubscribe.waitToFinish(); + + assertEquals(3, busyObserver.onNextCount.get()); + assertFalse(busyObserver.onError); + assertTrue(busyObserver.onCompleted); + // non-deterministic because unsubscribe happens after 'waitToFinish' releases + // so commenting out for now as this is not a critical thing to test here + // verify(s, times(1)).unsubscribe(); + + // we can have concurrency ... + assertTrue(onSubscribe.maxConcurrentThreads.get() > 1); + // ... but the onNext execution should be single threaded + assertEquals(1, busyObserver.maxConcurrentThreads.get()); + } + + @Test + public void testMultiThreadedBasicWithLock() { + Subscription s = mock(Subscription.class); + TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable(s, "one", "two", "three"); + Observable w = Observable.create(onSubscribe); + + SafeObservableSubscription as = new SafeObservableSubscription(s); + BusyObserver busyObserver = new BusyObserver(); + + Object lock = new Object(); + ExternalBusyThread externalBusyThread = new ExternalBusyThread(busyObserver, lock, 10, 100); + + SynchronizedObserver aw = new SynchronizedObserver(busyObserver, as, lock); + + externalBusyThread.start(); + + w.subscribe(aw); + onSubscribe.waitToFinish(); + + try { + externalBusyThread.join(10000); + assertFalse(externalBusyThread.isAlive()); + assertFalse(externalBusyThread.fail); + } catch (InterruptedException e) { + // ignore + } + + assertEquals(3, busyObserver.onNextCount.get()); + assertFalse(busyObserver.onError); + assertTrue(busyObserver.onCompleted); + // non-deterministic because unsubscribe happens after 'waitToFinish' releases + // so commenting out for now as this is not a critical thing to test here + // verify(s, times(1)).unsubscribe(); + + // we can have concurrency ... + assertTrue(onSubscribe.maxConcurrentThreads.get() > 1); + // ... but the onNext execution should be single threaded + assertEquals(1, busyObserver.maxConcurrentThreads.get()); + } + + @Test + public void testMultiThreadedWithNPE() { + Subscription s = mock(Subscription.class); + TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable(s, "one", "two", "three", null); + Observable w = Observable.create(onSubscribe); + + SafeObservableSubscription as = new SafeObservableSubscription(s); + BusyObserver busyObserver = new BusyObserver(); + SynchronizedObserver aw = new SynchronizedObserver(busyObserver, as); + + w.subscribe(aw); + onSubscribe.waitToFinish(); + + System.out.println("maxConcurrentThreads: " + onSubscribe.maxConcurrentThreads.get()); + + // we can't know how many onNext calls will occur since they each run on a separate thread + // that depends on thread scheduling so 0, 1, 2 and 3 are all valid options + // assertEquals(3, busyObserver.onNextCount.get()); + assertTrue(busyObserver.onNextCount.get() < 4); + assertTrue(busyObserver.onError); + // no onCompleted because onError was invoked + assertFalse(busyObserver.onCompleted); + // non-deterministic because unsubscribe happens after 'waitToFinish' releases + // so commenting out for now as this is not a critical thing to test here + //verify(s, times(1)).unsubscribe(); + + // we can have concurrency ... + assertTrue(onSubscribe.maxConcurrentThreads.get() > 1); + // ... but the onNext execution should be single threaded + assertEquals(1, busyObserver.maxConcurrentThreads.get()); + } + + @Test + public void testMultiThreadedWithNPEAndLock() { + Subscription s = mock(Subscription.class); + TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable(s, "one", "two", "three", null); + Observable w = Observable.create(onSubscribe); + + SafeObservableSubscription as = new SafeObservableSubscription(s); + BusyObserver busyObserver = new BusyObserver(); + + Object lock = new Object(); + ExternalBusyThread externalBusyThread = new ExternalBusyThread(busyObserver, lock, 10, 100); + + SynchronizedObserver aw = new SynchronizedObserver(busyObserver, as, lock); + + externalBusyThread.start(); + + w.subscribe(aw); + onSubscribe.waitToFinish(); + + try { + externalBusyThread.join(10000); + assertFalse(externalBusyThread.isAlive()); + assertFalse(externalBusyThread.fail); + } catch (InterruptedException e) { + // ignore + } + + System.out.println("maxConcurrentThreads: " + onSubscribe.maxConcurrentThreads.get()); + + // we can't know how many onNext calls will occur since they each run on a separate thread + // that depends on thread scheduling so 0, 1, 2 and 3 are all valid options + // assertEquals(3, busyObserver.onNextCount.get()); + assertTrue(busyObserver.onNextCount.get() < 4); + assertTrue(busyObserver.onError); + // no onCompleted because onError was invoked + assertFalse(busyObserver.onCompleted); + // non-deterministic because unsubscribe happens after 'waitToFinish' releases + // so commenting out for now as this is not a critical thing to test here + //verify(s, times(1)).unsubscribe(); + + // we can have concurrency ... + assertTrue(onSubscribe.maxConcurrentThreads.get() > 1); + // ... but the onNext execution should be single threaded + assertEquals(1, busyObserver.maxConcurrentThreads.get()); + } + + @Test + public void testMultiThreadedWithNPEinMiddle() { + Subscription s = mock(Subscription.class); + TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable(s, "one", "two", "three", null, "four", "five", "six", "seven", "eight", "nine"); + Observable w = Observable.create(onSubscribe); + + SafeObservableSubscription as = new SafeObservableSubscription(s); + BusyObserver busyObserver = new BusyObserver(); + SynchronizedObserver aw = new SynchronizedObserver(busyObserver, as); + + w.subscribe(aw); + onSubscribe.waitToFinish(); + + System.out.println("maxConcurrentThreads: " + onSubscribe.maxConcurrentThreads.get()); + // this should not be the full number of items since the error should stop it before it completes all 9 + System.out.println("onNext count: " + busyObserver.onNextCount.get()); + assertTrue(busyObserver.onNextCount.get() < 9); + assertTrue(busyObserver.onError); + // no onCompleted because onError was invoked + assertFalse(busyObserver.onCompleted); + // non-deterministic because unsubscribe happens after 'waitToFinish' releases + // so commenting out for now as this is not a critical thing to test here + // verify(s, times(1)).unsubscribe(); + + // we can have concurrency ... + assertTrue(onSubscribe.maxConcurrentThreads.get() > 1); + // ... but the onNext execution should be single threaded + assertEquals(1, busyObserver.maxConcurrentThreads.get()); + } + + @Test + public void testMultiThreadedWithNPEinMiddleAndLock() { + Subscription s = mock(Subscription.class); + TestMultiThreadedObservable onSubscribe = new TestMultiThreadedObservable(s, "one", "two", "three", null, "four", "five", "six", "seven", "eight", "nine"); + Observable w = Observable.create(onSubscribe); + + SafeObservableSubscription as = new SafeObservableSubscription(s); + BusyObserver busyObserver = new BusyObserver(); + + Object lock = new Object(); + ExternalBusyThread externalBusyThread = new ExternalBusyThread(busyObserver, lock, 10, 100); + + SynchronizedObserver aw = new SynchronizedObserver(busyObserver, as, lock); + + externalBusyThread.start(); + + w.subscribe(aw); + onSubscribe.waitToFinish(); + + try { + externalBusyThread.join(10000); + assertFalse(externalBusyThread.isAlive()); + assertFalse(externalBusyThread.fail); + } catch (InterruptedException e) { + // ignore + } + + System.out.println("maxConcurrentThreads: " + onSubscribe.maxConcurrentThreads.get()); + // this should not be the full number of items since the error should stop it before it completes all 9 + System.out.println("onNext count: " + busyObserver.onNextCount.get()); + assertTrue(busyObserver.onNextCount.get() < 9); + assertTrue(busyObserver.onError); + // no onCompleted because onError was invoked + assertFalse(busyObserver.onCompleted); + // non-deterministic because unsubscribe happens after 'waitToFinish' releases + // so commenting out for now as this is not a critical thing to test here + // verify(s, times(1)).unsubscribe(); + + // we can have concurrency ... + assertTrue(onSubscribe.maxConcurrentThreads.get() > 1); + // ... but the onNext execution should be single threaded + assertEquals(1, busyObserver.maxConcurrentThreads.get()); + } + + /** + * A non-realistic use case that tries to expose thread-safety issues by throwing lots of out-of-order + * events on many threads. + * + * @param w + * @param tw + */ + @Test + public void runConcurrencyTest() { + ExecutorService tp = Executors.newFixedThreadPool(20); + try { + TestConcurrencyObserver tw = new TestConcurrencyObserver(); + SafeObservableSubscription s = new SafeObservableSubscription(); + SynchronizedObserver w = new SynchronizedObserver(tw, s); + + Future f1 = tp.submit(new OnNextThread(w, 12000)); + Future f2 = tp.submit(new OnNextThread(w, 5000)); + Future f3 = tp.submit(new OnNextThread(w, 75000)); + Future f4 = tp.submit(new OnNextThread(w, 13500)); + Future f5 = tp.submit(new OnNextThread(w, 22000)); + Future f6 = tp.submit(new OnNextThread(w, 15000)); + Future f7 = tp.submit(new OnNextThread(w, 7500)); + Future f8 = tp.submit(new OnNextThread(w, 23500)); + + Future f10 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onCompleted, f1, f2, f3, f4)); + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // ignore + } + Future f11 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onCompleted, f4, f6, f7)); + Future f12 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onCompleted, f4, f6, f7)); + Future f13 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onCompleted, f4, f6, f7)); + Future f14 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onCompleted, f4, f6, f7)); + // // the next 4 onError events should wait on same as f10 + Future f15 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onError, f1, f2, f3, f4)); + Future f16 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onError, f1, f2, f3, f4)); + Future f17 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onError, f1, f2, f3, f4)); + Future f18 = tp.submit(new CompletionThread(w, TestConcurrencyObserverEvent.onError, f1, f2, f3, f4)); + + waitOnThreads(f1, f2, f3, f4, f5, f6, f7, f8, f10, f11, f12, f13, f14, f15, f16, f17, f18); + @SuppressWarnings("unused") + int numNextEvents = tw.assertEvents(null); // no check of type since we don't want to test barging results here, just interleaving behavior + // System.out.println("Number of events executed: " + numNextEvents); + } catch (Throwable e) { + fail("Concurrency test failed: " + e.getMessage()); + e.printStackTrace(); + } finally { + tp.shutdown(); + try { + tp.awaitTermination(5000, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + private static void waitOnThreads(Future... futures) { + for (Future f : futures) { + try { + f.get(10, TimeUnit.SECONDS); + } catch (Throwable e) { + System.err.println("Failed while waiting on future."); + e.printStackTrace(); + } + } + } + + /** + * A thread that will pass data to onNext + */ + public static class OnNextThread implements Runnable { + + private final Observer Observer; + private final int numStringsToSend; + + OnNextThread(Observer Observer, int numStringsToSend) { + this.Observer = Observer; + this.numStringsToSend = numStringsToSend; + } + + @Override + public void run() { + for (int i = 0; i < numStringsToSend; i++) { + Observer.onNext("aString"); + } + } + } + + /** + * A thread that will call onError or onNext + */ + public static class CompletionThread implements Runnable { + + private final Observer Observer; + private final TestConcurrencyObserverEvent event; + private final Future[] waitOnThese; + + CompletionThread(Observer Observer, TestConcurrencyObserverEvent event, Future... waitOnThese) { + this.Observer = Observer; + this.event = event; + this.waitOnThese = waitOnThese; + } + + @Override + public void run() { + /* if we have 'waitOnThese' futures, we'll wait on them before proceeding */ + if (waitOnThese != null) { + for (Future f : waitOnThese) { + try { + f.get(); + } catch (Throwable e) { + System.err.println("Error while waiting on future in CompletionThread"); + } + } + } + + /* send the event */ + if (event == TestConcurrencyObserverEvent.onError) { + Observer.onError(new RuntimeException("mocked exception")); + } else if (event == TestConcurrencyObserverEvent.onCompleted) { + Observer.onCompleted(); + + } else { + throw new IllegalArgumentException("Expecting either onError or onCompleted"); + } + } + } + + private static enum TestConcurrencyObserverEvent { + onCompleted, onError, onNext + } + + private static class TestConcurrencyObserver implements Observer { + + /** + * used to store the order and number of events received + */ + private final LinkedBlockingQueue events = new LinkedBlockingQueue(); + private final int waitTime; + + @SuppressWarnings("unused") + public TestConcurrencyObserver(int waitTimeInNext) { + this.waitTime = waitTimeInNext; + } + + public TestConcurrencyObserver() { + this.waitTime = 0; + } + + @Override + public void onCompleted() { + events.add(TestConcurrencyObserverEvent.onCompleted); + } + + @Override + public void onError(Throwable e) { + events.add(TestConcurrencyObserverEvent.onError); + } + + @Override + public void onNext(String args) { + events.add(TestConcurrencyObserverEvent.onNext); + // do some artificial work to make the thread scheduling/timing vary + int s = 0; + for (int i = 0; i < 20; i++) { + s += s * i; + } + + if (waitTime > 0) { + try { + Thread.sleep(waitTime); + } catch (InterruptedException e) { + // ignore + } + } + } + + /** + * Assert the order of events is correct and return the number of onNext executions. + * + * @param expectedEndingEvent + * @return int count of onNext calls + * @throws IllegalStateException + * If order of events was invalid. + */ + public int assertEvents(TestConcurrencyObserverEvent expectedEndingEvent) throws IllegalStateException { + int nextCount = 0; + boolean finished = false; + for (TestConcurrencyObserverEvent e : events) { + if (e == TestConcurrencyObserverEvent.onNext) { + if (finished) { + // already finished, we shouldn't get this again + throw new IllegalStateException("Received onNext but we're already finished."); + } + nextCount++; + } else if (e == TestConcurrencyObserverEvent.onError) { + if (finished) { + // already finished, we shouldn't get this again + throw new IllegalStateException("Received onError but we're already finished."); + } + if (expectedEndingEvent != null && TestConcurrencyObserverEvent.onError != expectedEndingEvent) { + throw new IllegalStateException("Received onError ending event but expected " + expectedEndingEvent); + } + finished = true; + } else if (e == TestConcurrencyObserverEvent.onCompleted) { + if (finished) { + // already finished, we shouldn't get this again + throw new IllegalStateException("Received onCompleted but we're already finished."); + } + if (expectedEndingEvent != null && TestConcurrencyObserverEvent.onCompleted != expectedEndingEvent) { + throw new IllegalStateException("Received onCompleted ending event but expected " + expectedEndingEvent); + } + finished = true; + } + } + + return nextCount; + } + + } + + /** + * This spawns a single thread for the subscribe execution + */ + private static class TestSingleThreadedObservable implements Observable.OnSubscribeFunc { + + final Subscription s; + final String[] values; + private Thread t = null; + + public TestSingleThreadedObservable(final Subscription s, final String... values) { + this.s = s; + this.values = values; + + } + + public Subscription onSubscribe(final Observer observer) { + System.out.println("TestSingleThreadedObservable subscribed to ..."); + t = new Thread(new Runnable() { + + @Override + public void run() { + try { + System.out.println("running TestSingleThreadedObservable thread"); + for (String s : values) { + System.out.println("TestSingleThreadedObservable onNext: " + s); + observer.onNext(s); + } + observer.onCompleted(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + }); + System.out.println("starting TestSingleThreadedObservable thread"); + t.start(); + System.out.println("done starting TestSingleThreadedObservable thread"); + return s; + } + + public void waitToFinish() { + try { + t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + } + + /** + * This spawns a thread for the subscription, then a separate thread for each onNext call. + */ + private static class TestMultiThreadedObservable implements Observable.OnSubscribeFunc { + + final Subscription s; + final String[] values; + Thread t = null; + AtomicInteger threadsRunning = new AtomicInteger(); + AtomicInteger maxConcurrentThreads = new AtomicInteger(); + ExecutorService threadPool; + + public TestMultiThreadedObservable(Subscription s, String... values) { + this.s = s; + this.values = values; + this.threadPool = Executors.newCachedThreadPool(); + } + + @Override + public Subscription onSubscribe(final Observer observer) { + System.out.println("TestMultiThreadedObservable subscribed to ..."); + t = new Thread(new Runnable() { + + @Override + public void run() { + try { + System.out.println("running TestMultiThreadedObservable thread"); + for (final String s : values) { + threadPool.execute(new Runnable() { + + @Override + public void run() { + threadsRunning.incrementAndGet(); + try { + // perform onNext call + System.out.println("TestMultiThreadedObservable onNext: " + s); + if (s == null) { + // force an error + throw new NullPointerException(); + } + observer.onNext(s); + // capture 'maxThreads' + int concurrentThreads = threadsRunning.get(); + int maxThreads = maxConcurrentThreads.get(); + if (concurrentThreads > maxThreads) { + maxConcurrentThreads.compareAndSet(maxThreads, concurrentThreads); + } + } catch (Throwable e) { + observer.onError(e); + } finally { + threadsRunning.decrementAndGet(); + } + } + }); + } + // we are done spawning threads + threadPool.shutdown(); + } catch (Throwable e) { + throw new RuntimeException(e); + } + + // wait until all threads are done, then mark it as COMPLETED + try { + // wait for all the threads to finish + threadPool.awaitTermination(2, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + observer.onCompleted(); + } + }); + System.out.println("starting TestMultiThreadedObservable thread"); + t.start(); + System.out.println("done starting TestMultiThreadedObservable thread"); + return s; + } + + public void waitToFinish() { + try { + t.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + + private static class BusyObserver implements Observer { + volatile boolean onCompleted = false; + volatile boolean onError = false; + AtomicInteger onNextCount = new AtomicInteger(); + AtomicInteger threadsRunning = new AtomicInteger(); + AtomicInteger maxConcurrentThreads = new AtomicInteger(); + + @Override + public void onCompleted() { + threadsRunning.incrementAndGet(); + + System.out.println(">>> BusyObserver received onCompleted"); + onCompleted = true; + + int concurrentThreads = threadsRunning.get(); + int maxThreads = maxConcurrentThreads.get(); + if (concurrentThreads > maxThreads) { + maxConcurrentThreads.compareAndSet(maxThreads, concurrentThreads); + } + threadsRunning.decrementAndGet(); + } + + @Override + public void onError(Throwable e) { + threadsRunning.incrementAndGet(); + + System.out.println(">>> BusyObserver received onError: " + e.getMessage()); + onError = true; + + int concurrentThreads = threadsRunning.get(); + int maxThreads = maxConcurrentThreads.get(); + if (concurrentThreads > maxThreads) { + maxConcurrentThreads.compareAndSet(maxThreads, concurrentThreads); + } + threadsRunning.decrementAndGet(); + } + + @Override + public void onNext(String args) { + threadsRunning.incrementAndGet(); + try { + onNextCount.incrementAndGet(); + System.out.println(">>> BusyObserver received onNext: " + args); + try { + // simulate doing something computational + Thread.sleep(200); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } finally { + // capture 'maxThreads' + int concurrentThreads = threadsRunning.get(); + int maxThreads = maxConcurrentThreads.get(); + if (concurrentThreads > maxThreads) { + maxConcurrentThreads.compareAndSet(maxThreads, concurrentThreads); + } + threadsRunning.decrementAndGet(); + } + } + + } + + private static class ExternalBusyThread extends Thread { + + private BusyObserver observer; + private Object lock; + private int lockTimes; + private int waitTime; + public volatile boolean fail; + + public ExternalBusyThread(BusyObserver observer, Object lock, int lockTimes, int waitTime) { + this.observer = observer; + this.lock = lock; + this.lockTimes = lockTimes; + this.waitTime = waitTime; + this.fail = false; + } + + @Override + public void run() { + Random r = new Random(); + for (int i = 0; i < lockTimes; i++) { + synchronized (lock) { + int oldOnNextCount = observer.onNextCount.get(); + boolean oldOnCompleted = observer.onCompleted; + boolean oldOnError = observer.onError; + try { + Thread.sleep(r.nextInt(waitTime)); + } catch (InterruptedException e) { + // ignore + } + // Since we own the lock, onNextCount, onCompleted and + // onError must not be changed. + int newOnNextCount = observer.onNextCount.get(); + boolean newOnCompleted = observer.onCompleted; + boolean newOnError = observer.onError; + if (oldOnNextCount != newOnNextCount) { + System.out.println(">>> ExternalBusyThread received different onNextCount: " + + oldOnNextCount + + " -> " + + newOnNextCount); + fail = true; + break; + } + if (oldOnCompleted != newOnCompleted) { + System.out.println(">>> ExternalBusyThread received different onCompleted: " + + oldOnCompleted + + " -> " + + newOnCompleted); + fail = true; + break; + } + if (oldOnError != newOnError) { + System.out.println(">>> ExternalBusyThread received different onError: " + + oldOnError + + " -> " + + newOnError); + fail = true; + break; + } + } + } + } + + } +} diff --git a/rxjava-core/src/test/java/rx/operators/TakeWhileTest.java b/rxjava-core/src/test/java/rx/operators/TakeWhileTest.java new file mode 100644 index 0000000000..25e17ba13b --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/TakeWhileTest.java @@ -0,0 +1,22 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import org.junit.Ignore; + +@Ignore("WIP") +public class TakeWhileTest { +} diff --git a/rxjava-core/src/test/java/rx/operators/TimeIntervalObserverTest.java b/rxjava-core/src/test/java/rx/operators/TimeIntervalObserverTest.java new file mode 100644 index 0000000000..29337619ff --- /dev/null +++ b/rxjava-core/src/test/java/rx/operators/TimeIntervalObserverTest.java @@ -0,0 +1,22 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.operators; + +import org.junit.Ignore; + +@Ignore("WIP") +public class TimeIntervalObserverTest { +} diff --git a/rxjava-core/src/test/java/rx/plugins/RxJavaPluginsTest.java b/rxjava-core/src/test/java/rx/plugins/RxJavaPluginsTest.java new file mode 100644 index 0000000000..ce96b43edc --- /dev/null +++ b/rxjava-core/src/test/java/rx/plugins/RxJavaPluginsTest.java @@ -0,0 +1,92 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.plugins; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class RxJavaPluginsTest { + + @Test + public void testErrorHandlerDefaultImpl() { + RxJavaErrorHandler impl = new RxJavaPlugins().getErrorHandler(); + assertTrue(impl instanceof RxJavaErrorHandlerDefault); + } + + @Test + public void testErrorHandlerViaRegisterMethod() { + RxJavaPlugins p = new RxJavaPlugins(); + p.registerErrorHandler(new RxJavaErrorHandlerTestImpl()); + RxJavaErrorHandler impl = p.getErrorHandler(); + assertTrue(impl instanceof RxJavaErrorHandlerTestImpl); + } + + @Test + public void testErrorHandlerViaProperty() { + try { + RxJavaPlugins p = new RxJavaPlugins(); + String fullClass = getFullClassNameForTestClass(RxJavaErrorHandlerTestImpl.class); + System.setProperty("rxjava.plugin.RxJavaErrorHandler.implementation", fullClass); + RxJavaErrorHandler impl = p.getErrorHandler(); + assertTrue(impl instanceof RxJavaErrorHandlerTestImpl); + } finally { + System.clearProperty("rxjava.plugin.RxJavaErrorHandler.implementation"); + } + } + + // inside test so it is stripped from Javadocs + public static class RxJavaErrorHandlerTestImpl extends RxJavaErrorHandler { + // just use defaults + } + + @Test + public void testObservableExecutionHookDefaultImpl() { + RxJavaPlugins p = new RxJavaPlugins(); + RxJavaObservableExecutionHook impl = p.getObservableExecutionHook(); + assertTrue(impl instanceof RxJavaObservableExecutionHookDefault); + } + + @Test + public void testObservableExecutionHookViaRegisterMethod() { + RxJavaPlugins p = new RxJavaPlugins(); + p.registerObservableExecutionHook(new RxJavaObservableExecutionHookTestImpl()); + RxJavaObservableExecutionHook impl = p.getObservableExecutionHook(); + assertTrue(impl instanceof RxJavaObservableExecutionHookTestImpl); + } + + @Test + public void testObservableExecutionHookViaProperty() { + try { + RxJavaPlugins p = new RxJavaPlugins(); + String fullClass = getFullClassNameForTestClass(RxJavaObservableExecutionHookTestImpl.class); + System.setProperty("rxjava.plugin.RxJavaObservableExecutionHook.implementation", fullClass); + RxJavaObservableExecutionHook impl = p.getObservableExecutionHook(); + assertTrue(impl instanceof RxJavaObservableExecutionHookTestImpl); + } finally { + System.clearProperty("rxjava.plugin.RxJavaObservableExecutionHook.implementation"); + } + } + + // inside test so it is stripped from Javadocs + public static class RxJavaObservableExecutionHookTestImpl extends RxJavaObservableExecutionHook { + // just use defaults + } + + private static String getFullClassNameForTestClass(Class cls) { + return RxJavaPlugins.class.getPackage().getName() + "." + RxJavaPluginsTest.class.getSimpleName() + "$" + cls.getSimpleName(); + } +} diff --git a/rxjava-core/src/test/java/rx/subjects/AsyncSubjectTest.java b/rxjava-core/src/test/java/rx/subjects/AsyncSubjectTest.java new file mode 100644 index 0000000000..b483b9c99f --- /dev/null +++ b/rxjava-core/src/test/java/rx/subjects/AsyncSubjectTest.java @@ -0,0 +1,304 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.subjects; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; + +import rx.Observer; +import rx.Subscription; +import rx.util.functions.Action1; +import rx.util.functions.Func0; + +public class AsyncSubjectTest { + + private final Throwable testException = new Throwable(); + + @Test + public void testNeverCompleted() { + AsyncSubject subject = AsyncSubject.create(); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + subject.subscribe(aObserver); + + subject.onNext("one"); + subject.onNext("two"); + subject.onNext("three"); + + verify(aObserver, Mockito.never()).onNext(anyString()); + verify(aObserver, Mockito.never()).onError(testException); + verify(aObserver, Mockito.never()).onCompleted(); + } + + @Test + public void testCompleted() { + AsyncSubject subject = AsyncSubject.create(); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + subject.subscribe(aObserver); + + subject.onNext("one"); + subject.onNext("two"); + subject.onNext("three"); + subject.onCompleted(); + + verify(aObserver, times(1)).onNext("three"); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testNull() { + AsyncSubject subject = AsyncSubject.create(); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + subject.subscribe(aObserver); + + subject.onNext(null); + subject.onCompleted(); + + verify(aObserver, times(1)).onNext(null); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testSubscribeAfterCompleted() { + AsyncSubject subject = AsyncSubject.create(); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + + subject.onNext("one"); + subject.onNext("two"); + subject.onNext("three"); + subject.onCompleted(); + + subject.subscribe(aObserver); + + verify(aObserver, times(1)).onNext("three"); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testSubscribeAfterError() { + AsyncSubject subject = AsyncSubject.create(); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + + subject.onNext("one"); + subject.onNext("two"); + subject.onNext("three"); + + RuntimeException re = new RuntimeException("failed"); + subject.onError(re); + + subject.subscribe(aObserver); + + verify(aObserver, times(1)).onError(re); + verify(aObserver, Mockito.never()).onNext(any(String.class)); + verify(aObserver, Mockito.never()).onCompleted(); + } + + @Test + public void testError() { + AsyncSubject subject = AsyncSubject.create(); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + subject.subscribe(aObserver); + + subject.onNext("one"); + subject.onNext("two"); + subject.onNext("three"); + subject.onError(testException); + subject.onNext("four"); + subject.onError(new Throwable()); + subject.onCompleted(); + + verify(aObserver, Mockito.never()).onNext(anyString()); + verify(aObserver, times(1)).onError(testException); + verify(aObserver, Mockito.never()).onCompleted(); + } + + @Test + public void testUnsubscribeBeforeCompleted() { + AsyncSubject subject = AsyncSubject.create(); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + Subscription subscription = subject.subscribe(aObserver); + + subject.onNext("one"); + subject.onNext("two"); + + subscription.unsubscribe(); + + verify(aObserver, Mockito.never()).onNext(anyString()); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, Mockito.never()).onCompleted(); + + subject.onNext("three"); + subject.onCompleted(); + + verify(aObserver, Mockito.never()).onNext(anyString()); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, Mockito.never()).onCompleted(); + } + + @Test + public void testUnsubscribe() { + UnsubscribeTester.test( + new Func0>() { + @Override + public AsyncSubject call() { + return AsyncSubject.create(); + } + }, new Action1>() { + @Override + public void call(AsyncSubject DefaultSubject) { + DefaultSubject.onCompleted(); + } + }, new Action1>() { + @Override + public void call(AsyncSubject DefaultSubject) { + DefaultSubject.onError(new Throwable()); + } + }, + null + ); + } + + @Test + public void testEmptySubjectCompleted() { + AsyncSubject subject = AsyncSubject.create(); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + subject.subscribe(aObserver); + + subject.onCompleted(); + + InOrder inOrder = inOrder(aObserver); + inOrder.verify(aObserver, never()).onNext(null); + inOrder.verify(aObserver, never()).onNext(any(String.class)); + inOrder.verify(aObserver, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + /** + * Can receive timeout if subscribe never receives an onError/onCompleted ... which reveals a race condition. + */ + @Test + public void testSubscribeCompletionRaceCondition() { + /* + * With non-threadsafe code this fails most of the time on my dev laptop and is non-deterministic enough + * to act as a unit test to the race conditions. + * + * With the synchronization code in place I can not get this to fail on my laptop. + */ + for (int i = 0; i < 50; i++) { + final AsyncSubject subject = AsyncSubject.create(); + final AtomicReference value1 = new AtomicReference(); + + subject.subscribe(new Action1() { + + @Override + public void call(String t1) { + try { + // simulate a slow observer + Thread.sleep(50); + } catch (InterruptedException e) { + e.printStackTrace(); + } + value1.set(t1); + } + + }); + + Thread t1 = new Thread(new Runnable() { + + @Override + public void run() { + subject.onNext("value"); + subject.onCompleted(); + } + }); + + SubjectObserverThread t2 = new SubjectObserverThread(subject); + SubjectObserverThread t3 = new SubjectObserverThread(subject); + SubjectObserverThread t4 = new SubjectObserverThread(subject); + SubjectObserverThread t5 = new SubjectObserverThread(subject); + + t2.start(); + t3.start(); + t1.start(); + t4.start(); + t5.start(); + try { + t1.join(); + t2.join(); + t3.join(); + t4.join(); + t5.join(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + assertEquals("value", value1.get()); + assertEquals("value", t2.value.get()); + assertEquals("value", t3.value.get()); + assertEquals("value", t4.value.get()); + assertEquals("value", t5.value.get()); + } + + } + + private static class SubjectObserverThread extends Thread { + + private final AsyncSubject subject; + private final AtomicReference value = new AtomicReference(); + + public SubjectObserverThread(AsyncSubject subject) { + this.subject = subject; + } + + @Override + public void run() { + try { + // a timeout exception will happen if we don't get a terminal state + String v = subject.timeout(2000, TimeUnit.MILLISECONDS).toBlockingObservable().single(); + value.set(v); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + +} diff --git a/rxjava-core/src/test/java/rx/subjects/BehaviorSubjectTest.java b/rxjava-core/src/test/java/rx/subjects/BehaviorSubjectTest.java new file mode 100644 index 0000000000..57958c108f --- /dev/null +++ b/rxjava-core/src/test/java/rx/subjects/BehaviorSubjectTest.java @@ -0,0 +1,182 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.subjects; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; + +import rx.Observer; +import rx.Subscription; +import rx.util.functions.Action1; +import rx.util.functions.Func0; + +public class BehaviorSubjectTest { + + private final Throwable testException = new Throwable(); + + @Test + public void testThatObserverReceivesDefaultValueIfNothingWasPublished() { + BehaviorSubject subject = BehaviorSubject.createWithDefaultValue("default"); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + subject.subscribe(aObserver); + + subject.onNext("one"); + subject.onNext("two"); + subject.onNext("three"); + + verify(aObserver, times(1)).onNext("default"); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, times(1)).onNext("two"); + verify(aObserver, times(1)).onNext("three"); + verify(aObserver, Mockito.never()).onError(testException); + verify(aObserver, Mockito.never()).onCompleted(); + } + + @Test + public void testThatObserverDoesNotReceiveDefaultValueIfSomethingWasPublished() { + BehaviorSubject subject = BehaviorSubject.create("default"); + + subject.onNext("one"); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + subject.subscribe(aObserver); + + subject.onNext("two"); + subject.onNext("three"); + + verify(aObserver, Mockito.never()).onNext("default"); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, times(1)).onNext("two"); + verify(aObserver, times(1)).onNext("three"); + verify(aObserver, Mockito.never()).onError(testException); + verify(aObserver, Mockito.never()).onCompleted(); + } + + @Test + public void testCompleted() { + BehaviorSubject subject = BehaviorSubject.create("default"); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + subject.subscribe(aObserver); + + subject.onNext("one"); + subject.onCompleted(); + + verify(aObserver, times(1)).onNext("default"); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testCompletedStopsEmittingData() { + BehaviorSubject channel = BehaviorSubject.create(2013); + @SuppressWarnings("unchecked") + Observer observerA = mock(Observer.class); + @SuppressWarnings("unchecked") + Observer observerB = mock(Observer.class); + @SuppressWarnings("unchecked") + Observer observerC = mock(Observer.class); + + Subscription a = channel.subscribe(observerA); + Subscription b = channel.subscribe(observerB); + + InOrder inOrderA = inOrder(observerA); + InOrder inOrderB = inOrder(observerB); + InOrder inOrderC = inOrder(observerC); + + inOrderA.verify(observerA).onNext(2013); + inOrderB.verify(observerB).onNext(2013); + + channel.onNext(42); + + inOrderA.verify(observerA).onNext(42); + inOrderB.verify(observerB).onNext(42); + + a.unsubscribe(); + inOrderA.verifyNoMoreInteractions(); + + channel.onNext(4711); + + inOrderB.verify(observerB).onNext(4711); + + channel.onCompleted(); + + inOrderB.verify(observerB).onCompleted(); + + Subscription c = channel.subscribe(observerC); + + inOrderC.verify(observerC).onCompleted(); + + channel.onNext(13); + + inOrderB.verifyNoMoreInteractions(); + inOrderC.verifyNoMoreInteractions(); + } + + @Test + public void testCompletedAfterError() { + BehaviorSubject subject = BehaviorSubject.create("default"); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + subject.subscribe(aObserver); + + subject.onNext("one"); + subject.onError(testException); + subject.onNext("two"); + subject.onCompleted(); + + verify(aObserver, times(1)).onNext("default"); + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, times(1)).onError(testException); + } + + @Test + public void testUnsubscribe() { + UnsubscribeTester.test( + new Func0>() { + @Override + public BehaviorSubject call() { + return BehaviorSubject.create("default"); + } + }, new Action1>() { + @Override + public void call(BehaviorSubject DefaultSubject) { + DefaultSubject.onCompleted(); + } + }, new Action1>() { + @Override + public void call(BehaviorSubject DefaultSubject) { + DefaultSubject.onError(new Throwable()); + } + }, new Action1>() { + @Override + public void call(BehaviorSubject DefaultSubject) { + DefaultSubject.onNext("one"); + } + } + ); + } +} diff --git a/rxjava-core/src/test/java/rx/subjects/PublishSubjectTest.java b/rxjava-core/src/test/java/rx/subjects/PublishSubjectTest.java new file mode 100644 index 0000000000..5e9cc2790f --- /dev/null +++ b/rxjava-core/src/test/java/rx/subjects/PublishSubjectTest.java @@ -0,0 +1,331 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.subjects; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.util.functions.Action1; +import rx.util.functions.Func0; +import rx.util.functions.Func1; + +public class PublishSubjectTest { + + @Test + public void testCompleted() { + PublishSubject subject = PublishSubject.create(); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + subject.subscribe(aObserver); + + subject.onNext("one"); + subject.onNext("two"); + subject.onNext("three"); + subject.onCompleted(); + + @SuppressWarnings("unchecked") + Observer anotherObserver = mock(Observer.class); + subject.subscribe(anotherObserver); + + subject.onNext("four"); + subject.onCompleted(); + subject.onError(new Throwable()); + + assertCompletedObserver(aObserver); + // todo bug? assertNeverObserver(anotherObserver); + } + + @Test + public void testCompletedStopsEmittingData() { + PublishSubject channel = PublishSubject.create(); + @SuppressWarnings("unchecked") + Observer observerA = mock(Observer.class); + @SuppressWarnings("unchecked") + Observer observerB = mock(Observer.class); + @SuppressWarnings("unchecked") + Observer observerC = mock(Observer.class); + + Subscription a = channel.subscribe(observerA); + Subscription b = channel.subscribe(observerB); + + InOrder inOrderA = inOrder(observerA); + InOrder inOrderB = inOrder(observerB); + InOrder inOrderC = inOrder(observerC); + + channel.onNext(42); + + inOrderA.verify(observerA).onNext(42); + inOrderB.verify(observerB).onNext(42); + + a.unsubscribe(); + inOrderA.verifyNoMoreInteractions(); + + channel.onNext(4711); + + inOrderB.verify(observerB).onNext(4711); + + channel.onCompleted(); + + inOrderB.verify(observerB).onCompleted(); + + Subscription c = channel.subscribe(observerC); + + inOrderC.verify(observerC).onCompleted(); + + channel.onNext(13); + + inOrderB.verifyNoMoreInteractions(); + inOrderC.verifyNoMoreInteractions(); + } + + private void assertCompletedObserver(Observer aObserver) { + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, times(1)).onNext("two"); + verify(aObserver, times(1)).onNext("three"); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testError() { + PublishSubject subject = PublishSubject.create(); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + subject.subscribe(aObserver); + + subject.onNext("one"); + subject.onNext("two"); + subject.onNext("three"); + subject.onError(testException); + + @SuppressWarnings("unchecked") + Observer anotherObserver = mock(Observer.class); + subject.subscribe(anotherObserver); + + subject.onNext("four"); + subject.onError(new Throwable()); + subject.onCompleted(); + + assertErrorObserver(aObserver); + // todo bug? assertNeverObserver(anotherObserver); + } + + private void assertErrorObserver(Observer aObserver) { + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, times(1)).onNext("two"); + verify(aObserver, times(1)).onNext("three"); + verify(aObserver, times(1)).onError(testException); + verify(aObserver, Mockito.never()).onCompleted(); + } + + @Test + public void testSubscribeMidSequence() { + PublishSubject subject = PublishSubject.create(); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + subject.subscribe(aObserver); + + subject.onNext("one"); + subject.onNext("two"); + + assertObservedUntilTwo(aObserver); + + @SuppressWarnings("unchecked") + Observer anotherObserver = mock(Observer.class); + subject.subscribe(anotherObserver); + + subject.onNext("three"); + subject.onCompleted(); + + assertCompletedObserver(aObserver); + assertCompletedStartingWithThreeObserver(anotherObserver); + } + + private void assertCompletedStartingWithThreeObserver(Observer aObserver) { + verify(aObserver, Mockito.never()).onNext("one"); + verify(aObserver, Mockito.never()).onNext("two"); + verify(aObserver, times(1)).onNext("three"); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, times(1)).onCompleted(); + } + + @Test + public void testUnsubscribeFirstObserver() { + PublishSubject subject = PublishSubject.create(); + + @SuppressWarnings("unchecked") + Observer aObserver = mock(Observer.class); + Subscription subscription = subject.subscribe(aObserver); + + subject.onNext("one"); + subject.onNext("two"); + + subscription.unsubscribe(); + assertObservedUntilTwo(aObserver); + + @SuppressWarnings("unchecked") + Observer anotherObserver = mock(Observer.class); + subject.subscribe(anotherObserver); + + subject.onNext("three"); + subject.onCompleted(); + + assertObservedUntilTwo(aObserver); + assertCompletedStartingWithThreeObserver(anotherObserver); + } + + private void assertObservedUntilTwo(Observer aObserver) { + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, times(1)).onNext("two"); + verify(aObserver, Mockito.never()).onNext("three"); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, Mockito.never()).onCompleted(); + } + + @Test + public void testUnsubscribe() { + UnsubscribeTester.test( + new Func0>() { + @Override + public PublishSubject call() { + return PublishSubject.create(); + } + }, new Action1>() { + @Override + public void call(PublishSubject DefaultSubject) { + DefaultSubject.onCompleted(); + } + }, new Action1>() { + @Override + public void call(PublishSubject DefaultSubject) { + DefaultSubject.onError(new Throwable()); + } + }, new Action1>() { + @Override + public void call(PublishSubject DefaultSubject) { + DefaultSubject.onNext("one"); + } + } + ); + } + + @Test + public void testNestedSubscribe() { + final PublishSubject s = PublishSubject.create(); + + final AtomicInteger countParent = new AtomicInteger(); + final AtomicInteger countChildren = new AtomicInteger(); + final AtomicInteger countTotal = new AtomicInteger(); + + final ArrayList list = new ArrayList(); + + s.mapMany(new Func1>() { + + @Override + public Observable call(final Integer v) { + countParent.incrementAndGet(); + + // then subscribe to subject again (it will not receive the previous value) + return s.map(new Func1() { + + @Override + public String call(Integer v2) { + countChildren.incrementAndGet(); + return "Parent: " + v + " Child: " + v2; + } + + }); + } + + }).subscribe(new Action1() { + + @Override + public void call(String v) { + countTotal.incrementAndGet(); + list.add(v); + } + + }); + + for (int i = 0; i < 10; i++) { + s.onNext(i); + } + s.onCompleted(); + + // System.out.println("countParent: " + countParent.get()); + // System.out.println("countChildren: " + countChildren.get()); + // System.out.println("countTotal: " + countTotal.get()); + + // 9+8+7+6+5+4+3+2+1+0 == 45 + assertEquals(45, list.size()); + } + + /** + * Should be able to unsubscribe all Observers, have it stop emitting, then subscribe new ones and it start emitting again. + */ + @Test + public void testReSubscribe() { + final PublishSubject ps = PublishSubject.create(); + + Observer o1 = mock(Observer.class); + Subscription s1 = ps.subscribe(o1); + + // emit + ps.onNext(1); + + // validate we got it + InOrder inOrder1 = inOrder(o1); + inOrder1.verify(o1, times(1)).onNext(1); + inOrder1.verifyNoMoreInteractions(); + + // unsubscribe + s1.unsubscribe(); + + // emit again but nothing will be there to receive it + ps.onNext(2); + + Observer o2 = mock(Observer.class); + Subscription s2 = ps.subscribe(o2); + + // emit + ps.onNext(3); + + // validate we got it + InOrder inOrder2 = inOrder(o2); + inOrder2.verify(o2, times(1)).onNext(3); + inOrder2.verifyNoMoreInteractions(); + + s2.unsubscribe(); + } + + + private final Throwable testException = new Throwable(); + +} diff --git a/rxjava-core/src/test/java/rx/subjects/ReplaySubjectTest.java b/rxjava-core/src/test/java/rx/subjects/ReplaySubjectTest.java new file mode 100644 index 0000000000..fef45ca63d --- /dev/null +++ b/rxjava-core/src/test/java/rx/subjects/ReplaySubjectTest.java @@ -0,0 +1,185 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.subjects; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; + +import rx.Observer; +import rx.Subscription; +import rx.util.functions.Action1; +import rx.util.functions.Func0; + +public class ReplaySubjectTest { + + private final Throwable testException = new Throwable(); + + @SuppressWarnings("unchecked") + @Test + public void testCompleted() { + ReplaySubject subject = ReplaySubject.create(); + + Observer o1 = mock(Observer.class); + subject.subscribe(o1); + + subject.onNext("one"); + subject.onNext("two"); + subject.onNext("three"); + subject.onCompleted(); + + subject.onNext("four"); + subject.onCompleted(); + subject.onError(new Throwable()); + + assertCompletedObserver(o1); + + // assert that subscribing a 2nd time gets the same data + Observer o2 = mock(Observer.class); + subject.subscribe(o2); + assertCompletedObserver(o2); + } + + private void assertCompletedObserver(Observer aObserver) { + InOrder inOrder = inOrder(aObserver); + + inOrder.verify(aObserver, times(1)).onNext("one"); + inOrder.verify(aObserver, times(1)).onNext("two"); + inOrder.verify(aObserver, times(1)).onNext("three"); + inOrder.verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + inOrder.verify(aObserver, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @SuppressWarnings("unchecked") + @Test + public void testError() { + ReplaySubject subject = ReplaySubject.create(); + + Observer aObserver = mock(Observer.class); + subject.subscribe(aObserver); + + subject.onNext("one"); + subject.onNext("two"); + subject.onNext("three"); + subject.onError(testException); + + subject.onNext("four"); + subject.onError(new Throwable()); + subject.onCompleted(); + + assertErrorObserver(aObserver); + + aObserver = mock(Observer.class); + subject.subscribe(aObserver); + assertErrorObserver(aObserver); + } + + private void assertErrorObserver(Observer aObserver) { + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, times(1)).onNext("two"); + verify(aObserver, times(1)).onNext("three"); + verify(aObserver, times(1)).onError(testException); + verify(aObserver, Mockito.never()).onCompleted(); + } + + @SuppressWarnings("unchecked") + @Test + public void testSubscribeMidSequence() { + ReplaySubject subject = ReplaySubject.create(); + + Observer aObserver = mock(Observer.class); + subject.subscribe(aObserver); + + subject.onNext("one"); + subject.onNext("two"); + + assertObservedUntilTwo(aObserver); + + Observer anotherObserver = mock(Observer.class); + subject.subscribe(anotherObserver); + assertObservedUntilTwo(anotherObserver); + + subject.onNext("three"); + subject.onCompleted(); + + assertCompletedObserver(aObserver); + assertCompletedObserver(anotherObserver); + } + + @SuppressWarnings("unchecked") + @Test + public void testUnsubscribeFirstObserver() { + ReplaySubject subject = ReplaySubject.create(); + + Observer aObserver = mock(Observer.class); + Subscription subscription = subject.subscribe(aObserver); + + subject.onNext("one"); + subject.onNext("two"); + + subscription.unsubscribe(); + assertObservedUntilTwo(aObserver); + + Observer anotherObserver = mock(Observer.class); + subject.subscribe(anotherObserver); + assertObservedUntilTwo(anotherObserver); + + subject.onNext("three"); + subject.onCompleted(); + + assertObservedUntilTwo(aObserver); + assertCompletedObserver(anotherObserver); + } + + private void assertObservedUntilTwo(Observer aObserver) { + verify(aObserver, times(1)).onNext("one"); + verify(aObserver, times(1)).onNext("two"); + verify(aObserver, Mockito.never()).onNext("three"); + verify(aObserver, Mockito.never()).onError(any(Throwable.class)); + verify(aObserver, Mockito.never()).onCompleted(); + } + + @Test + public void testUnsubscribe() { + UnsubscribeTester.test( + new Func0>() { + @Override + public ReplaySubject call() { + return ReplaySubject.create(); + } + }, new Action1>() { + @Override + public void call(ReplaySubject repeatSubject) { + repeatSubject.onCompleted(); + } + }, new Action1>() { + @Override + public void call(ReplaySubject repeatSubject) { + repeatSubject.onError(new Throwable()); + } + }, new Action1>() { + @Override + public void call(ReplaySubject repeatSubject) { + repeatSubject.onNext("one"); + } + } + ); + } +} diff --git a/rxjava-core/src/test/java/rx/subscriptions/CompositeSubscriptionTest.java b/rxjava-core/src/test/java/rx/subscriptions/CompositeSubscriptionTest.java new file mode 100644 index 0000000000..551a6b2f66 --- /dev/null +++ b/rxjava-core/src/test/java/rx/subscriptions/CompositeSubscriptionTest.java @@ -0,0 +1,147 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.subscriptions; + +import static org.junit.Assert.*; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import rx.Subscription; +import rx.util.CompositeException; + +public class CompositeSubscriptionTest { + + @Test + public void testSuccess() { + final AtomicInteger counter = new AtomicInteger(); + CompositeSubscription s = new CompositeSubscription(); + s.add(new Subscription() { + + @Override + public void unsubscribe() { + counter.incrementAndGet(); + } + }); + + s.add(new Subscription() { + + @Override + public void unsubscribe() { + counter.incrementAndGet(); + } + }); + + s.unsubscribe(); + + assertEquals(2, counter.get()); + } + + @Test + public void testException() { + final AtomicInteger counter = new AtomicInteger(); + CompositeSubscription s = new CompositeSubscription(); + s.add(new Subscription() { + + @Override + public void unsubscribe() { + throw new RuntimeException("failed on first one"); + } + }); + + s.add(new Subscription() { + + @Override + public void unsubscribe() { + counter.incrementAndGet(); + } + }); + + try { + s.unsubscribe(); + fail("Expecting an exception"); + } catch (CompositeException e) { + // we expect this + assertEquals(1, e.getExceptions().size()); + } + + // we should still have unsubscribed to the second one + assertEquals(1, counter.get()); + } + + @Test + public void testRemoveUnsubscribes() { + BooleanSubscription s1 = new BooleanSubscription(); + BooleanSubscription s2 = new BooleanSubscription(); + + CompositeSubscription s = new CompositeSubscription(); + s.add(s1); + s.add(s2); + + s.remove(s1); + + assertTrue(s1.isUnsubscribed()); + assertFalse(s2.isUnsubscribed()); + } + + @Test + public void testClear() { + BooleanSubscription s1 = new BooleanSubscription(); + BooleanSubscription s2 = new BooleanSubscription(); + + CompositeSubscription s = new CompositeSubscription(); + s.add(s1); + s.add(s2); + + assertFalse(s1.isUnsubscribed()); + assertFalse(s2.isUnsubscribed()); + + s.clear(); + + assertTrue(s1.isUnsubscribed()); + assertTrue(s1.isUnsubscribed()); + assertFalse(s.isUnsubscribed()); + + BooleanSubscription s3 = new BooleanSubscription(); + + s.add(s3); + s.unsubscribe(); + + assertTrue(s3.isUnsubscribed()); + assertTrue(s.isUnsubscribed()); + } + + @Test + public void testUnsubscribeIdempotence() { + final AtomicInteger counter = new AtomicInteger(); + CompositeSubscription s = new CompositeSubscription(); + s.add(new Subscription() { + + @Override + public void unsubscribe() { + counter.incrementAndGet(); + } + }); + + s.unsubscribe(); + s.unsubscribe(); + s.unsubscribe(); + + // we should have only unsubscribed once + assertEquals(1, counter.get()); + } +} diff --git a/rxjava-core/src/test/java/rx/subscriptions/SerialSubscriptionTests.java b/rxjava-core/src/test/java/rx/subscriptions/SerialSubscriptionTests.java new file mode 100644 index 0000000000..cd63539c87 --- /dev/null +++ b/rxjava-core/src/test/java/rx/subscriptions/SerialSubscriptionTests.java @@ -0,0 +1,76 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.subscriptions; + +import static org.mockito.Mockito.*; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockitoAnnotations; + +import rx.Subscription; + +public class SerialSubscriptionTests { + private SerialSubscription serialSubscription; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + serialSubscription = new SerialSubscription(); + } + + @Test + public void unsubscribingWithoutUnderlyingDoesNothing() { + serialSubscription.unsubscribe(); + } + + @Test + public void unsubscribingWithSingleUnderlyingUnsubscribes() { + Subscription underlying = mock(Subscription.class); + serialSubscription.setSubscription(underlying); + underlying.unsubscribe(); + verify(underlying).unsubscribe(); + } + + @Test + public void replacingFirstUnderlyingCausesUnsubscription() { + Subscription first = mock(Subscription.class); + serialSubscription.setSubscription(first); + Subscription second = mock(Subscription.class); + serialSubscription.setSubscription(second); + verify(first).unsubscribe(); + } + + @Test + public void whenUnsubscribingSecondUnderlyingUnsubscribed() { + Subscription first = mock(Subscription.class); + serialSubscription.setSubscription(first); + Subscription second = mock(Subscription.class); + serialSubscription.setSubscription(second); + serialSubscription.unsubscribe(); + verify(second).unsubscribe(); + } + + @Test + public void settingUnderlyingWhenUnsubscribedCausesImmediateUnsubscription() + { + serialSubscription.unsubscribe(); + Subscription underlying = mock(Subscription.class); + serialSubscription.setSubscription(underlying); + verify(underlying).unsubscribe(); + } +} diff --git a/rxjava-core/src/test/java/rx/subscriptions/SubscriptionsTest.java b/rxjava-core/src/test/java/rx/subscriptions/SubscriptionsTest.java new file mode 100644 index 0000000000..abeab16833 --- /dev/null +++ b/rxjava-core/src/test/java/rx/subscriptions/SubscriptionsTest.java @@ -0,0 +1,36 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.subscriptions; + +import static org.mockito.Mockito.*; +import static rx.subscriptions.Subscriptions.*; + +import org.junit.Test; + +import rx.Subscription; +import rx.util.functions.Action0; + +public class SubscriptionsTest { + + @Test + public void testUnsubscribeOnlyOnce() { + Action0 unsubscribe = mock(Action0.class); + Subscription subscription = create(unsubscribe); + subscription.unsubscribe(); + subscription.unsubscribe(); + verify(unsubscribe, times(1)).call(); + } +} diff --git a/rxjava-core/src/test/java/rx/test/OperatorTester.java b/rxjava-core/src/test/java/rx/test/OperatorTester.java new file mode 100644 index 0000000000..6bd905c264 --- /dev/null +++ b/rxjava-core/src/test/java/rx/test/OperatorTester.java @@ -0,0 +1,94 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.test; + +import java.util.concurrent.TimeUnit; + +import rx.Scheduler; +import rx.Subscription; +import rx.util.functions.Action0; +import rx.util.functions.Func2; + +/** + * Common utility functions for testing operator implementations. + */ +public class OperatorTester { + /* + * This is purposefully package-only so it does not leak into the public API outside of this package. + * + * This package is implementation details and not part of the Javadocs and thus can change without breaking backwards compatibility. + * + * benjchristensen => I'm procrastinating the decision of where and how these types of classes (see rx.subjects.UnsubscribeTester) should exist. + * If they are only for internal implementations then I don't want them as part of the API. + * If they are truly useful for everyone to use then an "rx.testing" package may make sense. + */ + + private OperatorTester() { + } + + /** + * Used for mocking of Schedulers since many Scheduler implementations are static/final. + * + * @param underlying + * @return + */ + public static Scheduler forwardingScheduler(Scheduler underlying) { + return new ForwardingScheduler(underlying); + } + + public static class ForwardingScheduler extends Scheduler { + private final Scheduler underlying; + + public ForwardingScheduler(Scheduler underlying) { + this.underlying = underlying; + } + + @Override + public Subscription schedule(Action0 action) { + return underlying.schedule(action); + } + + @Override + public Subscription schedule(T state, Func2 action) { + return underlying.schedule(state, action); + } + + @Override + public Subscription schedule(Action0 action, long dueTime, TimeUnit unit) { + return underlying.schedule(action, dueTime, unit); + } + + @Override + public Subscription schedule(T state, Func2 action, long dueTime, TimeUnit unit) { + return underlying.schedule(state, action, dueTime, unit); + } + + @Override + public Subscription schedulePeriodically(Action0 action, long initialDelay, long period, TimeUnit unit) { + return underlying.schedulePeriodically(action, initialDelay, period, unit); + } + + @Override + public Subscription schedulePeriodically(T state, Func2 action, long initialDelay, long period, TimeUnit unit) { + return underlying.schedulePeriodically(state, action, initialDelay, period, unit); + } + + @Override + public long now() { + return underlying.now(); + } + } +} \ No newline at end of file diff --git a/rxjava-core/src/test/java/rx/util/RangeTest.java b/rxjava-core/src/test/java/rx/util/RangeTest.java new file mode 100644 index 0000000000..297e7a769a --- /dev/null +++ b/rxjava-core/src/test/java/rx/util/RangeTest.java @@ -0,0 +1,65 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.util; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +public class RangeTest { + + @Test + public void testSimpleRange() { + assertEquals(Arrays.asList(1, 2, 3, 4), toList(Range.create(1, 5))); + } + + @Test + public void testRangeWithStep() { + assertEquals(Arrays.asList(1, 3, 5, 7, 9), toList(Range.createWithStep(1, 10, 2))); + } + + @Test + public void testRangeWithCount() { + assertEquals(Arrays.asList(1, 2, 3, 4, 5), toList(Range.createWithCount(1, 5))); + } + + @Test + public void testRangeWithCount2() { + assertEquals(Arrays.asList(2, 3, 4, 5), toList(Range.createWithCount(2, 4))); + } + + @Test + public void testRangeWithCount3() { + assertEquals(Arrays.asList(0, 1, 2, 3), toList(Range.createWithCount(0, 4))); + } + + @Test + public void testRangeWithCount4() { + assertEquals(Arrays.asList(10, 11, 12, 13, 14), toList(Range.createWithCount(10, 5))); + } + + private static List toList(Iterable iterable) { + List result = new ArrayList(); + for (T element : iterable) { + result.add(element); + } + return result; + } +} diff --git a/settings.gradle b/settings.gradle index 700c698e27..01cf356da0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,7 +2,9 @@ rootProject.name='rxjava' include 'rxjava-core', \ 'language-adaptors:rxjava-groovy', \ 'language-adaptors:rxjava-clojure', \ +'language-adaptors:rxjava-jruby', \ 'language-adaptors:rxjava-scala', \ -'language-adaptors:rxjava-scala-java', \ +'language-adaptors:rxjava-kotlin', \ 'rxjava-contrib:rxjava-swing', \ -'rxjava-contrib:rxjava-android' +'rxjava-contrib:rxjava-android', \ +'rxjava-contrib:rxjava-apache-http'