diff --git a/CHANGES.md b/CHANGES.md index 8e30cb39fd..527d52ba3c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,102 @@ # RxJava Releases # +### Version 0.16.1 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.16.1%22)) ### + +* [Pull 730](https://github.com/Netflix/RxJava/pull/730) Improve Error Handling and Stacktraces When Unsubscribe Fails +* [Pull 720](https://github.com/Netflix/RxJava/pull/720) Added `Observable.timeout` wrappers to scala adapter +* [Pull 731](https://github.com/Netflix/RxJava/pull/731) Fix non-deterministic unit test +* [Pull 742](https://github.com/Netflix/RxJava/pull/742) Build with Gradle 1.10 +* [Pull 718](https://github.com/Netflix/RxJava/pull/718) Merge overloads +* [Pull 733](https://github.com/Netflix/RxJava/pull/733) Buffer with Observable boundary +* [Pull 734](https://github.com/Netflix/RxJava/pull/734) Delay with subscription and item delaying observables +* [Pull 735](https://github.com/Netflix/RxJava/pull/735) Window with Observable boundary +* [Pull 736](https://github.com/Netflix/RxJava/pull/736) MergeMap with Iterable and resultSelector overloads +* [Pull 738](https://github.com/Netflix/RxJava/pull/738) Publish and PublishLast overloads +* [Pull 739](https://github.com/Netflix/RxJava/pull/739) Debounce with selector +* [Pull 740](https://github.com/Netflix/RxJava/pull/740) Timeout with selector overloads +* [Pull 745](https://github.com/Netflix/RxJava/pull/745) Fixed `switch` bug +* [Pull 741](https://github.com/Netflix/RxJava/pull/741) Zip with iterable, removed old aggregator version and updated tests +* [Pull 749](https://github.com/Netflix/RxJava/pull/749) Separated Android test code from source +* [Pull 732](https://github.com/Netflix/RxJava/pull/732) Ported groupByUntil function to scala-adapter + + +### Version 0.16.0 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.16.0%22)) ### + + +This is a significant release with the following changes: + +- Refactor of Subjects and Subscriptions to non-blocking implementations +- Many bug fixes, new operators and behavior changes to match Rx.Net. +- Deprecation of some operators due to renaming or eliminating duplicates +- The `rx.concurrency` package has been renamed to `rx.schedulers`. Existing classes still remain in `rx.concurrency` but are deprecated. Use of `rx.concurrency` should be migrated to `rx.schedulers` as these deprecated classes will be removed in a future release. +- Breaking changes to Scala bindings. See [Release Notes](https://github.com/Netflix/RxJava/blob/master/language-adaptors/rxjava-scala/ReleaseNotes.md) for details. +- New modules: rxjava-string, rxjava-async-util and rxjava-computation-expressions for operators deemed not applicable to the core library. + +--- + +* [Pull 516](https://github.com/Netflix/RxJava/pull/516) rxjava-string module with StringObservable +* [Pull 533](https://github.com/Netflix/RxJava/pull/533) Operator: ToAsync +* [Pull 535](https://github.com/Netflix/RxJava/pull/535) Fix compilation errors due to referencing the Android support library directly +* [Pull 545](https://github.com/Netflix/RxJava/pull/545) Fixed Zip issue with infinite streams +* [Pull 539](https://github.com/Netflix/RxJava/pull/539) Zipping a finite and an infinite Observable +* [Pull 541](https://github.com/Netflix/RxJava/pull/541) Operator: SkipUntil +* [Pull 537](https://github.com/Netflix/RxJava/pull/537) Add scala adapters for doOnEach operator +* [Pull 560](https://github.com/Netflix/RxJava/pull/560) Add type variances for doOnEach actions +* [Pull 562](https://github.com/Netflix/RxJava/pull/562) Scala Adaptor Improvements +* [Pull 563](https://github.com/Netflix/RxJava/pull/563) Operator: GroupByUntil +* [Pull 561](https://github.com/Netflix/RxJava/pull/561) Revised Approach to Creating Observables in Scala +* [Pull 565](https://github.com/Netflix/RxJava/pull/565) Operator: GroupJoin v2 +* [Pull 567](https://github.com/Netflix/RxJava/pull/567) Operator: Timestamp with Scheduler +* [Pull 568](https://github.com/Netflix/RxJava/pull/568) Use lock free strategy for several Subscription implementations +* [Pull 571](https://github.com/Netflix/RxJava/pull/571) Operator: Sample with Observable v2 +* [Pull 572](https://github.com/Netflix/RxJava/pull/572) Multiple Subscriptions to ObserveOn +* [Pull 573](https://github.com/Netflix/RxJava/pull/573) Removed Opening and Closing historical artifacts +* [Pull 575](https://github.com/Netflix/RxJava/pull/575) Operator: SequenceEqual reimplementation +* [Pull 587](https://github.com/Netflix/RxJava/pull/587) Operator: LongCount +* [Pull 586](https://github.com/Netflix/RxJava/pull/586) Fix Concat to allow multiple observers +* [Pull 598](https://github.com/Netflix/RxJava/pull/598) New Scala Bindings +* [Pull 596](https://github.com/Netflix/RxJava/pull/596) Fix for buffer not stopping when unsubscribed +* [Pull 576](https://github.com/Netflix/RxJava/pull/576) Operators: Timer and Delay +* [Pull 593](https://github.com/Netflix/RxJava/pull/593) Lock-free subscriptions +* [Pull 599](https://github.com/Netflix/RxJava/pull/599) Refactor rx.concurrency to rx.schedulers +* [Pull 600](https://github.com/Netflix/RxJava/pull/600) BugFix: Replay Subject +* [Pull 594](https://github.com/Netflix/RxJava/pull/594) Operator: Start +* [Pull 604](https://github.com/Netflix/RxJava/pull/604) StringObservable.join +* [Pull 609](https://github.com/Netflix/RxJava/pull/609) Operation: Timer +* [Pull 612](https://github.com/Netflix/RxJava/pull/612) Operation: Replay (overloads) +* [Pull 628](https://github.com/Netflix/RxJava/pull/628) BugFix: MergeDelayError Synchronization +* [Pull 602](https://github.com/Netflix/RxJava/pull/602) BugFix: ObserveOn Subscription leak +* [Pull 631](https://github.com/Netflix/RxJava/pull/631) Make NewThreadScheduler create Daemon threads +* [Pull 651](https://github.com/Netflix/RxJava/pull/651) Subjects Refactor - Non-Blocking, Common Abstraction, Performance +* [Pull 661](https://github.com/Netflix/RxJava/pull/661) Subscriptions Rewrite +* [Pull 520](https://github.com/Netflix/RxJava/pull/520) BugFix: blocking/non-blocking `first` +* [Pull 621](https://github.com/Netflix/RxJava/pull/621) Scala: SerialSubscription & From +* [Pull 626](https://github.com/Netflix/RxJava/pull/626) BO.Latest, fixed: BO.next, BO.mostRecent, BO.toIterable +* [Pull 633](https://github.com/Netflix/RxJava/pull/633) BugFix: null in toList operator +* [Pull 635](https://github.com/Netflix/RxJava/pull/635) Conditional Operators +* [Pull 638](https://github.com/Netflix/RxJava/pull/638) Operations: DelaySubscription, TakeLast w/ time, TakeLastBuffer +* [Pull 659](https://github.com/Netflix/RxJava/pull/659) Missing fixes from the subject rewrite +* [Pull 688](https://github.com/Netflix/RxJava/pull/688) Fix SafeObserver handling of onComplete errors +* [Pull 690](https://github.com/Netflix/RxJava/pull/690) Fixed Scala bindings +* [Pull 693](https://github.com/Netflix/RxJava/pull/693) Kotlin M6.2 +* [Pull 689](https://github.com/Netflix/RxJava/pull/689) Removed ObserverBase +* [Pull 664](https://github.com/Netflix/RxJava/pull/664) Operation: AsObservable +* [Pull 697](https://github.com/Netflix/RxJava/pull/697) Operations: Skip, SkipLast, Take with time +* [Pull 698](https://github.com/Netflix/RxJava/pull/698) Operations: Average, Sum +* [Pull 699](https://github.com/Netflix/RxJava/pull/699) Operation: Repeat +* [Pull 701](https://github.com/Netflix/RxJava/pull/701) Operation: Collect +* [Pull 707](https://github.com/Netflix/RxJava/pull/707) Module: rxjava-async-util +* [Pull 708](https://github.com/Netflix/RxJava/pull/708) BugFix: combineLatest +* [Pull 712](https://github.com/Netflix/RxJava/pull/712) Fix Scheduler Memory Leaks +* [Pull 714](https://github.com/Netflix/RxJava/pull/714) Module: rxjava-computation-expressions +* [Pull 715](https://github.com/Netflix/RxJava/pull/715) Add missing type hint to clojure example +* [Pull 717](https://github.com/Netflix/RxJava/pull/717) Scala: Added ConnectableObservable +* [Pull 723](https://github.com/Netflix/RxJava/pull/723) Deprecate multiple arity ‘from’ +* [Pull 724](https://github.com/Netflix/RxJava/pull/724) Revert use of CurrentThreadScheduler for Observable.from +* [Pull 725](https://github.com/Netflix/RxJava/pull/725) Simpler computation/io naming for Schedulers +* [Pull 727](https://github.com/Netflix/RxJava/pull/727) ImmediateScheduler optimization for toObservableIterable + + ### 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. @@ -445,11 +542,4 @@ Also [removed](https://github.com/Netflix/RxJava/issues/173) were the `Observabl ### Version 0.5.1 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.5.1%22)) ### * variety of code cleanup commits -* [Pull 132](https://github.com/Netflix/RxJava/pull/132) Broke rxjava-examples module into each language-adaptor module -* [Issue 118](https://github.com/Netflix/RxJava/issues/118) & [Issue 119](https://github.com/Netflix/Hystrix/issues/119) Cleaned up Javadocs still referencing internal Netflix paths -* Javadoc and README changes - -### Version 0.5.0 ([Maven Central](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.netflix.rxjava%22%20AND%20v%3A%220.5.0%22)) ### - -* Initial open source release -* See [Netflix Tech Blog](http://techblog.netflix.com/2013/02/rxjava-netflix-api.html) for introduction +* [Pull 132](https://github.com/Netflix/RxJava/pull/132) Broke rxjava-examples mo diff --git a/build.gradle b/build.gradle index c144c169e6..304a26643c 100644 --- a/build.gradle +++ b/build.gradle @@ -15,6 +15,8 @@ buildscript { } allprojects { + apply plugin: 'eclipse' + apply plugin: 'idea' repositories { mavenLocal() mavenCentral() // maven { url: 'http://jcenter.bintray.com' } @@ -23,37 +25,50 @@ allprojects { subprojects { apply plugin: 'java' - apply plugin: 'eclipse' - apply plugin: 'idea' - group = "com.netflix.rxjava" // make 'examples' use the same classpath configurations { examplesCompile.extendsFrom compile examplesRuntime.extendsFrom runtime + perfCompile.extendsFrom compile + perfRuntime.extendsFrom runtime } - sourceSets.test.java.srcDir 'src/main/java' tasks.withType(Javadoc).each { it.classpath = sourceSets.main.compileClasspath } - //include /src/examples folder sourceSets { + //include /src/examples folder examples + //include /src/perf folder + perf { + java { + srcDir 'src/perf/java' + compileClasspath += main.output + runtimeClasspath += main.output + } + } } - - //include 'examples' in build task + + dependencies { + perfCompile 'org.openjdk.jmh:jmh-core:0.2' + } + tasks.build { + //include 'examples' in build task dependsOn(examplesClasses) + //include 'perf' in build task + // dependsOn(perfClasses) //-> Not working so commented out } eclipse { classpath { // include 'provided' dependencies on the classpath plusConfigurations += configurations.provided + plusConfigurations += configurations.perfCompile downloadSources = true downloadJavadoc = true @@ -64,6 +79,8 @@ subprojects { module { // include 'provided' dependencies on the classpath scopes.PROVIDED.plus += configurations.provided + // TODO not sure what to add it to + //scopes.PROVIDED.plus += configurations.perfCompile } } } diff --git a/gradle.properties b/gradle.properties index 92fc8fd0ca..80d3fa81f1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=0.15.2-SNAPSHOT +version=0.17.0-RC4-SNAPSHOT diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6d35fb3bc3..19fa1710c4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Sep 03 10:20:57 PDT 2013 +#Wed Feb 05 12:05:54 CET 2014 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=http\://services.gradle.org/distributions/gradle-1.6-bin.zip +distributionUrl=http\://services.gradle.org/distributions/gradle-1.8-all.zip diff --git a/language-adaptors/rxjava-clojure/README.md b/language-adaptors/rxjava-clojure/README.md index bb452b98a3..b2f69b3853 100644 --- a/language-adaptors/rxjava-clojure/README.md +++ b/language-adaptors/rxjava-clojure/README.md @@ -46,6 +46,19 @@ The `rx/action` macro is identical to `rx/fn` except that the object returned im (rx/action [] (println "Sequence complete")))) ``` +## Using Observable/create +As of 0.17, `rx.Observable/create` takes an implementation of `rx.Observable$OnSubscribe` which is basically an alias for `rx.util.functions.Action1` that takes an `rx.Subscriber` as its argument. Thus, you can just use `rx/action` when creating new observables: + +```clojure +; A simple observable that emits 0..9 taking unsubscribe into account +(Observable/create (rx/action [^rx.Subscriber s] + (loop [i 0] + (when (and (< i 10) (.isUnsubscribed s)) + (.onNext s i) + (recur (inc i)))) + (.onCompleted s))) +``` + # Gotchas Here are a few things to keep in mind when using this interop: diff --git a/language-adaptors/rxjava-clojure/build.gradle b/language-adaptors/rxjava-clojure/build.gradle index 6e59b61305..7332fb350e 100644 --- a/language-adaptors/rxjava-clojure/build.gradle +++ b/language-adaptors/rxjava-clojure/build.gradle @@ -50,3 +50,26 @@ jar { instruction 'Fragment-Host', 'com.netflix.rxjava.core' } } + + +//////////////////////////////////////////////////////////////////////////////// +// Define a task that runs an nrepl server. The port is given with the nreplPort +// property: +// gradlew nrepl -PnreplPort=9999 +// or put the property in ~/.gradle/gradle.properties + +def nreplPortValue = (project.hasProperty('nreplPort') && !project.nreplPort.isEmpty()) ? project.nreplPort : '9999' +configurations { nrepl } +dependencies { nrepl 'org.clojure:tools.nrepl:0.2.2' } +task nrepl(type: JavaExec) { + classpath configurations.nrepl, + project.sourceSets.main.clojure.srcDirs, + project.sourceSets.test.clojure.srcDirs, + sourceSets.main.runtimeClasspath, + sourceSets.test.runtimeClasspath + + main = "clojure.main" + args '--eval', "(ns gradle-nrepl (:require [clojure.tools.nrepl.server :refer (start-server stop-server)]))", + '--eval', "(println \"Starting nrepl server on port $nreplPortValue\")", + '--eval', "(def server (start-server :port $nreplPortValue))" +} diff --git a/language-adaptors/rxjava-clojure/src/examples/clojure/rx/lang/clojure/examples/http_examples.clj b/language-adaptors/rxjava-clojure/src/examples/clojure/rx/lang/clojure/examples/http_examples.clj index 857df10020..958d527486 100644 --- a/language-adaptors/rxjava-clojure/src/examples/clojure/rx/lang/clojure/examples/http_examples.clj +++ b/language-adaptors/rxjava-clojure/src/examples/clojure/rx/lang/clojure/examples/http_examples.clj @@ -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. @@ -27,14 +27,14 @@ return Observable of HTML" (Observable/create - (rx/fn [observer] + (rx/action [observer] (let [f (future (doseq [articleName wikipediaArticleNames] (-> observer (.onNext (http/get (str "http://en.wikipedia.org/wiki/" articleName))))) ; after sending response to onnext we complete the sequence (-> observer .onCompleted))] ; a subscription that cancels the future if unsubscribed - (Subscriptions/create (rx/action [] (future-cancel f))))))) + (.add observer (Subscriptions/create (rx/action [] (future-cancel f)))))))) ; To see output (comment @@ -52,7 +52,7 @@ return Observable of HTML" (Observable/create - (rx/fn [observer] + (rx/action [observer] (let [f (future (try (doseq [articleName wikipediaArticleNames] @@ -62,7 +62,7 @@ ; after sending response to onNext we complete the sequence (-> observer .onCompleted))] ; a subscription that cancels the future if unsubscribed - (Subscriptions/create (rx/action [] (future-cancel f))))))) + (.add observer (Subscriptions/create (rx/action [] (future-cancel f)))))))) ; To see output (comment diff --git a/language-adaptors/rxjava-clojure/src/examples/clojure/rx/lang/clojure/examples/rx_examples.clj b/language-adaptors/rxjava-clojure/src/examples/clojure/rx/lang/clojure/examples/rx_examples.clj index 5b9d1df94d..af0d7328c0 100644 --- a/language-adaptors/rxjava-clojure/src/examples/clojure/rx/lang/clojure/examples/rx_examples.clj +++ b/language-adaptors/rxjava-clojure/src/examples/clojure/rx/lang/clojure/examples/rx_examples.clj @@ -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. @@ -27,7 +27,9 @@ (defn hello [& args] - (-> (Observable/from args) + (-> + ; type hint required due to `Observable/from` overloading + (Observable/from ^java.lang.Iterable args) (.subscribe (rx/action [v] (println (str "Hello " v "!")))))) ; To see output @@ -56,53 +58,37 @@ ; Custom Observable ; -------------------------------------------------- -(defn customObservableBlocking [] - "This example shows a custom Observable that blocks - when subscribed to (does not spawn an extra thread). +(defn customObservable + "This example shows a custom Observable. Note the + .isUnsubscribed check so that it can be stopped early. returns Observable" + [] (Observable/create - (rx/fn [observer] - (doseq [x (range 50)] (-> observer (.onNext (str "value_" x)))) + (rx/action [^rx.Subscriber s] + (loop [x (range 50)] + (when (and (not (.isUnsubscribed s)) x) + ; TODO + (println "HERE " (.isUnsubscribed s) (first x)) + (-> s (.onNext (str "value_" (first x)))) + (recur (next x)))) ; after sending all values we complete the sequence - (-> observer .onCompleted) - ; return a NoOpSubsription since this blocks and thus - ; can't be unsubscribed from - (Subscriptions/empty)))) - -; To see output -(comment - (.subscribe (customObservableBlocking) (rx/action* println))) - -(defn customObservableNonBlocking [] - "This example shows a custom Observable that does not block - when subscribed to as it spawns a separate thread. - - returns Observable" - (Observable/create - (rx/fn [observer] - (let [f (future - (doseq [x (range 50)] - (-> observer (.onNext (str "anotherValue_" x)))) - ; after sending all values we complete the sequence - (-> observer .onCompleted))] - ; return a subscription that cancels the future - (Subscriptions/create (rx/action [] (future-cancel f))))))) + (-> s .onCompleted)))) ; To see output (comment - (.subscribe (customObservableNonBlocking) (rx/action* println))) - + (.subscribe (customObservable) (rx/action* println))) ; -------------------------------------------------- ; Composition - Simple ; -------------------------------------------------- -(defn simpleComposition [] - "Asynchronously calls 'customObservableNonBlocking' and defines +(defn simpleComposition + "Calls 'customObservable' and defines a chain of operators to apply to the callback sequence." + [] (-> - (customObservableNonBlocking) + (customObservable) (.skip 10) (.take 5) (.map (rx/fn [v] (str v "_transformed"))) @@ -117,66 +103,73 @@ ; Composition - Multiple async calls combined ; -------------------------------------------------- -(defn getUser [userId] +(defn getUser "Asynchronously fetch user data return Observable" + [userId] (Observable/create - (rx/fn [observer] + (rx/action [^rx.Subscriber s] (let [f (future (try ; simulate fetching user data via network service call with latency (Thread/sleep 60) - (-> observer (.onNext {:user-id userId - :name "Sam Harris" - :preferred-language (if (= 0 (rand-int 2)) "en-us" "es-us") })) - (-> observer .onCompleted) - (catch Exception e (-> observer (.onError e))))) ] + (-> s (.onNext {:user-id userId + :name "Sam Harris" + :preferred-language (if (= 0 (rand-int 2)) "en-us" "es-us") })) + (-> s .onCompleted) + (catch Exception e + (-> s (.onError e))))) ] ; a subscription that cancels the future if unsubscribed - (Subscriptions/create (rx/action [] (future-cancel f))))))) + (.add s (Subscriptions/create (rx/action [] (future-cancel f)))))))) -(defn getVideoBookmark [userId, videoId] +(defn getVideoBookmark "Asynchronously fetch bookmark for video return Observable" + [userId, videoId] (Observable/create - (rx/fn [observer] + (rx/action [^rx.Subscriber s] (let [f (future (try ; simulate fetching user data via network service call with latency (Thread/sleep 20) - (-> observer (.onNext {:video-id videoId - ; 50/50 chance of giving back position 0 or 0-2500 - :position (if (= 0 (rand-int 2)) 0 (rand-int 2500))})) - (-> observer .onCompleted) - (catch Exception e (-> observer (.onError e)))))] + (-> s (.onNext {:video-id videoId + ; 50/50 chance of giving back position 0 or 0-2500 + :position (if (= 0 (rand-int 2)) 0 (rand-int 2500))})) + (-> s .onCompleted) + (catch Exception e + (-> s (.onError e)))))] ; a subscription that cancels the future if unsubscribed - (Subscriptions/create (rx/action [] (future-cancel f))))))) + (.add s (Subscriptions/create (rx/action [] (future-cancel f)))))))) -(defn getVideoMetadata [videoId, preferredLanguage] +(defn getVideoMetadata "Asynchronously fetch movie metadata for a given language return Observable" + [videoId, preferredLanguage] (Observable/create - (rx/fn [observer] + (rx/action [^rx.Subscriber s] (let [f (future + (println "getVideoMetadata " videoId) (try ; simulate fetching video data via network service call with latency (Thread/sleep 50) ; contrived metadata for en-us or es-us (if (= "en-us" preferredLanguage) - (-> observer (.onNext {:video-id videoId + (-> s (.onNext {:video-id videoId :title "House of Cards: Episode 1" :director "David Fincher" :duration 3365}))) (if (= "es-us" preferredLanguage) - (-> observer (.onNext {:video-id videoId + (-> s (.onNext {:video-id videoId :title "Cámara de Tarjetas: Episodio 1" :director "David Fincher" :duration 3365}))) - (-> observer .onCompleted) - (catch Exception e (-> observer (.onError e))))) ] + (-> s .onCompleted) + (catch Exception e + (-> s (.onError e))))) ] ; a subscription that cancels the future if unsubscribed - (Subscriptions/create (rx/action [] (future-cancel f))))))) + (.add s (Subscriptions/create (rx/action [] (future-cancel f)))))))) (defn getVideoForUser [userId videoId] @@ -186,8 +179,9 @@ - user data return Observable" (let [user-observable (-> (getUser userId) - (.map (rx/fn [user] {:user-name (:name user) - :language (:preferred-language user)}))) + (.map (rx/fn [user] + {:user-name (:name user) + :language (:preferred-language user)}))) bookmark-observable (-> (getVideoBookmark userId videoId) (.map (rx/fn [bookmark] {:viewed-position (:position bookmark)}))) ; getVideoMetadata requires :language from user-observable so nest inside map function @@ -216,8 +210,6 @@ ; (comment (-> (getVideoForUser 12345 78965) - (.subscribe - (rx/action [x] (println "--- Object ---\n" x)) - (rx/action [e] (println "--- Error ---\n" e)) - (rx/action [] (println "--- Completed ---"))))) + (.toBlockingObservable) + .single)) diff --git a/language-adaptors/rxjava-clojure/src/examples/clojure/rx/lang/clojure/examples/video_example.clj b/language-adaptors/rxjava-clojure/src/examples/clojure/rx/lang/clojure/examples/video_example.clj index e3fa48fa68..11078aad22 100644 --- a/language-adaptors/rxjava-clojure/src/examples/clojure/rx/lang/clojure/examples/video_example.clj +++ b/language-adaptors/rxjava-clojure/src/examples/clojure/rx/lang/clojure/examples/video_example.clj @@ -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. @@ -108,10 +108,10 @@ "Returns an observable that executes (f observer) in a future, returning a subscription that will cancel the future." [f] - (Observable/create (rx/fn [^Observer observer] + (Observable/create (rx/action [^rx.Subscriber s] (println "Starting f") - (let [f (future (f observer))] - (Subscriptions/create (rx/action [] (future-cancel f))))))) + (let [f (future (f s))] + (.add s (Subscriptions/create (rx/action [] (future-cancel f)))))))) (defn ^Observable get-list-of-lists " @@ -120,15 +120,17 @@ Observable is the \"push\" equivalent to List " [user-id] - (future-observable (fn [^Observer observer] + (future-observable (fn [^rx.Subscriber s] (Thread/sleep 180) (dotimes [i 15] - (.onNext observer (video-list i))) - (.onCompleted observer)))) + (.onNext s (video-list i))) + (.onCompleted s)))) -(comment (.subscribe (get-list-of-lists 7777) - (rx/action* println))) +(comment (-> (get-list-of-lists 7777) + .toList + .toBlockingObservable + .single)) (defn video-list [position] @@ -137,24 +139,28 @@ (defn ^Observable video-list->videos [{:keys [position] :as video-list}] - (Observable/create (rx/fn [^Observer observer] + (Observable/create (rx/action [^rx.Subscriber s] (dotimes [i 50] - (.onNext observer (+ (* position 1000) i))) - (.onCompleted observer) - (Subscriptions/empty)))) + (.onNext s (+ (* position 1000) i))) + (.onCompleted s)))) -(comment (.subscribe (video-list->videos (video-list 2)) (rx/action* println))) +(comment (-> (video-list->videos (video-list 2)) + .toList + .toBlockingObservable + .single)) (defn ^Observable video->metadata [video-id] - (Observable/create (rx/fn [^Observer observer] - (.onNext observer {:title (str "video-" video-id "-title") - :actors ["actor1" "actor2"] - :duration 5428 }) - (.onCompleted observer) - (Subscriptions/empty)))) + (Observable/create (rx/action [^rx.Subscriber s] + (.onNext s {:title (str "video-" video-id "-title") + :actors ["actor1" "actor2"] + :duration 5428 }) + (.onCompleted s)))) -(comment (.subscribe (video->metadata 10) (rx/action* println))) +(comment (-> (video->metadata 10) + .toList + .toBlockingObservable + .single)) (defn ^Observable video->bookmark [video-id user-id] @@ -165,7 +171,10 @@ (println "onComplete") (.onCompleted observer)))) -(comment (.subscribe (video->bookmark 112345 99999) (rx/action* println))) +(comment (-> (video->bookmark 112345 99999) + .toList + .toBlockingObservable + .single)) (defn ^Observable video->rating [video-id user-id] @@ -178,5 +187,8 @@ :actual-star-rating (rand-int 5) }) (.onCompleted observer)))) -(comment (.subscribe (video->rating 234345 8888) (rx/action* println))) +(comment (-> (video->rating 234345 8888) + .toList + .toBlockingObservable + .single)) diff --git a/language-adaptors/rxjava-clojure/src/main/clojure/rx/lang/clojure/interop.clj b/language-adaptors/rxjava-clojure/src/main/clojure/rx/lang/clojure/interop.clj index 140f5367c5..5f8b803980 100644 --- a/language-adaptors/rxjava-clojure/src/main/clojure/rx/lang/clojure/interop.clj +++ b/language-adaptors/rxjava-clojure/src/main/clojure/rx/lang/clojure/interop.clj @@ -19,12 +19,19 @@ (reify ; If they want Func1, give them onSubscribe as well so Observable/create can be ; used seemlessly with rx/fn. - ~@(if (and (= prefix "rx.util.functions.Func") + ; TODO remove this when OnSubscriberFunc is removed + ~@(if (and (= prefix "rx.functions.Func") (some #{1} arities)) `(rx.Observable$OnSubscribeFunc (~'onSubscribe [~'this observer#] (~f-name observer#)))) + ; OnSubscribe is just an Action1, so add it to the list of implemented interfaces + ; so an action cab be used with Observable/create + ~@(if (and (= prefix "rx.functions.Action") + (some #{1} arities)) + `(rx.Observable$OnSubscribe)) + ~@(mapcat (clojure.core/fn [n] (let [ifc-sym (symbol (str prefix n)) arg-syms (map #(symbol (str "v" %)) (range n))] @@ -34,7 +41,7 @@ arities) )))) (defn fn* - "Given function f, returns an object that implements rx.util.functions.Func0-9 + "Given function f, returns an object that implements rx.functions.Func0-9 by delegating the call() method to the given function. If the f has the wrong arity, an ArityException will be thrown at runtime. @@ -48,28 +55,28 @@ (.reduce my-numbers (rx/fn* +)) See: - http://netflix.github.io/RxJava/javadoc/rx/util/functions/Func0.html + http://netflix.github.io/RxJava/javadoc/rx/functions/Func0.html " [f] - (reify-callable "rx.util.functions.Func" [0 1 2 3 4 5 6 7 8 9] f)) + (reify-callable "rx.functions.Func" [0 1 2 3 4 5 6 7 8 9] f)) (defn fnN* - "Given function f, returns an object that implements rx.util.functions.FuncN + "Given function f, returns an object that implements rx.functions.FuncN by delegating to the given function. Unfortunately, this can't be included in fn* because of ambiguities between the single arg call() method and the var args call method. See: - http://netflix.github.io/RxJava/javadoc/rx/util/functions/FuncN.html + http://netflix.github.io/RxJava/javadoc/rx/functions/FuncN.html " [f] - (reify rx.util.functions.FuncN + (reify rx.functions.FuncN (call [this objects] (apply f objects)))) (defmacro fn - "Like clojure.core/fn, but returns the appropriate rx.util.functions.Func* + "Like clojure.core/fn, but returns the appropriate rx.functions.Func* interface. Example: @@ -93,21 +100,22 @@ (meta &form))) (defn action* - "Given function f, returns an object that implements rx.util.functions.Action0-3 - by delegating to the given function. + "Given function f, returns an object that implements rx.functions.Action0-3 + by delegating to the given function. Also implements rx.Observable$OnSubscribe which + is just an Action1. Example: (.subscribe my-observable (rx/action* println)) See: - http://netflix.github.io/RxJava/javadoc/rx/util/functions/Action0.html + http://netflix.github.io/RxJava/javadoc/rx/functions/Action0.html " [f] - (reify-callable "rx.util.functions.Action" [0 1 2 3] f)) + (reify-callable "rx.functions.Action" [0 1 2 3] f)) (defmacro action - "Like clojure.core/fn, but returns the appropriate rx.util.functions.Action* + "Like clojure.core/fn, but returns the appropriate rx.functions.Action* interface. Example: diff --git a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/interop/DummyObservable.java b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/interop/DummyObservable.java index 107c0a9053..44a5a0b66d 100644 --- a/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/interop/DummyObservable.java +++ b/language-adaptors/rxjava-clojure/src/main/java/rx/lang/clojure/interop/DummyObservable.java @@ -23,20 +23,20 @@ public String call(Object f) { return "Object"; } - public String call(rx.util.functions.Func1 f) { - return "rx.util.functions.Func1"; + public String call(rx.functions.Func1 f) { + return "rx.functions.Func1"; } - public String call(rx.util.functions.Func2 f) { - return "rx.util.functions.Func2"; + public String call(rx.functions.Func2 f) { + return "rx.functions.Func2"; } - public String call(rx.util.functions.Action1 f) { - return "rx.util.functions.Action1"; + public String call(rx.functions.Action1 f) { + return "rx.functions.Action1"; } - public String call(rx.util.functions.Action2 f) { - return "rx.util.functions.Action2"; + public String call(rx.functions.Action2 f) { + return "rx.functions.Action2"; } } diff --git a/language-adaptors/rxjava-clojure/src/test/clojure/rx/lang/clojure/interop_test.clj b/language-adaptors/rxjava-clojure/src/test/clojure/rx/lang/clojure/interop_test.clj index 2eae2329ed..811ec94728 100644 --- a/language-adaptors/rxjava-clojure/src/test/clojure/rx/lang/clojure/interop_test.clj +++ b/language-adaptors/rxjava-clojure/src/test/clojure/rx/lang/clojure/interop_test.clj @@ -9,16 +9,16 @@ (testing "implements Func0-9" (let [f (rx/fn* vector)] (is (instance? rx.Observable$OnSubscribeFunc f)) - (is (instance? rx.util.functions.Func0 f)) - (is (instance? rx.util.functions.Func1 f)) - (is (instance? rx.util.functions.Func2 f)) - (is (instance? rx.util.functions.Func3 f)) - (is (instance? rx.util.functions.Func4 f)) - (is (instance? rx.util.functions.Func5 f)) - (is (instance? rx.util.functions.Func6 f)) - (is (instance? rx.util.functions.Func7 f)) - (is (instance? rx.util.functions.Func8 f)) - (is (instance? rx.util.functions.Func9 f)) + (is (instance? rx.functions.Func0 f)) + (is (instance? rx.functions.Func1 f)) + (is (instance? rx.functions.Func2 f)) + (is (instance? rx.functions.Func3 f)) + (is (instance? rx.functions.Func4 f)) + (is (instance? rx.functions.Func5 f)) + (is (instance? rx.functions.Func6 f)) + (is (instance? rx.functions.Func7 f)) + (is (instance? rx.functions.Func8 f)) + (is (instance? rx.functions.Func9 f)) (is (= [] (.call f))) (is (= [1] (.call f 1))) (is (= [1 2] (.call f 1 2))) @@ -35,12 +35,12 @@ ; No type hint, picks Object overload (is (= "Object" (.call dummy (rx/fn* +)))) - (is (= "rx.util.functions.Func1" + (is (= "rx.functions.Func1" (.call dummy - ^rx.util.functions.Func1 (rx/fn* +)))) - (is (= "rx.util.functions.Func2" + ^rx.functions.Func1 (rx/fn* +)))) + (is (= "rx.functions.Func2" (.call dummy - ^rx.util.functions.Func2 (rx/fn* *))))))) + ^rx.functions.Func2 (rx/fn* *))))))) (deftest test-fn (testing "makes appropriate Func*" @@ -53,12 +53,12 @@ (is (= "Object" (.call dummy (rx/fn [a] a)))) - (is (= "rx.util.functions.Func1" + (is (= "rx.functions.Func1" (.call dummy - ^rx.util.functions.Func1 (rx/fn [a] a)))) - (is (= "rx.util.functions.Func2" + ^rx.functions.Func1 (rx/fn [a] a)))) + (is (= "rx.functions.Func2" (.call dummy - ^rx.util.functions.Func2 (rx/fn [a b] (* a b)))))))) + ^rx.functions.Func2 (rx/fn [a b] (* a b)))))))) (deftest test-fnN* @@ -70,10 +70,11 @@ (testing "implements Action0-3" (let [calls (atom []) a (rx/action* #(swap! calls conj (vec %&)))] - (is (instance? rx.util.functions.Action0 a)) - (is (instance? rx.util.functions.Action1 a)) - (is (instance? rx.util.functions.Action2 a)) - (is (instance? rx.util.functions.Action3 a)) + (is (instance? rx.Observable$OnSubscribe a)) + (is (instance? rx.functions.Action0 a)) + (is (instance? rx.functions.Action1 a)) + (is (instance? rx.functions.Action2 a)) + (is (instance? rx.functions.Action3 a)) (.call a) (.call a 1) (.call a 1 2) @@ -85,12 +86,12 @@ (is (= "Object" (.call dummy (rx/action* println)))) - (is (= "rx.util.functions.Action1" + (is (= "rx.functions.Action1" (.call dummy - ^rx.util.functions.Action1 (rx/action* println)))) - (is (= "rx.util.functions.Action2" + ^rx.functions.Action1 (rx/action* println)))) + (is (= "rx.functions.Action2" (.call dummy - ^rx.util.functions.Action2 (rx/action* prn))))))) + ^rx.functions.Action2 (rx/action* prn))))))) (deftest test-action (testing "makes appropriate Action*" @@ -105,16 +106,16 @@ (is (= "Object" (.call dummy (rx/action [a] a)))) - (is (= "rx.util.functions.Action1" + (is (= "rx.functions.Action1" (.call dummy - ^rx.util.functions.Action1 (rx/action [a] a)))) - (is (= "rx.util.functions.Action2" + ^rx.functions.Action1 (rx/action [a] a)))) + (is (= "rx.functions.Action2" (.call dummy - ^rx.util.functions.Action2 (rx/action [a b] (* a b)))))))) + ^rx.functions.Action2 (rx/action [a b] (* a b)))))))) (deftest test-basic-usage - (testing "can create an observable" + (testing "can create an observable with old style fn" (is (= 99 (-> (Observable/create (rx/fn [^rx.Observer o] (.onNext o 99) @@ -123,6 +124,14 @@ .toBlockingObservable .single)))) + (testing "can create an observable with new-style action" + (is (= 99 + (-> (Observable/create (rx/action [^rx.Subscriber s] + (when-not (.isUnsubscribed s) + (.onNext s 99)) + (.onCompleted s))) + .toBlockingObservable + .single)))) (testing "can pass rx/fn to map and friends" (is (= (+ 1 4 9) (-> (Observable/from [1 2 3]) diff --git a/language-adaptors/rxjava-groovy/src/examples/groovy/rx/lang/groovy/examples/RxExamples.groovy b/language-adaptors/rxjava-groovy/src/examples/groovy/rx/lang/groovy/examples/RxExamples.groovy index 971c65f330..8d7eabb3d7 100644 --- a/language-adaptors/rxjava-groovy/src/examples/groovy/rx/lang/groovy/examples/RxExamples.groovy +++ b/language-adaptors/rxjava-groovy/src/examples/groovy/rx/lang/groovy/examples/RxExamples.groovy @@ -19,7 +19,8 @@ import rx.Observable; import rx.Observer; import rx.Subscription; import rx.subscriptions.Subscriptions; -import rx.util.functions.Func1; +import rx.functions.Action0 +import rx.functions.Func1; // -------------------------------------------------- // Hello World! @@ -123,13 +124,13 @@ def customObservableNonBlocking() { }); t.start(); - return new Subscription() { - public void unsubscribe() { + return Subscriptions.create(new Action0() { + public void call() { // Ask the thread to stop doing work. // For this simple example it just interrupts. t.interrupt(); } - }; + }); }; }); } diff --git a/language-adaptors/rxjava-groovy/src/examples/groovy/rx/lang/groovy/examples/VideoExample.groovy b/language-adaptors/rxjava-groovy/src/examples/groovy/rx/lang/groovy/examples/VideoExample.groovy index 8c1ec2392c..5323349fb1 100644 --- a/language-adaptors/rxjava-groovy/src/examples/groovy/rx/lang/groovy/examples/VideoExample.groovy +++ b/language-adaptors/rxjava-groovy/src/examples/groovy/rx/lang/groovy/examples/VideoExample.groovy @@ -19,7 +19,7 @@ import rx.Observable; import rx.Observer; import rx.Subscription; import rx.subscriptions.BooleanSubscription; -import rx.util.functions.Func1; +import rx.functions.Func1; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyActionWrapper.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyActionWrapper.java index 24394e1db7..4c05c96b4a 100644 --- a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyActionWrapper.java +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyActionWrapper.java @@ -16,11 +16,11 @@ package rx.lang.groovy; import groovy.lang.Closure; -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; +import rx.functions.Action; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Action2; +import rx.functions.Action3; /** * Concrete wrapper that accepts a {@link Closure} and produces any needed Rx {@link Action}. diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyCreateWrapper.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyCreateWrapper.java new file mode 100644 index 0000000000..c94b0aaf57 --- /dev/null +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyCreateWrapper.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.lang.groovy; + +import groovy.lang.Closure; +import rx.Observable.OnSubscribe; +import rx.Subscriber; +import rx.Subscription; + +public class GroovyCreateWrapper implements OnSubscribe { + + private final Closure closure; + + public GroovyCreateWrapper(Closure closure) { + this.closure = closure; + } + + @Override + public void call(Subscriber op) { + Object o = closure.call(op); + /* + * If the new signature is being used, we will get NULL back. + * If the old is being used we will get a Subscription back. + */ + if (o != null) { + op.add((Subscription) o); + } + } + +} diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java index 9937ae1a9a..4adb8de014 100644 --- a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/GroovyFunctionWrapper.java @@ -16,18 +16,18 @@ package rx.lang.groovy; import groovy.lang.Closure; -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; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.functions.Func3; +import rx.functions.Func4; +import rx.functions.Func5; +import rx.functions.Func6; +import rx.functions.Func7; +import rx.functions.Func8; +import rx.functions.Func9; +import rx.functions.FuncN; +import rx.functions.Function; /** * Concrete wrapper that accepts a {@link Closure} and produces any needed Rx {@link Function}. diff --git a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/RxGroovyExtensionModule.java b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/RxGroovyExtensionModule.java index a4cf011d85..8f12c8430b 100644 --- a/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/RxGroovyExtensionModule.java +++ b/language-adaptors/rxjava-groovy/src/main/java/rx/lang/groovy/RxGroovyExtensionModule.java @@ -16,27 +16,22 @@ package rx.lang.groovy; import groovy.lang.Closure; -import groovy.lang.GroovySystem; import groovy.lang.MetaMethod; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Properties; import org.codehaus.groovy.reflection.CachedClass; import org.codehaus.groovy.reflection.ReflectionCache; import org.codehaus.groovy.runtime.m12n.ExtensionModule; -import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl; import rx.Observable; import rx.Observable.OnSubscribeFunc; +import rx.functions.Action; +import rx.functions.Function; import rx.observables.BlockingObservable; -import rx.util.functions.Action; -import rx.util.functions.Function; /** * ExtensionModule that adds extension methods to support groovy.lang.Closure @@ -75,6 +70,9 @@ public List getMetaMethods() { } private MetaMethod createMetaMethod(final Method m) { + if (m.getDeclaringClass().equals(Observable.class) && m.getName().equals("create")) { + return specialCasedOverrideForCreate(m); + } return new MetaMethod() { @Override @@ -109,12 +107,11 @@ public Object invoke(Object object, Object[] arguments) { if (o instanceof Closure) { if (Action.class.isAssignableFrom(m.getParameterTypes()[i])) { newArgs[i] = new GroovyActionWrapper((Closure) o); - } else if(OnSubscribeFunc.class.isAssignableFrom(m.getParameterTypes()[i])) { + } else if (OnSubscribeFunc.class.isAssignableFrom(m.getParameterTypes()[i])) { newArgs[i] = new GroovyOnSubscribeFuncWrapper((Closure) o); } else { newArgs[i] = new GroovyFunctionWrapper((Closure) o); } - } else { newArgs[i] = o; } @@ -152,4 +149,55 @@ public CachedClass[] getParameterTypes() { } }; } + + /** + * Special case until we finish migrating off the deprecated 'create' method signature + */ + private MetaMethod specialCasedOverrideForCreate(final Method m) { + return new MetaMethod() { + + @Override + public int getModifiers() { + return m.getModifiers(); + } + + @Override + public String getName() { + return m.getName(); + } + + @Override + public Class getReturnType() { + return m.getReturnType(); + } + + @Override + public CachedClass getDeclaringClass() { + return ReflectionCache.getCachedClass(m.getDeclaringClass()); + } + + @Override + public Object invoke(Object object, final Object[] arguments) { + return Observable.create(new GroovyCreateWrapper((Closure) arguments[0])); + } + + @SuppressWarnings("rawtypes") + @Override + public CachedClass[] getParameterTypes() { + Class[] pts = m.getParameterTypes(); + CachedClass[] cc = new CachedClass[pts.length]; + for (int i = 0; i < pts.length; i++) { + if (Function.class.isAssignableFrom(pts[i])) { + // function type to be replaced by closure + cc[i] = ReflectionCache.getCachedClass(Closure.class); + } else { + // non-function type + cc[i] = ReflectionCache.getCachedClass(pts[i]); + } + } + return cc; + } + }; + } + } 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 9c6e244149..1bc87c0ee5 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 @@ -15,28 +15,21 @@ */ package rx.lang.groovy -import static org.mockito.Matchers.*; -import static org.mockito.Mockito.*; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; -import static org.junit.Assert.*; - -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import rx.Notification; -import rx.Observable; -import rx.Observable.OnSubscribeFunc; -import rx.Observer; -import rx.Subscription; -import rx.observables.GroupedObservable; -import rx.subscriptions.Subscriptions; -import rx.util.functions.Func1; +import static org.junit.Assert.* +import static org.mockito.Matchers.* +import static org.mockito.Mockito.* + +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +import rx.Notification +import rx.Observable +import rx.Observer +import rx.Subscription +import rx.Observable.OnSubscribeFunc +import rx.subscriptions.Subscriptions def class ObservableTests { @@ -278,7 +271,7 @@ def class ObservableTests { assertEquals("one", s) } - @Test(expected = IllegalStateException.class) + @Test(expected = IllegalArgumentException.class) public void testSingle2() { Observable.from("one", "two").toBlockingObservable().single({ x -> x.length() == 3}) } @@ -537,12 +530,7 @@ def class ObservableTests { observer.onNext("hello_" + count); observer.onCompleted(); - return new Subscription() { - - public void unsubscribe() { - // unregister ... will never be called here since we are executing synchronously - } - }; + return Subscriptions.empty(); } } } \ No newline at end of file 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 index 509b7b0ca5..414a992392 100644 --- 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 @@ -19,8 +19,8 @@ import org.junit.Test import rx.Observable import rx.Scheduler -import rx.concurrency.Schedulers -import rx.util.functions.Func1 +import rx.schedulers.Schedulers +import rx.functions.Func1 class TestParallel { 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 index 6beb4f1d59..d85ffbbf41 100644 --- 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 @@ -15,17 +15,17 @@ */ package rx.lang.jruby; -import org.jruby.RubyProc; import org.jruby.Ruby; +import org.jruby.RubyProc; +import org.jruby.javasupport.JavaUtil; 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; +import rx.functions.Action; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Action2; +import rx.functions.Action3; /** * Concrete wrapper that accepts a {@link RubyProc} and produces any needed Rx {@link Action}. 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 index f049827d8e..d372ee136a 100644 --- 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 @@ -15,24 +15,24 @@ */ package rx.lang.jruby; -import org.jruby.RubyProc; import org.jruby.Ruby; +import org.jruby.RubyProc; +import org.jruby.javasupport.JavaUtil; 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; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.functions.Func3; +import rx.functions.Func4; +import rx.functions.Func5; +import rx.functions.Func6; +import rx.functions.Func7; +import rx.functions.Func8; +import rx.functions.Func9; +import rx.functions.FuncN; +import rx.functions.Function; /** * Concrete wrapper that accepts a {@link RubyProc} and produces any needed Rx {@link Function}. diff --git a/language-adaptors/rxjava-kotlin/README.md b/language-adaptors/rxjava-kotlin/README.md index ffd2c05d79..c0eb072c26 100644 --- a/language-adaptors/rxjava-kotlin/README.md +++ b/language-adaptors/rxjava-kotlin/README.md @@ -3,11 +3,11 @@ 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() +Observable.create(OnSubscribeFunc { + it!!.onNext("Hello") + it.onCompleted() Subscriptions.empty() -}!!.subscribe { result -> +})!!.subscribe { result -> a!!.received(result) } ``` @@ -21,7 +21,7 @@ import rx.lang.kotlin.* observer.onNext("Hello") observer.onCompleted() Subscriptions.empty()!! -}.asObservable().subscribe { result -> +}.asObservableFunc().subscribe { result -> a!!.received(result) } ``` diff --git a/language-adaptors/rxjava-kotlin/build.gradle b/language-adaptors/rxjava-kotlin/build.gradle index 9beef6d08c..aa44049d05 100644 --- a/language-adaptors/rxjava-kotlin/build.gradle +++ b/language-adaptors/rxjava-kotlin/build.gradle @@ -4,7 +4,7 @@ buildscript { } dependencies { - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.6.800' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.6.1673' } } @@ -13,7 +13,7 @@ apply plugin: 'osgi' dependencies { compile project(':rxjava-core') - compile 'org.jetbrains.kotlin:kotlin-stdlib:0.6.800' + compile 'org.jetbrains.kotlin:kotlin-stdlib:0.6.1673' provided 'junit:junit-dep:4.10' provided 'org.mockito:mockito-core:1.8.5' } 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 index 62293d5448..d5eb8420f5 100644 --- 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 @@ -16,12 +16,23 @@ package rx.lang.kotlin -import rx.Subscription import rx.Observer import rx.Observable +import rx.Observable.OnSubscribe +import rx.Subscription +import rx.Observable.OnSubscribeFunc + + +public fun Function1, Unit>.asObservable(): Observable { + return Observable.create(OnSubscribe{ t1 -> + this(t1!!) + })!! +} -public fun Function1, Subscription>.asObservable(): Observable { - return Observable.create { this(it!!) }!! +public fun Function1, Subscription>.asObservableFunc(): Observable { + return Observable.create(OnSubscribeFunc{ op -> + this(op!!) + })!! } public fun Function0>.defer(): Observable { @@ -41,11 +52,11 @@ public fun Throwable.asObservable(): Observable { } public fun Pair.asObservable(): Observable { - return Observable.from(this.component1(), this.component2())!! + return Observable.from(listOf(this.component1(), this.component2()))!! } public fun Triple.asObservable(): Observable { - return Observable.from(this.component1(), this.component2(), this.component3())!! + return Observable.from(listOf(this.component1(), this.component2(), this.component3()))!! } public fun Pair, Observable>.merge(): Observable { 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 index d826585ced..cf4d3787b3 100644 --- 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 @@ -35,27 +35,18 @@ import rx.lang.kotlin.BasicKotlinTests.AsyncObservable /** * This class use plain Kotlin without extensions from the language adaptor */ -public class BasicKotlinTests { +public class BasicKotlinTests:KotlinTests() { - [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{ + + Observable.create(OnSubscribeFunc { it!!.onNext("Hello") it.onCompleted() Subscriptions.empty() - }!!.subscribe { result -> + })!!.subscribe { result -> a!!.received(result) } @@ -64,7 +55,7 @@ public class BasicKotlinTests { [Test] public fun testFilter() { - Observable.from(1, 2, 3)!!.filter { it!! >= 2 }!!.subscribe(received()) + Observable.from(listOf(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); @@ -72,12 +63,12 @@ public class BasicKotlinTests { [Test] public fun testLast() { - assertEquals("three", Observable.from("one", "two", "three")!!.toBlockingObservable()!!.last()) + assertEquals("three", Observable.from(listOf("one", "two", "three"))!!.toBlockingObservable()!!.last()) } [Test] public fun testLastWithPredicate() { - assertEquals("two", Observable.from("one", "two", "three")!!.toBlockingObservable()!!.last { x -> x!!.length == 3 }) + assertEquals("two", Observable.from(listOf("one", "two", "three"))!!.toBlockingObservable()!!.last { x -> x!!.length == 3 }) } [Test] @@ -88,7 +79,7 @@ public class BasicKotlinTests { [Test] public fun testMap2() { - Observable.from(1, 2, 3)!!.map { v -> "hello_$v" }!!.subscribe((received())) + Observable.from(listOf(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") @@ -96,7 +87,7 @@ public class BasicKotlinTests { [Test] public fun testMaterialize() { - Observable.from(1, 2, 3)!!.materialize()!!.subscribe((received())) + Observable.from(listOf(1, 2, 3))!!.materialize()!!.subscribe((received())) verify(a, times(4))!!.received(any(javaClass>())) verify(a, times(0))!!.error(any(javaClass())) } @@ -104,13 +95,13 @@ public class BasicKotlinTests { [Test] public fun testMergeDelayError() { Observable.mergeDelayError( - Observable.from(1, 2, 3), + Observable.from(listOf(1, 2, 3)), Observable.merge( Observable.from(6), Observable.error(NullPointerException()), Observable.from(7) ), - Observable.from(4, 5) + Observable.from(listOf(4, 5)) )!!.subscribe(received(), { e -> a!!.error(e) }) verify(a, times(1))!!.received(1) verify(a, times(1))!!.received(2) @@ -125,13 +116,13 @@ public class BasicKotlinTests { [Test] public fun testMerge() { Observable.merge( - Observable.from(1, 2, 3), + Observable.from(listOf(1, 2, 3)), Observable.merge( Observable.from(6), Observable.error(NullPointerException()), Observable.from(7) ), - Observable.from(4, 5) + Observable.from(listOf(4, 5)) )!!.subscribe(received(), { e -> a!!.error(e) }) verify(a, times(1))!!.received(1) verify(a, times(1))!!.received(2) @@ -166,7 +157,7 @@ public class BasicKotlinTests { [Test] public fun testFromWithObjects() { val list = listOf(1, 2, 3, 4, 5) - assertEquals(2, Observable.from(list, 6)!!.count()!!.toBlockingObservable()!!.single()) + assertEquals(2, Observable.from(listOf(list, 6))!!.count()!!.toBlockingObservable()!!.single()) } [Test] @@ -185,7 +176,7 @@ public class BasicKotlinTests { [Test] public fun testSkipTake() { - Observable.from(1, 2, 3)!!.skip(1)!!.take(1)!!.subscribe(received()) + Observable.from(listOf(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) @@ -193,7 +184,7 @@ public class BasicKotlinTests { [Test] public fun testSkip() { - Observable.from(1, 2, 3)!!.skip(2)!!.subscribe(received()) + Observable.from(listOf(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) @@ -201,7 +192,7 @@ public class BasicKotlinTests { [Test] public fun testTake() { - Observable.from(1, 2, 3)!!.take(2)!!.subscribe(received()) + Observable.from(listOf(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) @@ -215,7 +206,7 @@ public class BasicKotlinTests { [Test] public fun testTakeWhile() { - Observable.from(1, 2, 3)!!.takeWhile { x -> x!! < 3 }!!.subscribe(received()) + Observable.from(listOf(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) @@ -223,7 +214,7 @@ public class BasicKotlinTests { [Test] public fun testTakeWhileWithIndex() { - Observable.from(1, 2, 3)!!.takeWhileWithIndex { x, i -> i!! < 2 }!!.subscribe(received()) + Observable.from(listOf(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) @@ -251,35 +242,35 @@ public class BasicKotlinTests { [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 }) + assertEquals("two", Observable.from(listOf("one", "two"))!!.toBlockingObservable()!!.lastOrDefault("default") { x -> x!!.length == 3 }) + assertEquals("default", Observable.from(listOf("one", "two"))!!.toBlockingObservable()!!.lastOrDefault("default") { x -> x!!.length > 3 }) } - [Test(expected = javaClass())] + [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 } + Observable.from(listOf("one", "two"))!!.toBlockingObservable()!!.single { x -> x!!.length == 3 } fail() } [Test] public fun testDefer() { - Observable.defer { Observable.from(1, 2) }!!.subscribe(received()) + Observable.defer { Observable.from(listOf(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()) + Observable.from(listOf(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 o1 = Observable.from(listOf(1, 2, 3))!! + val o2 = Observable.from(listOf(4, 5, 6))!! + val o3 = Observable.from(listOf(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]) @@ -289,9 +280,9 @@ public class BasicKotlinTests { [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 o1 = Observable.from(listOf(1, 2, 3))!! + val o2 = Observable.from(listOf(4, 5, 6))!! + val o3 = Observable.from(listOf(7, 8, 9))!! val values = Observable.zip(listOf(o1, o2, o3)) { args -> listOf(*args) }!!.toList()!!.toBlockingObservable()!!.single()!! assertEquals(listOf(1, 4, 7), values[0]) @@ -303,14 +294,13 @@ public class BasicKotlinTests { public fun testGroupBy() { var count = 0 - Observable.from("one", "two", "three", "four", "five", "six")!! + Observable.from(listOf("one", "two", "three", "four", "five", "six"))!! .groupBy { s -> s!!.length }!! - .mapMany { groupObervable -> + .flatMap { groupObervable -> groupObervable!!.map { s -> "Value: $s Group ${groupObervable.getKey()}" } - }!! - .toBlockingObservable()!!.forEach { s -> + }!!.toBlockingObservable()!!.forEach { s -> println(s) count++ } @@ -318,18 +308,14 @@ public class BasicKotlinTests { 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)!! + return Observable.from(listOf(1, 3, 2, 5, 4))!! } val onSubscribe: TestOnSubscribe @@ -345,23 +331,23 @@ public class BasicKotlinTests { } class AsyncObservable : OnSubscribeFunc{ - override fun onSubscribe(t1: Observer?): Subscription? { + override fun onSubscribe(op: Observer?): Subscription? { thread { Thread.sleep(50) - t1!!.onNext(1) - t1.onNext(2) - t1.onNext(3) - t1.onCompleted() + op!!.onNext(1) + op.onNext(2) + op.onNext(3) + op.onCompleted() } return Subscriptions.empty() } } class TestOnSubscribe(val count: Int) : OnSubscribeFunc{ - override fun onSubscribe(t1: Observer?): Subscription? { - t1!!.onNext("hello_$count") - t1.onCompleted() - return Subscription { } + override fun onSubscribe(op: Observer?): Subscription? { + op!!.onNext("hello_$count") + op.onCompleted() + return Subscriptions.empty()!! } } 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 index b5ffbcddec..c9fa67a6a1 100644 --- 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 @@ -33,18 +33,8 @@ 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 +public class ExtensionTests : KotlinTests() { - [Before] - public fun before() { - MockitoAnnotations.initMocks(this) - } - - fun received(): (T?) -> Unit { - return {(result: T?) -> a!!.received(result) } - } [Test] public fun testCreate() { @@ -53,7 +43,7 @@ public class ExtensionTests { observer.onNext("Hello") observer.onCompleted() Subscriptions.empty()!! - }.asObservable().subscribe { result -> + }.asObservableFunc().subscribe { result -> a!!.received(result) } @@ -226,7 +216,7 @@ public class ExtensionTests { [Test] public fun testForEach() { - asyncObservable.asObservable().toBlockingObservable()!!.forEach(received()) + asyncObservable.asObservableFunc().toBlockingObservable()!!.forEach(received()) verify(a, times(1))!!.received(1) verify(a, times(1))!!.received(2) verify(a, times(1))!!.received(3) @@ -234,7 +224,7 @@ public class ExtensionTests { [Test(expected = javaClass())] public fun testForEachWithError() { - asyncObservable.asObservable().toBlockingObservable()!!.forEach { throw RuntimeException("err") } + asyncObservable.asObservableFunc().toBlockingObservable()!!.forEach { throw RuntimeException("err") } fail("we expect an exception to be thrown") } @@ -259,9 +249,9 @@ public class ExtensionTests { [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 o1 = Triple(1, 2, 3).asObservable() + val o2 = Triple(4, 5, 6).asObservable() + val o3 = Triple(7, 8, 9).asObservable() val values = Observable.zip(o1, o2, o3) { a, b, c -> listOf(a, b, c) }!!.toList()!!.toBlockingObservable()!!.single()!! assertEquals(listOf(1, 4, 7), values[0]) @@ -269,16 +259,10 @@ public class ExtensionTests { 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 { } + Subscriptions.empty()!! } val asyncObservable: (Observer) -> Subscription = { observer -> @@ -314,7 +298,7 @@ public class ExtensionTests { val observable: Observable get(){ - return onSubscribe.asObservable() + return onSubscribe.asObservableFunc() } } diff --git a/language-adaptors/rxjava-kotlin/src/test/kotlin/rx/lang/kotlin/KotlinTests.kt b/language-adaptors/rxjava-kotlin/src/test/kotlin/rx/lang/kotlin/KotlinTests.kt new file mode 100644 index 0000000000..59827c7b5c --- /dev/null +++ b/language-adaptors/rxjava-kotlin/src/test/kotlin/rx/lang/kotlin/KotlinTests.kt @@ -0,0 +1,42 @@ +/** + * Copyright 2014 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.MockitoAnnotations +import org.junit.Before +import rx.Observable +import org.mockito.Mock + +public abstract class KotlinTests { + [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) } + } + + public trait ScriptAssertion{ + fun error(e: Throwable?) + + fun received(e: Any?) + } +} \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/Rationale.md b/language-adaptors/rxjava-scala/Rationale.md index 5afb2392cc..a8cd47d954 100644 --- a/language-adaptors/rxjava-scala/Rationale.md +++ b/language-adaptors/rxjava-scala/Rationale.md @@ -91,7 +91,7 @@ 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 { …} +object Observable { …} trait Observable[+T] { def asJavaObservable: rx.Observable[_ <: T] } diff --git a/language-adaptors/rxjava-scala/ReleaseNotes.md b/language-adaptors/rxjava-scala/ReleaseNotes.md new file mode 100644 index 0000000000..4fdb3a06fd --- /dev/null +++ b/language-adaptors/rxjava-scala/ReleaseNotes.md @@ -0,0 +1,228 @@ +RxScala Release Notes +===================== + +This release of the RxScala bindings builds on the previous 0.15 release to make the Rx bindings for Scala +include all Rx types. In particular this release focuses on fleshing out the bindings for the `Subject` and `Scheduler` +types, as well as aligning the constructor functions for `Observable` with those in the RxJava. + +Expect to see ongoing additions to make the Scala binding match the equivalent underlying Java API, +as well as minor changes in the existing API as we keep fine-tuning the experience on our way to a V1.0 release. + +Observer +-------- + +In this release we have made the `asJavaObserver` property in `Observable[T]`as well the the factory method in the +companion object that takes an `rx.Observer` private to the Scala bindings package, thus properly hiding irrelevant +implementation details from the user-facing API. The `Observer[T]` trait now looks like a clean, native Scala type: + +```scala +trait Observer[-T] { + def onNext(value: T): Unit + def onError(error: Throwable): Unit + def onCompleted(): Unit +} + +object Observer {...} +``` + +To create an instance of a specific `Observer`, say `Observer[SensorEvent]` in user code, you can create a new instance +of the `Observer` trait by implementing any of the methods that you care about: +```scala + val printObserver = new Observer[SensorEvent] { + override def onNext(value: SensorEvent): Unit = {...value.toString...} + } +``` + or you can use one of the overloads of the companion `Observer` object by passing in implementations of the `onNext`, + `onError` or `onCompleted` methods. + +Note that typically you do not need to create an `Observer` since all of the methods that accept an `Observer[T]` +(for instance `subscribe`) usually come with overloads that accept the individual methods +`onNext`, `onError`, and `onCompleted` and will automatically create an `Observer` for you under the covers. + +While *technically* it is a breaking change make the `asJavaObserver` property private, you should probably not have +touched `asJavaObserver` in the first place. If you really feel you need to access the underlying `rx.Observer` +call `toJava`. + +Observable +---------- + +Just like for `Observer`, the `Observable` trait now also hides its `asJavaObservable` property and makes the constructor +function in the companion object that takes an `rx.Observable` private (but leaves the companion object itself public). +Again, while *technically* this is a breaking change, this should not have any influence on user code. + +```scala +trait Observable[+T] { + def subscribe(observer: Observer[T]): Subscription = {...} + def apply(observer: Observer[T]): Subscription = {...} + ... +} +object Observable { + def create[T](func: Observer[T] => Subscription): Observable[T] = {...} + ... +} +``` + +The major changes in `Observable` are wrt to the factory methods where too libral use of overloading of the `apply` +method hindered type inference and made Scala code look unnecessarily different than that in other language bindings. +All factory methods now have their own name corresponding to the Java and .NET operators +(plus overloads that take a `Scheduler`). + +* `def from[T](future: Future[T]): Observable[T]`, +* `def from[T](iterable: Iterable[T]): Observable[T]`, +* `def error[T](exception: Throwable): Observable[T]`, +* `def empty[T]: Observable[T]`, +* `def items[T](items: T*): Observable[T], +* Extension method on `toObservable: Observable[T]` on `List[T]`. + +In the *pre-release* of this version, we expose both `apply` and `create` for the mother of all creation functions. +We would like to solicit feedback which of these two names is preferred +(or both, but there is a high probability that only one will be chosen). + +* `def apply[T](subscribe: Observer[T]=>Subscription): Observable[T]` +* `def create[T](subscribe: Observer[T] => Subscription): Observable[T]` + +Subject +------- + +The `Subject` trait now also hides the underlying Java `asJavaSubject: rx.subjects.Subject[_ >: T, _<: T]` +and takes only a single *invariant* type parameter `T`. all existing implementations of `Subject` are parametrized +by a single type, and this reflects that reality. + +```scala +trait Subject[T] extends Observable[T] with Observer[T] {} +object Subject { + def apply(): Subject[T] = {...} +} +``` +For each kind of subject, there is a class with a private constructor and a companion object that you should use +to create a new kind of subject. The subjects that are available are: + +* `AsyncSubject[T]()`, +* `BehaviorSubject[T](value)`, +* `Subject[T]()`, +* `ReplaySubject[T]()`. + +The latter is still missing various overloads http://msdn.microsoft.com/en-us/library/hh211810(v=vs.103).aspx which +you can expect to appear once they are added to the underlying RxJava implementation. + +Compared with release 0.15.1, the breaking changes in `Subject` for this release are +making `asJavaSubject` private, and collapsing its type parameters, neither of these should cause trouble, +and renaming `PublishSubject` to `Subject`. + +Schedulers +---------- + +The biggest breaking change compared to the 0.15.1 release is giving `Scheduler` the same structure as the other types. +The trait itself remains unchanged, except that we made the underlying Java representation hidden as above. +as part of this reshuffling, the scheduler package has been renamed from `rx.lang.scala.concurrency` +to `rx.lang.scala.schedulers`. There is a high probability that this package renaming will also happen in RxJava. + +```scala +trait Scheduler {...} +``` + +In the previous release, you created schedulers by selecting them from the `Schedulers` object, +as in `Schedulers.immediate` or `Schedulers.newThread` where each would return an instance of the `Scheduler` trait. +However, several of the scheduler implementations have additional methods, such as the `TestScheduler`, +which already deviated from the pattern. + +In this release, we changed this to make scheduler more like `Subject` and provide a family of schedulers +that you create using their factory function: + +* `CurrentThreadScheduler()`, +* `ExecutorScheduler(executor)`, +* `ImmediateScheduler()`, +* `NewThreadScheduler()`, +* `ScheduledExecutorServiceScheduler(scheduledExecutorService)`, +* `TestScheduler()`, +* `ThreadPoolForComputationScheduler()`, +* `ThreadPoolForIOScheduler()`. + +In the future we expect that this list will grow further with new schedulers as they are imported from .NET +(http://msdn.microsoft.com/en-us/library/system.reactive.concurrency(v=vs.103).aspx). + +To make your code compile in the new release you will have to change all occurrences of `Schedulers.xxx` +into `XxxScheduler()`, and import `rx.lang.scala.schedulers` instead of `rx.lang.scala.schedulers`. + +Subscriptions +------------- + +The `Subscription` trait in Scala now has `isUnsubscribed` as a member, effectively collapsing the old `Subscription` +and `BooleanSubscription`, and the latter has been removed from the public surface. Pending a bug fix in RxJava, +`SerialSubscription` implements its own `isUnsubscribed`. + + +```scala +trait Subscription { + def unsubscribe(): Unit = { ... } + def isUnsubscribed: Boolean = ... +} + +object Subscription {...} + ``` + + To create a `Subscription` use one of the following factory methods: + + * `Subscription{...}`, `Subscription()`, + * `CompositeSubscription(subscriptions)`, + * `MultipleAssignmentSubscription()`, + * `SerialSubscription()`. + + In case you do feel tempted to call `new Subscription{...}` directly make sure you wire up `isUnsubscribed` + and `unsubscribe()` properly, but for all practical purposes you should just use one of the factory methods. + +Notifications +------------- + +All underlying wrapped `Java` types in the `Notification` trait are made private like all previous types. The companion +objects of `Notification` now have both constructor (`apply`) and extractor (`unapply`) functions: + +```scala +object Notification {...} +trait Notification[+T] { + override def equals(that: Any): Boolean = {...} + override def hashCode(): Int = {...} + def apply[R](onNext: T=>R, onError: Throwable=>R, onCompleted: ()=>R): R = {...} +} +``` +The nested companion objects of `Notification` now have both constructor (`apply`) and extractor (`unapply`) functions: +```scala +object Notification { + object OnNext { def apply(...){}; def unapply(...){...} } + object OnError { def apply(...){}; def unapply(...){...} } + object OnCompleted { def apply(...){}; def unapply(...){...} } +} +``` +To construct a `Notification`, you import `rx.lang.scala.Notification._` and use `OnNext("hello")`, +or `OnError(new Exception("Oops!"))`, or `OnCompleted()`. + +To pattern match on a notification you create a partial function like so: `case Notification.OnNext(v) => { ... v ... }`, +or you use the `apply` function to pass in functions for each possibility. + +There are no breaking changes for notifications. + +Java Interop Helpers +-------------------- + +Since the Scala traits *wrap* the underlying Java types, yoo may occasionally will have to wrap an unwrap +between the two representations. The `JavaConversion` object provides helper functions of the form `toJavaXXX` and +`toScalaXXX` for this purpose, properly hiding how precisely the wrapped types are stored. +Note the (un)wrap conversions are defined as implicits in Scala, but in the unlikely event that you do need them +be kind to the reader of your code and call them explicitly. + +```scala +object JavaConversions { + import language.implicitConversions + + implicit def toJavaNotification[T](s: Notification[T]): rx.Notification[_ <: T] = {...} + implicit def toScalaNotification[T](s: rx.Notification[_ <: T]): Notification[T] = {...} + implicit def toJavaSubscription(s: Subscription): rx.Subscription = {...} + implicit def toScalaSubscription(s: rx.Subscription): Subscription = {...} + implicit def scalaSchedulerToJavaScheduler(s: Scheduler): rx.Scheduler = {...} + implicit def javaSchedulerToScalaScheduler(s: rx.Scheduler): Scheduler = {...} + implicit def toJavaObserver[T](s: Observer[T]): rx.Observer[_ >: T] = {...} + implicit def toScalaObserver[T](s: rx.Observer[_ >: T]): Observer[T] = {...} + implicit def toJavaObservable[T](s: Observable[T]): rx.Observable[_ <: T] = {...} + implicit def toScalaObservable[T](observable: rx.Observable[_ <: T]): Observable[T] = {...} +} +``` \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/build.gradle b/language-adaptors/rxjava-scala/build.gradle index db194a6d28..8083feaf37 100644 --- a/language-adaptors/rxjava-scala/build.gradle +++ b/language-adaptors/rxjava-scala/build.gradle @@ -23,7 +23,7 @@ sourceSets { srcDir 'src/main/scala' srcDir 'src/test/scala' srcDir 'src/examples/scala' - srcDir 'src/examples/java' + //srcDir 'src/examples/java' } java.srcDirs = [] } @@ -34,7 +34,7 @@ sourceSets { // the scala source set: scala { srcDir 'src/examples/scala' - srcDir 'src/examples/java' + //srcDir 'src/examples/java' } java.srcDirs = [] } 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 index e9b3f6be64..a8edadaf6b 100644 --- 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 @@ -16,9 +16,9 @@ package rx.lang.scala.examples; import rx.Observable; +import rx.functions.Action1; import rx.lang.scala.examples.Movie; import rx.lang.scala.examples.MovieLib; -import rx.util.functions.Action1; import static rx.lang.scala.JavaConversions.toScalaObservable; public class MovieLibUsage { 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 index 7a11bdf539..e91de4b51d 100644 --- 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 @@ -21,8 +21,8 @@ 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( + def mountainBikeMedals: Observable[Medal] = Observable.items( + Observable.items( 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"), @@ -31,7 +31,7 @@ object Olympics { Medal(1996, "Atlanta 1996", "cross-country women", "Bronze", "Susan DEMATTEI", "United States of America") ), fourYearsEmpty, - Observable( + Observable.items( 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"), @@ -40,7 +40,7 @@ object Olympics { Medal(2000, "Sydney 2000", "cross-country men", "Bronze", "Christoph SAUSER", "Switzerland") ), fourYearsEmpty, - Observable( + Observable.items( 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"), @@ -49,7 +49,7 @@ object Olympics { Medal(2004, "Athens 2004", "cross-country women", "Bronze", "Sabine SPITZ", "Germany") ), fourYearsEmpty, - Observable( + Observable.items( 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"), @@ -58,7 +58,7 @@ object Olympics { Medal(2008, "Beijing 2008", "cross-country men", "Bronze", "Nino SCHURTER", "Switzerland") ), fourYearsEmpty, - Observable( + Observable.items( 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"), @@ -80,7 +80,7 @@ object Olympics { // 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() + Observable.empty } } \ 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 index 3dbd9461fd..6b6faf4f7d 100644 --- 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 @@ -30,7 +30,7 @@ import org.junit.Test import org.scalatest.junit.JUnitSuite import rx.lang.scala._ -import rx.lang.scala.concurrency._ +import rx.lang.scala.schedulers._ @Ignore // Since this doesn't do automatic testing, don't increase build time unnecessarily class RxScalaDemo extends JUnitSuite { @@ -69,14 +69,14 @@ class RxScalaDemo extends JUnitSuite { } @Test def testSwitchOnObservableOfInt() { - // Correctly rejected with error + // 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 first = Observable.from(List(10, 11, 12)) + val second = Observable.from(List(10, 11, 12)) val b1 = (first zip second) map (p => p._1 == p._2) forall (b => b) @@ -88,8 +88,8 @@ class RxScalaDemo extends JUnitSuite { } @Test def testObservableComparisonWithForComprehension() { - val first = Observable(10, 11, 12) - val second = Observable(10, 11, 12) + val first = Observable.from(List(10, 11, 12)) + val second = Observable.from(List(10, 11, 12)) val booleans = for ((n1, n2) <- (first zip second)) yield (n1 == n2) @@ -99,8 +99,8 @@ class RxScalaDemo extends JUnitSuite { } @Test def testStartWithIsUnnecessary() { - val before = Observable(-2, -1, 0) - val source = Observable(1, 2, 3) + val before = List(-2, -1, 0).toObservable + val source = List(1, 2, 3).toObservable println((before ++ source).toBlockingObservable.toList) } @@ -124,11 +124,11 @@ class RxScalaDemo extends JUnitSuite { @Test def fattenSomeExample() { // To merge some observables which are all known already: - Observable( + List( Observable.interval(200 millis), Observable.interval(400 millis), Observable.interval(800 millis) - ).flatten.take(12).toBlockingObservable.foreach(println(_)) + ).toObservable.flatten.take(12).toBlockingObservable.foreach(println(_)) } @Test def rangeAndBufferExample() { @@ -143,7 +143,7 @@ class RxScalaDemo extends JUnitSuite { } @Test def testReduce() { - assertEquals(10, Observable(1, 2, 3, 4).reduce(_ + _).toBlockingObservable.single) + assertEquals(10, List(1, 2, 3, 4).toObservable.reduce(_ + _).toBlockingObservable.single) } @Test def testForeach() { @@ -157,7 +157,7 @@ class RxScalaDemo extends JUnitSuite { } @Test def testForComprehension() { - val observables = Observable(Observable(1, 2, 3), Observable(10, 20, 30)) + val observables = List(List(1, 2, 3).toObservable, List(10, 20, 30).toObservable).toObservable val squares = (for (o <- observables; i <- o if i % 2 == 0) yield i*i) assertEquals(squares.toBlockingObservable.toList, List(4, 100, 400, 900)) } @@ -185,14 +185,14 @@ class RxScalaDemo extends JUnitSuite { } @Test def testGroupByThenFlatMap() { - val m = Observable(1, 2, 3, 4) + val m = List(1, 2, 3, 4).toObservable 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 m = List(1, 2, 3, 4).toObservable 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) @@ -242,25 +242,42 @@ class RxScalaDemo extends JUnitSuite { waitFor(firstMedalOfEachCountry) } + @Test def groupByUntilExample() { + val numbers = Observable.interval(250 millis).take(14) + val grouped = numbers.groupByUntil[Long](x => x % 2, {case (key, obs) => obs.filter(x => x == 7)}) + val sequenced = (grouped.map({ case (key, obs) => obs.toSeq })).flatten + sequenced.subscribe(x => println(s"Emitted group: $x")) + } + + @Test def combineLatestExample() { + val first_counter = Observable.interval(250 millis) + val second_counter = Observable.interval(550 millis) + val combined_counter = first_counter.combineLatest(second_counter, + (x: Long, y: Long) => List(x,y)) take 10 + + combined_counter subscribe {x => println(s"Emitted group: $x")} + } + + @Test def olympicsExample() { - val (go, medals) = Olympics.mountainBikeMedals.publish + val medals = Olympics.mountainBikeMedals.publish medals.subscribe(println(_)) - go() + medals.connect //waitFor(medals) } @Test def exampleWithoutPublish() { - val unshared = Observable(1 to 4) + val unshared = List(1 to 4).toObservable 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 + val unshared = List(1 to 4).toObservable + val shared = unshared.publish shared.subscribe(n => println(s"subscriber 1 gets $n")) shared.subscribe(n => println(s"subscriber 2 gets $n")) - startFunc() + shared.connect } def doLater(waitTime: Duration, action: () => Unit): Unit = { @@ -269,9 +286,9 @@ class RxScalaDemo extends JUnitSuite { @Test def exampleWithoutReplay() { val numbers = Observable.interval(1000 millis).take(6) - val (startFunc, sharedNumbers) = numbers.publish + val sharedNumbers = numbers.publish sharedNumbers.subscribe(n => println(s"subscriber 1 gets $n")) - startFunc() + sharedNumbers.connect // subscriber 2 misses 0, 1, 2! doLater(3500 millis, () => { sharedNumbers.subscribe(n => println(s"subscriber 2 gets $n")) }) waitFor(sharedNumbers) @@ -288,9 +305,9 @@ class RxScalaDemo extends JUnitSuite { } @Test def testSingleOption() { - assertEquals(None, Observable(1, 2).toBlockingObservable.singleOption) - assertEquals(Some(1), Observable(1) .toBlockingObservable.singleOption) - assertEquals(None, Observable() .toBlockingObservable.singleOption) + assertEquals(None, List(1, 2).toObservable.toBlockingObservable.singleOption) + assertEquals(Some(1), List(1).toObservable.toBlockingObservable.singleOption) + assertEquals(None, List().toObservable.toBlockingObservable.singleOption) } // We can't put a general average method into Observable.scala, because Scala's Numeric @@ -301,58 +318,58 @@ class RxScalaDemo extends JUnitSuite { } @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) + println(doubleAverage(Observable.empty).toBlockingObservable.single) + println(doubleAverage(List(0.0).toObservable).toBlockingObservable.single) + println(doubleAverage(List(4.44).toObservable).toBlockingObservable.single) + println(doubleAverage(List(1, 2, 3.5).toObservable).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) + assertEquals(10, List(1, 2, 3, 4).toObservable.sum.toBlockingObservable.single) + assertEquals(6, List(4, 2).toObservable.sum.toBlockingObservable.single) + assertEquals(0, List[Int]().toObservable.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) + assertEquals(24, List(1, 2, 3, 4).toObservable.product.toBlockingObservable.single) + assertEquals(8, List(4, 2).toObservable.product.toBlockingObservable.single) + assertEquals(1, List[Int]().toObservable.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) + List("a", "b", "c").toObservable.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) + (for ((letter, index) <- List("a", "b", "c").toObservable.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)) + val o = Observable.zip(List(1, 2).toObservable, List(10, 20).toObservable, List(100, 200).toObservable) (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)) + val observables = List(List(1, 2).toObservable, List(10, 20).toObservable, List(100, 200).toObservable).toObservable (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) + assertEquals(3, List(1, 2, 3, 4).toObservable.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) + assertEquals(3, List(1, 2, 3, 4).toObservable.filter(condition).firstOrElse(10).toBlockingObservable.single) + assertEquals(10, List(-1, 0, 1).toObservable.filter(condition).firstOrElse(10).toBlockingObservable.single) } def square(x: Int): Int = { @@ -379,9 +396,9 @@ class RxScalaDemo extends JUnitSuite { } @Test def toSortedList() { - assertEquals(Seq(7, 8, 9, 10), Observable(10, 7, 8, 9).toSeq.map(_.sorted).toBlockingObservable.single) + assertEquals(Seq(7, 8, 9, 10), List(10, 7, 8, 9).toObservable.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) + assertEquals(Seq(10, 9, 8, 7), List(10, 7, 8, 9).toObservable.toSeq.map(_.sortWith(f)).toBlockingObservable.single) } @Test def timestampExample() { @@ -396,7 +413,7 @@ class RxScalaDemo extends JUnitSuite { import Notification._ o.materialize.subscribe(n => n match { case OnNext(v) => println("Got value " + v) - case OnCompleted() => println("Completed") + case OnCompleted => println("Completed") case OnError(err) => println("Error: " + err.getMessage) }) } @@ -410,25 +427,33 @@ class RxScalaDemo extends JUnitSuite { @Test def materializeExample2() { import Notification._ - Observable(1, 2, 3).materialize.subscribe(n => n match { + List(1, 2, 3).toObservable.materialize.subscribe(n => n match { case OnNext(v) => println("Got value " + v) - case OnCompleted() => println("Completed") + case OnCompleted => println("Completed") case OnError(err) => println("Error: " + err.getMessage) }) } + @Test def notificationSubtyping() { + import Notification._ + val oc1: Notification[Nothing] = OnCompleted + val oc2: Notification[Int] = OnCompleted + val oc3: rx.Notification[_ <: Int] = oc2.asJavaNotification + val oc4: rx.Notification[_ <: Any] = oc2.asJavaNotification + } + @Test def elementAtReplacement() { - assertEquals("b", Observable("a", "b", "c").drop(1).first.toBlockingObservable.single) + assertEquals("b", List("a", "b", "c").toObservable.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) + assertEquals("b", List("a", "b", "c").toObservable.drop(1).firstOrElse("!").toBlockingObservable.single) + assertEquals("!!", List("a", "b", "c").toObservable.drop(10).firstOrElse("!!").toBlockingObservable.single) } @Test def takeWhileWithIndexAlternative { val condition = true - Observable("a", "b").zipWithIndex.takeWhile{case (elem, index) => condition}.map(_._1) + List("a", "b").toObservable.zipWithIndex.takeWhile{case (elem, index) => condition}.map(_._1) } @Test def createExample() { @@ -449,4 +474,25 @@ class RxScalaDemo extends JUnitSuite { obs.toBlockingObservable.toIterable.last } -} \ No newline at end of file + @Test def timeoutExample(): Unit = { + val other = List(100L, 200L, 300L).toObservable + val result = Observable.interval(100 millis).timeout(50 millis, other).toBlockingObservable.toList + println(result) + } + + @Test def timeoutExample2(): Unit = { + val firstTimeoutSelector = () => { + Observable.timer(10 seconds, 10 seconds, ComputationScheduler()).take(1) + } + val timeoutSelector = (t: Long) => { + Observable.timer( + (500 - t * 100) max 1 millis, + (500 - t * 100) max 1 millis, + ComputationScheduler()).take(1) + } + val other = List(100L, 200L, 300L).toObservable + val result = Observable.interval(100 millis).timeout(firstTimeoutSelector, timeoutSelector, other).toBlockingObservable.toList + println(result) + } + +} 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 index bc1817bdf4..bf4afa9c7a 100644 --- 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 @@ -1,18 +1,33 @@ +/** + * 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 scala.concurrent.duration.DurationInt +import scala.language.postfixOps import org.junit.Test +import org.mockito.Matchers._ +import org.mockito.Mockito._ 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 +import rx.lang.scala._ +import rx.lang.scala.schedulers.TestScheduler +import rx.observers.TestObserver 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]]) @@ -20,7 +35,7 @@ class TestSchedulerExample extends JUnitSuite { val o = Observable.interval(1 second, scheduler) // Wrap Java Observer in Scala Observer, then subscribe - val sub = o.subscribe(Observer(observer)) + val sub = o.subscribe(Observer(new TestObserver(observer))) verify(observer, never).onNext(0L) verify(observer, never).onCompleted() @@ -46,3 +61,5 @@ class TestSchedulerExample extends JUnitSuite { } } + + 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 index 77cb577277..3c6aa5c29a 100644 --- 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 @@ -21,7 +21,7 @@ import java.{ lang => jlang } import scala.language.implicitConversions import scala.collection.Seq -import rx.util.functions._ +import rx.functions._ import rx.lang.scala.JavaConversions._ diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/JavaConversions.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/JavaConversions.scala index 276322f935..d003842be4 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/JavaConversions.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/JavaConversions.scala @@ -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.lang.scala /** @@ -9,7 +24,7 @@ package rx.lang.scala object JavaConversions { import language.implicitConversions - implicit def toJavaNotification[T](s: Notification[T]): rx.Notification[_ <: T] = s.asJava + implicit def toJavaNotification[T](s: Notification[T]): rx.Notification[_ <: T] = s.asJavaNotification implicit def toScalaNotification[T](s: rx.Notification[_ <: T]): Notification[T] = Notification(s) @@ -18,9 +33,12 @@ object JavaConversions { 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 scalaInnerToJavaInner(s: Inner): rx.Scheduler.Inner = s.asJavaInner + implicit def javaInnerToScalaInner(s: rx.Scheduler.Inner): Inner = Inner(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) @@ -29,8 +47,7 @@ object JavaConversions { implicit def toScalaObservable[T](observable: rx.Observable[_ <: T]): Observable[T] = { new Observable[T]{ - def asJavaObservable = observable + val asJavaObservable = observable } } - } \ No newline at end of file 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 index 8a254af08c..84f1642dbc 100644 --- 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 @@ -19,12 +19,50 @@ package rx.lang.scala * Emitted by Observables returned by [[rx.lang.scala.Observable.materialize]]. */ sealed trait Notification[+T] { - def asJava: rx.Notification[_ <: T] + private [scala] val asJavaNotification: rx.Notification[_ <: T] + override def equals(that: Any): Boolean = that match { - case other: Notification[_] => asJava.equals(other.asJava) + case other: Notification[_] => asJavaNotification.equals(other.asJavaNotification) case _ => false } - override def hashCode(): Int = asJava.hashCode() + override def hashCode(): Int = asJavaNotification.hashCode() + + /** + * Invokes the function corresponding to the notification. + * + * @param onNext + * The function to invoke for an [[rx.lang.scala.Notification.OnNext]] notification. + * @param onError + * The function to invoke for an [[rx.lang.scala.Notification.OnError]] notification. + * @param onCompleted + * The function to invoke for an [[rx.lang.scala.Notification.OnCompleted]] notification. + */ + def accept[R](onNext: T=>R, onError: Throwable=>R, onCompleted: ()=>R): R = { + this match { + case Notification.OnNext(value) => onNext(value) + case Notification.OnError(error) => onError(error) + case Notification.OnCompleted => onCompleted() + } + } + + def apply[R](onNext: T=>R, onError: Throwable=>R, onCompleted: ()=>R): R = + accept(onNext, onError, onCompleted) + + /** + * Invokes the observer corresponding to the notification + * + * @param observer + * The observer that to observe the notification + */ + def accept(observer: Observer[T]): Unit = { + this match { + case Notification.OnNext(value) => observer.onNext(value) + case Notification.OnError(error) => observer.onError(error) + case Notification.OnCompleted => observer.onCompleted() + } + } + + def apply(observer: Observer[T]): Unit = accept(observer) } /** @@ -34,71 +72,87 @@ sealed trait Notification[+T] { * {{{ * import Notification._ * Observable(1, 2, 3).materialize.subscribe(n => n match { - * case OnNext(v) => println("Got value " + v) + * case OnNext(v) => println("Got value " + v) * case OnCompleted() => println("Completed") - * case OnError(err) => println("Error: " + err.getMessage) + * case OnError(err) => println("Error: " + err.getMessage) * }) * }}} */ object Notification { - def apply[T](n: rx.Notification[_ <: T]): Notification[T] = n.getKind match { + private [scala] 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.OnCompleted => OnCompleted 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 - override def toString = s"OnNext($value)" - } - + object OnNext { + /** + * Constructor for onNext notifications. + * + * @param value + * The item passed to the onNext method. + */ def apply[T](value: T): Notification[T] = { - Notification(new rx.Notification[T](value)) + Notification(rx.Notification.createOnNext[T](value)) } - def unapply[U](n: Notification[U]): Option[U] = n match { - case n2: OnNext[U] => Some(n.asJava.getValue) + /** + * Extractor for onNext notifications. + * @param notification + * The [[rx.lang.scala.Notification]] to be destructed. + * @return + * The item contained in this notification. + */ + def unapply[U](notification: Notification[U]): Option[U] = notification match { + case onNext: OnNext[U] => Some(onNext.value) case _ => None } } - - class OnError[+T](val asJava: rx.Notification[_ <: T]) extends Notification[T] { - def error: Throwable = asJava.getThrowable - override def toString = s"OnError($error)" + + class OnNext[+T] private[scala] (val asJavaNotification: rx.Notification[_ <: T]) extends Notification[T] { + def value: T = asJavaNotification.getValue + override def toString = s"OnNext($value)" } - + object OnError { + /** + * Constructor for onError notifications. + * + * @param error + * The exception passed to the onNext method. + */ def apply[T](error: Throwable): Notification[T] = { - Notification(new rx.Notification[T](error)) + Notification(rx.Notification.createOnError[T](error)) } - def unapply[U](n: Notification[U]): Option[Throwable] = n match { - case n2: OnError[U] => Some(n2.asJava.getThrowable) + /** + * Destructor for onError notifications. + * + * @param notification + * The [[rx.lang.scala.Notification]] to be deconstructed + * @return + * The [[java.lang.Throwable]] value contained in this notification. + */ + def unapply[U](notification: Notification[U]): Option[Throwable] = notification match { + case onError: OnError[U] => Some(onError.error) case _ => None } } - - class OnCompleted[T](val asJava: rx.Notification[_ <: T]) extends Notification[T] { - override def toString = "OnCompleted()" - } - - object OnCompleted { - def apply[T](): Notification[T] = { - Notification(new rx.Notification()) - } + class OnError[+T] private[scala] (val asJavaNotification: rx.Notification[_ <: T]) extends Notification[T] { + def error: Throwable = asJavaNotification.getThrowable + override def toString = s"OnError($error)" + } - def unapply[U](n: Notification[U]): Option[Unit] = n match { - case n2: OnCompleted[U] => Some() - case _ => None - } + object OnCompleted extends Notification[Nothing] { + override def toString = "OnCompleted" + val asJavaNotification = rx.Notification.createOnCompleted[Nothing]() } } 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 8d6ffbb48e..9243ec3e6c 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 @@ -16,8 +16,11 @@ package rx.lang.scala -import rx.util.functions.FuncN +import rx.functions.FuncN import rx.Observable.OnSubscribeFunc +import rx.lang.scala.observables.ConnectableObservable +import scala.concurrent.duration + /** * The Observable interface that implements the Reactive Pattern. @@ -74,12 +77,21 @@ trait Observable[+T] { import scala.collection.Seq import scala.concurrent.duration.{Duration, TimeUnit} - import rx.util.functions._ + import rx.functions._ import rx.lang.scala.observables.BlockingObservable import ImplicitFunctionConversions._ import JavaConversions._ - def asJavaObservable: rx.Observable[_ <: T] + private [scala] val asJavaObservable: rx.Observable[_ <: T] + + /** + * $subscribeObserverMain + * + * @return $subscribeAllReturn + */ + def subscribe(): Subscription = { + asJavaObservable.subscribe() + } /** * $subscribeObserverMain @@ -102,9 +114,17 @@ trait Observable[+T] asJavaObservable.subscribe(observer.asJavaObserver) } + /** + * $subscribeObserverMain + * + * @param observer $subscribeObserverParamObserver + * @return $subscribeAllReturn + */ + def apply(observer: Observer[T]): Subscription = subscribe(observer) + /** * $subscribeCallbacksMainNoNotifications - * `` + * * @param onNext $subscribeCallbacksParamOnNext * @return $subscribeAllReturn */ @@ -190,14 +210,13 @@ trait Observable[+T] * * @param subject * the `rx.lang.scala.subjects.Subject` to push source items into - * @tparam R - * result type * @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 */ - def multicast[R](subject: rx.lang.scala.Subject[T, R]): (() => Subscription, Observable[R]) = { - val javaCO = asJavaObservable.multicast[R](subject.asJavaSubject) - (() => javaCO.connect(), toScalaObservable[R](javaCO)) + def multicast[R >: T](subject: rx.lang.scala.Subject[R]): (() => Subscription, Observable[R]) = { + val s: rx.subjects.Subject[_ >: T, _<: R] = subject.asJavaSubject + val javaCO: rx.observables.ConnectableObservable[R] = asJavaObservable.multicast(s) + (() => javaCO.connect(), toScalaObservable(javaCO)) } /** @@ -259,8 +278,23 @@ trait Observable[+T] * @return an Observable that emits timestamped items from the source Observable */ def timestamp: Observable[(Long, T)] = { - toScalaObservable[rx.util.Timestamped[_ <: T]](asJavaObservable.timestamp()) - .map((t: rx.util.Timestamped[_ <: T]) => (t.getTimestampMillis, t.getValue)) + toScalaObservable[rx.schedulers.Timestamped[_ <: T]](asJavaObservable.timestamp()) + .map((t: rx.schedulers.Timestamped[_ <: T]) => (t.getTimestampMillis, t.getValue)) + } + + /** + * Wraps each item emitted by a source Observable in a timestamped tuple + * with timestamps provided by the given Scheduler. + *

+ * + * + * @param scheduler [[rx.lang.scala.Scheduler]] to use as a time source. + * @return an Observable that emits timestamped items from the source + * Observable with timestamps provided by the given Scheduler + */ + def timestamp(scheduler: Scheduler): Observable[(Long, T)] = { + toScalaObservable[rx.schedulers.Timestamped[_ <: T]](asJavaObservable.timestamp(scheduler)) + .map((t: rx.schedulers.Timestamped[_ <: T]) => (t.getTimestampMillis, t.getValue)) } /** @@ -292,9 +326,8 @@ trait Observable[+T] * 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 - toScalaObservable[(T, Int)](asJavaObservable.mapWithIndex[(T, Int)](fJava)) + var n = 0; + this.map(x => { val result = (x,n); n += 1; result }) } /** @@ -335,10 +368,10 @@ trait Observable[+T] * @return * 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[Opening, Closing](openings: Observable[Opening], closings: Opening => Observable[Closing]): Observable[Seq[T]] = { + def buffer[Opening](openings: Observable[Opening], closings: Opening => Observable[Any]): 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](opening, closing) + val closing: Func1[_ >: Opening, _ <: rx.Observable[_ <: Any]] = (o: Opening) => closings(o).asJavaObservable + val jObs: rx.Observable[_ <: java.util.List[_]] = asJavaObservable.buffer[Opening, Any](opening, closing) Observable.jObsOfListToScObsOfSeq(jObs.asInstanceOf[rx.Observable[_ <: java.util.List[T]]]) } @@ -522,10 +555,10 @@ trait Observable[+T] * 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 an object. */ - def window[Closing](closings: () => Observable[Closing]): Observable[Observable[T]] = { - val func : Func0[_ <: rx.Observable[_ <: Closing]] = closings().asJavaObservable - val o1: rx.Observable[_ <: rx.Observable[_]] = asJavaObservable.window[Closing](func) - val o2 = Observable[rx.Observable[_]](o1).map((x: rx.Observable[_]) => { + def window(closings: () => Observable[Any]): Observable[Observable[T]] = { + val func : Func0[_ <: rx.Observable[_ <: Any]] = closings().asJavaObservable + val o1: rx.Observable[_ <: rx.Observable[_]] = asJavaObservable.window[Any](func) + val o2 = Observable.items(o1).map((x: rx.Observable[_]) => { val x2 = x.asInstanceOf[rx.Observable[_ <: T]] toScalaObservable[T](x2) }) @@ -548,9 +581,9 @@ trait Observable[+T] * @return * 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[Opening, Closing](openings: Observable[Opening], closings: Opening => Observable[Closing]) = { + def window[Opening](openings: Observable[Opening], closings: Opening => Observable[Any]) = { Observable.jObsOfJObsToScObsOfScObs( - asJavaObservable.window[Opening, Closing](openings.asJavaObservable, (op: Opening) => closings(op).asJavaObservable)) + asJavaObservable.window[Opening, Any](openings.asJavaObservable, (op: Opening) => closings(op).asJavaObservable)) : Observable[Observable[T]] // SI-7818 } @@ -836,7 +869,7 @@ trait Observable[+T] // with =:= it does not work, why? 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 o2: Observable[rx.Notification[_ <: U]] = o1.map(_.asJavaNotification) val o3 = o2.asJavaObservable.dematerialize[U]() toScalaObservable[U](o3) } @@ -1034,12 +1067,10 @@ trait Observable[+T] * * * - * @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 + * @return an [[rx.lang.scala.observables.ConnectableObservable]]. */ - def publish: (() => Subscription, Observable[T]) = { - val javaCO = asJavaObservable.publish() - (() => javaCO.connect(), toScalaObservable[T](javaCO)) + def publish: ConnectableObservable[T] = { + new ConnectableObservable[T](asJavaObservable.publish()) } // TODO add Scala-like aggregate function @@ -1118,7 +1149,8 @@ trait Observable[+T] * 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 [[rx.lang.scala.Observer]]s via [[rx.lang.scala.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 */ def scan[R](initialValue: R)(accumulator: (R, T) => R): Observable[R] = { @@ -1127,6 +1159,30 @@ trait Observable[+T] })) } + /** + * 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. + *

+ * + *

+ * + * @param accumulator + * an accumulator function to be invoked on each item emitted by the source + * 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 + */ + def scan[U >: T](accumulator: (U, U) => U): Observable[U] = { + val func: Func2[_ >: U, _ >: U, _ <: U] = accumulator + val func2 = func.asInstanceOf[Func2[T, T, T]] + toScalaObservable[U](asJavaObservable.asInstanceOf[rx.Observable[T]].scan(func2)) + } + /** * Returns an Observable that emits a Boolean that indicates whether all of the items emitted by * the source Observable satisfy a condition. @@ -1140,7 +1196,7 @@ trait Observable[+T] */ 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)) + // new Observable[Boolean](asJavaNotification.all(predicate)) // it's more fun in Scala: this.map(predicate).foldLeft(true)(_ && _) } @@ -1284,6 +1340,72 @@ trait Observable[+T] toScalaObservable[(K, Observable[T])](o1.map[(K, Observable[T])](func)) } + /** + * Groups the items emitted by this Observable according to a specified discriminator function and terminates these groups + * according to a function. + * + * @param f + * a function that extracts the key from an item + * @param closings + * the function that accepts the key of a given group and an observable representing that group, and returns + * an observable that emits a single Closing when the group should be closed. + * @tparam K + * the type of the keys returned by the discriminator function. + * @return an Observable that emits `(key, observable)` pairs, where `observable` + * contains all items for which `f` returned `key` before `closings` emits a value. + */ + def groupByUntil[K](f: T => K, closings: (K, Observable[T])=>Observable[Any]): Observable[(K, Observable[T])] = { + val fclosing: Func1[_ >: rx.observables.GroupedObservable[K, _ <: T], _ <: rx.Observable[_ <: Any]] = + (jGrObs: rx.observables.GroupedObservable[K, _ <: T]) => closings(jGrObs.getKey, toScalaObservable[T](jGrObs)).asJavaObservable + val o1 = asJavaObservable.groupByUntil[K, Any](f, fclosing) : rx.Observable[_ <: rx.observables.GroupedObservable[K, _ <: T]] + val func = (o: rx.observables.GroupedObservable[K, _ <: T]) => (o.getKey, toScalaObservable[T](o)) + toScalaObservable[(K, Observable[T])](o1.map[(K, Observable[T])](func)) + } + + /** + * Correlates the items emitted by two Observables based on overlapping durations. + *

+ * + * + * @param other + * the second Observable to join items from + * @param leftDurationSelector + * a function to select a duration for each item emitted by the source Observable, + * used to determine overlap + * @param rightDurationSelector + * a function to select a duration for each item emitted by the inner Observable, + * used to determine overlap + * @param resultSelector + * a function that computes an item to be emitted by the resulting Observable for any + * two overlapping items emitted by the two Observables + * @return + * an Observable that emits items correlating to items emitted by the source Observables + * that have overlapping durations + * @see RxJava Wiki: join() + * @see MSDN: Observable.Join + */ + def join[S, R] ( + other: Observable[S], + leftDurationSelector: T => Observable[Any], + rightDurationSelector: S => Observable[Any], + resultSelector: (T,S) => R + ): Observable[R] = { + + val outer : rx.Observable[_ <: T] = this.asJavaObservable + val inner : rx.Observable[_ <: S] = other.asJavaObservable + val left: Func1[_ >: T, _<: rx.Observable[_ <: Any]] = (t: T) => leftDurationSelector(t).asJavaObservable + val right: Func1[_ >: S, _<: rx.Observable[_ <: Any]] = (s: S) => rightDurationSelector(s).asJavaObservable + val f: Func2[_>: T, _ >: S, _ <: R] = resultSelector + + toScalaObservable[R]( + outer.asInstanceOf[rx.Observable[T]].join[S, Any, Any, R]( + inner.asInstanceOf[rx.Observable[S]], + left. asInstanceOf[Func1[T, rx.Observable[Any]]], + right.asInstanceOf[Func1[S, rx.Observable[Any]]], + f.asInstanceOf[Func2[T,S,R]]) + ) + } + /** * Given an Observable that emits Observables, creates a single Observable that * emits the items emitted by the most recently published of those Observables. @@ -1421,6 +1543,22 @@ trait Observable[+T] toScalaObservable[(T, U)](rx.Observable.combineLatest[T, U, (T, U)](this.asJavaObservable, that.asJavaObservable, f)) } + + /** + * Combines two observables, emitting some type `R` specified in the function f, + * 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. + *@param f + The function that is used combine the emissions of the two observables. + *@return An Observable that combines the source Observables according to the function f. + */ + def combineLatest[U,R](that: Observable[U], f: (T, U) => R): Observable[R] = { + toScalaObservable[R](rx.Observable.combineLatest[T, U, R](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. * @@ -1559,6 +1697,177 @@ trait Observable[+T] toScalaObservable[T](asJavaObservable.throttleLast(intervalDuration.length, intervalDuration.unit, scheduler)) } + /** + * Applies a timeout policy for each item emitted by the Observable, using + * the specified scheduler to run timeout timers. If the next item isn't + * observed within the specified timeout duration starting from its + * predecessor, observers are notified of a `TimeoutException`. + *

+ * + * + * @param timeout maximum duration between items before a timeout occurs + * @return the source Observable modified to notify observers of a + * `TimeoutException` in case of a timeout + */ + def timeout(timeout: Duration): Observable[T] = { + toScalaObservable[T](asJavaObservable.timeout(timeout.length, timeout.unit)) + } + + /** + * Applies a timeout policy for each item emitted by the Observable, using + * the specified scheduler to run timeout timers. If the next item isn't + * observed within the specified timeout duration starting from its + * predecessor, a specified fallback Observable produces future items and + * notifications from that point on. + *

+ * + * + * @param timeout maximum duration between items before a timeout occurs + * @param other fallback Observable to use in case of a timeout + * @return the source Observable modified to switch to the fallback + * Observable in case of a timeout + */ + def timeout[U >: T](timeout: Duration, other: Observable[U]): Observable[U] = { + val otherJava: rx.Observable[_ <: U] = other.asJavaObservable + val thisJava = this.asJavaObservable.asInstanceOf[rx.Observable[U]] + toScalaObservable[U](thisJava.timeout(timeout.length, timeout.unit, otherJava)) + } + + /** + * Applies a timeout policy for each item emitted by the Observable, using + * the specified scheduler to run timeout timers. If the next item isn't + * observed within the specified timeout duration starting from its + * predecessor, the observer is notified of a `TimeoutException`. + *

+ * + * + * @param timeout maximum duration between items before a timeout occurs + * @param scheduler Scheduler to run the timeout timers on + * @return the source Observable modified to notify observers of a + * `TimeoutException` in case of a timeout + */ + def timeout(timeout: Duration, scheduler: Scheduler): Observable[T] = { + toScalaObservable[T](asJavaObservable.timeout(timeout.length, timeout.unit, scheduler.asJavaScheduler)) + } + + /** + * Applies a timeout policy for each item emitted by the Observable, using + * the specified scheduler to run timeout timers. If the next item isn't + * observed within the specified timeout duration starting from its + * predecessor, a specified fallback Observable sequence produces future + * items and notifications from that point on. + *

+ * + * + * @param timeout maximum duration between items before a timeout occurs + * @param other Observable to use as the fallback in case of a timeout + * @param scheduler Scheduler to run the timeout timers on + * @return the source Observable modified so that it will switch to the + * fallback Observable in case of a timeout + */ + def timeout[U >: T](timeout: Duration, other: Observable[U], scheduler: Scheduler): Observable[U] = { + val otherJava: rx.Observable[_ <: U] = other.asJavaObservable + val thisJava = this.asJavaObservable.asInstanceOf[rx.Observable[U]] + toScalaObservable[U](thisJava.timeout(timeout.length, timeout.unit, otherJava, scheduler.asJavaScheduler)) + } + + /** + * Returns an Observable that mirrors the source Observable, but emits a TimeoutException if an item emitted by + * the source Observable doesn't arrive within a window of time after the emission of the + * previous item, where that period of time is measured by an Observable that is a function + * of the previous item. + *

+ * + *

+ * Note: The arrival of the first source item is never timed out. + * + * @param timeoutSelector + * a function that returns an observable for each item emitted by the source + * Observable and that determines the timeout window for the subsequent item + * @return an Observable that mirrors the source Observable, but emits a TimeoutException if a item emitted by + * the source Observable takes longer to arrive than the time window defined by the + * selector for the previously emitted item + */ + def timeout[V](timeoutSelector: T => Observable[V]): Observable[T] = { + toScalaObservable[T](asJavaObservable.timeout({ t: T => timeoutSelector(t).asJavaObservable.asInstanceOf[rx.Observable[V]] })) + } + + /** + * Returns an Observable that mirrors the source Observable, but that switches to a fallback + * Observable if an item emitted by the source Observable doesn't arrive within a window of time + * after the emission of the previous item, where that period of time is measured by an + * Observable that is a function of the previous item. + *

+ * + *

+ * Note: The arrival of the first source item is never timed out. + * + * @param timeoutSelector + * a function that returns an observable for each item emitted by the source + * Observable and that determines the timeout window for the subsequent item + * @param other + * the fallback Observable to switch to if the source Observable times out + * @return an Observable that mirrors the source Observable, but switches to mirroring a + * fallback Observable if a item emitted by the source Observable takes longer to arrive + * than the time window defined by the selector for the previously emitted item + */ + def timeout[V, O >: T](timeoutSelector: T => Observable[V], other: Observable[O]): Observable[O] = { + val thisJava = this.asJavaObservable.asInstanceOf[rx.Observable[O]] + toScalaObservable[O](thisJava.timeout( + { t: O => timeoutSelector(t.asInstanceOf[T]).asJavaObservable.asInstanceOf[rx.Observable[V]] }, + other.asJavaObservable)) + } + + /** + * Returns an Observable that mirrors the source Observable, but emits a TimeoutException + * if either the first item emitted by the source Observable or any subsequent item + * don't arrive within time windows defined by other Observables. + *

+ * + *

+ * @param firstTimeoutSelector + * a function that returns an Observable that determines the timeout window for the + * first source item + * @param timeoutSelector + * a function that returns an Observable for each item emitted by the source + * Observable and that determines the timeout window in which the subsequent source + * item must arrive in order to continue the sequence + * @return an Observable that mirrors the source Observable, but emits a TimeoutException if either the first item or any subsequent item doesn't + * arrive within the time windows specified by the timeout selectors + */ + def timeout[U, V](firstTimeoutSelector: () => Observable[U], timeoutSelector: T => Observable[V]): Observable[T] = { + toScalaObservable[T](asJavaObservable.timeout( + { firstTimeoutSelector().asJavaObservable.asInstanceOf[rx.Observable[U]] }, + { t: T => timeoutSelector(t).asJavaObservable.asInstanceOf[rx.Observable[V]] })) + } + + /** + * Returns an Observable that mirrors the source Observable, but switches to a fallback + * Observable if either the first item emitted by the source Observable or any subsequent item + * don't arrive within time windows defined by other Observables. + *

+ * + *

+ * @param firstTimeoutSelector + * a function that returns an Observable which determines the timeout window for the + * first source item + * @param timeoutSelector + * a function that returns an Observable for each item emitted by the source + * Observable and that determines the timeout window in which the subsequent source + * item must arrive in order to continue the sequence + * @param other + * the fallback Observable to switch to if the source Observable times out + * @return an Observable that mirrors the source Observable, but switches to the {@code other} Observable if either the first item emitted by the source Observable or any + * subsequent item don't arrive within time windows defined by the timeout selectors + */ + def timeout[U, V, O >: T](firstTimeoutSelector: () => Observable[U], timeoutSelector: T => Observable[V], other: Observable[O]): Observable[O] = { + val thisJava = this.asJavaObservable.asInstanceOf[rx.Observable[O]] + toScalaObservable[O](thisJava.timeout( + { firstTimeoutSelector().asJavaObservable.asInstanceOf[rx.Observable[U]] }, + { t: O => timeoutSelector(t.asInstanceOf[T]).asJavaObservable.asInstanceOf[rx.Observable[V]] }, + other.asJavaObservable)) + } + /** * Returns an Observable that sums up the elements of this Observable. * @@ -1842,6 +2151,38 @@ trait Observable[+T] toScalaObservable[T](asJavaObservable.doOnEach(observer.asJavaObserver)) } + /** + * Invokes an action when the source Observable calls onNext. + * + * @param onNext the action to invoke when the source Observable calls onNext + * @return the source Observable with the side-effecting behavior applied + */ + def doOnNext(onNext: T => Unit): Observable[T] = { + toScalaObservable[T](asJavaObservable.doOnNext(onNext)) + } + + /** + * Invokes an action if the source Observable calls `onError`. + * + * @param onError the action to invoke if the source Observable calls + * `onError` + * @return the source Observable with the side-effecting behavior applied + */ + def doOnError(onError: Throwable => Unit): Observable[T] = { + toScalaObservable[T](asJavaObservable.doOnError(onError)) + } + + /** + * Invokes an action when the source Observable calls `onCompleted`. + * + * @param onCompleted the action to invoke when the source Observable calls + * `onCompleted` + * @return the source Observable with the side-effecting behavior applied + */ + def doOnCompleted(onCompleted: () => Unit): Observable[T] = { + toScalaObservable[T](asJavaObservable.doOnCompleted(onCompleted)) + } + /** * Returns an Observable that applies the given function to each item emitted by an * Observable. @@ -1851,9 +2192,7 @@ trait Observable[+T] * @return an Observable with the side-effecting behavior applied. */ def doOnEach(onNext: T => Unit): Observable[T] = { - toScalaObservable[T](asJavaObservable.doOnEach( - onNext - )) + toScalaObservable[T](asJavaObservable.doOnNext(onNext)) } /** @@ -1866,10 +2205,7 @@ trait Observable[+T] * @return an Observable with the side-effecting behavior applied. */ def doOnEach(onNext: T => Unit, onError: Throwable => Unit): Observable[T] = { - toScalaObservable[T](asJavaObservable.doOnEach( - onNext, - onError - )) + toScalaObservable[T](asJavaObservable.doOnEach(Observer(onNext, onError, ()=>{}))) } /** @@ -1883,11 +2219,7 @@ trait Observable[+T] * @return an Observable with the side-effecting behavior applied. */ def doOnEach(onNext: T => Unit, onError: Throwable => Unit, onCompleted: () => Unit): Observable[T] = { - toScalaObservable[T](asJavaObservable.doOnEach( - onNext, - onError, - onCompleted - )) + toScalaObservable[T](asJavaObservable.doOnEach(Observer(onNext, onError,onCompleted))) } } @@ -1906,13 +2238,13 @@ object Observable { 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]]{ def asJavaObservable = jObs } + val oScala1: Observable[java.util.List[T]] = new Observable[java.util.List[T]]{ val asJavaObservable = jObs } oScala1.map((lJava: java.util.List[T]) => lJava.asScala) } 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]]{ def asJavaObservable = jObs } + val oScala1: Observable[rx.Observable[_ <: T]] = new Observable[rx.Observable[_ <: T]]{ val asJavaObservable = jObs } oScala1.map((oJava: rx.Observable[_ <: T]) => oJava) } @@ -1966,6 +2298,44 @@ object Observable { toScalaObservable[T](rx.Observable.error(exception)) } + /** + * Returns an Observable that emits no data to the [[rx.lang.scala.Observer]] and + * immediately invokes its [[rx.lang.scala.Observer#onCompleted onCompleted]] method + * with the specified scheduler. + *

+ * + * + * @return an Observable that returns no data to the [[rx.lang.scala.Observer]] and + * immediately invokes the [[rx.lang.scala.Observer]]r's + * [[rx.lang.scala.Observer#onCompleted onCompleted]] method with the + * specified scheduler + * @see RxJava Wiki: empty() + * @see MSDN: Observable.Empty Method (IScheduler) + */ + def empty: Observable[Nothing] = { + toScalaObservable(rx.Observable.empty[Nothing]()) + } + + /** + * Returns an Observable that emits no data to the [[rx.lang.scala.Observer]] and + * immediately invokes its [[rx.lang.scala.Observer#onCompleted onCompleted]] method + * with the specified scheduler. + *

+ * + * + * @param scheduler the scheduler to call the + [[rx.lang.scala.Observer#onCompleted onCompleted]] method + * @return an Observable that returns no data to the [[rx.lang.scala.Observer]] and + * immediately invokes the [[rx.lang.scala.Observer]]r's + * [[rx.lang.scala.Observer#onCompleted onCompleted]] method with the + * specified scheduler + * @see RxJava Wiki: empty() + * @see MSDN: Observable.Empty Method (IScheduler) + */ + def empty(scheduler: Scheduler): Observable[Nothing] = { + toScalaObservable(rx.Observable.empty[Nothing](scalaSchedulerToJavaScheduler(scheduler))) + } + /** * Converts a sequence of values into an Observable. * @@ -1982,7 +2352,7 @@ object Observable { * resulting Observable * @return an Observable that emits each item in the source Array */ - def apply[T](items: T*): Observable[T] = { + def items[T](items: T*): Observable[T] = { toScalaObservable[T](rx.Observable.from(items.toIterable.asJava)) } @@ -2015,7 +2385,7 @@ object Observable { * the sequence before it completes. * * @param iterable the source `Iterable` sequence - * @param the type of items in the `Iterable` sequence and the + * @param T the type of items in the `Iterable` sequence and the * type of items to be emitted by the resulting Observable * @return an Observable that emits each item in the source `Iterable` * sequence @@ -2024,6 +2394,20 @@ object Observable { toScalaObservable(rx.Observable.from(iterable.asJava)) } + /** + * + * @param iterable the source `Iterable` sequence + * @param scheduler the scheduler to use + * @tparam T the type of items in the `Iterable` sequence and the + * type of items to be emitted by the resulting Observable + * @return an Observable that emits each item in the source `Iterable` + * sequence + */ + def from[T](iterable: Iterable[T], scheduler: Scheduler): Observable[T] = { + toScalaObservable(rx.Observable.from(iterable.asJava, scheduler.asJavaScheduler)) + } + + /** * Returns an Observable that calls an Observable factory to create its Observable for each * new Observer that subscribes. That is, for each subscriber, the actual Observable is determined @@ -2124,17 +2508,53 @@ object Observable { * * * - * @param duration + * @param period * 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] = { - toScalaObservable[java.lang.Long](rx.Observable.interval(duration.length, duration.unit, scheduler)).map(_.longValue()) + def interval(period: Duration, scheduler: Scheduler): Observable[Long] = { + toScalaObservable[java.lang.Long](rx.Observable.interval(period.length, period.unit, scheduler)).map(_.longValue()) + } + + /** + * Return an Observable that emits a 0L after the {@code initialDelay} and ever increasing + * numbers after each {@code period} of time thereafter, on a specified Scheduler. + *

+ * + * + * @param initialDelay + * the initial delay time to wait before emitting the first value of 0L + * @param period + * the period of time between emissions of the subsequent numbers + * @return an Observable that emits a 0L after the { @code initialDelay} and ever increasing + * numbers after each { @code period} of time thereafter, while running on the given { @code scheduler} + */ + def timer(initialDelay: Duration, period: Duration): Observable[Long] = { + toScalaObservable[java.lang.Long](rx.Observable.timer(initialDelay.toNanos, period.toNanos, duration.NANOSECONDS)).map(_.longValue()) /*XXX*/ } + /** + * Return an Observable that emits a 0L after the {@code initialDelay} and ever increasing + * numbers after each {@code period} of time thereafter, on a specified Scheduler. + *

+ * + * + * @param initialDelay + * the initial delay time to wait before emitting the first value of 0L + * @param period + * the period of time between emissions of the subsequent numbers + * @param scheduler + * the scheduler on which the waiting happens and items are emitted + * @return an Observable that emits a 0L after the { @code initialDelay} and ever increasing + * numbers after each { @code period} of time thereafter, while running on the given { @code scheduler} + */ + def timer(initialDelay: Duration, period: Duration, scheduler: Scheduler): Observable[Long] = { + toScalaObservable[java.lang.Long](rx.Observable.timer(initialDelay.toNanos, period.toNanos, duration.NANOSECONDS, scheduler)).map(_.longValue()) + } + } 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 index 68f3e95cd1..6e956dd1a1 100644 --- 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 @@ -15,8 +15,6 @@ */ package rx.lang.scala -import rx.joins.ObserverBase - /** Provides a mechanism for receiving push-based notifications. * @@ -26,10 +24,11 @@ import rx.joins.ObserverBase */ trait Observer[-T] { - private [scala] def asJavaObserver: rx.Observer[_ >: T] = new ObserverBase[T] { - protected def onCompletedCore(): Unit = onCompleted() - protected def onErrorCore(error: Throwable): Unit = onError(error) - protected def onNextCore(value: T): Unit = onNext(value) + // Java calls XXX, Scala receives XXX + private [scala] val asJavaObserver: rx.Observer[_ >: T] = new rx.Observer[T] { + def onNext(value: T): Unit = Observer.this.onNext(value) + def onError(error: Throwable): Unit = Observer.this.onError(error) + def onCompleted(): Unit = Observer.this.onCompleted() } /** @@ -39,37 +38,51 @@ trait Observer[-T] { * * The [[rx.lang.scala.Observable]] will not call this method again after it calls either `onCompleted` or `onError`. */ - def onNext(value: T): Unit + def onNext(value: T): Unit = {} /** * 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 + def onError(error: Throwable): Unit= {} /** * 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 + def onCompleted(): Unit = {} } object Observer { + /** - * Assume that the underlying rx.Observer does not need to be wrapped. + * Scala calls XXX; Java receives XXX. */ private [scala] def apply[T](observer: rx.Observer[T]) : Observer[T] = { new Observer[T] { + override val asJavaObserver = observer - override def asJavaObserver = observer - - def onNext(value: T): Unit = asJavaObserver.onNext(value) - def onError(error: Throwable): Unit = asJavaObserver.onError(error) - def onCompleted(): Unit = asJavaObserver.onCompleted() - + override def onNext(value: T): Unit = asJavaObserver.onNext(value) + override def onError(error: Throwable): Unit = asJavaObserver.onError(error) + override def onCompleted(): Unit = asJavaObserver.onCompleted() } + } + + def apply[T]( ): Observer[T] = apply[T]((v:T)=>(), (e: Throwable)=>(), ()=>()) + def apply[T](onNext: T=>Unit ): Observer[T] = apply[T](onNext, (e: Throwable)=>(), ()=>()) + def apply[T](onNext: T=>Unit, onError: Throwable=>Unit ): Observer[T] = apply[T](onNext, onError, ()=>()) + def apply[T](onNext: T=>Unit, onCompleted: ()=>Unit): Observer[T] = apply[T](onNext, (e: Throwable)=>(), onCompleted) + def apply[T](onNext: T=>Unit, onError: Throwable=>Unit, onCompleted: ()=>Unit): Observer[T] = { + val n = onNext; val e = onError; val c = onCompleted + // Java calls XXX; Scala receives XXX. + Observer(new rx.Observer[T] { + override def onNext(value: T): Unit = n(value) + override def onError(error: Throwable): Unit = e(error) + override def onCompleted(): Unit = c() + }) + } } \ 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 index 4f1f89e808..1bfbb69c4e 100644 --- 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 @@ -15,210 +15,111 @@ */ package rx.lang.scala -import java.util.Date import scala.concurrent.duration.Duration -import rx.util.functions.{Action0, Action1, Func2} +import rx.functions.Action1 +import rx.lang.scala.schedulers._ +import scala.concurrent.duration +import rx.lang.scala.JavaConversions._ /** * Represents an object that schedules units of work. */ trait Scheduler { - import rx.lang.scala.ImplicitFunctionConversions._ - val asJavaScheduler: rx.Scheduler + private [scala] val asJavaScheduler: rx.Scheduler /** - * Schedules a cancelable action to be executed. + * Parallelism available to a Scheduler. * - * @param action Action to schedule. - * @return a subscription to be able to unsubscribe from action. - */ - def schedule(action: Scheduler => Subscription): Subscription = { - this.schedule[Integer](0, (s: Scheduler, x: Integer) => action(s): Subscription): Subscription - } - - /** - * Schedules a cancelable action to be executed. + * This defaults to {@code Runtime.getRuntime().availableProcessors()} but can be overridden for use cases such as scheduling work on a computer cluster. * - * @param state State to pass into the action. - * @param action Action to schedule. - * @return a subscription to be able to unsubscribe from action. + * @return the scheduler's available degree of parallelism. */ - 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 - } - })) - } + def degreeOfParallelism: Int = asJavaScheduler.degreeOfParallelism /** - * 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. + * @return the scheduler's notion of current absolute time in milliseconds. */ - def schedule(delayTime: Duration, action: Scheduler => Subscription): Subscription = { - this.schedule[Integer](0, (s: Scheduler, x: Integer) => action(s), delayTime: Duration): Subscription - } + def now: Long = this.asJavaScheduler.now() /** - * Schedules a cancelable action to be executed in delayTime. + * Schedules a cancelable action to be executed. * - * @param state - * State to pass into the action. - * @param action - * Action to schedule. - * @param delayTime - * Time the action is to be delayed before executing. + * @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, delayTime: Duration): Subscription = { - val xxx = schedulerActionToFunc2(action) - Subscription(asJavaScheduler.schedule(state, xxx, delayTime.length, delayTime.unit)) - } + def schedule(action: Inner => Unit): Subscription = this.asJavaScheduler.schedule(action) /** * 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. + * The action to execute periodically. * @param initialDelay - * Time to wait before executing the action for the first time. + * 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. + * 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 - } + def schedulePeriodically(action: Inner => Unit, initialDelay: Duration, period: Duration): Subscription = + this.asJavaScheduler.schedulePeriodically ( + new Action1[rx.Scheduler.Inner] { + override def call(inner: rx.Scheduler.Inner): Unit = action(javaInnerToScalaInner(inner)) + }, + initialDelay.toNanos, + period.toNanos, + duration.NANOSECONDS + ) - /** - * 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)) + def scheduleRec(work: (=>Unit)=>Unit): Subscription = { + Subscription(asJavaScheduler.schedule(new Action1[rx.Scheduler.Inner] { + override def call(inner: rx.Scheduler.Inner): Unit = work{ inner.schedule(this) } + })) } +} - /** - * 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)) - } +object Inner { + def apply(inner: rx.Scheduler.Inner): Inner = new Inner { private[scala] val asJavaInner = inner } +} - /** - * 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)) - } +trait Inner extends Subscription { + private [scala] val asJavaInner: rx.Scheduler.Inner /** - * 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. + * Schedules a cancelable action to be executed in delayTime. */ - 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 - } + def schedule(action: Inner => Unit, delayTime: Duration): Unit = + this.asJavaInner.schedule( + new Action1[rx.Scheduler.Inner] { + override def call(inner: rx.Scheduler.Inner): Unit = action(javaInnerToScalaInner(inner)) + }, + delayTime.length, + delayTime.unit) /** - * Returns the scheduler's notion of current absolute time in milliseconds. + * Schedules a cancelable action to be executed immediately. */ - def now: Long = { - asJavaScheduler.now - } + def schedule(action: Inner=>Unit): Unit = this.asJavaInner.schedule( + new Action1[rx.Scheduler.Inner]{ + override def call(inner: rx.Scheduler.Inner): Unit = action(javaInnerToScalaInner(inner)) + } + ) /** - * 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. + * @return the scheduler's notion of current absolute time in milliseconds. */ - def degreeOfParallelism: Int = { - asJavaScheduler.degreeOfParallelism - } - + def now: Long = this.asJavaInner.now() } -object Scheduler { - private [scala] def apply(scheduler: rx.Scheduler): Scheduler = { - new Scheduler() { - val asJavaScheduler = scheduler - } + +private [scala] object Scheduler { + def apply(scheduler: rx.Scheduler): Scheduler = scheduler match { + case s: rx.schedulers.TestScheduler => new TestScheduler(s) + case s: rx.Scheduler => new Scheduler{ val asJavaScheduler = s } } + } diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Subject.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Subject.scala index d60b2f3c3d..eb9efa02ac 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Subject.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Subject.scala @@ -15,26 +15,25 @@ */ package rx.lang.scala -import rx.joins.ObserverBase - /** * 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 +trait Subject[T] extends Observable[T] with Observer[T] { + private [scala] val asJavaSubject: rx.subjects.Subject[_ >: T, _<: T] - // temporary hack to workaround bugs in rx Subjects - override def asJavaObserver: rx.Observer[_ >: T] = new ObserverBase[T] { - protected def onNextCore(value: T) = asJavaSubject.onNext(value) - protected def onErrorCore(error: Throwable) = asJavaSubject.onError(error) - protected def onCompletedCore() = asJavaSubject.onCompleted() - } + val asJavaObservable: rx.Observable[_ <: T] = asJavaSubject - def onNext(value: T): Unit = asJavaObserver.onNext(value) - def onError(error: Throwable): Unit = asJavaObserver.onError(error) - def onCompleted(): Unit = asJavaObserver.onCompleted() + override val asJavaObserver: rx.Observer[_ >: T] = asJavaSubject + override def onNext(value: T): Unit = { asJavaObserver.onNext(value)} + override def onError(error: Throwable): Unit = { asJavaObserver.onError(error) } + override def onCompleted() { asJavaObserver.onCompleted() } +} +object Subject { + def apply[T](): Subject[T] = new rx.lang.scala.subjects.PublishSubject[T](rx.subjects.PublishSubject.create()) } + + + + 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 index 93e76e4524..618e0abe8d 100644 --- 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 @@ -1,3 +1,18 @@ +/** + * Copyright 2014 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. + */ /** * Copyright 2013 Netflix, Inc. @@ -17,59 +32,63 @@ package rx.lang.scala +import java.util.concurrent.atomic.AtomicBoolean + /** * 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 + + private [scala] val unsubscribed = new AtomicBoolean(false) + private [scala] val asJavaSubscription: rx.Subscription = new rx.Subscription { + override def unsubscribe() { unsubscribed.compareAndSet(false, true) } + override def isUnsubscribed(): Boolean = { unsubscribed.get() } + } + /** * Call this method to stop receiving notifications on the Observer that was registered when * this Subscription was received. */ - def unsubscribe(): Unit = asJavaSubscription.unsubscribe() + def unsubscribe() = asJavaSubscription.unsubscribe() /** * Checks if the subscription is unsubscribed. */ - def isUnsubscribed: Boolean + def isUnsubscribed = unsubscribed.get() + } object Subscription { - import java.util.concurrent.atomic.AtomicBoolean - import rx.lang.scala.subscriptions._ - - /** - * Creates an [[rx.lang.scala.Subscription]] from an [[rx.Subscription]]. + * Creates an [[rx.lang.scala.Subscription]] from an [[rx.Subscription]]. ß */ private [scala] 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 => apply { x.unsubscribe() } + case x: rx.subscriptions.BooleanSubscription => new rx.lang.scala.subscriptions.BooleanSubscription(x) + case x: rx.subscriptions.CompositeSubscription => new rx.lang.scala.subscriptions.CompositeSubscription(x) + case x: rx.subscriptions.MultipleAssignmentSubscription => new rx.lang.scala.subscriptions.MultipleAssignmentSubscription(x) + case x: rx.subscriptions.SerialSubscription => new rx.lang.scala.subscriptions.SerialSubscription(x) + case x: rx.Subscription => apply{ 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) }} - } + def apply(u: => Unit): Subscription = new Subscription() { + override val asJavaSubscription = new rx.Subscription { + override def unsubscribe() { if(unsubscribed.compareAndSet(false, true)) { u } } + override def isUnsubscribed(): Boolean = { unsubscribed.get() } } } + /** + * Creates an empty [[rx.lang.scala.Subscription]]. + */ + def apply(): Subscription = Subscription {} + } \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/ScheduledExecutorServiceScheduler.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/ScheduledExecutorServiceScheduler.scala deleted file mode 100644 index d6e8b11d98..0000000000 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/ScheduledExecutorServiceScheduler.scala +++ /dev/null @@ -1,33 +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.concurrency - -import java.util.concurrent.ScheduledExecutorService -import rx.lang.scala.Scheduler - -object ScheduledExecutorServiceScheduler { - - /** - * Returns a [[rx.lang.scala.Scheduler]] that queues work on an `java.util.concurrent.ScheduledExecutorService`. - */ - def apply(executor: ScheduledExecutorService): ScheduledExecutorServiceScheduler = { - new ScheduledExecutorServiceScheduler(rx.concurrency.Schedulers.executor(executor)) - } -} - -class ScheduledExecutorServiceScheduler private[scala] (val asJavaScheduler: rx.Scheduler) - extends Scheduler {} - diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/ThreadPoolForComputationScheduler.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/ThreadPoolForComputationScheduler.scala deleted file mode 100644 index 8b51083b89..0000000000 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/ThreadPoolForComputationScheduler.scala +++ /dev/null @@ -1,37 +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.concurrency - -import rx.lang.scala.Scheduler - -object ThreadPoolForComputationScheduler { - - /** - * 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.ThreadPoolForIOScheduler]] instead. - */ - def apply(): ThreadPoolForComputationScheduler = { - new ThreadPoolForComputationScheduler(rx.concurrency.Schedulers.threadPoolForComputation()) - } -} - -class ThreadPoolForComputationScheduler private[scala] (val asJavaScheduler: rx.Scheduler) - extends Scheduler {} 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 f9e98efa63..c1ce913095 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 @@ -18,6 +18,7 @@ package rx.lang.scala.observables import scala.collection.JavaConverters._ import rx.lang.scala.ImplicitFunctionConversions._ + /** * An Observable that provides blocking operators. * diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/observables/ConnectableObservable.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/observables/ConnectableObservable.scala new file mode 100644 index 0000000000..bba27ecb22 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/observables/ConnectableObservable.scala @@ -0,0 +1,38 @@ +/** + * 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 + +import rx.lang.scala.{Observable, Subscription} +import rx.lang.scala.JavaConversions._ + +class ConnectableObservable[+T] private[scala](val asJavaObservable: rx.observables.ConnectableObservable[_ <: T]) + extends Observable[T] { + + /** + * Call a ConnectableObservable's connect method to instruct it to begin emitting the + * items from its underlying [[rx.lang.scala.Observable]] to its [[rx.lang.scala.Observer]]s. + */ + def connect: Subscription = toScalaSubscription(asJavaObservable.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 [[rx.lang.scala.Observable]] + */ + def refCount: Observable[T] = toScalaObservable[T](asJavaObservable.refCount()) +} 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 83352c8426..aa6cd4339e 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 @@ -20,4 +20,16 @@ package rx.lang * * It basically mirrors the structure of package `rx`, but some changes were made to make it more Scala-idiomatic. */ -package object scala {} +package object scala { + + /** + * Placeholder for extension methods into Observable[T] from other types + */ + implicit class ObservableExtensions[T](val source: Iterable[T]) extends AnyVal { + def toObservable: Observable[T] = { Observable.from(source) } + def toObservable(scheduler: Scheduler): Observable[T] = { Observable.from(source, scheduler) } + } + + + +} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/ComputationScheduler.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/ComputationScheduler.scala new file mode 100644 index 0000000000..608d390c64 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/ComputationScheduler.scala @@ -0,0 +1,22 @@ +package rx.lang.scala.schedulers + +import rx.lang.scala.Scheduler + + +object ComputationScheduler { + /** + * {@link Scheduler} intended for computational work. + *

+ * This can be used for event-loops, processing callbacks and other computational work. + *

+ * Do not perform IO-bound work on this scheduler. Use {@link IOScheduler()} instead. + * + * @return { @link Scheduler} for computation-bound work. + */ + def apply(): ComputationScheduler = { + new ComputationScheduler(rx.schedulers.Schedulers.computation()) + } +} + +class ComputationScheduler private[scala] (val asJavaScheduler: rx.Scheduler) + extends Scheduler {} \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/ExecutorScheduler.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/ExecutorScheduler.scala similarity index 76% rename from language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/ExecutorScheduler.scala rename to language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/ExecutorScheduler.scala index 03c7e79d44..0ffe30b2a2 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/ExecutorScheduler.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/ExecutorScheduler.scala @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package rx.lang.scala.concurrency +package rx.lang.scala.schedulers -import java.util.concurrent.Executor +import java.util.concurrent.{ScheduledExecutorService, Executor} import rx.lang.scala.Scheduler object ExecutorScheduler { @@ -26,10 +26,13 @@ object ExecutorScheduler { * Note that this does not support scheduled actions with a delay. */ def apply(executor: Executor): ExecutorScheduler = { - new ExecutorScheduler(rx.concurrency.Schedulers.executor(executor)) + new ExecutorScheduler(rx.schedulers.Schedulers.executor(executor)) } -} + def apply(executor: ScheduledExecutorService): ExecutorScheduler = { + new ExecutorScheduler(rx.schedulers.Schedulers.executor(executor)) + } +} class ExecutorScheduler private[scala] (val asJavaScheduler: rx.Scheduler) extends Scheduler {} diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/ThreadPoolForIOScheduler.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/IOScheduler.scala similarity index 55% rename from language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/ThreadPoolForIOScheduler.scala rename to language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/IOScheduler.scala index 8869cf96b3..b7453016cf 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/ThreadPoolForIOScheduler.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/IOScheduler.scala @@ -13,25 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package rx.lang.scala.concurrency +package rx.lang.scala.schedulers import rx.lang.scala.Scheduler -object ThreadPoolForIOScheduler { - +object IOScheduler { /** - * [[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. - * + * {@link Scheduler} intended for IO-bound work. + *

+ * The implementation is backed by an {@link 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 {@link ComputationScheduler()} instead. * - * Do not perform computational work on this scheduler. Use [[rx.lang.scala.concurrency.ThreadPoolForComputationScheduler]] instead. + * @return { @link ExecutorScheduler} for IO-bound work. */ - def apply(): ThreadPoolForIOScheduler = { - new ThreadPoolForIOScheduler(rx.concurrency.Schedulers.threadPoolForIO()) + def apply(): IOScheduler = { + new IOScheduler(rx.schedulers.Schedulers.io) } } -class ThreadPoolForIOScheduler private[scala] (val asJavaScheduler: rx.Scheduler) +class IOScheduler private[scala] (val asJavaScheduler: rx.Scheduler) extends Scheduler {} + diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/ImmediateScheduler.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/ImmediateScheduler.scala similarity index 90% rename from language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/ImmediateScheduler.scala rename to language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/ImmediateScheduler.scala index 91843a5746..cc22456afa 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/ImmediateScheduler.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/ImmediateScheduler.scala @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package rx.lang.scala.concurrency +package rx.lang.scala.schedulers import rx.lang.scala.Scheduler @@ -23,7 +23,7 @@ object ImmediateScheduler { * Returns a [[rx.lang.scala.Scheduler]] that executes work immediately on the current thread. */ def apply(): ImmediateScheduler = { - new ImmediateScheduler(rx.concurrency.Schedulers.immediate()) + new ImmediateScheduler(rx.schedulers.Schedulers.immediate()) } } diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/NewThreadScheduler.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/NewThreadScheduler.scala similarity index 86% rename from language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/NewThreadScheduler.scala rename to language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/NewThreadScheduler.scala index dc69578082..e24450f89a 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/NewThreadScheduler.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/NewThreadScheduler.scala @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package rx.lang.scala.concurrency +package rx.lang.scala.schedulers import rx.lang.scala.Scheduler @@ -23,9 +23,8 @@ object NewThreadScheduler { * Returns a [[rx.lang.scala.Scheduler]] that creates a new {@link Thread} for each unit of work. */ def apply(): NewThreadScheduler = { - new NewThreadScheduler(rx.concurrency.Schedulers.newThread()) + new NewThreadScheduler(rx.schedulers.Schedulers.newThread()) } } -class NewThreadScheduler private[scala] (val asJavaScheduler: rx.Scheduler) - extends Scheduler {} +class NewThreadScheduler private[scala] (val asJavaScheduler: rx.Scheduler) extends Scheduler {} 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/schedulers/TestScheduler.scala similarity index 91% rename from language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/TestScheduler.scala rename to language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/TestScheduler.scala index 98a0d241b8..c8ddb76673 100644 --- 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/schedulers/TestScheduler.scala @@ -13,17 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package rx.lang.scala.concurrency +package rx.lang.scala.schedulers import scala.concurrent.duration.Duration import rx.lang.scala.Scheduler +import rx.schedulers /** * Provides constructors for `TestScheduler`. */ object TestScheduler { def apply(): TestScheduler = { - new TestScheduler(new rx.concurrency.TestScheduler()) + new TestScheduler(new schedulers.TestScheduler()) } } @@ -64,7 +65,7 @@ object TestScheduler { * } * }}} */ -class TestScheduler private[scala] (val asJavaScheduler: rx.concurrency.TestScheduler) extends Scheduler { +class TestScheduler private[scala] (val asJavaScheduler: rx.schedulers.TestScheduler) extends Scheduler { def advanceTimeBy(time: Duration) { asJavaScheduler.advanceTimeBy(time.length, time.unit) diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/TrampolineScheduler.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/TrampolineScheduler.scala new file mode 100644 index 0000000000..0ff50c1770 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/schedulers/TrampolineScheduler.scala @@ -0,0 +1,15 @@ +package rx.lang.scala.schedulers + +import rx.lang.scala.Scheduler + +object TrampolineScheduler { + /** + * {@link Scheduler} that queues work on the current thread to be executed after the current work completes. + */ + def apply(): TrampolineScheduler = { + new TrampolineScheduler(rx.schedulers.Schedulers.trampoline()) + } +} + +class TrampolineScheduler private[scala] (val asJavaScheduler: rx.Scheduler) + extends Scheduler {} 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 index 80892b9ac1..44cb6adb79 100644 --- 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 @@ -23,4 +23,4 @@ object AsyncSubject { } } -class AsyncSubject[T] private[scala] (val asJavaSubject: rx.subjects.AsyncSubject[T]) extends Subject[T,T] {} \ No newline at end of file +class AsyncSubject[T] private[scala] (val asJavaSubject: rx.subjects.AsyncSubject[T]) extends Subject[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 index 9ee8ba9db4..64e0ac4671 100644 --- 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 @@ -23,7 +23,7 @@ object BehaviorSubject { } } -class BehaviorSubject[T] private[scala] (val asJavaSubject: rx.subjects.BehaviorSubject[T]) extends Subject[T,T] {} +class BehaviorSubject[T] private[scala] (val asJavaSubject: rx.subjects.BehaviorSubject[T]) extends Subject[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 index 7c06101460..129717d8cc 100644 --- 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 @@ -17,11 +17,8 @@ package rx.lang.scala.subjects import rx.lang.scala.Subject -object PublishSubject { - def apply[T](): PublishSubject[T] = { - new PublishSubject[T](rx.subjects.PublishSubject.create()) - } +private [scala] object PublishSubject { + def apply[T](): PublishSubject[T] = new PublishSubject[T](rx.subjects.PublishSubject.create[T]()) } -class PublishSubject[T] private[scala] (val asJavaSubject: rx.subjects.PublishSubject[T]) extends Subject[T,T] { - } +private [scala] class PublishSubject[T] private [scala] (val asJavaSubject: rx.subjects.PublishSubject[T]) extends Subject[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 index f88fb65280..3f64bf1e7d 100644 --- 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 @@ -23,7 +23,7 @@ object ReplaySubject { } } -class ReplaySubject[T] private[scala] (val asJavaSubject: rx.subjects.ReplaySubject[T]) extends Subject[T,T] { +class ReplaySubject[T] private[scala] (val asJavaSubject: rx.subjects.ReplaySubject[T]) extends Subject[T] { } 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 index a9f2070345..5f07497ca3 100644 --- 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 @@ -17,39 +17,26 @@ 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() - } - } - }) - } +private [scala] object BooleanSubscription { + def apply(): BooleanSubscription = new BooleanSubscription(new rx.subscriptions.BooleanSubscription()) } /** * Represents a [[rx.lang.scala.Subscription]] that can be checked for status. */ -class BooleanSubscription private[scala] (val asJavaSubscription: rx.subscriptions.BooleanSubscription) +private [scala] class BooleanSubscription private[scala] (boolean: rx.subscriptions.BooleanSubscription) extends Subscription { - /** - * Checks whether the subscription has been unsubscribed. - */ - def isUnsubscribed: Boolean = asJavaSubscription.isUnsubscribed - + override val asJavaSubscription: rx.subscriptions.BooleanSubscription = boolean } + +/* +new rx.subscriptions.BooleanSubscription() { + override def unsubscribe(): Unit = { + if(unsubscribed.compareAndSet(false, true)) { + if(!boolean.isUnsubscribed) { boolean.unsubscribe() } + } + } + override def isUnsubscribed(): Boolean = unsubscribed.get() || boolean.isUnsubscribed + } + */ \ No newline at end of file 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 index 1fe4a4afa5..abe2e721c4 100644 --- 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 @@ -36,7 +36,7 @@ object CompositeSubscription { /** * Creates a [[rx.lang.scala.subscriptions.CompositeSubscription]]. */ - def apply(subscription: rx.subscriptions.CompositeSubscription): CompositeSubscription = { + private [scala] def apply(subscription: rx.subscriptions.CompositeSubscription): CompositeSubscription = { new CompositeSubscription(subscription) } } @@ -44,9 +44,10 @@ object CompositeSubscription { /** * Represents a group of [[rx.lang.scala.Subscription]] that are disposed together. */ -class CompositeSubscription private[scala] (val asJavaSubscription: rx.subscriptions.CompositeSubscription) - extends Subscription +class CompositeSubscription private[scala] (override val asJavaSubscription: rx.subscriptions.CompositeSubscription) extends Subscription { + //override def asJavaSubscription = subscription + /** * Adds a subscription to the group, * or unsubscribes immediately is the [[rx.subscriptions.CompositeSubscription]] is unsubscribed. @@ -68,9 +69,7 @@ class CompositeSubscription private[scala] (val asJavaSubscription: rx.subscript this } - /** - * Checks whether the subscription has been unsubscribed. - */ - def isUnsubscribed: Boolean = asJavaSubscription.isUnsubscribed + override def unsubscribe(): Unit = asJavaSubscription.unsubscribe() + override 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 index 84740870c7..1cbb0f8d6d 100644 --- 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 @@ -41,13 +41,13 @@ object 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 { +class MultipleAssignmentSubscription private[scala] (override val asJavaSubscription: rx.subscriptions.MultipleAssignmentSubscription) extends Subscription { + //override def asJavaSubscription = s /** * Gets the underlying subscription. */ - def subscription: Subscription = Subscription(asJavaSubscription.getSubscription) + def subscription: Subscription = Subscription(asJavaSubscription.get) /** * Gets the underlying subscription @@ -55,14 +55,12 @@ class MultipleAssignmentSubscription private[scala] (val asJavaSubscription: rx. * @return the [[rx.lang.scala.subscriptions.MultipleAssignmentSubscription]] itself. */ def subscription_=(that: Subscription): this.type = { - asJavaSubscription.setSubscription(that.asJavaSubscription) + asJavaSubscription.set(that.asJavaSubscription) this } - /** - * Checks whether the subscription has been unsubscribed. - */ - def isUnsubscribed: Boolean = asJavaSubscription.isUnsubscribed + override def unsubscribe(): Unit = asJavaSubscription.unsubscribe() + override 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 index 94b50f93e8..77b52e3b5f 100644 --- 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 @@ -15,49 +15,36 @@ */ package rx.lang.scala.subscriptions -import rx.lang.scala.Subscription -import java.util.concurrent.atomic.AtomicBoolean - +import rx.lang.scala._ object SerialSubscription { /** * Creates a [[rx.lang.scala.subscriptions.SerialSubscription]]. */ - def apply(): SerialSubscription = { - new SerialSubscription(new rx.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 + SerialSubscription().subscription = Subscription(unsubscribe) } } /** * 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) +class SerialSubscription private[scala] (override val asJavaSubscription: rx.subscriptions.SerialSubscription) extends Subscription { - /** - * Checks whether the subscription has been unsubscribed. - */ - def isUnsubscribed: Boolean = unsubscribed.get() + override def unsubscribe(): Unit = asJavaSubscription.unsubscribe() + override def isUnsubscribed: Boolean = asJavaSubscription.isUnsubscribed - /** - * 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) + def subscription_=(value: Subscription): this.type = { + asJavaSubscription.set(value.asJavaSubscription) + this + } + def subscription: Subscription = Subscription(asJavaSubscription.get) } 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 index 7a161c0f8f..d4778d7d5e 100644 --- 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 @@ -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.lang.scala import java.util.Calendar @@ -15,37 +30,37 @@ 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`). " + + "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 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 */ @@ -61,7 +76,7 @@ class CompletenessTest extends JUnitSuite { "elementAt(Int)" -> "[use `.drop(index).first`]", "elementAtOrDefault(Int, T)" -> "[use `.drop(index).firstOrElse(default)`]", "first(Func1[_ >: T, Boolean])" -> commentForFirstWithPredicate, - "firstOrDefault(T)" -> "firstOrElse(=> U)", + "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])", @@ -90,7 +105,7 @@ class CompletenessTest extends JUnitSuite { "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, @@ -103,7 +118,7 @@ class CompletenessTest extends JUnitSuite { "empty()" -> "apply(T*)", "error(Throwable)" -> "apply(Throwable)", "from(Array[T])" -> "apply(T*)", - "from(Iterable[_ <: T])" -> "apply(T*)", + "from(Iterable[_ <: T])" -> "apply(T*)", "from(Future[_ <: T])" -> fromFuture, "from(Future[_ <: T], Long, TimeUnit)" -> fromFuture, "from(Future[_ <: T], Scheduler)" -> fromFuture, @@ -151,9 +166,9 @@ class CompletenessTest extends JUnitSuite { 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 @@ -167,18 +182,18 @@ class CompletenessTest extends JUnitSuite { 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 + // 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] = + def getPublicInstanceAndCompanionMethods(tp: Type): Iterable[String] = getPublicInstanceMethods(tp) ++ getPublicInstanceMethods(tp.typeSymbol.companionSymbol.typeSignature) @@ -187,31 +202,31 @@ class CompletenessTest extends JUnitSuite { println(title.map(_ => '-') + "\n") getPublicInstanceMethods(tp).toList.sorted.foreach(println(_)) } - + @Ignore // because spams output @Test def printJavaInstanceMethods(): Unit = { - printMethodSet("Instance methods of rx.Observable", + 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", + 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", + 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") @@ -233,20 +248,20 @@ class CompletenessTest extends JUnitSuite { 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 + "\"")) + 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 + "\"")) + println("%s -> %s,".format("\"" + javaM + "\"", "\"" + scalaM + "\"")) } } - + def checkMethodPresence(expectedMethods: Iterable[String], tp: Type): Unit = { val actualMethods = getPublicInstanceAndCompanionMethods(tp).toSet val expMethodsSorted = expectedMethods.toList.sorted @@ -261,15 +276,15 @@ class CompletenessTest extends JUnitSuite { 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 :_*)) { + for ((javaM, scalaM) <- SortedMap(correspondence.toSeq :_*)) { if (actualMethods.contains(scalaM) || scalaM.charAt(0) == '[') { good += 1 } else { @@ -282,40 +297,40 @@ class CompletenessTest extends JUnitSuite { 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) = + + 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) = + 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 => { @@ -327,21 +342,21 @@ class CompletenessTest extends JUnitSuite { } }).mkString("
") } - def formatScalaCol(s: String): String = + 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: + +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 { @@ -350,5 +365,5 @@ Note: 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/main/scala/rx/lang/scala/concurrency/CurrentThreadScheduler.scala b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/ConstructorTest.scala similarity index 54% rename from language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/CurrentThreadScheduler.scala rename to language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/ConstructorTest.scala index 500d9c1d33..6669db2f77 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/concurrency/CurrentThreadScheduler.scala +++ b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/ConstructorTest.scala @@ -13,19 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package rx.lang.scala.concurrency +package rx.lang.scala +import scala.language.postfixOps +import org.junit.Assert._ +import org.junit.Test +import org.scalatest.junit.JUnitSuite -import rx.lang.scala.Scheduler +class ConstructorTest extends JUnitSuite { -object CurrentThreadScheduler { + @Test def toObservable() { + val xs = List(1,2,3).toObservable.toBlockingObservable.toList + assertEquals(List(1,2,3), xs) + + val ys = Observable.from(List(1,2,3)).toBlockingObservable.toList + assertEquals(List(1,2,3), xs) + + val zs = Observable.items(1,2,3).toBlockingObservable.toList + assertEquals(List(1,2,3), xs) - /** - * Returns a [[rx.lang.scala.Scheduler]] that queues work on the current thread to be executed after the current work completes. - */ - def apply(): CurrentThreadScheduler = { - new CurrentThreadScheduler(rx.concurrency.Schedulers.currentThread()) } } - -class CurrentThreadScheduler private[scala] (val asJavaScheduler: rx.Scheduler) - extends Scheduler {} \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/NotificationTests.scala b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/NotificationTests.scala new file mode 100644 index 0000000000..354fd71a5c --- /dev/null +++ b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/NotificationTests.scala @@ -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.lang.scala + + +import org.junit.{Assert, Test} +import org.junit.Assert._ +import org.scalatest.junit.JUnitSuite +import scala.concurrent.duration._ +import scala.language.postfixOps +import org.mockito.Mockito._ +import org.mockito.Matchers._ +import rx.lang.scala.Notification.{OnCompleted, OnError, OnNext} + + +class NotificationTests extends JUnitSuite { + @Test + def creation() { + + val onNext = OnNext(42) + assertEquals(42, onNext match { case OnNext(value) => value }) + + val oops = new Exception("Oops") + val onError = OnError(oops) + assertEquals(oops, onError match { case OnError(error) => error }) + + val onCompleted = OnCompleted + assertEquals((), onCompleted match { case OnCompleted => () }) + } + + @Test + def accept() { + + val onNext = OnNext(42) + assertEquals(42, onNext(x=>42, e=>4711,()=>13)) + + val oops = new Exception("Oops") + val onError = OnError(oops) + assertEquals(4711, onError(x=>42, e=>4711,()=>13)) + + val onCompleted = OnCompleted + assertEquals(13, onCompleted(x=>42, e=>4711,()=>13)) + + } +} 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 index 48c4423373..397907a6cb 100644 --- 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 @@ -1,11 +1,33 @@ +/** + * 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 scala.collection.mutable.ListBuffer import scala.concurrent.{Future, Await} import scala.concurrent.duration.Duration import scala.concurrent.ExecutionContext.Implicits.global import org.junit.Assert._ import org.junit.{ Ignore, Test } import org.scalatest.junit.JUnitSuite +import scala.concurrent.duration._ +import scala.language.postfixOps +import rx.lang.scala.schedulers.TestScheduler +import rx.lang.scala.subjects.BehaviorSubject +import org.mockito.Mockito._ +import org.mockito.Matchers._ class ObservableTests extends JUnitSuite { @@ -15,7 +37,7 @@ class ObservableTests extends JUnitSuite { def testCovariance = { //println("hey, you shouldn't run this test") - val o1: Observable[Nothing] = Observable() + val o1: Observable[Nothing] = Observable.empty val o2: Observable[Int] = o1 val o3: Observable[App] = o1 val o4: Observable[Any] = o2 @@ -26,28 +48,36 @@ class ObservableTests extends JUnitSuite { @Test def testDematerialize() { - val o = Observable(1, 2, 3) + val o = List(1, 2, 3).toObservable val mat = o.materialize val demat = mat.dematerialize - // correctly rejected: + //correctly rejected: //val wrongDemat = Observable("hello").dematerialize assertEquals(demat.toBlockingObservable.toIterable.toList, List(1, 2, 3)) +} + + @Test def TestScan() { + val xs = Observable.items(0,1,2,3) + val ys = xs.scan(0)(_+_) + assertEquals(List(0,0,1,3,6), ys.toBlockingObservable.toList) + val zs = xs.scan((x: Int, y:Int) => x*y) + assertEquals(List(0, 0, 0, 0), zs.toBlockingObservable.toList) } // 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) + 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 + rx.Observable.error(new Exception(msg)).firstOrDefault(10).toBlockingObservable().single } catch { - case e: Exception => receivedMsg = e.getCause.getMessage + case e: Exception => receivedMsg = e.getCause().getMessage() } assertEquals(receivedMsg, msg) } @@ -55,17 +85,17 @@ class ObservableTests extends JUnitSuite { @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) + assertEquals("hello", Observable.items("hello").firstOrElse(mustNotBeCalled).toBlockingObservable.single) + assertEquals("this is the default value", Observable.empty.firstOrElse(mustBeCalled).toBlockingObservable.single) } - @Test def testFirstOrElseWithError() { + @Test def testTestWithError() { val msg = "msg6251" var receivedMsg = "none" try { Observable.error[Int](new Exception(msg)).firstOrElse(10).toBlockingObservable.single } catch { - case e: Exception => receivedMsg = e.getCause.getMessage + case e: Exception => receivedMsg = e.getCause().getMessage() } assertEquals(receivedMsg, msg) } @@ -94,6 +124,28 @@ class ObservableTests extends JUnitSuite { assertEquals(6, o.toBlockingObservable.single) } + @Test def testJoin() { + val xs = Observable.items(1,2,3) + val ys = Observable.items("a") + val zs = xs.join[String,String](ys, x => Observable.never, y => Observable.never, (x,y) => y+x) + assertEquals(List("a1", "a2", "a3"),zs.toBlockingObservable.toList) + } + + @Test def testTimestampWithScheduler() { + val c = 10 + val s = TestScheduler() + val o1 = Observable interval (1.milliseconds, s) map (_ + 1) + val o2 = o1 timestamp s + val l = ListBuffer[(Long, Long)]() + o2.subscribe ( + onNext = (l += _) + ) + s advanceTimeTo c.milliseconds + val (l1, l2) = l.toList.unzip + assertTrue(l1.size == c) + assertEquals(l2, l1) + } + /* @Test def testHead() { val observer = mock(classOf[Observer[Int]]) @@ -106,10 +158,4 @@ class ObservableTests extends JUnitSuite { } */ - @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/SubjectTests.scala b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/SubjectTests.scala new file mode 100644 index 0000000000..f8d72e3520 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/SubjectTests.scala @@ -0,0 +1,323 @@ +/** + * 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.junit.{Assert, Test} +import org.scalatest.junit.JUnitSuite +import scala.concurrent.duration._ +import scala.language.postfixOps +import rx.lang.scala.schedulers.TestScheduler +import rx.lang.scala.subjects.{AsyncSubject, ReplaySubject, BehaviorSubject} +import org.mockito.Mockito._ +import org.mockito.Matchers._ +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Assert.assertFalse +import org.junit.Ignore +import org.junit.Test +import org.scalatest.junit.JUnitSuite + +class SubjectTest extends JUnitSuite { + + @Test def SubjectIsAChannel() { + + var lastA: Integer = null + var errorA: Throwable = null + var completedA: Boolean = false + val observerA = Observer[Integer]( + (next: Integer) => { lastA = next }, + (error: Throwable) => { errorA = error }, + () => { completedA = true } + ) + + var lastB: Integer = null + var errorB: Throwable = null + var completedB: Boolean = false + val observerB = Observer[Integer]( + (next: Integer) => { lastB = next }, + (error: Throwable) => { errorB = error }, + () => { completedB = true } + ) + + var lastC: Integer = null + var errorC: Throwable = null + var completedC: Boolean = false + val observerC = Observer[Integer]( + (next: Integer) => { lastC = next }, + (error: Throwable) => { errorC = error }, + () => { completedC = true } + ) + + val channel: Subject[Integer] = Subject[Integer]() + + val a = channel(observerA) + val b = channel(observerB) + + assertEquals(null, lastA) + assertEquals(null, lastB) + + channel.onNext(42) + + assertEquals(42, lastA) + assertEquals(42, lastB) + + a.unsubscribe() + channel.onNext(4711) + + assertEquals(42, lastA) + assertEquals(4711, lastB) + + channel.onCompleted() + + assertFalse(completedA) + assertTrue(completedB) + assertEquals(42, lastA) + assertEquals(4711, lastB) + + val c = channel.subscribe(observerC) + channel.onNext(13) + + assertEquals(null, lastC) + assertTrue(completedC) + + assertFalse(completedA) + assertTrue(completedB) + assertEquals(42, lastA) + assertEquals(4711, lastB) + + channel.onError(new Exception("!")) + + assertEquals(null, lastC) + assertTrue(completedC) + + assertFalse(completedA) + assertTrue(completedB) + assertEquals(42, lastA) + assertEquals(4711, lastB) + } + + @Test def ReplaySubjectIsAChannel() { + + val channel = ReplaySubject[Integer] + + var lastA: Integer = null + var errorA, completedA: Boolean = false + val a = channel.subscribe(x => { lastA = x}, e => { errorA = true} , () => { completedA = true }) + + var lastB: Integer = null + var errorB, completedB: Boolean = false + + val b = channel(new Observer[Integer] { + override def onNext(value: Integer): Unit = { lastB = value } + override def onError(error: Throwable): Unit = { errorB = true } + override def onCompleted(): Unit = { completedB = true } + }) + + channel.onNext(42) + + assertEquals(42, lastA) + assertEquals(42, lastB) + + a.unsubscribe() + + channel.onNext(4711) + + assertEquals(42, lastA) + assertEquals(4711, lastB) + + channel.onCompleted() + + assertEquals(42, lastA) + assertFalse(completedA) + assertFalse(errorA) + + assertEquals(4711, lastB) + assertTrue(completedB) + assertFalse(errorB) + + var lastC: Integer = null + var errorC, completedC: Boolean = false + val c = channel.subscribe(x => { lastC = x}, e => { errorC = true} , () => { completedC = true }) + + assertEquals(4711, lastC) + assertTrue(completedC) + assertFalse(errorC) + + channel.onNext(13) + + assertEquals(42, lastA) + assertFalse(completedA) + assertFalse(errorA) + + assertEquals(4711, lastB) + assertTrue(completedB) + assertFalse(errorB) + + assertEquals(4711, lastC) + assertTrue(completedC) + assertFalse(errorC) + + channel.onError(new Exception("Boom")) + + assertEquals(42, lastA) + assertFalse(completedA) + assertFalse(errorA) + + assertEquals(4711, lastB) + assertTrue(completedB) + assertFalse(errorB) + + assertEquals(4711, lastC) + assertTrue(completedC) + assertFalse(errorC) + } + + @Test def BehaviorSubjectIsACache() { + + val channel = BehaviorSubject(2013) + + var lastA: Integer = null + var errorA, completedA: Boolean = false + val a = channel.subscribe(x => { lastA = x}, e => { errorA = true} , () => { completedA = true }) + + var lastB: Integer = null + var errorB, completedB: Boolean = false + val b = channel.subscribe(x => { lastB = x}, e => { errorB = true} , () => { completedB = true }) + + assertEquals(2013, lastA) + assertEquals(2013, lastB) + + channel.onNext(42) + + assertEquals(42, lastA) + assertEquals(42, lastB) + + a.unsubscribe() + + channel.onNext(4711) + + assertEquals(42, lastA) + assertEquals(4711, lastB) + + channel.onCompleted() + + var lastC: Integer = null + var errorC, completedC: Boolean = false + val c = channel.subscribe(x => { lastC = x}, e => { errorC = true} , () => { completedC = true }) + + assertEquals(null, lastC) + assertTrue(completedC) + assertFalse(errorC) + + channel.onNext(13) + + assertEquals(42, lastA) + assertFalse(completedA) + assertFalse(errorA) + + assertEquals(4711, lastB) + assertTrue(completedB) + assertFalse(errorB) + + assertEquals(null, lastC) + assertTrue(completedC) + assertFalse(errorC) + + channel.onError(new Exception("Boom")) + + assertEquals(42, lastA) + assertFalse(completedA) + assertFalse(errorA) + + assertEquals(4711, lastB) + assertTrue(completedB) + assertFalse(errorB) + + assertEquals(null, lastC) + assertTrue(completedC) + assertFalse(errorC) + + } + + @Test def AsyncSubjectIsAFuture() { + + val channel = AsyncSubject[Int]() + + var lastA: Integer = null + var errorA, completedA: Boolean = false + val a = channel.subscribe(x => { lastA = x}, e => { errorA = true} , () => { completedA = true }) + + var lastB: Integer = null + var errorB, completedB: Boolean = false + val b = channel.subscribe(x => { lastB = x}, e => { errorB = true} , () => { completedB = true }) + + channel.onNext(42) + + Assert.assertEquals(null, lastA) + Assert.assertEquals(null, lastB) + + a.unsubscribe() + channel.onNext(4711) + channel.onCompleted() + + Assert.assertEquals(null, lastA) + Assert.assertFalse(completedA) + Assert.assertFalse(errorA) + + Assert.assertEquals(4711, lastB) + Assert.assertTrue(completedB) + Assert.assertFalse(errorB) + + + var lastC: Integer = null + var errorC, completedC: Boolean = false + val c = channel.subscribe(x => { lastC = x}, e => { errorC = true} , () => { completedC = true }) + + Assert.assertEquals(4711, lastC) + Assert.assertTrue(completedC) + Assert.assertFalse(errorC) + + channel.onNext(13) + + Assert.assertEquals(null, lastA) + Assert.assertFalse(completedA) + Assert.assertFalse(errorA) + + Assert.assertEquals(4711, lastB) + Assert.assertTrue(completedB) + Assert.assertFalse(errorB) + + Assert.assertEquals(4711, lastC) + Assert.assertTrue(completedC) + Assert.assertFalse(errorC) + + channel.onError(new Exception("Boom")) + + Assert.assertEquals(null, lastA) + Assert.assertFalse(completedA) + Assert.assertFalse(errorA) + + Assert.assertEquals(4711, lastB) + Assert.assertTrue(completedB) + Assert.assertFalse(errorB) + + Assert.assertEquals(4711, lastC) + Assert.assertTrue(completedC) + Assert.assertFalse(errorC) + + } + +} \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/SubscriptionTests.scala b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/SubscriptionTests.scala new file mode 100644 index 0000000000..b53d4fe265 --- /dev/null +++ b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/SubscriptionTests.scala @@ -0,0 +1,186 @@ +/** + * 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.junit.{Assert, Test} +import org.junit.Assert +import org.scalatest.junit.JUnitSuite +import scala.concurrent.duration._ +import scala.language.postfixOps +import org.mockito.Mockito._ +import org.mockito.Matchers._ +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Assert.assertFalse +import rx.lang.scala.subscriptions.{SerialSubscription, MultipleAssignmentSubscription, CompositeSubscription} + + + +class SubscriptionTests extends JUnitSuite { + @Test + def subscriptionCreate() { + + val subscription = Subscription() + + assertFalse(subscription.isUnsubscribed) + + subscription.unsubscribe() + + assertTrue(subscription.isUnsubscribed) + } + + @Test + def subscriptionUnsubscribeIdempotent() { + + var called = false + + val subscription = Subscription{ called = !called } + + assertFalse(called) + assertFalse(subscription.isUnsubscribed) + + subscription.unsubscribe() + + assertTrue(called) + assertTrue(subscription.isUnsubscribed) + + subscription.unsubscribe() + + assertTrue(called) + assertTrue(subscription.isUnsubscribed) + } + + @Test + def compositeSubscriptionAdd() { + + val s0 = Subscription() + val s1 = Subscription() + + val composite = CompositeSubscription() + + assertFalse(composite.isUnsubscribed) + + composite += s0 + composite += s1 + + composite.unsubscribe() + + assertTrue(composite.isUnsubscribed) + assertTrue(s0.isUnsubscribed) + assertTrue(s1.isUnsubscribed) + + val s2 = Subscription{} + + assertFalse(s2.isUnsubscribed) + + composite += s2 + + assertTrue(s2.isUnsubscribed) + + } + + @Test + def compositeSubscriptionRemove() { + + val s0 = Subscription() + val composite = CompositeSubscription() + + composite += s0 + assertFalse(s0.isUnsubscribed) + + composite -= s0 + assertTrue(s0.isUnsubscribed) + + composite.unsubscribe() + + assertTrue(composite.isUnsubscribed) + assertTrue(s0.isUnsubscribed) + } + + @Test + def multiAssignmentSubscriptionAdd() { + + val s0 = Subscription() + val s1 = Subscription() + val multiple = MultipleAssignmentSubscription() + + assertFalse(multiple.isUnsubscribed) + assertFalse(s0.isUnsubscribed) + assertFalse(s1.isUnsubscribed) + + multiple.subscription = s0 + + assertFalse(s0.isUnsubscribed) + assertFalse(s1.isUnsubscribed) + + multiple.subscription = s1 + + assertFalse(s0.isUnsubscribed) // difference with SerialSubscription + assertFalse(s1.isUnsubscribed) + + multiple.unsubscribe() + + assertTrue(multiple.isUnsubscribed) + assertFalse(s0.isUnsubscribed) + assertTrue(s1.isUnsubscribed) + + val s2 = Subscription() + + assertFalse(s2.isUnsubscribed) + + multiple.subscription = s2 + + assertTrue(s2.isUnsubscribed) + assertFalse(s0.isUnsubscribed) + } + + @Test + def serialSubscriptionAdd() { + + val s0 = Subscription() + val s1 = Subscription() + val serial = SerialSubscription() + + assertFalse(serial.isUnsubscribed) + assertFalse(s0.isUnsubscribed) + assertFalse(s1.isUnsubscribed) + + serial.subscription = s0 + + assertFalse(s0.isUnsubscribed) + assertFalse(s1.isUnsubscribed) + + serial.subscription = s1 + + assertTrue(s0.isUnsubscribed) // difference with MultipleAssignmentSubscription + assertFalse(s1.isUnsubscribed) + + serial.unsubscribe() + + assertTrue(serial.isUnsubscribed) + assertTrue(s1.isUnsubscribed) + + val s2 = Subscription() + + assertFalse(s2.isUnsubscribed) + + serial.subscription = s2 + + assertTrue(s2.isUnsubscribed) + } + +} 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 deleted file mode 100644 index 4309967c0a..0000000000 --- a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/subscriptions/SubscriptionTests.scala +++ /dev/null @@ -1,118 +0,0 @@ -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 144d3cd68a..d3bc3e2e8d 100644 --- a/rxjava-contrib/rxjava-android/build.gradle +++ b/rxjava-contrib/rxjava-android/build.gradle @@ -8,7 +8,7 @@ dependencies { // testing provided 'junit:junit-dep:4.10' provided 'org.mockito:mockito-core:1.8.5' - provided 'org.robolectric:robolectric:2.1.1' + provided 'org.robolectric:robolectric:2.2' } javadoc { @@ -27,6 +27,7 @@ jar { 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/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 0b238b1644..f0e42f0d6d 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 @@ -15,37 +15,25 @@ */ package rx.android.concurrency; -import android.os.Handler; -import android.os.Looper; import rx.Scheduler; +import android.os.Handler; /** - * Schedulers that have Android specific functionality + * Deprecated. Package changed from rx.android.concurrency to rx.android.schedulers. + * + * @deprecated Use {@link rx.android.schedulers.AndroidSchedulers} instead. This will be removed before 1.0 release. */ +@Deprecated public class AndroidSchedulers { - private static final Scheduler MAIN_THREAD_SCHEDULER = - new HandlerThreadScheduler(new Handler(Looper.getMainLooper())); - - private AndroidSchedulers(){ - - } - - /** - * {@link Scheduler} which uses the provided {@link Handler} to execute an action - * @param handler The handler that will be used when executing the action - * @return A handler based scheduler - */ + @Deprecated public static Scheduler handlerThread(final Handler handler) { - return new HandlerThreadScheduler(handler); + return rx.android.schedulers.AndroidSchedulers.handlerThread(handler); } - /** - * {@link Scheduler} which will execute an action on the main Android UI thread. - * - * @return A Main {@link Looper} based scheduler - */ + @Deprecated public static Scheduler mainThread() { - return MAIN_THREAD_SCHEDULER; + return rx.android.schedulers.AndroidSchedulers.mainThread(); } + } 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 ae01d17c1a..7c117ccf44 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 @@ -17,116 +17,16 @@ import android.os.Handler; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import rx.Scheduler; -import rx.Subscription; -import rx.operators.SafeObservableSubscription; -import rx.util.functions.Func2; - -import java.util.concurrent.TimeUnit; - -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - /** - * Schedules actions to run on an Android Handler thread. + * Deprecated. Package changed from rx.android.concurrency to rx.android.schedulers. + * + * @deprecated Use {@link rx.android.schedulers.HandlerThreadScheduler} instead. This will be removed before 1.0 release. */ -public class HandlerThreadScheduler extends Scheduler { - - private final Handler handler; +@Deprecated +public class HandlerThreadScheduler extends rx.android.schedulers.HandlerThreadScheduler { - /** - * Constructs a {@link HandlerThreadScheduler} using the given {@link Handler} - * @param handler {@link Handler} to use when scheduling actions - */ public HandlerThreadScheduler(Handler handler) { - this.handler = handler; + super(handler); } - /** - * Calls {@link HandlerThreadScheduler#schedule(Object, rx.util.functions.Func2, long, java.util.concurrent.TimeUnit)} - * with a delay of zero milliseconds. - * - * See {@link #schedule(Object, rx.util.functions.Func2, long, java.util.concurrent.TimeUnit)} - */ - @Override - public Subscription schedule(final T state, final Func2 action) { - return schedule(state, action, 0L, TimeUnit.MILLISECONDS); - } - - /** - * Calls {@link Handler#postDelayed(Runnable, long)} with a runnable that executes the given action. - * @param state - * State to pass into the action. - * @param action - * Action to schedule. - * @param delayTime - * Time the action is to be delayed before executing. - * @param unit - * Time unit of the delay time. - * @return A Subscription from which one can unsubscribe from. - */ - @Override - public Subscription schedule(final T state, final Func2 action, long delayTime, TimeUnit unit) { - final SafeObservableSubscription subscription = new SafeObservableSubscription(); - final Scheduler _scheduler = this; - handler.postDelayed(new Runnable() { - @Override - public void run() { - subscription.wrap(action.call(_scheduler, state)); - } - }, unit.toMillis(delayTime)); - return subscription; - } - - @RunWith(RobolectricTestRunner.class) - @Config(manifest=Config.NONE) - public static final class UnitTest { - - @Test - public void shouldScheduleImmediateActionOnHandlerThread() { - final Handler handler = mock(Handler.class); - final Object state = new Object(); - @SuppressWarnings("unchecked") - final Func2 action = mock(Func2.class); - - Scheduler scheduler = new HandlerThreadScheduler(handler); - scheduler.schedule(state, action); - - // verify that we post to the given Handler - ArgumentCaptor runnable = ArgumentCaptor.forClass(Runnable.class); - verify(handler).postDelayed(runnable.capture(), eq(0L)); - - // verify that the given handler delegates to our action - runnable.getValue().run(); - verify(action).call(scheduler, state); - } - - @Test - public void shouldScheduleDelayedActionOnHandlerThread() { - final Handler handler = mock(Handler.class); - final Object state = new Object(); - @SuppressWarnings("unchecked") - final Func2 action = mock(Func2.class); - - Scheduler scheduler = new HandlerThreadScheduler(handler); - scheduler.schedule(state, action, 1L, TimeUnit.SECONDS); - - // verify that we post to the given Handler - ArgumentCaptor runnable = ArgumentCaptor.forClass(Runnable.class); - verify(handler).postDelayed(runnable.capture(), eq(1000L)); - - // verify that the given handler delegates to our action - runnable.getValue().run(); - verify(action).call(scheduler, state); - } - } } - - 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 index 5ae8a11a74..d55617ddea 100644 --- 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 @@ -15,24 +15,11 @@ */ package rx.android.observables; -import static org.mockito.Mockito.verify; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.Robolectric; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; import rx.Observable; -import rx.Observer; -import rx.operators.OperationObserveFromAndroidComponent; - +import rx.operators.OperatorObserveFromAndroidComponent; import android.app.Activity; import android.app.Fragment; import android.os.Build; -import android.support.v4.app.FragmentActivity; public final class AndroidObservable { @@ -72,7 +59,7 @@ private AndroidObservable() {} * @return a new observable sequence that will emit notifications on the main UI thread */ public static Observable fromActivity(Activity activity, Observable sourceObservable) { - return OperationObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, activity); + return OperatorObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, activity); } /** @@ -101,59 +88,11 @@ public static Observable fromActivity(Activity activity, Observable so */ public static Observable fromFragment(Object fragment, Observable sourceObservable) { if (USES_SUPPORT_FRAGMENTS && fragment instanceof android.support.v4.app.Fragment) { - return OperationObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, (android.support.v4.app.Fragment) fragment); + return OperatorObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, (android.support.v4.app.Fragment) fragment); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && fragment instanceof Fragment) { - return OperationObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, (Fragment) fragment); + return OperatorObserveFromAndroidComponent.observeFromAndroidComponent(sourceObservable, (Fragment) fragment); } else { throw new IllegalArgumentException("Target fragment is neither a native nor support library Fragment"); } } - - @RunWith(RobolectricTestRunner.class) - @Config(manifest = Config.NONE) - public static final class AndroidObservableTest { - - // support library fragments - private FragmentActivity fragmentActivity; - private android.support.v4.app.Fragment supportFragment; - - // native fragments - private Activity activity; - private Fragment fragment; - - @Mock - private Observer observer; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - supportFragment = new android.support.v4.app.Fragment(); - fragmentActivity = Robolectric.buildActivity(FragmentActivity.class).create().get(); - fragmentActivity.getSupportFragmentManager().beginTransaction().add(supportFragment, null).commit(); - - fragment = new Fragment(); - activity = Robolectric.buildActivity(Activity.class).create().get(); - activity.getFragmentManager().beginTransaction().add(fragment, null).commit(); - } - - @Test - public void itSupportsFragmentsFromTheSupportV4Library() { - fromFragment(supportFragment, Observable.just("success")).subscribe(observer); - verify(observer).onNext("success"); - verify(observer).onCompleted(); - } - - @Test - public void itSupportsNativeFragments() { - fromFragment(fragment, Observable.just("success")).subscribe(observer); - verify(observer).onNext("success"); - verify(observer).onCompleted(); - } - - @Test(expected = IllegalArgumentException.class) - public void itThrowsIfObjectPassedIsNotAFragment() { - fromFragment("not a fragment", Observable.never()); - } - } - } diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/android/observables/ViewObservable.java b/rxjava-contrib/rxjava-android/src/main/java/rx/android/observables/ViewObservable.java new file mode 100644 index 0000000000..6e31af051e --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/android/observables/ViewObservable.java @@ -0,0 +1,47 @@ +/** + * Copyright 2014 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 android.os.Looper; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.EditText; +import rx.Observable; +import rx.operators.OperatorCompoundButtonInput; +import rx.operators.OperatorEditTextInput; +import rx.operators.OperatorViewClick; + +public class ViewObservable { + + public static Observable clicks(final View view, final boolean emitInitialValue) { + return Observable.create(new OperatorViewClick(view, emitInitialValue)); + } + + public static Observable input(final EditText input, final boolean emitInitialValue) { + return Observable.create(new OperatorEditTextInput(input, emitInitialValue)); + } + + public static Observable input(final CompoundButton button, final boolean emitInitialValue) { + return Observable.create(new OperatorCompoundButtonInput(button, emitInitialValue)); + } + + public static void assertUiThread() { + if (Looper.getMainLooper() != Looper.myLooper()) { + throw new IllegalStateException("Observers must subscribe from the main UI thread, but was " + Thread.currentThread()); + } + } +} + diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/android/schedulers/AndroidSchedulers.java b/rxjava-contrib/rxjava-android/src/main/java/rx/android/schedulers/AndroidSchedulers.java new file mode 100644 index 0000000000..d43f0d2db2 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/android/schedulers/AndroidSchedulers.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.android.schedulers; + +import rx.Scheduler; +import android.os.Handler; +import android.os.Looper; + +/** + * Schedulers that have Android specific functionality + */ +public class AndroidSchedulers { + + private static final Scheduler MAIN_THREAD_SCHEDULER = + new HandlerThreadScheduler(new Handler(Looper.getMainLooper())); + + private AndroidSchedulers(){ + + } + + /** + * {@link Scheduler} which uses the provided {@link Handler} to execute an action + * @param handler The handler that will be used when executing the action + * @return A handler based scheduler + */ + public static Scheduler handlerThread(final Handler handler) { + return new HandlerThreadScheduler(handler); + } + + /** + * {@link Scheduler} which will execute an action on the main Android UI thread. + * + * @return A Main {@link Looper} based scheduler + */ + public static Scheduler mainThread() { + return MAIN_THREAD_SCHEDULER; + } +} diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/android/schedulers/HandlerThreadScheduler.java b/rxjava-contrib/rxjava-android/src/main/java/rx/android/schedulers/HandlerThreadScheduler.java new file mode 100644 index 0000000000..9bfa548880 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/android/schedulers/HandlerThreadScheduler.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.android.schedulers; + +import java.util.concurrent.TimeUnit; + +import rx.Scheduler; +import rx.Subscription; +import rx.functions.Action1; +import rx.subscriptions.BooleanSubscription; +import android.os.Handler; + +/** + * Schedules actions to run on an Android Handler thread. + */ +public class HandlerThreadScheduler extends Scheduler { + + private final Handler handler; + + /** + * Constructs a {@link HandlerThreadScheduler} using the given {@link Handler} + * + * @param handler + * {@link Handler} to use when scheduling actions + */ + public HandlerThreadScheduler(Handler handler) { + this.handler = handler; + } + + /** + * Calls {@link HandlerThreadScheduler#schedule(Object, rx.functions.Func2, long, java.util.concurrent.TimeUnit)} with a delay of zero milliseconds. + * + * See {@link #schedule(Object, rx.functions.Func2, long, java.util.concurrent.TimeUnit)} + */ + @Override + public Subscription schedule(Action1 action) { + InnerHandlerThreadScheduler inner = new InnerHandlerThreadScheduler(handler); + inner.schedule(action); + return inner; + } + + /** + * Calls {@link Handler#postDelayed(Runnable, long)} with a runnable that executes the given action. + * + * @param state + * State to pass into the action. + * @param action + * Action to schedule. + * @param delayTime + * Time the action is to be delayed before executing. + * @param unit + * Time unit of the delay time. + * @return A Subscription from which one can unsubscribe from. + */ + @Override + public Subscription schedule(Action1 action, long delayTime, TimeUnit unit) { + InnerHandlerThreadScheduler inner = new InnerHandlerThreadScheduler(handler); + inner.schedule(action, delayTime, unit); + return inner; + } + + private static class InnerHandlerThreadScheduler extends Inner { + + private final Handler handler; + private BooleanSubscription innerSubscription = new BooleanSubscription(); + private Inner _inner = this; + + public InnerHandlerThreadScheduler(Handler handler) { + this.handler = handler; + } + + @Override + public void unsubscribe() { + innerSubscription.unsubscribe(); + } + + @Override + public boolean isUnsubscribed() { + return innerSubscription.isUnsubscribed(); + } + + @Override + public void schedule(final Action1 action, long delayTime, TimeUnit unit) { + handler.postDelayed(new Runnable() { + @Override + public void run() { + if (_inner.isUnsubscribed()) { + return; + } + action.call(_inner); + } + }, unit.toMillis(delayTime)); + } + + @Override + public void schedule(final Action1 action) { + handler.postDelayed(new Runnable() { + + @Override + public void run() { + if (_inner.isUnsubscribed()) { + return; + } + action.call(_inner); + } + + }, 0L); + } + + } + +} diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/android/subscriptions/AndroidSubscriptions.java b/rxjava-contrib/rxjava-android/src/main/java/rx/android/subscriptions/AndroidSubscriptions.java new file mode 100644 index 0000000000..8e2f54ab3e --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/android/subscriptions/AndroidSubscriptions.java @@ -0,0 +1,55 @@ +/** + * Copyright 2014 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.subscriptions; + +import rx.Scheduler.Inner; +import rx.Subscription; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.subscriptions.Subscriptions; +import android.os.Looper; + +public final class AndroidSubscriptions { + + private AndroidSubscriptions() { + // no instance + } + + /** + * Create an Subscription that always runs unsubscribe in the UI thread. + * + * @param unsubscribe + * @return an Subscription that always runs unsubscribe in the UI thread. + */ + public static Subscription unsubscribeInUiThread(final Action0 unsubscribe) { + return Subscriptions.create(new Action0() { + @Override + public void call() { + if (Looper.getMainLooper() == Looper.myLooper()) { + unsubscribe.call(); + } else { + AndroidSchedulers.mainThread().schedule(new Action1() { + @Override + public void call(Inner inner) { + unsubscribe.call(); + } + }); + } + } + }); + } +} 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 deleted file mode 100644 index cfce38f8bb..0000000000 --- a/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperationObserveFromAndroidComponent.java +++ /dev/null @@ -1,313 +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 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-android/src/main/java/rx/operators/OperatorCompoundButtonInput.java b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorCompoundButtonInput.java new file mode 100644 index 0000000000..9a61093699 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorCompoundButtonInput.java @@ -0,0 +1,107 @@ +/** + * Copyright 2014 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.Map; +import java.util.WeakHashMap; + +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.android.observables.ViewObservable; +import rx.android.subscriptions.AndroidSubscriptions; +import rx.functions.Action0; +import android.view.View; +import android.widget.CompoundButton; + +public class OperatorCompoundButtonInput implements Observable.OnSubscribe { + private final boolean emitInitialValue; + private final CompoundButton button; + + public OperatorCompoundButtonInput(final CompoundButton button, final boolean emitInitialValue) { + this.emitInitialValue = emitInitialValue; + this.button = button; + } + + @Override + public void call(final Subscriber observer) { + ViewObservable.assertUiThread(); + final CompositeOnCheckedChangeListener composite = CachedListeners.getFromViewOrCreate(button); + + final CompoundButton.OnCheckedChangeListener listener = new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(final CompoundButton button, final boolean checked) { + observer.onNext(checked); + } + }; + + final Subscription subscription = AndroidSubscriptions.unsubscribeInUiThread(new Action0() { + @Override + public void call() { + composite.removeOnCheckedChangeListener(listener); + } + }); + + if (emitInitialValue) { + observer.onNext(button.isChecked()); + } + + composite.addOnCheckedChangeListener(listener); + observer.add(subscription); + } + + private static class CompositeOnCheckedChangeListener implements CompoundButton.OnCheckedChangeListener { + private final List listeners = new ArrayList(); + + public boolean addOnCheckedChangeListener(final CompoundButton.OnCheckedChangeListener listener) { + return listeners.add(listener); + } + + public boolean removeOnCheckedChangeListener(final CompoundButton.OnCheckedChangeListener listener) { + return listeners.remove(listener); + } + + @Override + public void onCheckedChanged(final CompoundButton button, final boolean checked) { + for (final CompoundButton.OnCheckedChangeListener listener : listeners) { + listener.onCheckedChanged(button, checked); + } + } + } + + private static class CachedListeners { + private static final Map sCachedListeners = new WeakHashMap(); + + public static CompositeOnCheckedChangeListener getFromViewOrCreate(final CompoundButton button) { + final CompositeOnCheckedChangeListener cached = sCachedListeners.get(button); + + if (cached != null) { + return cached; + } + + final CompositeOnCheckedChangeListener listener = new CompositeOnCheckedChangeListener(); + + sCachedListeners.put(button, listener); + button.setOnCheckedChangeListener(listener); + + return listener; + } + } +} + + diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorEditTextInput.java b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorEditTextInput.java new file mode 100644 index 0000000000..1dcbac0014 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorEditTextInput.java @@ -0,0 +1,79 @@ +/** + * Copyright 2014 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.Subscriber; +import rx.Subscription; +import rx.android.observables.ViewObservable; +import rx.android.subscriptions.AndroidSubscriptions; +import rx.functions.Action0; +import android.text.Editable; +import android.text.TextWatcher; +import android.widget.EditText; + +public class OperatorEditTextInput implements Observable.OnSubscribe { + private final EditText input; + private final boolean emitInitialValue; + + public OperatorEditTextInput(final EditText input, final boolean emitInitialValue) { + this.input = input; + this.emitInitialValue = emitInitialValue; + } + + @Override + public void call(final Subscriber observer) { + ViewObservable.assertUiThread(); + final TextWatcher watcher = new SimpleTextWatcher() { + @Override + public void afterTextChanged(final Editable editable) { + observer.onNext(editable.toString()); + } + }; + + final Subscription subscription = AndroidSubscriptions.unsubscribeInUiThread(new Action0() { + @Override + public void call() { + input.removeTextChangedListener(watcher); + } + }); + + if (emitInitialValue) { + observer.onNext(input.getEditableText().toString()); + } + + input.addTextChangedListener(watcher); + observer.add(subscription); + } + + private static class SimpleTextWatcher implements TextWatcher { + @Override + public void beforeTextChanged(final CharSequence sequence, final int start, final int count, final int after) { + // nothing to do + } + + @Override + public void onTextChanged(final CharSequence sequence, final int start, final int before, final int count) { + // nothing to do + } + + @Override + public void afterTextChanged(final Editable editable) { + // nothing to do + } + } +} + diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorObserveFromAndroidComponent.java b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorObserveFromAndroidComponent.java new file mode 100644 index 0000000000..3e656ccfbb --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorObserveFromAndroidComponent.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 rx.Observable; +import rx.Observer; +import rx.Subscriber; +import rx.android.schedulers.AndroidSchedulers; +import rx.android.subscriptions.AndroidSubscriptions; +import rx.functions.Action0; +import android.app.Activity; +import android.os.Looper; +import android.util.Log; + +public class OperatorObserveFromAndroidComponent { + + 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.OnSubscribe { + + 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 void call(Subscriber subscriber) { + assertUiThread(); + observerRef = subscriber; + source.observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber(subscriber) { + @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"); + } + } + }); + subscriber.add(AndroidSubscriptions.unsubscribeInUiThread(new Action0() { + @Override + public void call() { + log("unsubscribing from source sequence"); + releaseReferences(); + } + })); + } + + 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(); + } + } +} diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorViewClick.java b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorViewClick.java new file mode 100644 index 0000000000..1fb2bb8b91 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/main/java/rx/operators/OperatorViewClick.java @@ -0,0 +1,104 @@ +/** + * Copyright 2014 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.Map; +import java.util.WeakHashMap; + +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.android.observables.ViewObservable; +import rx.android.subscriptions.AndroidSubscriptions; +import rx.functions.Action0; +import android.view.View; + +public final class OperatorViewClick implements Observable.OnSubscribe { + private final boolean emitInitialValue; + private final View view; + + public OperatorViewClick(final View view, final boolean emitInitialValue) { + this.emitInitialValue = emitInitialValue; + this.view = view; + } + + @Override + public void call(final Subscriber observer) { + ViewObservable.assertUiThread(); + final CompositeOnClickListener composite = CachedListeners.getFromViewOrCreate(view); + + final View.OnClickListener listener = new View.OnClickListener() { + @Override + public void onClick(final View clicked) { + observer.onNext(view); + } + }; + + final Subscription subscription = AndroidSubscriptions.unsubscribeInUiThread(new Action0() { + @Override + public void call() { + composite.removeOnClickListener(listener); + } + }); + + if (emitInitialValue) { + observer.onNext(view); + } + + composite.addOnClickListener(listener); + observer.add(subscription); + } + + private static class CompositeOnClickListener implements View.OnClickListener { + private final List listeners = new ArrayList(); + + public boolean addOnClickListener(final View.OnClickListener listener) { + return listeners.add(listener); + } + + public boolean removeOnClickListener(final View.OnClickListener listener) { + return listeners.remove(listener); + } + + @Override + public void onClick(final View view) { + for (final View.OnClickListener listener : listeners) { + listener.onClick(view); + } + } + } + + private static class CachedListeners { + private static final Map sCachedListeners = new WeakHashMap(); + + public static CompositeOnClickListener getFromViewOrCreate(final View view) { + final CompositeOnClickListener cached = sCachedListeners.get(view); + + if (cached != null) { + return cached; + } + + final CompositeOnClickListener listener = new CompositeOnClickListener(); + + sCachedListeners.put(view, listener); + view.setOnClickListener(listener); + + return listener; + } + } +} diff --git a/rxjava-contrib/rxjava-android/src/test/java/rx/android/observables/AndroidObservableTest.java b/rxjava-contrib/rxjava-android/src/test/java/rx/android/observables/AndroidObservableTest.java new file mode 100644 index 0000000000..a5006bdf91 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/test/java/rx/android/observables/AndroidObservableTest.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.android.observables; + +import static org.mockito.Mockito.*; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import rx.Observable; +import rx.Observer; +import rx.observers.TestObserver; +import android.app.Activity; +import android.app.Fragment; +import android.support.v4.app.FragmentActivity; + + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class AndroidObservableTest { + + // support library fragments + private FragmentActivity fragmentActivity; + private android.support.v4.app.Fragment supportFragment; + + // native fragments + private Activity activity; + private Fragment fragment; + + @Mock + private Observer observer; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + supportFragment = new android.support.v4.app.Fragment(); + fragmentActivity = Robolectric.buildActivity(FragmentActivity.class).create().get(); + fragmentActivity.getSupportFragmentManager().beginTransaction().add(supportFragment, null).commit(); + + fragment = new Fragment(); + activity = Robolectric.buildActivity(Activity.class).create().get(); + activity.getFragmentManager().beginTransaction().add(fragment, null).commit(); + } + + @Test + public void itSupportsFragmentsFromTheSupportV4Library() { + AndroidObservable.fromFragment(supportFragment, Observable.just("success")).subscribe(new TestObserver(observer)); + verify(observer).onNext("success"); + verify(observer).onCompleted(); + } + + @Test + public void itSupportsNativeFragments() { + AndroidObservable.fromFragment(fragment, Observable.just("success")).subscribe(new TestObserver(observer)); + verify(observer).onNext("success"); + verify(observer).onCompleted(); + } + + @Test(expected = IllegalArgumentException.class) + public void itThrowsIfObjectPassedIsNotAFragment() { + AndroidObservable.fromFragment("not a fragment", Observable.never()); + } +} diff --git a/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorCompoundButtonInputTest.java b/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorCompoundButtonInputTest.java new file mode 100644 index 0000000000..5939f5f7c4 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorCompoundButtonInputTest.java @@ -0,0 +1,148 @@ +/** + * Copyright 2014 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.operators; + +import android.app.Activity; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.android.observables.ViewObservable; +import rx.observers.TestObserver; + +import static org.mockito.Mockito.*; + +@RunWith(RobolectricTestRunner.class) +public class OperatorCompoundButtonInputTest { + + private static CompoundButton createCompoundButton(final boolean value) { + final Activity activity = Robolectric.buildActivity(Activity.class).create().get(); + final CheckBox checkbox = new CheckBox(activity); + + checkbox.setChecked(value); + return checkbox; + } + + @Test + @SuppressWarnings("unchecked") + public void testWithoutInitialValue() { + final CompoundButton button = createCompoundButton(true); + final Observable observable = ViewObservable.input(button, false); + final Observer observer = mock(Observer.class); + final Subscription subscription = observable.subscribe(new TestObserver(observer)); + + final InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, never()).onNext(anyBoolean()); + + button.setChecked(true); + inOrder.verify(observer, never()).onNext(anyBoolean()); + + button.setChecked(false); + inOrder.verify(observer, times(1)).onNext(false); + + button.setChecked(true); + inOrder.verify(observer, times(1)).onNext(true); + + button.setChecked(false); + inOrder.verify(observer, times(1)).onNext(false); + subscription.unsubscribe(); + + button.setChecked(true); + inOrder.verify(observer, never()).onNext(anyBoolean()); + + inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(observer, never()).onCompleted(); + } + + @Test + @SuppressWarnings("unchecked") + public void testWithInitialValue() { + final CompoundButton button = createCompoundButton(true); + final Observable observable = ViewObservable.input(button, true); + final Observer observer = mock(Observer.class); + final Subscription subscription = observable.subscribe(new TestObserver(observer)); + + final InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, times(1)).onNext(true); + + button.setChecked(false); + inOrder.verify(observer, times(1)).onNext(false); + + button.setChecked(true); + inOrder.verify(observer, times(1)).onNext(true); + + button.setChecked(true); + inOrder.verify(observer, never()).onNext(anyBoolean()); + + button.setChecked(false); + inOrder.verify(observer, times(1)).onNext(false); + subscription.unsubscribe(); + + button.setChecked(true); + inOrder.verify(observer, never()).onNext(anyBoolean()); + + inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(observer, never()).onCompleted(); + } + + @Test + @SuppressWarnings("unchecked") + public void testMultipleSubscriptions() { + final CompoundButton button = createCompoundButton(false); + final Observable observable = ViewObservable.input(button, false); + + final Observer observer1 = mock(Observer.class); + final Observer observer2 = mock(Observer.class); + + final Subscription subscription1 = observable.subscribe(new TestObserver(observer1)); + final Subscription subscription2 = observable.subscribe(new TestObserver(observer2)); + + final InOrder inOrder1 = inOrder(observer1); + final InOrder inOrder2 = inOrder(observer2); + + button.setChecked(true); + inOrder1.verify(observer1, times(1)).onNext(true); + inOrder2.verify(observer2, times(1)).onNext(true); + + button.setChecked(false); + inOrder1.verify(observer1, times(1)).onNext(false); + inOrder2.verify(observer2, times(1)).onNext(false); + subscription1.unsubscribe(); + + button.setChecked(true); + inOrder1.verify(observer1, never()).onNext(anyBoolean()); + inOrder2.verify(observer2, times(1)).onNext(true); + subscription2.unsubscribe(); + + button.setChecked(false); + inOrder1.verify(observer1, never()).onNext(anyBoolean()); + inOrder2.verify(observer2, never()).onNext(anyBoolean()); + + inOrder1.verify(observer1, never()).onError(any(Throwable.class)); + inOrder1.verify(observer1, never()).onCompleted(); + + inOrder2.verify(observer2, never()).onError(any(Throwable.class)); + inOrder2.verify(observer2, never()).onCompleted(); + } +} diff --git a/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorEditTextInputTest.java b/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorEditTextInputTest.java new file mode 100644 index 0000000000..98d34e90a5 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorEditTextInputTest.java @@ -0,0 +1,145 @@ +/** + * Copyright 2014 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.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import android.app.Activity; +import android.widget.EditText; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.android.observables.ViewObservable; +import rx.observers.TestObserver; + +@RunWith(RobolectricTestRunner.class) +public class OperatorEditTextInputTest { + + private static EditText createEditText(final String value) { + final Activity activity = Robolectric.buildActivity(Activity.class).create().get(); + final EditText text = new EditText(activity); + + if (value != null) { + text.setText(value); + } + + return text; + } + + @Test + @SuppressWarnings("unchecked") + public void testWithoutInitialValue() { + final EditText input = createEditText("initial"); + final Observable observable = ViewObservable.input(input, false); + final Observer observer = mock(Observer.class); + final Subscription subscription = observable.subscribe(new TestObserver(observer)); + + final InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, never()).onNext(anyString()); + + input.setText("1"); + inOrder.verify(observer, times(1)).onNext("1"); + + input.setText("2"); + inOrder.verify(observer, times(1)).onNext("2"); + + input.setText("3"); + inOrder.verify(observer, times(1)).onNext("3"); + + subscription.unsubscribe(); + input.setText("4"); + inOrder.verify(observer, never()).onNext(anyString()); + + inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(observer, never()).onCompleted(); + } + + @Test + @SuppressWarnings("unchecked") + public void testWithInitialValue() { + final EditText input = createEditText("initial"); + final Observable observable = ViewObservable.input(input, true); + final Observer observer = mock(Observer.class); + final Subscription subscription = observable.subscribe(new TestObserver(observer)); + + final InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, times(1)).onNext("initial"); + + input.setText("one"); + inOrder.verify(observer, times(1)).onNext("one"); + + input.setText("two"); + inOrder.verify(observer, times(1)).onNext("two"); + + input.setText("three"); + inOrder.verify(observer, times(1)).onNext("three"); + + subscription.unsubscribe(); + input.setText("four"); + inOrder.verify(observer, never()).onNext(anyString()); + + inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(observer, never()).onCompleted(); + } + + @Test + @SuppressWarnings("unchecked") + public void testMultipleSubscriptions() { + final EditText input = createEditText("initial"); + final Observable observable = ViewObservable.input(input, false); + + final Observer observer1 = mock(Observer.class); + final Observer observer2 = mock(Observer.class); + + final Subscription subscription1 = observable.subscribe(new TestObserver(observer1)); + final Subscription subscription2 = observable.subscribe(new TestObserver(observer2)); + + final InOrder inOrder1 = inOrder(observer1); + final InOrder inOrder2 = inOrder(observer2); + + input.setText("1"); + inOrder1.verify(observer1, times(1)).onNext("1"); + inOrder2.verify(observer2, times(1)).onNext("1"); + + input.setText("2"); + inOrder1.verify(observer1, times(1)).onNext("2"); + inOrder2.verify(observer2, times(1)).onNext("2"); + subscription1.unsubscribe(); + + input.setText("3"); + inOrder1.verify(observer1, never()).onNext(anyString()); + inOrder2.verify(observer2, times(1)).onNext("3"); + subscription2.unsubscribe(); + + input.setText("4"); + inOrder1.verify(observer1, never()).onNext(anyString()); + inOrder2.verify(observer2, never()).onNext(anyString()); + + inOrder1.verify(observer1, never()).onError(any(Throwable.class)); + inOrder2.verify(observer2, never()).onError(any(Throwable.class)); + + inOrder1.verify(observer1, never()).onCompleted(); + inOrder2.verify(observer2, never()).onCompleted(); + } +} diff --git a/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorObserveFromAndroidComponentTest.java b/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorObserveFromAndroidComponentTest.java new file mode 100644 index 0000000000..70a9c881fb --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorObserveFromAndroidComponentTest.java @@ -0,0 +1,234 @@ +/** + * 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.operators; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +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 java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.Before; +import org.junit.Ignore; +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.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; +import rx.observers.TestObserver; +import rx.observers.TestSubscriber; +import rx.operators.OperatorObserveFromAndroidComponent; +import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; +import android.app.Fragment; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class OperatorObserveFromAndroidComponentTest { + + @Mock + private Observer mockObserver; + + @Mock + private Fragment mockFragment; + + @Before + public void setupMocks() { + MockitoAnnotations.initMocks(this); + when(mockFragment.isAdded()).thenReturn(true); + } + + @Test + public void itThrowsIfObserverSubscribesFromBackgroundThread() throws Exception { + final Observable testObservable = Observable.from(1); + final Future future = Executors.newSingleThreadExecutor().submit(new Callable() { + @Override + public Object call() throws Exception { + OperatorObserveFromAndroidComponent.observeFromAndroidComponent( + testObservable, mockFragment).subscribe(mockObserver); + return null; + } + }); + future.get(1, TimeUnit.SECONDS); + verify(mockObserver).onError(any(IllegalStateException.class)); + verifyNoMoreInteractions(mockObserver); + } + + // TODO needs to be fixed, see comments inline below + @Ignore + public void itObservesTheSourceSequenceOnTheMainUIThread() { + final Observable testObservable = Observable.from(1) + .observeOn(Schedulers.newThread()) + .doOnNext(new Action1() { + + @Override + public void call(Integer t1) { + System.out.println("threadA: " + Thread.currentThread()); + } + }) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext(new Action1() { + + @Override + public void call(Integer t1) { + System.out.println("threadB: " + Thread.currentThread()); + } + }); + + final AtomicReference currentThreadName = new AtomicReference(); + OperatorObserveFromAndroidComponent.observeFromAndroidComponent(testObservable, mockFragment).subscribe(new Action1() { + + @Override + public void call(Integer i) { + System.out.println("threadV: " + Thread.currentThread()); + currentThreadName.set(Thread.currentThread().getName()); + } + }); + + assertEquals("androidMainThreadName???", currentThreadName.get()); + + //TODO Can't use Mockito to validate Observable.observeOn as it is now marked as final. + // I can't figure out what to validate about the AndroidSchedulers.mainThread() + // as the code above doesn't print `threadB` so I can't see what Thread it should be. + // I was going to run it on NewThread then observeOn to AndroidThread and validate it jumped + // to the correct thread, but it doesn't do anything. Need to work with Android devs. + } + + @Test + public void itForwardsOnNextOnCompletedSequenceToTargetObserver() { + Observable source = Observable.from(Arrays.asList(1, 2, 3)); + OperatorObserveFromAndroidComponent.observeFromAndroidComponent(source, mockFragment).subscribe(new TestObserver(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); + OperatorObserveFromAndroidComponent.observeFromAndroidComponent(source, mockFragment).subscribe(new TestObserver(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 Observable.OnSubscribe operator = newOnSubscribeFragmentInstance(source, mockFragment); + operator.call(new TestSubscriber(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 Observable.OnSubscribe operator = newOnSubscribeFragmentInstance(source, mockFragment); + operator.call(new TestSubscriber(mockObserver)); + + source.onNext(1); + releaseComponentRef(operator); + + source.onError(new Exception()); + + verify(mockObserver).onNext(1); + verifyNoMoreInteractions(mockObserver); + } + + @SuppressWarnings("unchecked") + private Observable.OnSubscribe newOnSubscribeFragmentInstance(Observable source, Fragment fragment) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { + final Class[] klasses = OperatorObserveFromAndroidComponent.class.getDeclaredClasses(); + Class onSubscribeFragmentClass = null; + for (Class klass : klasses) { + if ("rx.operators.OperatorObserveFromAndroidComponent$OnSubscribeFragment".equals(klass.getName())) { + onSubscribeFragmentClass = klass; + break; + } + } + Constructor constructor = onSubscribeFragmentClass.getDeclaredConstructor(Observable.class, Fragment.class); + constructor.setAccessible(true); + Object object = constructor.newInstance(source, fragment); + return (Observable.OnSubscribe) object; + } + + private void releaseComponentRef(Observable.OnSubscribe 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(); + OperatorObserveFromAndroidComponent.observeFromAndroidComponent(source, mockFragment).subscribe(new TestObserver(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(); + OperatorObserveFromAndroidComponent.observeFromAndroidComponent(source, mockFragment).subscribe(new TestObserver(mockObserver)); + + source.onNext(1); + + when(mockFragment.isAdded()).thenReturn(false); + source.onError(new Exception()); + + verify(mockObserver).onNext(1); + verify(mockObserver, never()).onError(any(Exception.class)); + } + +} diff --git a/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorViewClickTest.java b/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorViewClickTest.java new file mode 100644 index 0000000000..c570e77bd6 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/test/java/rx/android/operators/OperatorViewClickTest.java @@ -0,0 +1,126 @@ +/** + * Copyright 2014 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.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import android.app.Activity; +import android.view.View; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.android.observables.ViewObservable; +import rx.observers.TestObserver; + +@RunWith(RobolectricTestRunner.class) +public class OperatorViewClickTest { + + @Test + @SuppressWarnings("unchecked") + public void testWithoutInitialValue() { + final View view = new View(Robolectric.buildActivity(Activity.class).create().get()); + final Observable observable = ViewObservable.clicks(view, false); + final Observer observer = mock(Observer.class); + final Subscription subscription = observable.subscribe(new TestObserver(observer)); + + final InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, never()).onNext(any(View.class)); + + view.performClick(); + inOrder.verify(observer, times(1)).onNext(view); + + view.performClick(); + inOrder.verify(observer, times(1)).onNext(view); + + subscription.unsubscribe(); + inOrder.verify(observer, never()).onNext(any(View.class)); + + inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(observer, never()).onCompleted(); + } + + @Test + @SuppressWarnings("unchecked") + public void testWithInitialValue() { + final View view = new View(Robolectric.buildActivity(Activity.class).create().get()); + final Observable observable = ViewObservable.clicks(view, true); + final Observer observer = mock(Observer.class); + final Subscription subscription = observable.subscribe(new TestObserver(observer)); + + final InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, times(1)).onNext(view); + + view.performClick(); + inOrder.verify(observer, times(1)).onNext(view); + + view.performClick(); + inOrder.verify(observer, times(1)).onNext(view); + + subscription.unsubscribe(); + inOrder.verify(observer, never()).onNext(any(View.class)); + + inOrder.verify(observer, never()).onError(any(Throwable.class)); + inOrder.verify(observer, never()).onCompleted(); + } + + @Test + @SuppressWarnings("unchecked") + public void testMultipleSubscriptions() { + final View view = new View(Robolectric.buildActivity(Activity.class).create().get()); + final Observable observable = ViewObservable.clicks(view, false); + + final Observer observer1 = mock(Observer.class); + final Observer observer2 = mock(Observer.class); + + final Subscription subscription1 = observable.subscribe(new TestObserver(observer1)); + final Subscription subscription2 = observable.subscribe(new TestObserver(observer2)); + + final InOrder inOrder1 = inOrder(observer1); + final InOrder inOrder2 = inOrder(observer2); + + view.performClick(); + inOrder1.verify(observer1, times(1)).onNext(view); + inOrder2.verify(observer2, times(1)).onNext(view); + + view.performClick(); + inOrder1.verify(observer1, times(1)).onNext(view); + inOrder2.verify(observer2, times(1)).onNext(view); + subscription1.unsubscribe(); + + view.performClick(); + inOrder1.verify(observer1, never()).onNext(any(View.class)); + inOrder2.verify(observer2, times(1)).onNext(view); + subscription2.unsubscribe(); + + view.performClick(); + inOrder1.verify(observer1, never()).onNext(any(View.class)); + inOrder2.verify(observer2, never()).onNext(any(View.class)); + + inOrder1.verify(observer1, never()).onError(any(Throwable.class)); + inOrder2.verify(observer2, never()).onError(any(Throwable.class)); + + inOrder1.verify(observer1, never()).onCompleted(); + inOrder2.verify(observer2, never()).onCompleted(); + } +} diff --git a/rxjava-contrib/rxjava-android/src/test/java/rx/android/schedulers/HandlerThreadSchedulerTest.java b/rxjava-contrib/rxjava-android/src/test/java/rx/android/schedulers/HandlerThreadSchedulerTest.java new file mode 100644 index 0000000000..d68bd258f0 --- /dev/null +++ b/rxjava-contrib/rxjava-android/src/test/java/rx/android/schedulers/HandlerThreadSchedulerTest.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.android.schedulers; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +import rx.Scheduler; +import rx.Scheduler.Inner; +import rx.functions.Action1; +import android.os.Handler; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest=Config.NONE) +public class HandlerThreadSchedulerTest { + + @Test + public void shouldScheduleImmediateActionOnHandlerThread() { + final Handler handler = mock(Handler.class); + @SuppressWarnings("unchecked") + final Action1 action = mock(Action1.class); + + Scheduler scheduler = new HandlerThreadScheduler(handler); + scheduler.schedule(action); + + // verify that we post to the given Handler + ArgumentCaptor runnable = ArgumentCaptor.forClass(Runnable.class); + verify(handler).postDelayed(runnable.capture(), eq(0L)); + + // verify that the given handler delegates to our action + runnable.getValue().run(); + verify(action).call(any(Inner.class)); + } + + @Test + public void shouldScheduleDelayedActionOnHandlerThread() { + final Handler handler = mock(Handler.class); + @SuppressWarnings("unchecked") + final Action1 action = mock(Action1.class); + + Scheduler scheduler = new HandlerThreadScheduler(handler); + scheduler.schedule(action, 1L, TimeUnit.SECONDS); + + // verify that we post to the given Handler + ArgumentCaptor runnable = ArgumentCaptor.forClass(Runnable.class); + verify(handler).postDelayed(runnable.capture(), eq(1000L)); + + // verify that the given handler delegates to our action + runnable.getValue().run(); + verify(action).call(any(Inner.class)); + } +} diff --git a/rxjava-contrib/rxjava-apache-http/build.gradle b/rxjava-contrib/rxjava-apache-http/build.gradle index 81d150ccc3..c93f831374 100644 --- a/rxjava-contrib/rxjava-apache-http/build.gradle +++ b/rxjava-contrib/rxjava-apache-http/build.gradle @@ -16,5 +16,6 @@ jar { 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/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 index 1ed64ea838..6ea424848a 100644 --- 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 @@ -21,6 +21,8 @@ import org.apache.http.nio.client.HttpAsyncClient; import org.apache.http.nio.client.methods.HttpAsyncMethods; import org.apache.http.nio.protocol.HttpAsyncRequestProducer; +import org.apache.http.protocol.HttpContext; +import org.apache.http.protocol.BasicHttpContext; import rx.Observable; import rx.Observable.OnSubscribeFunc; @@ -134,6 +136,42 @@ public static ObservableHttp createGet(String uri, final * @return */ public static ObservableHttp createRequest(final HttpAsyncRequestProducer requestProducer, final HttpAsyncClient client) { + return createRequest(requestProducer, client, new BasicHttpContext()); + } + + /** + * 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 + * @param context The HttpContext + * @return + */ + public static ObservableHttp createRequest(final HttpAsyncRequestProducer requestProducer, final HttpAsyncClient client, final HttpContext context) { return ObservableHttp.create(new OnSubscribeFunc() { @@ -144,7 +182,7 @@ public Subscription onSubscribe(final Observer o // 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() { + context, new FutureCallback() { @Override public void completed(HttpResponse result) { 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 index b352ecb61a..24671e7051 100644 --- 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 @@ -93,7 +93,9 @@ protected HttpResponse buildResult(HttpContext context) throws Exception { @Override protected void releaseResources() { - consumer._releaseResources(); + if (consumer != null) { + consumer._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 index 4fe7e0549a..37fe6e8925 100644 --- 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 @@ -26,8 +26,8 @@ import rx.Observable; import rx.apache.http.ObservableHttp; import rx.apache.http.ObservableHttpResponse; -import rx.util.functions.Action1; -import rx.util.functions.Func1; +import rx.functions.Action1; +import rx.functions.Func1; public class ExampleObservableHttp { diff --git a/rxjava-contrib/rxjava-async-util/build.gradle b/rxjava-contrib/rxjava-async-util/build.gradle new file mode 100644 index 0000000000..7acd6a856d --- /dev/null +++ b/rxjava-contrib/rxjava-async-util/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'osgi' + +sourceCompatibility = JavaVersion.VERSION_1_6 +targetCompatibility = JavaVersion.VERSION_1_6 + +dependencies { + compile project(':rxjava-core') + testCompile project(":rxjava-core").sourceSets.test.output + provided 'junit:junit-dep:4.10' + provided 'org.mockito:mockito-core:1.8.5' +} + +jar { + manifest { + name = 'rxjava-async-util' + 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/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/Async.java b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/Async.java new file mode 100644 index 0000000000..4559032877 --- /dev/null +++ b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/Async.java @@ -0,0 +1,1770 @@ +/** + * 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.async; + +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; + +import rx.Observable; +import rx.Observer; +import rx.Scheduler; +import rx.Scheduler.Inner; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Action2; +import rx.functions.Action3; +import rx.functions.Action4; +import rx.functions.Action5; +import rx.functions.Action6; +import rx.functions.Action7; +import rx.functions.Action8; +import rx.functions.Action9; +import rx.functions.ActionN; +import rx.functions.Actions; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.functions.Func3; +import rx.functions.Func4; +import rx.functions.Func5; +import rx.functions.Func6; +import rx.functions.Func7; +import rx.functions.Func8; +import rx.functions.Func9; +import rx.functions.FuncN; +import rx.schedulers.Schedulers; +import rx.subjects.AsyncSubject; +import rx.subjects.PublishSubject; +import rx.subjects.Subject; +import rx.subscriptions.SerialSubscription; +import rx.util.async.operators.Functionals; +import rx.util.async.operators.OperationDeferFuture; +import rx.util.async.operators.OperationForEachFuture; +import rx.util.async.operators.OperationFromFunctionals; +import rx.util.async.operators.OperationStartFuture; + +/** + * Utility methods to convert functions and actions into asynchronous operations + * through the Observable/Observer pattern. + */ +public final class Async { + + private Async() { + throw new IllegalStateException("No instances!"); + } + + /** + * Invokes the specified function asynchronously and returns an Observable + * that emits the result. + *

+ * Note: The function is called immediately and once, not whenever an + * observer subscribes to the resulting Observable. Multiple subscriptions + * to this Observable observe the same return value. + *

+ * + * + * @param the result value type + * @param func function to run asynchronously + * @return an Observable that emits the function's result value, or notifies + * observers of an exception + * @see RxJava Wiki: start() + * @see MSDN: Observable.Start + */ + public static Observable start(Func0 func) { + return Async.toAsync(func).call(); + } + + /** + * Invokes the specified function asynchronously on the specified scheduler + * and returns an Observable that emits the result. + *

+ * Note: The function is called immediately and once, not whenever an + * observer subscribes to the resulting Observable. Multiple subscriptions + * to this Observable observe the same return value. + *

+ * + * + * @param the result value type + * @param func function to run asynchronously + * @param scheduler scheduler to run the function on + * @return an Observable that emits the function's result value, or notifies + * observers of an exception + * @see RxJava Wiki: start() + * @see MSDN: Observable.Start + */ + public static Observable start(Func0 func, Scheduler scheduler) { + return Async.toAsync(func, scheduler).call(); + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param action the action to convert + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func0> toAsync(Action0 action) { + return toAsync(action, Schedulers.computation()); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the result value type + * @param func the function to convert + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func0> toAsync(Func0 func) { + return toAsync(func, Schedulers.computation()); + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param first parameter type of the action + * @param action the action to convert + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func1> toAsync(Action1 action) { + return toAsync(action, Schedulers.computation()); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param first parameter type of the action + * @param the result type + * @param func the function to convert + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func1> toAsync(Func1 func) { + return toAsync(func, Schedulers.computation()); + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param action the action to convert + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func2> toAsync(Action2 action) { + return toAsync(action, Schedulers.computation()); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the result type + * @param func the function to convert + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func2> toAsync(Func2 func) { + return toAsync(func, Schedulers.computation()); + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param action the action to convert + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func3> toAsync(Action3 action) { + return toAsync(action, Schedulers.computation()); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the result type + * @param func the function to convert + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func3> toAsync(Func3 func) { + return toAsync(func, Schedulers.computation()); + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param action the action to convert + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func4> toAsync(Action4 action) { + return toAsync(action, Schedulers.computation()); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the result type + * @param func the function to convert + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func4> toAsync(Func4 func) { + return toAsync(func, Schedulers.computation()); + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param action the action to convert + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func5> toAsync(Action5 action) { + return toAsync(action, Schedulers.computation()); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the result type + * @param func the function to convert + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func5> toAsync(Func5 func) { + return toAsync(func, Schedulers.computation()); + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param action the action to convert + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func6> toAsync(Action6 action) { + return toAsync(action, Schedulers.computation()); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param the result type + * @param func the function to convert + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func6> toAsync(Func6 func) { + return toAsync(func, Schedulers.computation()); + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param the seventh parameter type + * @param action the action to convert + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func7> toAsync(Action7 action) { + return toAsync(action, Schedulers.computation()); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param the seventh parameter type + * @param the result type + * @param func the function to convert + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func7> toAsync(Func7 func) { + return toAsync(func, Schedulers.computation()); + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param the seventh parameter type + * @param the eighth parameter type + * @param action the action to convert + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func8> toAsync(Action8 action) { + return toAsync(action, Schedulers.computation()); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param the seventh parameter type + * @param the eighth parameter type + * @param the result type + * @param func the function to convert + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func8> toAsync(Func8 func) { + return toAsync(func, Schedulers.computation()); + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param the seventh parameter type + * @param the eighth parameter type + * @param the ninth parameter type + * @param action the action to convert + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func9> toAsync(Action9 action) { + return toAsync(action, Schedulers.computation()); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param the seventh parameter type + * @param the eighth parameter type + * @param the ninth parameter type + * @param the result type + * @param func the function to convert + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func9> toAsync(Func9 func) { + return toAsync(func, Schedulers.computation()); + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param action the action to convert + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + */ + public static FuncN> toAsync(ActionN action) { + return toAsync(action, Schedulers.computation()); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the result type + * @param func the function to convert + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + */ + public static FuncN> toAsync(FuncN func) { + return toAsync(func, Schedulers.computation()); + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param action the action to convert + * @param scheduler the scheduler used to execute the {@code action} + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func0> toAsync(final Action0 action, final Scheduler scheduler) { + return toAsync(Actions.toFunc(action), scheduler); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the result type + * @param func the function to convert + * @param scheduler the scheduler used to call the {@code func} + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func0> toAsync(final Func0 func, final Scheduler scheduler) { + return new Func0>() { + @Override + public Observable call() { + final AsyncSubject subject = AsyncSubject.create(); + scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + R result; + try { + result = func.call(); + } catch (Throwable t) { + subject.onError(t); + return; + } + subject.onNext(result); + subject.onCompleted(); + } + }); + return subject; + } + }; + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param action the action to convert + * @param scheduler the scheduler used to execute the {@code action} + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func1> toAsync(final Action1 action, final Scheduler scheduler) { + return toAsync(Actions.toFunc(action), scheduler); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the result type + * @param func the function to convert + * @param scheduler the scheduler used to call the {@code func} + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func1> toAsync(final Func1 func, final Scheduler scheduler) { + return new Func1>() { + @Override + public Observable call(final T1 t1) { + final AsyncSubject subject = AsyncSubject.create(); + scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + R result; + try { + result = func.call(t1); + } catch (Throwable t) { + subject.onError(t); + return; + } + subject.onNext(result); + subject.onCompleted(); + } + }); + return subject; + } + }; + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param action the action to convert + * @param scheduler the scheduler used to execute the {@code action} + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func2> toAsync(final Action2 action, final Scheduler scheduler) { + return toAsync(Actions.toFunc(action), scheduler); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the result type + * @param func the function to convert + * @param scheduler the scheduler used to call the {@code func} + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func2> toAsync(final Func2 func, final Scheduler scheduler) { + return new Func2>() { + @Override + public Observable call(final T1 t1, final T2 t2) { + final AsyncSubject subject = AsyncSubject.create(); + scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + R result; + try { + result = func.call(t1, t2); + } catch (Throwable t) { + subject.onError(t); + return; + } + subject.onNext(result); + subject.onCompleted(); + } + }); + return subject; + } + }; + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param action the action to convert + * @param scheduler the scheduler used to execute the {@code action} + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func3> toAsync(final Action3 action, final Scheduler scheduler) { + return toAsync(Actions.toFunc(action), scheduler); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the result type + * @param func the function to convert + * @param scheduler the scheduler used to call the {@code func} + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func3> toAsync(final Func3 func, final Scheduler scheduler) { + return new Func3>() { + @Override + public Observable call(final T1 t1, final T2 t2, final T3 t3) { + final AsyncSubject subject = AsyncSubject.create(); + scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + R result; + try { + result = func.call(t1, t2, t3); + } catch (Throwable t) { + subject.onError(t); + return; + } + subject.onNext(result); + subject.onCompleted(); + } + }); + return subject; + } + }; + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param action the action to convert + * @param scheduler the scheduler used to execute the {@code action} + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func4> toAsync(final Action4 action, final Scheduler scheduler) { + return toAsync(Actions.toFunc(action), scheduler); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the result type + * @param func the function to convert + * @param scheduler the scheduler used to call the {@code func} + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func4> toAsync(final Func4 func, final Scheduler scheduler) { + return new Func4>() { + @Override + public Observable call(final T1 t1, final T2 t2, final T3 t3, final T4 t4) { + final AsyncSubject subject = AsyncSubject.create(); + scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + R result; + try { + result = func.call(t1, t2, t3, t4); + } catch (Throwable t) { + subject.onError(t); + return; + } + subject.onNext(result); + subject.onCompleted(); + } + }); + return subject; + } + }; + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param action the action to convert + * @param scheduler the scheduler used to execute the {@code action} + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func5> toAsync(final Action5 action, final Scheduler scheduler) { + return toAsync(Actions.toFunc(action), scheduler); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the result type + * @param func the function to convert + * @param scheduler the scheduler used to call the {@code func} + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func5> toAsync(final Func5 func, final Scheduler scheduler) { + return new Func5>() { + @Override + public Observable call(final T1 t1, final T2 t2, final T3 t3, final T4 t4, final T5 t5) { + final AsyncSubject subject = AsyncSubject.create(); + scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + R result; + try { + result = func.call(t1, t2, t3, t4, t5); + } catch (Throwable t) { + subject.onError(t); + return; + } + subject.onNext(result); + subject.onCompleted(); + } + }); + return subject; + } + }; + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param action the action to convert + * @param scheduler the scheduler used to execute the {@code action} + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func6> toAsync(final Action6 action, final Scheduler scheduler) { + return toAsync(Actions.toFunc(action), scheduler); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param the result type + * @param func the function to convert + * @param scheduler the scheduler used to call the {@code func} + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func6> toAsync(final Func6 func, final Scheduler scheduler) { + return new Func6>() { + @Override + public Observable call(final T1 t1, final T2 t2, final T3 t3, final T4 t4, final T5 t5, final T6 t6) { + final AsyncSubject subject = AsyncSubject.create(); + scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + R result; + try { + result = func.call(t1, t2, t3, t4, t5, t6); + } catch (Throwable t) { + subject.onError(t); + return; + } + subject.onNext(result); + subject.onCompleted(); + } + }); + return subject; + } + }; + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param the seventh parameter type + * @param action the action to convert + * @param scheduler the scheduler used to execute the {@code action} + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func7> toAsync(final Action7 action, final Scheduler scheduler) { + return toAsync(Actions.toFunc(action), scheduler); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param the seventh parameter type + * @param the result type + * @param func the function to convert + * @param scheduler the scheduler used to call the {@code func} + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func7> toAsync(final Func7 func, final Scheduler scheduler) { + return new Func7>() { + @Override + public Observable call(final T1 t1, final T2 t2, final T3 t3, final T4 t4, final T5 t5, final T6 t6, final T7 t7) { + final AsyncSubject subject = AsyncSubject.create(); + scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + R result; + try { + result = func.call(t1, t2, t3, t4, t5, t6, t7); + } catch (Throwable t) { + subject.onError(t); + return; + } + subject.onNext(result); + subject.onCompleted(); + } + }); + return subject; + } + }; + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param the seventh parameter type + * @param the eighth parameter type + * @param action the action to convert + * @param scheduler the scheduler used to execute the {@code action} + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func8> toAsync(final Action8 action, final Scheduler scheduler) { + return toAsync(Actions.toFunc(action), scheduler); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param the seventh parameter type + * @param the eighth parameter type + * @param the result type + * @param func the function to convert + * @param scheduler the scheduler used to call the {@code func} + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func8> toAsync(final Func8 func, final Scheduler scheduler) { + return new Func8>() { + @Override + public Observable call(final T1 t1, final T2 t2, final T3 t3, final T4 t4, final T5 t5, final T6 t6, final T7 t7, final T8 t8) { + final AsyncSubject subject = AsyncSubject.create(); + scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + R result; + try { + result = func.call(t1, t2, t3, t4, t5, t6, t7, t8); + } catch (Throwable t) { + subject.onError(t); + return; + } + subject.onNext(result); + subject.onCompleted(); + } + }); + return subject; + } + }; + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param the seventh parameter type + * @param the eighth parameter type + * @param the ninth parameter type + * @param action the action to convert + * @param scheduler the scheduler used to execute the {@code action} + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func9> toAsync(final Action9 action, final Scheduler scheduler) { + return toAsync(Actions.toFunc(action), scheduler); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the first parameter type + * @param the second parameter type + * @param the third parameter type + * @param the fourth parameter type + * @param the fifth parameter type + * @param the sixth parameter type + * @param the seventh parameter type + * @param the eighth parameter type + * @param the ninth parameter type + * @param the result type + * @param func the function to convert + * @param scheduler the scheduler used to call the {@code func} + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + * @see MSDN: Observable.ToAsync + */ + public static Func9> toAsync(final Func9 func, final Scheduler scheduler) { + return new Func9>() { + @Override + public Observable call(final T1 t1, final T2 t2, final T3 t3, final T4 t4, final T5 t5, final T6 t6, final T7 t7, final T8 t8, final T9 t9) { + final AsyncSubject subject = AsyncSubject.create(); + scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + R result; + try { + result = func.call(t1, t2, t3, t4, t5, t6, t7, t8, t9); + } catch (Throwable t) { + subject.onError(t); + return; + } + subject.onNext(result); + subject.onCompleted(); + } + }); + return subject; + } + }; + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param action the action to convert + * @param scheduler the scheduler used to execute the {@code action} + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: toAsync() + */ + public static FuncN> toAsync(final ActionN action, final Scheduler scheduler) { + return toAsync(Actions.toFunc(action), scheduler); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + * + * @param the result type + * @param func the function to convert + * @param scheduler the scheduler used to call the {@code func} + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: toAsync() + */ + public static FuncN> toAsync(final FuncN func, final Scheduler scheduler) { + return new FuncN>() { + @Override + public Observable call(final Object... args) { + final AsyncSubject subject = AsyncSubject.create(); + scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + R result; + try { + result = func.call(args); + } catch (Throwable t) { + subject.onError(t); + return; + } + subject.onNext(result); + subject.onCompleted(); + } + }); + return subject; + } + }; + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + *

+ * Alias for toAsync(ActionN) intended for dynamic languages. + * + * @param action the action to convert + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: asyncAction() + */ + public static FuncN> asyncAction(final ActionN action) { + return toAsync(action); + } + + /** + * Convert a synchronous action call into an asynchronous function call + * through an Observable. + *

+ * + *

+ * Alias for toAsync(ActionN, Scheduler) intended for dynamic languages. + * + * @param action the action to convert + * @param scheduler the scheduler used to execute the {@code action} + * @return a function that returns an Observable that executes the + * {@code action} and emits {@code null} + * @see RxJava Wiki: asyncAction() + */ + public static FuncN> asyncAction(final ActionN action, final Scheduler scheduler) { + return toAsync(action, scheduler); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + *

+ * Alias for toAsync(FuncN) intended for dynamic languages. + * + * @param the result type + * @param func the function to convert + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: asyncFunc() + */ + public static FuncN> asyncFunc(final FuncN func) { + return toAsync(func); + } + + /** + * Convert a synchronous function call into an asynchronous function call + * through an Observable. + *

+ * + *

+ * Alias for toAsync(FuncN, Scheduler) intended for dynamic languages. + * + * @param the result type + * @param func the function to convert + * @param scheduler the scheduler used to call the {@code func} + * @return a function that returns an Observable that executes the + * {@code func} and emits its returned value + * @see RxJava Wiki: asyncFunc() + */ + public static FuncN> asyncFunc(final FuncN func, final Scheduler scheduler) { + return toAsync(func, scheduler); + } + + /** + * Invokes the asynchronous function immediately, surfacing the result + * through an Observable. + *

+ * Important note subscribing to the resulting Observable blocks + * until the future completes. + *

+ * + * + * @param the result type + * @param functionAsync the asynchronous function to run + * @return an Observable that surfaces the result of the future + * @see #startFuture(rx.functions.Func0, rx.Scheduler) + * @see RxJava Wiki: startFuture() + */ + public static Observable startFuture(Func0> functionAsync) { + return OperationStartFuture.startFuture(functionAsync); + } + + /** + * Invokes the asynchronous function immediately, surfacing the result + * through an Observable and waits on the specified scheduler. + *

+ * + * + * @param the result type + * @param functionAsync the asynchronous function to run + * @param scheduler the scheduler where the completion of the Future is + * awaited + * @return an Observable that surfaces the result of the future + * @see RxJava Wiki: startFuture() + */ + public static Observable startFuture(Func0> functionAsync, + Scheduler scheduler) { + return OperationStartFuture.startFuture(functionAsync, scheduler); + } + + /** + * Returns an Observable that starts the specified asynchronous factory + * function whenever a new observer subscribes. + *

+ * Important note subscribing to the resulting Observable blocks + * until the future completes. + *

+ * + * + * @param the result type + * @param observableFactoryAsync the asynchronous function to start for each + * observer + * @return the Observable emitting items produced by the asynchronous + * observer produced by the factory + * @see #deferFuture(rx.functions.Func0, rx.Scheduler) + * @see RxJava Wiki: deferFuture() + */ + public static Observable deferFuture(Func0>> observableFactoryAsync) { + return OperationDeferFuture.deferFuture(observableFactoryAsync); + } + + /** + * Returns an Observable that starts the specified asynchronous factory + * function whenever a new observer subscribes. + *

+ * + * + * @param the result type + * @param observableFactoryAsync the asynchronous function to start for each + * observer + * @param scheduler the scheduler where the completion of the Future is + * awaited + * @return the Observable emitting items produced by the asynchronous + * observer produced by the factory + * @see RxJava Wiki: deferFuture() + */ + public static Observable deferFuture( + Func0>> observableFactoryAsync, + Scheduler scheduler) { + return OperationDeferFuture.deferFuture(observableFactoryAsync, scheduler); + } + + /** + * Subscribes to the given source and calls the callback for each + * emitted item, and surfaces the completion or error through a Future. + *

+ * Important note: The returned task blocks indefinitely unless + * the run() method is called or the task is scheduled on an Executor. + *

+ * + * + * @param the source value type + * @param source the source Observable + * @param onNext the action to call with each emitted element + * @return the Future representing the entire for-each operation + * @see #forEachFuture(rx.functions.Action1, rx.Scheduler) + * @see RxJava Wiki: forEachFuture() + */ + public static FutureTask forEachFuture( + Observable source, + Action1 onNext) { + return OperationForEachFuture.forEachFuture(source, onNext); + } + + + /** + * Subscribes to the given source and calls the callback for each emitted + * item, and surfaces the completion or error through a Future. + *

+ * Important note: The returned task blocks indefinitely unless + * the run() method is called or the task is scheduled on an Executor. + *

+ * + * + * @param the source value type + * @param source the source Observable + * @param onNext the action to call with each emitted element + * @param onError the action to call when an exception is emitted + * @return the Future representing the entire for-each operation + * @see #forEachFuture(rx.functions.Action1, rx.functions.Action1, rx.Scheduler) + * @see RxJava Wiki: forEachFuture() + */ + public static FutureTask forEachFuture( + Observable source, + Action1 onNext, + Action1 onError) { + return OperationForEachFuture.forEachFuture(source, onNext, onError); + } + + + /** + * Subscribes to the given source and calls the callback for each emitted + * item, and surfaces the completion or error through a Future. + *

+ * Important note: The returned task blocks indefinitely unless + * the run() method is called or the task is scheduled on an Executor. + *

+ * + * + * @param the source value type + * @param source the source Observable + * @param onNext the action to call with each emitted element + * @param onError the action to call when an exception is emitted + * @param onCompleted the action to call when the source completes + * @return the Future representing the entire for-each operation + * @see #forEachFuture(rx.functions.Action1, rx.functions.Action1, rx.functions.Action0, rx.Scheduler) + * @see RxJava Wiki: forEachFuture() + */ + public static FutureTask forEachFuture( + Observable source, + Action1 onNext, + Action1 onError, + Action0 onCompleted) { + return OperationForEachFuture.forEachFuture(source, onNext, onError, onCompleted); + } + + + /** + * Subscribes to the given source and calls the callback for each emitted + * item, and surfaces the completion or error through a Future, scheduled on + * the given scheduler. + *

+ * + * + * @param the source value type + * @param source the source Observable + * @param onNext the action to call with each emitted element + * @param scheduler the scheduler where the task will await the termination + * of the for-each + * @return the Future representing the entire for-each operation + * @see RxJava Wiki: forEachFuture() + */ + public static FutureTask forEachFuture( + Observable source, + Action1 onNext, + Scheduler scheduler) { + FutureTask < Void > task = OperationForEachFuture.forEachFuture(source, onNext); + scheduler.schedule(Functionals.fromRunnable(task)); + return task; + } + + + /** + * Subscribes to the given source and calls the callback for each emitted + * item, and surfaces the completion or error through a Future, scheduled on + * the given scheduler. + *

+ * + * + * @param the source value type + * @param source the source Observable + * @param onNext the action to call with each emitted element + * @param onError the action to call when an exception is emitted + * @param scheduler the scheduler where the task will await the termination + * of the for-each + * @return the Future representing the entire for-each operation + * @see RxJava Wiki: forEachFuture() + */ + public static FutureTask forEachFuture( + Observable source, + Action1 onNext, + Action1 onError, + Scheduler scheduler) { + FutureTask < Void > task = OperationForEachFuture.forEachFuture(source, onNext, onError); + scheduler.schedule(Functionals.fromRunnable(task)); + return task; + } + + + /** + * Subscribes to the given source and calls the callback for each emitted + * item, and surfaces the completion or error through a Future, scheduled on + * the given scheduler. + *

+ * + * + * @param the source value type + * @param source the source Observable + * @param onNext the action to call with each emitted element + * @param onError the action to call when an exception is emitted + * @param onCompleted the action to call when the source completes + * @param scheduler the scheduler where the task will await the termination + * of the for-each + * @return the Future representing the entire for-each operation + * @see RxJava Wiki: forEachFuture() + */ + public static FutureTask forEachFuture( + Observable source, + Action1 onNext, + Action1 onError, + Action0 onCompleted, + Scheduler scheduler) { + FutureTask task = OperationForEachFuture.forEachFuture(source, onNext, onError, onCompleted); + scheduler.schedule(Functionals.fromRunnable(task)); + return task; + } + + /** + * Return an Observable that calls the given action and emits the given + * result when an Observer subscribes. + *

+ * + *

+ * The action is run on the default thread pool for computation. + * + * @param the return type + * @param action the action to invoke on each subscription + * @param result the result to emit to observers + * @return an Observable that calls the given action and emits the given + * result when an Observer subscribes + * @see RxJava Wiki: fromAction() + */ + public static Observable fromAction(Action0 action, R result) { + return fromAction(action, result, Schedulers.computation()); + } + + /** + * Return an Observable that calls the given function and emits its + * result when an Observer subscribes. + *

+ * + *

+ * The function is called on the default thread pool for computation. + * + * @param the return type + * @param function the function to call on each subscription + * @return an Observable that calls the given function and emits its + * result when an Observer subscribes + * @see #start(rx.functions.Func0) + * @see #fromCallable(java.util.concurrent.Callable) + * @see RxJava Wiki: fromFunc0() + */ + public static Observable fromFunc0(Func0 function) { + return fromFunc0(function, Schedulers.computation()); + } + + /** + * Return an Observable that calls the given Callable and emits its + * result or Exception when an Observer subscribes. + *

+ * + *

+ * The Callable is called on the default thread pool for computation. + * + * @param the return type + * @param callable the callable to call on each subscription + * @return an Observable that calls the given Callable and emits its + * result or Exception when an Observer subscribes + * @see #start(rx.functions.Func0) + * @see #fromFunc0(rx.functions.Func0) + * @see RxJava Wiki: fromCallable() + */ + public static Observable fromCallable(Callable callable) { + return fromCallable(callable, Schedulers.computation()); + } + + /** + * Return an Observable that calls the given Runnable and emits the given + * result when an Observer subscribes. + *

+ * + *

+ * The Runnable is called on the default thread pool for computation. + * + * @param the return type + * @param run the runnable to invoke on each subscription + * @param result the result to emit to observers + * @return an Observable that calls the given Runnable and emits the given + * result when an Observer subscribes + * @see RxJava Wiki: fromRunnable() + */ + public static Observable fromRunnable(final Runnable run, final R result) { + return fromRunnable(run, result, Schedulers.computation()); + } + + /** + * Return an Observable that calls the given action and emits the given + * result when an Observer subscribes. + *

+ * + * + * @param the return type + * @param action the action to invoke on each subscription + * @param scheduler the scheduler where the function is called and the + * result is emitted + * @param result the result to emit to observers + * @return an Observable that calls the given action and emits the given + * result when an Observer subscribes + * @see RxJava Wiki: fromAction() + */ + public static Observable fromAction(Action0 action, R result, Scheduler scheduler) { + return Observable.create(OperationFromFunctionals.fromAction(action, result)).subscribeOn(scheduler); + } + + /** + * Return an Observable that calls the given function and emits its + * result when an Observer subscribes. + *

+ * + * + * @param the return type + * @param function the function to call on each subscription + * @param scheduler the scheduler where the function is called and the + * result is emitted + * @return an Observable that calls the given function and emits its + * result when an Observer subscribes + * @see #start(rx.functions.Func0) + * @see #fromCallable(java.util.concurrent.Callable) + * @see RxJava Wiki: fromFunc0() + */ + public static Observable fromFunc0(Func0 function, Scheduler scheduler) { + return Observable.create(OperationFromFunctionals.fromFunc0(function)).subscribeOn(scheduler); + } + + /** + * Return an Observable that calls the given Callable and emits its + * result or Exception when an Observer subscribes. + *

+ * + * + * @param the return type + * @param callable the callable to call on each subscription + * @param scheduler the scheduler where the function is called and the + * result is emitted + * @return an Observable that calls the given Callable and emits its + * result or Exception when an Observer subscribes + * @see #start(rx.functions.Func0) + * @see #fromFunc0(rx.functions.Func0) + * @see RxJava Wiki: fromCallable() + */ + public static Observable fromCallable(Callable callable, Scheduler scheduler) { + return Observable.create(OperationFromFunctionals.fromCallable(callable)).subscribeOn(scheduler); + } + + /** + * Return an Observable that calls the given Runnable and emits the given + * result when an Observer subscribes. + *

+ * + * + * @param the return type + * @param run the runnable to invoke on each subscription + * @param scheduler the scheduler where the function is called and the + * result is emitted + * @param result the result to emit to observers + * @return an Observable that calls the given Runnable and emits the given + * result when an Observer subscribes + * @see RxJava Wiki: fromRunnable() + */ + public static Observable fromRunnable(final Runnable run, final R result, Scheduler scheduler) { + return Observable.create(OperationFromFunctionals.fromRunnable(run, result)).subscribeOn(scheduler); + } + /** + * Runs the provided action on the given scheduler and allows propagation + * of multiple events to the observers of the returned StoppableObservable. + * The action is immediately executed and unobserved values will be lost. + * @param the output value type + * @param scheduler the scheduler where the action is executed + * @param action the action to execute, receives an Observer where the events can be pumped + * and a Subscription which lets check for cancellation condition. + * @return an Observable which provides a Subscription interface to cancel the action + */ + public static StoppableObservable runAsync(Scheduler scheduler, + final Action2, ? super Subscription> action) { + return runAsync(scheduler, PublishSubject.create(), action); + } + /** + * Runs the provided action on the given scheduler and allows propagation + * of multiple events to the observers of the returned StoppableObservable. + * The action is immediately executed and unobserved values might be lost, + * depending on the subject type used. + * @param the output value of the action + * @param the output type of the observable sequence + * @param scheduler the scheduler where the action is executed + * @param subject the subject to use to distribute values emitted by the action + * @param action the action to execute, receives an Observer where the events can be pumped + * and a Subscription which lets check for cancellation condition. + * @return an Observable which provides a Subscription interface to cancel the action + */ + public static StoppableObservable runAsync(Scheduler scheduler, + final Subject subject, + final Action2, ? super Subscription> action) { + final SerialSubscription csub = new SerialSubscription(); + + StoppableObservable co = new StoppableObservable(new Observable.OnSubscribe() { + @Override + public void call(Subscriber t1) { + subject.subscribe(t1); + } + }, csub); + + csub.set(scheduler.schedule(new Action1() { + @Override + public void call(Inner t1) { + if (!csub.isUnsubscribed()) { + action.call(subject, csub); + } + } + })); + + return co; + } +} diff --git a/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/StoppableObservable.java b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/StoppableObservable.java new file mode 100644 index 0000000000..ebd9538ed5 --- /dev/null +++ b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/StoppableObservable.java @@ -0,0 +1,41 @@ +/** + * Copyright 2014 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.async; + +import rx.Observable; +import rx.Subscription; + +/** + * An Observable which provides a Subscription interface to signal a stop + * condition to an asynchronous task. + */ +public class StoppableObservable extends Observable implements Subscription { + private final Subscription token; + public StoppableObservable(Observable.OnSubscribe onSubscribe, Subscription token) { + super(onSubscribe); + this.token = token; + } + + @Override + public boolean isUnsubscribed() { + return token.isUnsubscribed(); + } + + @Override + public void unsubscribe() { + token.unsubscribe(); + } +} diff --git a/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/Functionals.java b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/Functionals.java new file mode 100644 index 0000000000..037c7b8573 --- /dev/null +++ b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/Functionals.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.util.async.operators; + +import rx.Scheduler.Inner; +import rx.functions.Action0; +import rx.functions.Action1; + +/** + * Utility methods convert between functional interfaces of actions and functions. + */ +public final class Functionals { + private Functionals() { + throw new IllegalStateException("No instances!"); + } + /** + * Return an action which takes a Throwable and does nothing. + *

(To avoid casting from the generic empty1().) + * @return the action + */ + public static Action1 emptyThrowable() { + return EMPTY_THROWABLE; + } + /** + * An action that takes a Throwable and does nothing. + */ + private static final Action1 EMPTY_THROWABLE = new EmptyThrowable(); + /** An empty throwable class. */ + private static final class EmptyThrowable implements Action1 { + @Override + public void call(Throwable t1) { + } + } + /** + * Return an Action0 instance which does nothing. + * @return an Action0 instance which does nothing + */ + public static Action0 empty() { + return EMPTY; + } + /** A single empty instance. */ + private static final Action0 EMPTY = new EmptyAction(); + /** An empty action class. */ + private static final class EmptyAction implements Action0 { + @Override + public void call() { + } + } + + /** + * Converts a runnable instance into an Action0 instance. + * @param run the Runnable to run when the Action0 is called + * @return the Action0 wrapping the Runnable + */ + public static Action1 fromRunnable(Runnable run) { + if (run == null) { + throw new NullPointerException("run"); + } + return new ActionWrappingRunnable(run); + } + /** An Action1 which wraps and calls a Runnable. */ + private static final class ActionWrappingRunnable implements Action1 { + final Runnable run; + + public ActionWrappingRunnable(Runnable run) { + this.run = run; + } + + @Override + public void call(Inner inner) { + run.run(); + } + + } + /** + * Converts an Action0 instance into a Runnable instance. + * @param action the Action0 to call when the Runnable is run + * @return the Runnable wrapping the Action0 + */ + public static Runnable toRunnable(Action0 action) { + if (action == null) { + throw new NullPointerException("action"); + } + return new RunnableAction(action); + } + /** An Action0 which wraps and calls a Runnable. */ + private static final class RunnableAction implements Runnable { + final Action0 action; + + public RunnableAction(Action0 action) { + this.action = action; + } + + @Override + public void run() { + action.call(); + } + + } +} diff --git a/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/LatchedObserver.java b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/LatchedObserver.java new file mode 100644 index 0000000000..e06dc97cfd --- /dev/null +++ b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/LatchedObserver.java @@ -0,0 +1,305 @@ +/** + * 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.async.operators; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import rx.Observer; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Action2; + +/** + * An observer implementation that calls a CountDownLatch in case + * a terminal state has been reached. + * @param the observed value type + */ +abstract class LatchedObserver implements Observer { + /** The CountDownLatch to count-down on a terminal state. */ + protected final CountDownLatch latch; + /** Contains the error. */ + protected volatile Throwable error; + /** + * Indicates the completion status. + */ + protected final AtomicBoolean done; + /** + * Consturcts a LatchedObserver instance. + * @param latch the CountDownLatch to use + */ + public LatchedObserver(CountDownLatch latch) { + this.latch = latch; + this.done = new AtomicBoolean(); + } + + /** + * Override this method to handle an onNext event. + * @param value + */ + protected abstract void onNextCore(T value); + /** + * Override this method to handle an onError event. + * @param e + */ + protected abstract void onErrorCore(Throwable e); + /** + * Override this to handle th onCompleted event. + */ + protected abstract void onCompletedCore(); + /** + * Try to move into an error state. + * @param e + * @return true if succeded, false if this observable has already terminated + */ + protected boolean fail(Throwable e) { + if (done.compareAndSet(false, true)) { + onErrorCore(e); + return true; + } + return false; + } + + @Override + public final void onNext(T args) { + if (!done.get()) { + onNextCore(args); + } + } + + @Override + public final void onError(Throwable e) { + fail(e); + } + + @Override + public final void onCompleted() { + if (done.compareAndSet(false, true)) { + onCompletedCore(); + } + } + + /** + * Block and await the latch. + * @throws InterruptedException if the wait is interrupted + */ + public void await() throws InterruptedException { + latch.await(); + } + /** + * Block and await the latch for a given amount of time. + * @see CountDownLatch#await(long, java.util.concurrent.TimeUnit) + */ + public boolean await(long time, TimeUnit unit) throws InterruptedException { + return latch.await(time, unit); + } + /** + * Returns the observed error or null if there was none. + *

+ * Should be generally called after the await() returns. + * @return the observed error + */ + public Throwable getThrowable() { + return error; + } + + /** + * Create a LatchedObserver with the given callback function(s). + */ + public static LatchedObserver create(Action1 onNext) { + return create(onNext, new CountDownLatch(1)); + } + + /** + * Create a LatchedObserver with the given callback function(s). + */ + public static LatchedObserver create(Action1 onNext, Action1 onError) { + return create(onNext, onError, new CountDownLatch(1)); + } + + /** + * Create a LatchedObserver with the given callback function(s). + */ + public static LatchedObserver create(Action1 onNext, Action1 onError, Action0 onCompleted) { + return create(onNext, onError, onCompleted, new CountDownLatch(1)); + } + + /** + * Create a LatchedObserver with the given callback function(s) and a shared latch. + */ + public static LatchedObserver create(Action1 onNext, CountDownLatch latch) { + return new LatchedObserverImpl(onNext, Functionals.emptyThrowable(), Functionals.empty(), latch); + } + + /** + * Create a LatchedObserver with the given callback function(s) and a shared latch. + */ + public static LatchedObserver create(Action1 onNext, Action1 onError, CountDownLatch latch) { + return new LatchedObserverImpl(onNext, onError, Functionals.empty(), latch); + } + + /** + * Create a LatchedObserver with the given callback function(s) and a shared latch. + */ + public static LatchedObserver create(Action1 onNext, Action1 onError, Action0 onCompleted, CountDownLatch latch) { + return new LatchedObserverImpl(onNext, onError, onCompleted, latch); + } + + /** + * Create a LatchedObserver with the given indexed callback function(s). + */ + public static LatchedObserver createIndexed(Action2 onNext) { + return createIndexed(onNext, new CountDownLatch(1)); + } + + /** + * Create a LatchedObserver with the given indexed callback function(s). + */ + public static LatchedObserver createIndexed(Action2 onNext, Action1 onError) { + return createIndexed(onNext, onError, new CountDownLatch(1)); + } + + /** + * Create a LatchedObserver with the given indexed callback function(s). + */ + public static LatchedObserver createIndexed(Action2 onNext, Action1 onError, Action0 onCompleted) { + return createIndexed(onNext, onError, onCompleted, new CountDownLatch(1)); + } + + /** + * Create a LatchedObserver with the given indexed callback function(s) and a shared latch. + */ + public static LatchedObserver createIndexed(Action2 onNext, CountDownLatch latch) { + return new LatchedObserverIndexedImpl(onNext, Functionals.emptyThrowable(), Functionals.empty(), latch); + } + + /** + * Create a LatchedObserver with the given indexed callback function(s) and a shared latch. + */ + public static LatchedObserver createIndexed(Action2 onNext, Action1 onError, CountDownLatch latch) { + return new LatchedObserverIndexedImpl(onNext, onError, Functionals.empty(), latch); + } + + /** + * Create a LatchedObserver with the given indexed callback function(s) and a shared latch. + */ + public static LatchedObserver createIndexed(Action2 onNext, Action1 onError, Action0 onCompleted, CountDownLatch latch) { + return new LatchedObserverIndexedImpl(onNext, onError, onCompleted, latch); + } + + /** + * A latched observer which calls an action for each observed value + * and checks if a cancellation token is not unsubscribed. + * @param the observed value type + */ + private static final class LatchedObserverImpl extends LatchedObserver { + final Action1 onNext; + final Action1 onError; + final Action0 onCompleted; + + public LatchedObserverImpl(Action1 onNext, + Action1 onError, + Action0 onCompleted, + CountDownLatch latch) { + super(latch); + this.onNext = onNext; + this.onError = onError; + this.onCompleted = onCompleted; + } + + @Override + protected void onNextCore(T args) { + try { + onNext.call(args); + } catch (Throwable t) { + fail(t); + } + } + + @Override + protected void onErrorCore(Throwable e) { + try { + error = e; + onError.call(e); + } finally { + latch.countDown(); + } + } + + @Override + protected void onCompletedCore() { + try { + onCompleted.call(); + } finally { + latch.countDown(); + } + } + } + /** + * A latched observer which calls an action for each observed value + * and checks if a cancellation token is not unsubscribed. + * @param the observed value type + */ + private static final class LatchedObserverIndexedImpl extends LatchedObserver { + final Action2 onNext; + final Action1 onError; + final Action0 onCompleted; + int index; + + public LatchedObserverIndexedImpl(Action2 onNext, + Action1 onError, + Action0 onCompleted, + CountDownLatch latch) { + super(latch); + this.onNext = onNext; + this.onError = onError; + this.onCompleted = onCompleted; + } + + @Override + protected void onNextCore(T args) { + if (index == Integer.MAX_VALUE) { + fail(new ArithmeticException("index overflow")); + return; + } + try { + onNext.call(args, index++); + } catch (Throwable t) { + fail(t); + } + } + + @Override + protected void onErrorCore(Throwable e) { + try { + error = e; + onError.call(e); + } finally { + latch.countDown(); + } + } + + @Override + protected void onCompletedCore() { + try { + onCompleted.call(); + } finally { + latch.countDown(); + } + } + } +} diff --git a/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/OperationDeferFuture.java b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/OperationDeferFuture.java new file mode 100644 index 0000000000..a4fde509d9 --- /dev/null +++ b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/OperationDeferFuture.java @@ -0,0 +1,88 @@ +/** + * 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.async.operators; + +import java.util.concurrent.Future; + +import rx.Observable; +import rx.Scheduler; +import rx.functions.Func0; + +/** + * Defer the execution of a factory method which produces an observable sequence. + */ +public final class OperationDeferFuture { + /** Utility class. */ + private OperationDeferFuture() { throw new IllegalStateException("No instances!"); } + + /** + * Returns an observable sequence that starts the specified asynchronous + * factory function whenever a new observer subscribes. + * @param the result type + * @param observableFactoryAsync the asynchronous function to start for each observer + * @return the observable sequence containing values produced by the asynchronous observer + * produced by the factory + */ + public static Observable deferFuture(Func0>> observableFactoryAsync) { + return Observable.defer(new DeferFutureFunc0(observableFactoryAsync)); + } + /** The function called by the defer operator. */ + private static final class DeferFutureFunc0 implements Func0> { + final Func0>> observableFactoryAsync; + + public DeferFutureFunc0(Func0>> observableFactoryAsync) { + this.observableFactoryAsync = observableFactoryAsync; + } + + @Override + public Observable call() { + return Observable.merge(OperationStartFuture.startFuture(observableFactoryAsync)); + } + + } + + /** + * Returns an observable sequence that starts the specified asynchronous + * factory function whenever a new observer subscribes. + * @param the result type + * @param observableFactoryAsync the asynchronous function to start for each observer + * @param scheduler the scheduler where the completion of the Future is awaited + * @return the observable sequence containing values produced by the asynchronous observer + * produced by the factory + */ + public static Observable deferFuture( + Func0>> observableFactoryAsync, + Scheduler scheduler) { + return Observable.defer(new DeferFutureFunc0Scheduled(observableFactoryAsync, scheduler)); + } + /** The function called by the defer operator. */ + private static final class DeferFutureFunc0Scheduled implements Func0> { + final Func0>> observableFactoryAsync; + final Scheduler scheduler; + + public DeferFutureFunc0Scheduled(Func0>> observableFactoryAsync, + Scheduler scheduler) { + this.observableFactoryAsync = observableFactoryAsync; + this.scheduler = scheduler; + } + + @Override + public Observable call() { + return Observable.merge(OperationStartFuture.startFuture(observableFactoryAsync, scheduler)); + } + + } +} diff --git a/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/OperationForEachFuture.java b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/OperationForEachFuture.java new file mode 100644 index 0000000000..42be310d36 --- /dev/null +++ b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/OperationForEachFuture.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.util.async.operators; + +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; + +import rx.Observable; +import rx.Subscription; +import rx.exceptions.Exceptions; +import rx.functions.Action0; +import rx.functions.Action1; + +/** + * Convert the observation of a source observable to a big Future call. + *

+ * Remark: the cancellation token version's behavior is in doubt, so left out. + */ +public final class OperationForEachFuture { + /** Utility class. */ + private OperationForEachFuture() { throw new IllegalStateException("No instances!"); } + + /** + * Subscribes to the given source and calls the callback for each emitted item, + * and surfaces the completion or error through a Future. + * @param the element type of the Observable + * @param source the source Observable + * @param onNext the action to call with each emitted element + * @return the Future representing the entire for-each operation + */ + public static FutureTask forEachFuture( + Observable source, + Action1 onNext) { + return forEachFuture(source, onNext, Functionals.emptyThrowable(), Functionals.empty()); + } + + /** + * Subscribes to the given source and calls the callback for each emitted item, + * and surfaces the completion or error through a Future. + * @param the element type of the Observable + * @param source the source Observable + * @param onNext the action to call with each emitted element + * @param onError the action to call when an exception is emitted + * @return the Future representing the entire for-each operation + */ + public static FutureTask forEachFuture( + Observable source, + Action1 onNext, + Action1 onError) { + return forEachFuture(source, onNext, onError, Functionals.empty()); + } + + /** + * Subscribes to the given source and calls the callback for each emitted item, + * and surfaces the completion or error through a Future. + * @param the element type of the Observable + * @param source the source Observable + * @param onNext the action to call with each emitted element + * @param onError the action to call when an exception is emitted + * @param onCompleted the action to call when the source completes + * @return the Future representing the entire for-each operation + */ + public static FutureTask forEachFuture( + Observable source, + Action1 onNext, + Action1 onError, + Action0 onCompleted) { + + LatchedObserver lo = LatchedObserver.create(onNext, onError, onCompleted); + + Subscription s = source.subscribe(lo); + + FutureTaskCancel task = new FutureTaskCancel(s, new RunAwait(lo)); + + return task; + } + /** + * A future task that unsubscribes the given subscription when cancelled. + * @param the return value type + */ + private static final class FutureTaskCancel extends FutureTask { + final Subscription cancel; + + public FutureTaskCancel(Subscription cancel, Callable callable) { + super(callable); + this.cancel = cancel; + } + + public FutureTaskCancel(Subscription cancel, Runnable runnable, T result) { + super(runnable, result); + this.cancel = cancel; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + cancel.unsubscribe(); + return super.cancel(mayInterruptIfRunning); + } + + } + + /** Await the completion of a latched observer and throw its exception if any. */ + private static final class RunAwait implements Callable { + final LatchedObserver observer; + + public RunAwait(LatchedObserver observer) { + this.observer = observer; + } + + @Override + public Void call() throws Exception { + observer.await(); + Throwable t = observer.getThrowable(); + if (t != null) { + throw Exceptions.propagate(t); + } + return null; + } + } +} diff --git a/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/OperationFromFunctionals.java b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/OperationFromFunctionals.java new file mode 100644 index 0000000000..bf890c3903 --- /dev/null +++ b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/OperationFromFunctionals.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.util.async.operators; + +import java.util.concurrent.Callable; + +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Actions; +import rx.functions.Func0; +import rx.subscriptions.Subscriptions; + +/** + * Operators that invoke a function or action if + * an observer subscribes. + * Asynchrony can be achieved by using subscribeOn or observeOn. + */ +public final class OperationFromFunctionals { + /** Utility class. */ + private OperationFromFunctionals() { throw new IllegalStateException("No instances!"); } + + /** Subscriber function that invokes an action and returns the given result. */ + public static OnSubscribeFunc fromAction(Action0 action, R result) { + return new InvokeAsync(Actions.toFunc(action, result)); + } + + /** Subscriber function that invokes a function and returns its value. */ + public static OnSubscribeFunc fromFunc0(Func0 function) { + return new InvokeAsync(function); + } + + /** + * Subscriber function that invokes the callable and returns its value or + * propagates its checked exception. + */ + public static OnSubscribeFunc fromCallable(Callable callable) { + return new InvokeAsyncCallable(callable); + } + /** Subscriber function that invokes a runnable and returns the given result. */ + public static OnSubscribeFunc fromRunnable(final Runnable run, final R result) { + return new InvokeAsync(new Func0() { + @Override + public R call() { + run.run(); + return result; + } + }); + } + + /** + * Invokes a function when an observer subscribes. + * @param the return type + */ + static final class InvokeAsync implements OnSubscribeFunc { + final Func0 function; + public InvokeAsync(Func0 function) { + if (function == null) { + throw new NullPointerException("function"); + } + this.function = function; + } + @Override + public Subscription onSubscribe(Observer t1) { + Subscription s = Subscriptions.empty(); + try { + t1.onNext(function.call()); + } catch (Throwable t) { + t1.onError(t); + return s; + } + t1.onCompleted(); + return s; + } + } + /** + * Invokes a java.util.concurrent.Callable when an observer subscribes. + * @param the return type + */ + static final class InvokeAsyncCallable implements OnSubscribeFunc { + final Callable callable; + public InvokeAsyncCallable(Callable callable) { + if (callable == null) { + throw new NullPointerException("function"); + } + this.callable = callable; + } + @Override + public Subscription onSubscribe(Observer t1) { + Subscription s = Subscriptions.empty(); + try { + t1.onNext(callable.call()); + } catch (Throwable t) { + t1.onError(t); + return s; + } + t1.onCompleted(); + return s; + } + } +} diff --git a/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/OperationStartFuture.java b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/OperationStartFuture.java new file mode 100644 index 0000000000..3cb0e434f9 --- /dev/null +++ b/rxjava-contrib/rxjava-async-util/src/main/java/rx/util/async/operators/OperationStartFuture.java @@ -0,0 +1,82 @@ +/** + * Copyright 2014 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. + */ + /** + * 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.async.operators; + +import java.util.concurrent.Future; + +import rx.Observable; +import rx.Scheduler; +import rx.functions.Func0; + +/** + * Start an asynchronous Future immediately and observe its result through + * an observable. + */ +public final class OperationStartFuture { + /** Utility class. */ + private OperationStartFuture() { throw new IllegalStateException("No instances!"); } + /** + * Invokes the asynchronous function, surfacing the result through an observable sequence. + *

+ * Important note subscribing to the resulting observable blocks until + * the future completes. + * @param the result type + * @param functionAsync the asynchronous function to run + * @return the observable + */ + public static Observable startFuture(Func0> functionAsync) { + Future task; + try { + task = functionAsync.call(); + } catch (Throwable t) { + return Observable.error(t); + } + return Observable.from(task); + } + /** + * Invokes the asynchronous function, surfacing the result through an observable sequence + * running on the given scheduler. + * @param the result type + * @param functionAsync the asynchronous function to run + * @param scheduler the scheduler where the completion of the Future is awaited + * @return the observable + */ + public static Observable startFuture(Func0> functionAsync, + Scheduler scheduler) { + Future task; + try { + task = functionAsync.call(); + } catch (Throwable t) { + return Observable.error(t); + } + return Observable.from(task, scheduler); + } +} diff --git a/rxjava-core/src/test/java/rx/util/functions/AsyncTest.java b/rxjava-contrib/rxjava-async-util/src/test/java/rx/util/async/AsyncTest.java similarity index 71% rename from rxjava-core/src/test/java/rx/util/functions/AsyncTest.java rename to rxjava-contrib/rxjava-async-util/src/test/java/rx/util/async/AsyncTest.java index cc76170894..aead8ef078 100644 --- a/rxjava-core/src/test/java/rx/util/functions/AsyncTest.java +++ b/rxjava-contrib/rxjava-async-util/src/test/java/rx/util/async/AsyncTest.java @@ -14,50 +14,64 @@ * limitations under the License. */ -package rx.util.functions; +package rx.util.async; +import java.util.concurrent.CountDownLatch; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; + import junit.framework.Assert; + import org.junit.Before; import org.junit.Test; -import static org.mockito.Matchers.any; +import org.mockito.InOrder; 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 org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import rx.Observable; import rx.Observer; -import rx.concurrency.Schedulers; -import rx.util.functions.Action0; -import rx.util.functions.Action1; -import rx.util.functions.Action2; -import rx.util.functions.Action3; -import rx.util.functions.Action4; -import rx.util.functions.Action5; -import rx.util.functions.Action6; -import rx.util.functions.Action7; -import rx.util.functions.Action8; -import rx.util.functions.Action9; -import rx.util.functions.ActionN; -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.Subscription; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Action2; +import rx.functions.Action3; +import rx.functions.Action4; +import rx.functions.Action5; +import rx.functions.Action6; +import rx.functions.Action7; +import rx.functions.Action8; +import rx.functions.Action9; +import rx.functions.ActionN; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.functions.Func3; +import rx.functions.Func4; +import rx.functions.Func5; +import rx.functions.Func6; +import rx.functions.Func7; +import rx.functions.Func8; +import rx.functions.Func9; +import rx.functions.FuncN; +import rx.observers.TestObserver; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; public class AsyncTest { @Mock Observer observer; + @Before public void before() { MockitoAnnotations.initMocks(this); } + @Test public void testAction0() { final AtomicInteger value = new AtomicInteger(); @@ -67,17 +81,18 @@ public void call() { value.incrementAndGet(); } }; - + Async.toAsync(action, Schedulers.immediate()) .call() - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(null); verify(observer, times(1)).onCompleted(); - + Assert.assertEquals(1, value.get()); } + @Test public void testAction0Error() { Action0 action = new Action0() { @@ -86,15 +101,16 @@ public void call() { throw new RuntimeException("Forced failure"); } }; - + Async.toAsync(action, Schedulers.immediate()) .call() - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onNext(null); verify(observer, never()).onCompleted(); } + @Test public void testAction1() { final AtomicInteger value = new AtomicInteger(); @@ -104,17 +120,18 @@ public void call(Integer t1) { value.set(t1); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(null); verify(observer, times(1)).onCompleted(); - + Assert.assertEquals(1, value.get()); } + @Test public void testAction1Error() { Action1 action = new Action1() { @@ -123,15 +140,16 @@ public void call(Integer t1) { throw new RuntimeException("Forced failure"); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onNext(null); verify(observer, never()).onCompleted(); } + @Test public void testAction2() { final AtomicInteger value = new AtomicInteger(); @@ -141,17 +159,18 @@ public void call(Integer t1, Integer t2) { value.set(t1 | t2); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(null); verify(observer, times(1)).onCompleted(); - + Assert.assertEquals(3, value.get()); } + @Test public void testAction2Error() { Action2 action = new Action2() { @@ -160,15 +179,16 @@ public void call(Integer t1, Integer t2) { throw new RuntimeException("Forced failure"); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onNext(null); verify(observer, never()).onCompleted(); } + @Test public void testAction3() { final AtomicInteger value = new AtomicInteger(); @@ -178,17 +198,18 @@ public void call(Integer t1, Integer t2, Integer t3) { value.set(t1 | t2 | t3); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(null); verify(observer, times(1)).onCompleted(); - + Assert.assertEquals(7, value.get()); } + @Test public void testAction3Error() { Action3 action = new Action3() { @@ -197,15 +218,16 @@ public void call(Integer t1, Integer t2, Integer t3) { throw new RuntimeException("Forced failure"); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onNext(null); verify(observer, never()).onCompleted(); } + @Test public void testAction4() { final AtomicInteger value = new AtomicInteger(); @@ -215,17 +237,18 @@ public void call(Integer t1, Integer t2, Integer t3, Integer t4) { value.set(t1 | t2 | t3 | t4); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4, 8) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(null); verify(observer, times(1)).onCompleted(); - + Assert.assertEquals(15, value.get()); } + @Test public void testAction4Error() { Action4 action = new Action4() { @@ -234,15 +257,16 @@ public void call(Integer t1, Integer t2, Integer t3, Integer t4) { throw new RuntimeException("Forced failure"); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4, 8) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onNext(null); verify(observer, never()).onCompleted(); } + @Test public void testAction5() { final AtomicInteger value = new AtomicInteger(); @@ -252,17 +276,18 @@ public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5) { value.set(t1 | t2 | t3 | t4 | t5); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4, 8, 16) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(null); verify(observer, times(1)).onCompleted(); - + Assert.assertEquals(31, value.get()); } + @Test public void testAction5Error() { Action5 action = new Action5() { @@ -271,15 +296,16 @@ public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5) { throw new RuntimeException("Forced failure"); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4, 8, 16) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onNext(null); verify(observer, never()).onCompleted(); } + @Test public void testAction6() { final AtomicInteger value = new AtomicInteger(); @@ -289,17 +315,18 @@ public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Int value.set(t1 | t2 | t3 | t4 | t5 | t6); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4, 8, 16, 32) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(null); verify(observer, times(1)).onCompleted(); - + Assert.assertEquals(63, value.get()); } + @Test public void testAction6Error() { Action6 action = new Action6() { @@ -308,15 +335,16 @@ public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Int throw new RuntimeException("Forced failure"); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4, 8, 16, 32) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onNext(null); verify(observer, never()).onCompleted(); } + @Test public void testAction7() { final AtomicInteger value = new AtomicInteger(); @@ -326,17 +354,18 @@ public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Int value.set(t1 | t2 | t3 | t4 | t5 | t6 | t7); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4, 8, 16, 32, 64) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(null); verify(observer, times(1)).onCompleted(); - + Assert.assertEquals(127, value.get()); } + @Test public void testAction7Error() { Action7 action = new Action7() { @@ -345,15 +374,16 @@ public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Int throw new RuntimeException("Forced failure"); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4, 8, 16, 32, 64) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onNext(null); verify(observer, never()).onCompleted(); } + @Test public void testAction8() { final AtomicInteger value = new AtomicInteger(); @@ -363,17 +393,18 @@ public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Int value.set(t1 | t2 | t3 | t4 | t5 | t6 | t7 | t8); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4, 8, 16, 32, 64, 128) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(null); verify(observer, times(1)).onCompleted(); - + Assert.assertEquals(255, value.get()); } + @Test public void testAction8Error() { Action8 action = new Action8() { @@ -382,15 +413,16 @@ public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Int throw new RuntimeException("Forced failure"); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4, 8, 16, 32, 64, 128) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onNext(null); verify(observer, never()).onCompleted(); } + @Test public void testAction9() { final AtomicInteger value = new AtomicInteger(); @@ -400,17 +432,18 @@ public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Int value.set(t1 | t2 | t3 | t4 | t5 | t6 | t7 | t8 | t9); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4, 8, 16, 32, 64, 128, 256) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(null); verify(observer, times(1)).onCompleted(); - + Assert.assertEquals(511, value.get()); } + @Test public void testAction9Error() { Action9 action = new Action9() { @@ -419,15 +452,16 @@ public void call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, Int throw new RuntimeException("Forced failure"); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4, 8, 16, 32, 64, 128, 256) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onNext(null); verify(observer, never()).onCompleted(); } + @Test public void testActionN() { final AtomicInteger value = new AtomicInteger(); @@ -436,22 +470,23 @@ public void testActionN() { public void call(Object... args) { int i = 0; for (Object o : args) { - i = i | (Integer)o; + i = i | (Integer) o; } value.set(i); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4, 8, 16, 32, 64, 128, 256, 512) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(null); verify(observer, times(1)).onCompleted(); - + Assert.assertEquals(1023, value.get()); } + @Test public void testActionNError() { ActionN action = new ActionN() { @@ -460,15 +495,16 @@ public void call(Object... args) { throw new RuntimeException("Forced failure"); } }; - + Async.toAsync(action, Schedulers.immediate()) .call(1, 2, 4, 8, 16, 32, 64, 128, 256, 512) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, times(1)).onError(any(Throwable.class)); verify(observer, never()).onNext(null); verify(observer, never()).onCompleted(); } + @Test public void testFunc0() { Func0 func = new Func0() { @@ -479,13 +515,14 @@ public Integer call() { }; Async.toAsync(func, Schedulers.immediate()) .call() - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(0); verify(observer, times(1)).onCompleted(); - + } + @Test public void testFunc1() { Func1 func = new Func1() { @@ -496,12 +533,13 @@ public Integer call(Integer t1) { }; Async.toAsync(func, Schedulers.immediate()) .call(1) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(1); verify(observer, times(1)).onCompleted(); } + @Test public void testFunc2() { Func2 func = new Func2() { @@ -512,12 +550,13 @@ public Integer call(Integer t1, Integer t2) { }; Async.toAsync(func, Schedulers.immediate()) .call(1, 2) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(3); verify(observer, times(1)).onCompleted(); } + @Test public void testFunc3() { Func3 func = new Func3() { @@ -528,12 +567,13 @@ public Integer call(Integer t1, Integer t2, Integer t3) { }; Async.toAsync(func, Schedulers.immediate()) .call(1, 2, 4) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(7); verify(observer, times(1)).onCompleted(); } + @Test public void testFunc4() { Func4 func = new Func4() { @@ -544,12 +584,13 @@ public Integer call(Integer t1, Integer t2, Integer t3, Integer t4) { }; Async.toAsync(func, Schedulers.immediate()) .call(1, 2, 4, 8) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(15); verify(observer, times(1)).onCompleted(); } + @Test public void testFunc5() { Func5 func = new Func5() { @@ -560,12 +601,13 @@ public Integer call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5) }; Async.toAsync(func, Schedulers.immediate()) .call(1, 2, 4, 8, 16) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(31); verify(observer, times(1)).onCompleted(); } + @Test public void testFunc6() { Func6 func = new Func6() { @@ -576,12 +618,13 @@ public Integer call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, }; Async.toAsync(func, Schedulers.immediate()) .call(1, 2, 4, 8, 16, 32) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(63); verify(observer, times(1)).onCompleted(); } + @Test public void testFunc7() { Func7 func = new Func7() { @@ -592,12 +635,13 @@ public Integer call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, }; Async.toAsync(func, Schedulers.immediate()) .call(1, 2, 4, 8, 16, 32, 64) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(127); verify(observer, times(1)).onCompleted(); } + @Test public void testFunc8() { Func8 func = new Func8() { @@ -608,12 +652,13 @@ public Integer call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, }; Async.toAsync(func, Schedulers.immediate()) .call(1, 2, 4, 8, 16, 32, 64, 128) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(255); verify(observer, times(1)).onCompleted(); } + @Test public void testFunc9() { Func9 func = new Func9() { @@ -624,12 +669,13 @@ public Integer call(Integer t1, Integer t2, Integer t3, Integer t4, Integer t5, }; Async.toAsync(func, Schedulers.immediate()) .call(1, 2, 4, 8, 16, 32, 64, 128, 256) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(511); verify(observer, times(1)).onCompleted(); } + @Test public void testFuncN() { FuncN func = new FuncN() { @@ -637,17 +683,184 @@ public void testFuncN() { public Integer call(Object... args) { int i = 0; for (Object o : args) { - i = i | (Integer)o; + i = i | (Integer) o; } return i; } }; Async.toAsync(func, Schedulers.immediate()) .call(1, 2, 4, 8, 16, 32, 64, 128, 256, 512) - .subscribe(observer); - + .subscribe(new TestObserver(observer)); + verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onNext(1023); verify(observer, times(1)).onCompleted(); } + + @Test + public void testStartWithFunc() { + Func0 func = new Func0() { + @Override + public String call() { + return "one"; + } + }; + assertEquals("one", Async.start(func).toBlockingObservable().single()); + } + + @Test(expected = RuntimeException.class) + public void testStartWithFuncError() { + Func0 func = new Func0() { + @Override + public String call() { + throw new RuntimeException("Some error"); + } + }; + Async.start(func).toBlockingObservable().single(); + } + + @Test + public void testStartWhenSubscribeRunBeforeFunc() { + TestScheduler scheduler = new TestScheduler(); + + Func0 func = new Func0() { + @Override + public String call() { + return "one"; + } + }; + + Observable observable = Async.start(func, scheduler); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + observable.subscribe(new TestObserver(observer)); + + InOrder inOrder = inOrder(observer); + inOrder.verifyNoMoreInteractions(); + + // Run func + scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); + + inOrder.verify(observer, times(1)).onNext("one"); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testStartWhenSubscribeRunAfterFunc() { + TestScheduler scheduler = new TestScheduler(); + + Func0 func = new Func0() { + @Override + public String call() { + return "one"; + } + }; + + Observable observable = Async.start(func, scheduler); + + // Run func + scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + observable.subscribe(new TestObserver(observer)); + + InOrder inOrder = inOrder(observer); + inOrder.verify(observer, times(1)).onNext("one"); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testStartWithFuncAndMultipleObservers() { + TestScheduler scheduler = new TestScheduler(); + + @SuppressWarnings("unchecked") + Func0 func = (Func0) mock(Func0.class); + doAnswer(new Answer() { + @Override + public String answer(InvocationOnMock invocation) throws Throwable { + return "one"; + } + }).when(func).call(); + + Observable observable = Async.start(func, scheduler); + + scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); + + @SuppressWarnings("unchecked") + Observer observer1 = mock(Observer.class); + @SuppressWarnings("unchecked") + Observer observer2 = mock(Observer.class); + @SuppressWarnings("unchecked") + Observer observer3 = mock(Observer.class); + + observable.subscribe(new TestObserver(observer1)); + observable.subscribe(new TestObserver(observer2)); + observable.subscribe(new TestObserver(observer3)); + + InOrder inOrder; + inOrder = inOrder(observer1); + inOrder.verify(observer1, times(1)).onNext("one"); + inOrder.verify(observer1, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + + inOrder = inOrder(observer2); + inOrder.verify(observer2, times(1)).onNext("one"); + inOrder.verify(observer2, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + + inOrder = inOrder(observer3); + inOrder.verify(observer3, times(1)).onNext("one"); + inOrder.verify(observer3, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + + verify(func, times(1)).call(); + } + + @Test + public void testRunAsync() throws InterruptedException { + final CountDownLatch cdl = new CountDownLatch(1); + final CountDownLatch cdl2 = new CountDownLatch(1); + Action2, Subscription> action = new Action2, Subscription>() { + @Override + public void call(Observer t1, Subscription t2) { + try { + cdl.await(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return; + } + for (int i = 0; i < 10 && !t2.isUnsubscribed(); i++) { + t1.onNext(i); + } + t1.onCompleted(); + cdl2.countDown(); + } + }; + + @SuppressWarnings("unchecked") + Observer o = mock(Observer.class); + InOrder inOrder = inOrder(o); + + StoppableObservable so = Async.runAsync(Schedulers.io(), action); + + so.subscribe(o); + + cdl.countDown(); + + if (!cdl2.await(2, TimeUnit.SECONDS)) { + fail("Didn't complete"); + } + + for (int i = 0; i < 10; i++) { + inOrder.verify(o).onNext(i); + } + inOrder.verify(o).onCompleted(); + inOrder.verifyNoMoreInteractions(); + verify(o, never()).onError(any(Throwable.class)); + + } } diff --git a/rxjava-contrib/rxjava-async-util/src/test/java/rx/util/async/operators/OperationDeferFutureTest.java b/rxjava-contrib/rxjava-async-util/src/test/java/rx/util/async/operators/OperationDeferFutureTest.java new file mode 100644 index 0000000000..f2d6091c96 --- /dev/null +++ b/rxjava-contrib/rxjava-async-util/src/test/java/rx/util/async/operators/OperationDeferFutureTest.java @@ -0,0 +1,105 @@ +/** + * 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.async.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; +import rx.functions.Func0; +import rx.schedulers.Schedulers; +import rx.util.async.Async; + +public class OperationDeferFutureTest { + @Test + @SuppressWarnings("unchecked") + public void testSimple() throws InterruptedException { + final ExecutorService exec = Executors.newCachedThreadPool(); + try { + final CountDownLatch ready = new CountDownLatch(1); + + Func0>> func = new Func0>>() { + @Override + public Future> call() { + return exec.submit(new Callable>() { + @Override + public Observable call() throws Exception { + if (!ready.await(1000, TimeUnit.MILLISECONDS)) { + throw new IllegalStateException("Not started in time"); + } + return Observable.from(1); + } + }); + } + }; + + Observable result = Async.deferFuture(func, Schedulers.threadPoolForComputation()); + + final Observer observer = mock(Observer.class); + + final CountDownLatch done = new CountDownLatch(1); + + result.subscribe(new OperationStartFutureTest.MockHelper(observer, done)); + + ready.countDown(); + + if (!done.await(1000, TimeUnit.MILLISECONDS)) { + fail("Not completed in time!"); + } + + InOrder inOrder = inOrder(observer); + + inOrder.verify(observer).onNext(1); + inOrder.verify(observer).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } finally { + exec.shutdown(); + } + } + + @Test + @SuppressWarnings("unchecked") + public void testSimpleFactoryThrows() { + Func0>> func = new Func0>>() { + + @Override + public Future> call() { + throw new OperationStartFutureTest.CustomException(); + } + }; + + Observable result = Async.deferFuture(func); + + final Observer observer = mock(Observer.class); + result.subscribe(observer); + + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + verify(observer).onError(any(OperationStartFutureTest.CustomException.class)); + } +} \ No newline at end of file diff --git a/rxjava-contrib/rxjava-async-util/src/test/java/rx/util/async/operators/OperationForEachFutureTest.java b/rxjava-contrib/rxjava-async-util/src/test/java/rx/util/async/operators/OperationForEachFutureTest.java new file mode 100644 index 0000000000..8fd6e4c91e --- /dev/null +++ b/rxjava-contrib/rxjava-async-util/src/test/java/rx/util/async/operators/OperationForEachFutureTest.java @@ -0,0 +1,187 @@ +/** + * Copyright 2014 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. + */ + /** + * 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.async.operators; + +import static org.junit.Assert.*; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Test; + +import rx.Observable; +import rx.functions.Action1; +import rx.schedulers.Schedulers; +import rx.util.async.Async; + +public class OperationForEachFutureTest { + @Test + public void testSimple() { + final ExecutorService exec = Executors.newCachedThreadPool(); + + try { + Observable source = Observable.from(1, 2, 3) + .subscribeOn(Schedulers.threadPoolForComputation()); + + final AtomicInteger sum = new AtomicInteger(); + Action1 add = new Action1() { + @Override + public void call(Integer t1) { + sum.addAndGet(t1); + } + }; + + FutureTask task = Async.forEachFuture(source, add); + + exec.execute(task); + + try { + Void value = task.get(1000, TimeUnit.MILLISECONDS); + + assertEquals(null, value); + + assertEquals(6, sum.get()); + } catch (TimeoutException ex) { + fail("Timed out: " + ex); + } catch (ExecutionException ex) { + fail("Exception: " + ex); + } catch (InterruptedException ex) { + fail("Exception: " + ex); + } + } finally { + exec.shutdown(); + } + } + private static final class CustomException extends RuntimeException { } + @Test + public void testSimpleThrowing() { + + final ExecutorService exec = Executors.newCachedThreadPool(); + + try { + Observable source = Observable.error(new CustomException()) + .subscribeOn(Schedulers.threadPoolForComputation()); + + final AtomicInteger sum = new AtomicInteger(); + Action1 add = new Action1() { + @Override + public void call(Integer t1) { + sum.addAndGet(t1); + } + }; + + FutureTask task = Async.forEachFuture(source, add); + + exec.execute(task); + + try { + task.get(1000, TimeUnit.MILLISECONDS); + } catch (TimeoutException ex) { + fail("Timed out: " + ex); + } catch (ExecutionException ex) { + if (!(ex.getCause() instanceof CustomException)) { + fail("Got different exception: " + ex.getCause()); + } + } catch (InterruptedException ex) { + fail("Exception: " + ex); + } + + assertEquals(0, sum.get()); + } finally { + exec.shutdown(); + } + } + + @Test + public void testSimpleScheduled() { + Observable source = Observable.from(1, 2, 3) + .subscribeOn(Schedulers.threadPoolForComputation()); + + final AtomicInteger sum = new AtomicInteger(); + Action1 add = new Action1() { + @Override + public void call(Integer t1) { + sum.addAndGet(t1); + } + }; + + FutureTask task = Async.forEachFuture(source, add, Schedulers.newThread()); + + try { + Void value = task.get(1000, TimeUnit.MILLISECONDS); + + assertEquals(null, value); + + assertEquals(6, sum.get()); + } catch (TimeoutException ex) { + fail("Timed out: " + ex); + } catch (ExecutionException ex) { + fail("Exception: " + ex); + } catch (InterruptedException ex) { + fail("Exception: " + ex); + } + } + @Test + public void testSimpleScheduledThrowing() { + + Observable source = Observable.error(new CustomException()) + .subscribeOn(Schedulers.threadPoolForComputation()); + + final AtomicInteger sum = new AtomicInteger(); + Action1 add = new Action1() { + @Override + public void call(Integer t1) { + sum.addAndGet(t1); + } + }; + + FutureTask task = Async.forEachFuture(source, add, Schedulers.newThread()); + + try { + task.get(1000, TimeUnit.MILLISECONDS); + } catch (TimeoutException ex) { + fail("Timed out: " + ex); + } catch (ExecutionException ex) { + if (!(ex.getCause() instanceof CustomException)) { + fail("Got different exception: " + ex.getCause()); + } + } catch (InterruptedException ex) { + fail("Exception: " + ex); + } + + assertEquals(0, sum.get()); + } +} diff --git a/rxjava-contrib/rxjava-async-util/src/test/java/rx/util/async/operators/OperationFromFunctionalsTest.java b/rxjava-contrib/rxjava-async-util/src/test/java/rx/util/async/operators/OperationFromFunctionalsTest.java new file mode 100644 index 0000000000..7580670d8a --- /dev/null +++ b/rxjava-contrib/rxjava-async-util/src/test/java/rx/util/async/operators/OperationFromFunctionalsTest.java @@ -0,0 +1,247 @@ +/** + * 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.async.operators; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; +import rx.functions.Action0; +import rx.functions.Func0; +import rx.observers.TestObserver; +import rx.schedulers.TestScheduler; +import rx.util.async.Async; + +public class OperationFromFunctionalsTest { + TestScheduler scheduler; + @Before + public void before() { + scheduler = new TestScheduler(); + } + private void testRunShouldThrow(Observable source, Class exception) { + for (int i = 0; i < 3; i++) { + + Observer observer = mock(Observer.class); + source.subscribe(new TestObserver(observer)); + + InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, never()).onError(any(Throwable.class)); + + scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); + + inOrder.verify(observer, times(1)).onError(any(exception)); + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + inOrder.verifyNoMoreInteractions(); + } + } + @Test + public void testFromAction() { + final AtomicInteger value = new AtomicInteger(); + + Action0 action = new Action0() { + @Override + public void call() { + value.set(2); + } + }; + + Observable source = Async.fromAction(action, 1, scheduler); + + for (int i = 0; i < 3; i++) { + + value.set(0); + + Observer observer = mock(Observer.class); + source.subscribe(new TestObserver(observer)); + + InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, never()).onNext(any()); + inOrder.verify(observer, never()).onCompleted(); + + scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); + + inOrder.verify(observer, times(1)).onNext(1); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + verify(observer, never()).onError(any(Throwable.class)); + + Assert.assertEquals(2, value.get()); + } + } + @Test + public void testFromActionThrows() { + Action0 action = new Action0() { + @Override + public void call() { + throw new RuntimeException("Forced failure!"); + } + }; + + Observable source = Async.fromAction(action, 1, scheduler); + + testRunShouldThrow(source, RuntimeException.class); + } + @Test + public void testFromFunc0() { + Func0 func = new Func0() { + @Override + public Integer call() { + return 1; + } + }; + + Observable source = Async.fromFunc0(func, scheduler); + + for (int i = 0; i < 3; i++) { + + Observer observer = mock(Observer.class); + source.subscribe(new TestObserver(observer)); + + InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, never()).onNext(any()); + inOrder.verify(observer, never()).onCompleted(); + + scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); + + inOrder.verify(observer, times(1)).onNext(1); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + verify(observer, never()).onError(any(Throwable.class)); + } + } + + @Test + public void testFromFunc0Throws() { + Func0 func = new Func0() { + @Override + public Integer call() { + throw new RuntimeException("Forced failure!"); + } + }; + + Observable source = Async.fromFunc0(func, scheduler); + + testRunShouldThrow(source, RuntimeException.class); + } + @Test + public void testFromRunnable() { + final AtomicInteger value = new AtomicInteger(); + + Runnable action = new Runnable() { + @Override + public void run() { + value.set(2); + } + }; + + Observable source = Async.fromRunnable(action, 1, scheduler); + + for (int i = 0; i < 3; i++) { + + value.set(0); + + Observer observer = mock(Observer.class); + source.subscribe(new TestObserver(observer)); + + InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, never()).onNext(any()); + inOrder.verify(observer, never()).onCompleted(); + + scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); + + inOrder.verify(observer, times(1)).onNext(1); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + verify(observer, never()).onError(any(Throwable.class)); + + Assert.assertEquals(2, value.get()); + } + } + @Test + public void testFromRunnableThrows() { + Runnable action = new Runnable() { + @Override + public void run() { + throw new RuntimeException("Forced failure!"); + } + }; + + Observable source = Async.fromRunnable(action, 1, scheduler); + + testRunShouldThrow(source, RuntimeException.class); + } + @Test + public void testFromCallable() { + Callable callable = new Callable() { + @Override + public Integer call() throws Exception { + return 1; + } + }; + + Observable source = Async.fromCallable(callable, scheduler); + + for (int i = 0; i < 3; i++) { + + Observer observer = mock(Observer.class); + source.subscribe(new TestObserver(observer)); + + InOrder inOrder = inOrder(observer); + + inOrder.verify(observer, never()).onNext(any()); + inOrder.verify(observer, never()).onCompleted(); + + scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); + + inOrder.verify(observer, times(1)).onNext(1); + inOrder.verify(observer, times(1)).onCompleted(); + inOrder.verifyNoMoreInteractions(); + verify(observer, never()).onError(any(Throwable.class)); + } + } + + @Test + public void testFromCallableThrows() { + Callable callable = new Callable() { + @Override + public Integer call() throws Exception { + throw new IOException("Forced failure!"); + } + }; + + Observable source = Async.fromCallable(callable, scheduler); + + testRunShouldThrow(source, IOException.class); + } +} diff --git a/rxjava-contrib/rxjava-async-util/src/test/java/rx/util/async/operators/OperationStartFutureTest.java b/rxjava-contrib/rxjava-async-util/src/test/java/rx/util/async/operators/OperationStartFutureTest.java new file mode 100644 index 0000000000..babee1c7ac --- /dev/null +++ b/rxjava-contrib/rxjava-async-util/src/test/java/rx/util/async/operators/OperationStartFutureTest.java @@ -0,0 +1,151 @@ +/** + * 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.async.operators; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Observable; +import rx.Observer; +import rx.functions.Func0; +import rx.schedulers.Schedulers; +import rx.util.async.Async; + +public class OperationStartFutureTest { + /** Custom exception to distinguish from any other RuntimeException. */ + static class CustomException extends RuntimeException {} + /** + * Forwards the events to the underlying observer and counts down the latch + * on terminal conditions. + * @param + */ + static class MockHelper implements Observer { + final Observer observer; + final CountDownLatch latch; + + public MockHelper(Observer observer, CountDownLatch latch) { + this.observer = observer; + this.latch = latch; + } + + @Override + public void onNext(T args) { + try { + observer.onNext(args); + } catch (Throwable t) { + onError(t); + } + } + + @Override + public void onError(Throwable e) { + try { + observer.onError(e); + } finally { + latch.countDown(); + } + } + + + @Override + public void onCompleted() { + try { + observer.onCompleted(); + } finally { + latch.countDown(); + } + } + + } + @Test + @SuppressWarnings("unchecked") + public void testSimple() throws InterruptedException { + final ExecutorService exec = Executors.newCachedThreadPool(); + try { + final CountDownLatch ready = new CountDownLatch(1); + + Func0> func = new Func0>() { + + @Override + public Future call() { + return exec.submit(new Callable() { + @Override + public Integer call() throws Exception { + if (!ready.await(1000, TimeUnit.MILLISECONDS)) { + throw new IllegalStateException("Not started in time"); + } + return 1; + } + }); + } + }; + + Observable result = Async.startFuture(func, Schedulers.threadPoolForComputation()); + + final Observer observer = mock(Observer.class); + + final CountDownLatch done = new CountDownLatch(1); + + result.subscribe(new MockHelper(observer, done)); + + ready.countDown(); + + if (!done.await(1000, TimeUnit.MILLISECONDS)) { + fail("Not completed in time!"); + } + + InOrder inOrder = inOrder(observer); + + inOrder.verify(observer).onNext(1); + inOrder.verify(observer).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } finally { + exec.shutdown(); + } + } + + @Test + @SuppressWarnings("unchecked") + public void testSimpleFactoryThrows() { + Func0> func = new Func0>() { + + @Override + public Future call() { + throw new CustomException(); + } + }; + + Observable result = Async.startFuture(func); + + final Observer observer = mock(Observer.class); + result.subscribe(observer); + + verify(observer, never()).onNext(any()); + verify(observer, never()).onCompleted(); + verify(observer).onError(any(CustomException.class)); + } +} diff --git a/rxjava-contrib/rxjava-computation-expressions/build.gradle b/rxjava-contrib/rxjava-computation-expressions/build.gradle new file mode 100644 index 0000000000..049cf185d4 --- /dev/null +++ b/rxjava-contrib/rxjava-computation-expressions/build.gradle @@ -0,0 +1,21 @@ +apply plugin: 'osgi' + +sourceCompatibility = JavaVersion.VERSION_1_6 +targetCompatibility = JavaVersion.VERSION_1_6 + +dependencies { + compile project(':rxjava-core') + testCompile project(":rxjava-core").sourceSets.test.output + provided 'junit:junit-dep:4.10' + provided 'org.mockito:mockito-core:1.8.5' +} + +jar { + manifest { + name = 'rxjava-computation-expressions' + 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/rxjava-contrib/rxjava-computation-expressions/src/main/java/rx/Statement.java b/rxjava-contrib/rxjava-computation-expressions/src/main/java/rx/Statement.java new file mode 100644 index 0000000000..4064750139 --- /dev/null +++ b/rxjava-contrib/rxjava-computation-expressions/src/main/java/rx/Statement.java @@ -0,0 +1,206 @@ +/** + * Copyright 2014 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.Map; + +import rx.functions.Func0; +import rx.operators.OperationConditionals; + +/** + * Imperative statements expressed as Observable operators. + */ +public class Statement { + + /** + * Return a particular one of several possible Observables based on a case + * selector. + *

+ * + * + * @param + * the case key type + * @param + * the result value type + * @param caseSelector + * the function that produces a case key when an + * Observer subscribes + * @param mapOfCases + * a map that maps a case key to an Observable + * @return a particular Observable chosen by key from the map of + * Observables, or an empty Observable if no Observable matches the + * key + */ + public static Observable switchCase(Func0 caseSelector, + Map> mapOfCases) { + return switchCase(caseSelector, mapOfCases, Observable. empty()); + } + + /** + * Return a particular one of several possible Observables based on a case + * selector and run it on the designated scheduler. + *

+ * + * + * @param + * the case key type + * @param + * the result value type + * @param caseSelector + * the function that produces a case key when an + * Observer subscribes + * @param mapOfCases + * a map that maps a case key to an Observable + * @param scheduler + * the scheduler where the empty observable is observed + * @return a particular Observable chosen by key from the map of + * Observables, or an empty Observable if no Observable matches the + * key, but one that runs on the designated scheduler in either case + */ + public static Observable switchCase(Func0 caseSelector, + Map> mapOfCases, Scheduler scheduler) { + return switchCase(caseSelector, mapOfCases, Observable. empty(scheduler)); + } + + /** + * Return a particular one of several possible Observables based on a case + * selector, or a default Observable if the case selector does not map to + * a particular one. + *

+ * + * + * @param + * the case key type + * @param + * the result value type + * @param caseSelector + * the function that produces a case key when an + * Observer subscribes + * @param mapOfCases + * a map that maps a case key to an Observable + * @param defaultCase + * the default Observable if the {@code mapOfCases} doesn't contain a value for the key returned by the {@case caseSelector} + * @return a particular Observable chosen by key from the map of + * Observables, or the default case if no Observable matches the key + */ + public static Observable switchCase(Func0 caseSelector, + Map> mapOfCases, + Observable defaultCase) { + return Observable.create(OperationConditionals.switchCase(caseSelector, mapOfCases, defaultCase)); + } + + /** + * Return an Observable that replays the emissions from the source + * Observable, and then continues to replay them so long as a condtion is + * true. + *

+ * + * + * @param postCondition + * the post condition to test after the source + * Observable completes + * @return an Observable that replays the emissions from the source + * Observable, and then continues to replay them so long as the post + * condition is true + */ + public static Observable doWhile(Observable source, Func0 postCondition) { + return Observable.create(OperationConditionals.doWhile(source, postCondition)); + } + + /** + * Return an Observable that replays the emissions from the source + * Observable so long as a condtion is true. + *

+ * + * + * @param preCondition + * the condition to evaluate before subscribing to or + * replaying the source Observable + * @return an Observable that replays the emissions from the source + * Observable so long as preCondition is true + */ + public static Observable whileDo(Observable source, Func0 preCondition) { + return Observable.create(OperationConditionals.whileDo(source, preCondition)); + } + + /** + * Return an Observable that emits the emissions from a specified Observable + * if a condition evaluates to true, otherwise return an empty Observable. + *

+ * + * + * @param + * the result value type + * @param condition + * the condition that decides whether to emit the emissions + * from the then Observable + * @param then + * the Observable sequence to emit to if {@code condition} is {@code true} + * @return an Observable that mimics the {@code then} Observable if the {@code condition} function evaluates to true, or an empty + * Observable otherwise + */ + public static Observable ifThen(Func0 condition, Observable then) { + return ifThen(condition, then, Observable. empty()); + } + + /** + * Return an Observable that emits the emissions from a specified Observable + * if a condition evaluates to true, otherwise return an empty Observable + * that runs on a specified Scheduler. + *

+ * + * + * @param + * the result value type + * @param condition + * the condition that decides whether to emit the emissions + * from the then Observable + * @param then + * the Observable sequence to emit to if {@code condition} is {@code true} + * @param scheduler + * the Scheduler on which the empty Observable runs if the + * in case the condition returns false + * @return an Observable that mimics the {@code then} Observable if the {@code condition} function evaluates to true, or an empty + * Observable running on the specified Scheduler otherwise + */ + public static Observable ifThen(Func0 condition, Observable then, Scheduler scheduler) { + return ifThen(condition, then, Observable. empty(scheduler)); + } + + /** + * Return an Observable that emits the emissions from one specified + * Observable if a condition evaluates to true, or from another specified + * Observable otherwise. + *

+ * + * + * @param + * the result value type + * @param condition + * the condition that decides which Observable to emit the + * emissions from + * @param then + * the Observable sequence to emit to if {@code condition} is {@code true} + * @param orElse + * the Observable sequence to emit to if {@code condition} is {@code false} + * @return an Observable that mimics either the {@code then} or {@code orElse} Observables depending on a condition function + */ + public static Observable ifThen(Func0 condition, Observable then, + Observable orElse) { + return Observable.create(OperationConditionals.ifThen(condition, then, orElse)); + } + +} diff --git a/rxjava-contrib/rxjava-computation-expressions/src/main/java/rx/operators/OperationConditionals.java b/rxjava-contrib/rxjava-computation-expressions/src/main/java/rx/operators/OperationConditionals.java new file mode 100644 index 0000000000..902d202727 --- /dev/null +++ b/rxjava-contrib/rxjava-computation-expressions/src/main/java/rx/operators/OperationConditionals.java @@ -0,0 +1,285 @@ +/** + * 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.Map; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.functions.Func0; +import rx.subscriptions.MultipleAssignmentSubscription; +import rx.subscriptions.Subscriptions; + +/** + * Implementation of conditional-based operations such as Case, If, DoWhile and While. + */ +public final class OperationConditionals { + /** Utility class. */ + private OperationConditionals() { + throw new IllegalStateException("No instances!"); + } + + /** + * Return a subscription function that subscribes to an observable sequence + * chosen from a map of observables via a selector function or to the + * default observable. + * + * @param + * the case key type + * @param + * the result value type + * @param caseSelector + * the function that produces a case key when an Observer subscribes + * @param mapOfCases + * a map that maps a case key to an observable sequence + * @param defaultCase + * the default observable if the {@code mapOfCases} doesn't contain a value for + * the key returned by the {@case caseSelector} + * @return a subscription function + */ + public static OnSubscribeFunc switchCase( + Func0 caseSelector, + Map> mapOfCases, + Observable defaultCase) { + return new SwitchCase(caseSelector, mapOfCases, defaultCase); + } + + /** + * Return a subscription function that subscribes to either the + * then or orElse Observables depending on a condition function. + * + * @param + * the result value type + * @param condition + * the condition to decide which Observables to subscribe to + * @param then + * the Observable sequence to subscribe to if {@code condition} is {@code true} + * @param orElse + * the Observable sequence to subscribe to if {@code condition} is {@code false} + * @return a subscription function + */ + public static OnSubscribeFunc ifThen( + Func0 condition, + Observable then, + Observable orElse) { + return new IfThen(condition, then, orElse); + } + + /** + * Return a subscription function that subscribes to the source Observable, + * then resubscribes only if the postCondition evaluates to true. + * + * @param + * the result value type + * @param source + * the source Observable + * @param postCondition + * the post condition after the source completes + * @return a subscription function. + */ + public static OnSubscribeFunc doWhile(Observable source, Func0 postCondition) { + return new WhileDoWhile(source, TRUE, postCondition); + } + + /** + * Return a subscription function that subscribes and resubscribes to the source + * Observable if the preCondition evaluates to true. + * + * @param + * the result value type + * @param source + * the source Observable + * @param preCondition + * the condition to evaluate before subscribing to source, + * and subscribe to source if it returns {@code true} + * @return a subscription function. + */ + public static OnSubscribeFunc whileDo(Observable source, Func0 preCondition) { + return new WhileDoWhile(source, preCondition, preCondition); + } + + /** + * Select an observable from a map based on a case key returned by a selector + * function when an observer subscribes. + * + * @param + * the case key type + * @param + * the result value type + */ + private static final class SwitchCase implements OnSubscribeFunc { + final Func0 caseSelector; + final Map> mapOfCases; + final Observable defaultCase; + + public SwitchCase(Func0 caseSelector, + Map> mapOfCases, + Observable defaultCase) { + this.caseSelector = caseSelector; + this.mapOfCases = mapOfCases; + this.defaultCase = defaultCase; + } + + @Override + public Subscription onSubscribe(Observer t1) { + Observable target; + try { + K caseKey = caseSelector.call(); + if (mapOfCases.containsKey(caseKey)) { + target = mapOfCases.get(caseKey); + } else { + target = defaultCase; + } + } catch (Throwable t) { + t1.onError(t); + return Subscriptions.empty(); + } + return target.subscribe(t1); + } + } + + /** Returns always true. */ + private static final class Func0True implements Func0 { + @Override + public Boolean call() { + return true; + } + } + + /** Returns always true function. */ + private static final Func0True TRUE = new Func0True(); + + /** + * Given a condition, subscribe to one of the observables when an Observer + * subscribes. + * + * @param + * the result value type + */ + private static final class IfThen implements OnSubscribeFunc { + final Func0 condition; + final Observable then; + final Observable orElse; + + public IfThen(Func0 condition, Observable then, Observable orElse) { + this.condition = condition; + this.then = then; + this.orElse = orElse; + } + + @Override + public Subscription onSubscribe(Observer t1) { + Observable target; + try { + if (condition.call()) { + target = then; + } else { + target = orElse; + } + } catch (Throwable t) { + t1.onError(t); + return Subscriptions.empty(); + } + return target.subscribe(t1); + } + } + + /** + * Repeatedly subscribes to the source observable if the pre- or + * postcondition is true. + *

+ * This combines the While and DoWhile into a single operation through + * the conditions. + * + * @param + * the result value type + */ + private static final class WhileDoWhile implements OnSubscribeFunc { + final Func0 preCondition; + final Func0 postCondition; + final Observable source; + + public WhileDoWhile(Observable source, + Func0 preCondition, Func0 postCondition) { + this.source = source; + this.preCondition = preCondition; + this.postCondition = postCondition; + } + + @Override + public Subscription onSubscribe(Observer t1) { + boolean first; + try { + first = preCondition.call(); + } catch (Throwable t) { + t1.onError(t); + return Subscriptions.empty(); + } + if (first) { + MultipleAssignmentSubscription ssub = new MultipleAssignmentSubscription(); + + ssub.set(source.subscribe(new SourceObserver(t1, ssub))); + + return ssub; + } else { + t1.onCompleted(); + } + return Subscriptions.empty(); + } + + /** Observe the source. */ + final class SourceObserver implements Observer { + final MultipleAssignmentSubscription cancel; + final Observer observer; + + public SourceObserver(Observer observer, MultipleAssignmentSubscription cancel) { + this.observer = observer; + this.cancel = cancel; + } + + @Override + public void onNext(T args) { + observer.onNext(args); + } + + @Override + public void onError(Throwable e) { + observer.onError(e); + cancel.unsubscribe(); + } + + @Override + public void onCompleted() { + boolean next; + try { + next = postCondition.call(); + } catch (Throwable t) { + observer.onError(t); + return; + } + if (next) { + cancel.set(source.subscribe(this)); + } else { + observer.onCompleted(); + cancel.unsubscribe(); + } + } + + } + } +} diff --git a/rxjava-contrib/rxjava-computation-expressions/src/test/java/rx/operators/OperationConditionalsTest.java b/rxjava-contrib/rxjava-computation-expressions/src/test/java/rx/operators/OperationConditionalsTest.java new file mode 100644 index 0000000000..f96d28bd09 --- /dev/null +++ b/rxjava-contrib/rxjava-computation-expressions/src/test/java/rx/operators/OperationConditionalsTest.java @@ -0,0 +1,484 @@ +/** + * 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.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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.Statement; +import rx.Subscription; +import rx.functions.Func0; +import rx.observers.TestObserver; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class OperationConditionalsTest { + @Mock + Observer observer; + TestScheduler scheduler; + Func0 func; + Func0 funcError; + Func0 condition; + Func0 conditionError; + int numRecursion = 250; + + @Before + public void before() { + MockitoAnnotations.initMocks(this); + scheduler = new TestScheduler(); + func = new Func0() { + int count = 1; + + @Override + public Integer call() { + return count++; + } + }; + funcError = new Func0() { + int count = 1; + + @Override + public Integer call() { + if (count == 2) { + throw new RuntimeException("Forced failure!"); + } + return count++; + } + }; + condition = new Func0() { + boolean r; + + @Override + public Boolean call() { + r = !r; + return r; + } + + }; + conditionError = new Func0() { + boolean r; + + @Override + public Boolean call() { + r = !r; + if (!r) { + throw new RuntimeException("Forced failure!"); + } + return r; + } + + }; + } + + Func0 just(final T value) { + return new Func0() { + @Override + public T call() { + return value; + } + }; + } + + @SuppressWarnings("unchecked") + void observe(Observable source, T... values) { + Observer o = mock(Observer.class); + + Subscription s = source.subscribe(new TestObserver(o)); + + InOrder inOrder = inOrder(o); + + for (T v : values) { + inOrder.verify(o, times(1)).onNext(v); + } + inOrder.verify(o, times(1)).onCompleted(); + verify(o, never()).onError(any(Throwable.class)); + + s.unsubscribe(); + + inOrder.verifyNoMoreInteractions(); + } + + @SuppressWarnings("unchecked") + void observeSequence(Observable source, Iterable values) { + Observer o = mock(Observer.class); + + Subscription s = source.subscribe(new TestObserver(o)); + + InOrder inOrder = inOrder(o); + + for (T v : values) { + inOrder.verify(o, times(1)).onNext(v); + } + inOrder.verify(o, times(1)).onCompleted(); + verify(o, never()).onError(any(Throwable.class)); + + s.unsubscribe(); + + inOrder.verifyNoMoreInteractions(); + } + + @SuppressWarnings("unchecked") + void observeError(Observable source, Class error, T... valuesBeforeError) { + Observer o = mock(Observer.class); + + Subscription s = source.subscribe(new TestObserver(o)); + + InOrder inOrder = inOrder(o); + + for (T v : valuesBeforeError) { + inOrder.verify(o, times(1)).onNext(v); + } + inOrder.verify(o, times(1)).onError(any(error)); + verify(o, never()).onCompleted(); + + s.unsubscribe(); + + inOrder.verifyNoMoreInteractions(); + } + + @SuppressWarnings("unchecked") + void observeSequenceError(Observable source, Class error, Iterable valuesBeforeError) { + Observer o = mock(Observer.class); + + Subscription s = source.subscribe(new TestObserver(o)); + + InOrder inOrder = inOrder(o); + + for (T v : valuesBeforeError) { + inOrder.verify(o, times(1)).onNext(v); + } + inOrder.verify(o, times(1)).onError(any(error)); + verify(o, never()).onCompleted(); + + s.unsubscribe(); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testSimple() { + Observable source1 = Observable.from(1, 2, 3); + Observable source2 = Observable.from(4, 5, 6); + + Map> map = new HashMap>(); + map.put(1, source1); + map.put(2, source2); + + Observable result = Statement.switchCase(func, map); + + observe(result, 1, 2, 3); + observe(result, 4, 5, 6); + observe(result); + } + + @Test + public void testDefaultCase() { + Observable source1 = Observable.from(1, 2, 3); + Observable source2 = Observable.from(4, 5, 6); + + Map> map = new HashMap>(); + map.put(1, source1); + + Observable result = Statement.switchCase(func, map, source2); + + observe(result, 1, 2, 3); + observe(result, 4, 5, 6); + observe(result, 4, 5, 6); + } + + @Test + public void testCaseSelectorThrows() { + Observable source1 = Observable.from(1, 2, 3); + + Map> map = new HashMap>(); + map.put(1, source1); + + Observable result = Statement.switchCase(funcError, map); + + observe(result, 1, 2, 3); + observeError(result, RuntimeException.class); + } + + @Test + public void testMapGetThrows() { + Observable source1 = Observable.from(1, 2, 3); + Observable source2 = Observable.from(4, 5, 6); + + Map> map = new HashMap>() { + + @Override + public Observable get(Object key) { + if (key.equals(2)) { + throw new RuntimeException("Forced failure!"); + } + return super.get(key); + } + + }; + map.put(1, source1); + map.put(2, source2); + + Observable result = Statement.switchCase(func, map); + + observe(result, 1, 2, 3); + observeError(result, RuntimeException.class); + } + + @Test + public void testMapContainsKeyThrows() { + Observable source1 = Observable.from(1, 2, 3); + + Map> map = new HashMap>() { + + @Override + public boolean containsKey(Object key) { + if (key.equals(2)) { + throw new RuntimeException("Forced failure!"); + } + return super.containsKey(key); + } + + }; + map.put(1, source1); + + Observable result = Statement.switchCase(func, map); + + observe(result, 1, 2, 3); + observeError(result, RuntimeException.class); + } + + @Test + public void testChosenObservableThrows() { + Observable source1 = Observable.from(1, 2, 3); + Observable source2 = Observable.error(new RuntimeException("Forced failure")); + + Map> map = new HashMap>(); + map.put(1, source1); + map.put(2, source2); + + Observable result = Statement.switchCase(func, map); + + observe(result, 1, 2, 3); + observeError(result, RuntimeException.class); + } + + @Test + public void testIfThen() { + Observable source1 = Observable.from(1, 2, 3); + + Observable result = Statement.ifThen(condition, source1); + + observe(result, 1, 2, 3); + observe(result); + observe(result, 1, 2, 3); + observe(result); + } + + @Test + public void testIfThenElse() { + Observable source1 = Observable.from(1, 2, 3); + Observable source2 = Observable.from(4, 5, 6); + + Observable result = Statement.ifThen(condition, source1, source2); + + observe(result, 1, 2, 3); + observe(result, 4, 5, 6); + observe(result, 1, 2, 3); + observe(result, 4, 5, 6); + } + + @Test + public void testIfThenConditonThrows() { + Observable source1 = Observable.from(1, 2, 3); + + Observable result = Statement.ifThen(conditionError, source1); + + observe(result, 1, 2, 3); + observeError(result, RuntimeException.class); + observe(result, 1, 2, 3); + observeError(result, RuntimeException.class); + } + + @Test + public void testIfThenObservableThrows() { + Observable source1 = Observable.error(new RuntimeException("Forced failure!")); + + Observable result = Statement.ifThen(condition, source1); + + observeError(result, RuntimeException.class); + observe(result); + + observeError(result, RuntimeException.class); + observe(result); + } + + @Test + public void testIfThenElseObservableThrows() { + Observable source1 = Observable.from(1, 2, 3); + Observable source2 = Observable.error(new RuntimeException("Forced failure!")); + + Observable result = Statement.ifThen(condition, source1, source2); + + observe(result, 1, 2, 3); + observeError(result, RuntimeException.class); + observe(result, 1, 2, 3); + observeError(result, RuntimeException.class); + } + + @Test + public void testDoWhile() { + Observable source1 = Observable.from(1, 2, 3); + + Observable result = Statement.doWhile(source1, condition); + + observe(result, 1, 2, 3, 1, 2, 3); + } + + @Test + public void testDoWhileOnce() { + Observable source1 = Observable.from(1, 2, 3); + + condition.call(); // toggle to false + Observable result = Statement.doWhile(source1, condition); + + observe(result, 1, 2, 3); + } + + @Test + public void testDoWhileConditionThrows() { + Observable source1 = Observable.from(1, 2, 3); + Observable result = Statement.doWhile(source1, conditionError); + + observeError(result, RuntimeException.class, 1, 2, 3); + } + + @Test + public void testDoWhileSourceThrows() { + Observable source1 = Observable.concat(Observable.from(1, 2, 3), + Observable. error(new RuntimeException("Forced failure!"))); + + Observable result = Statement.doWhile(source1, condition); + + observeError(result, RuntimeException.class, 1, 2, 3); + } + + Func0 countdown(final int n) { + return new Func0() { + int count = n; + + @Override + public Boolean call() { + return count-- > 0; + } + }; + } + + @Test + public void testDoWhileManyTimes() { + Observable source1 = Observable.from(1, 2, 3).subscribeOn(Schedulers.currentThread()); + + List expected = new ArrayList(numRecursion * 3); + for (int i = 0; i < numRecursion; i++) { + expected.add(1); + expected.add(2); + expected.add(3); + } + + Observable result = Statement.doWhile(source1, countdown(numRecursion)); + + observeSequence(result, expected); + } + + @Test + public void testWhileDo() { + Observable source1 = Observable.from(1, 2, 3); + Observable result = Statement.whileDo(source1, countdown(2)); + + observe(result, 1, 2, 3, 1, 2, 3); + } + + @Test + public void testWhileDoOnce() { + Observable source1 = Observable.from(1, 2, 3); + Observable result = Statement.whileDo(source1, countdown(1)); + + observe(result, 1, 2, 3); + } + + @Test + public void testWhileDoZeroTimes() { + Observable source1 = Observable.from(1, 2, 3); + Observable result = Statement.whileDo(source1, countdown(0)); + + observe(result); + } + + @Test + public void testWhileDoManyTimes() { + Observable source1 = Observable.from(1, 2, 3).subscribeOn(Schedulers.currentThread()); + + List expected = new ArrayList(numRecursion * 3); + for (int i = 0; i < numRecursion; i++) { + expected.add(1); + expected.add(2); + expected.add(3); + } + + Observable result = Statement.whileDo(source1, countdown(numRecursion)); + + observeSequence(result, expected); + } + + @Test + public void testWhileDoConditionThrows() { + Observable source1 = Observable.from(1, 2, 3); + Observable result = Statement.whileDo(source1, conditionError); + + observeError(result, RuntimeException.class, 1, 2, 3); + } + + @Test + public void testWhileDoConditionThrowsImmediately() { + Observable source1 = Observable.from(1, 2, 3); + conditionError.call(); + Observable result = Statement.whileDo(source1, conditionError); + + observeError(result, RuntimeException.class); + } + + @Test + public void testWhileDoSourceThrows() { + Observable source1 = Observable.concat(Observable.from(1, 2, 3), + Observable. error(new RuntimeException("Forced failure!"))); + + Observable result = Statement.whileDo(source1, condition); + + observeError(result, RuntimeException.class, 1, 2, 3); + } +} diff --git a/rxjava-contrib/rxjava-debug/build.gradle b/rxjava-contrib/rxjava-debug/build.gradle new file mode 100644 index 0000000000..6f8a913177 --- /dev/null +++ b/rxjava-contrib/rxjava-debug/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'osgi' + +sourceCompatibility = JavaVersion.VERSION_1_6 +targetCompatibility = JavaVersion.VERSION_1_6 + +dependencies { + compile project(':rxjava-core') + testCompile project(":rxjava-core").sourceSets.test.output + provided 'junit:junit-dep:4.10' + provided 'org.mockito:mockito-core:1.8.5' +} + +javadoc { + options { + doclet = "org.benjchristensen.doclet.DocletExclude" + docletpath = [rootProject.file('./gradle/doclet-exclude.jar')] + stylesheetFile = rootProject.file('./gradle/javadocStyleSheet.css') + windowTitle = "RxJava Javadoc ${project.version}" + } + options.addStringOption('top').value = '

RxJava

' +} + +jar { + manifest { + name = 'rxjava-debug' + 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/rxjava-contrib/rxjava-debug/src/main/java/rx/operators/DebugSubscriber.java b/rxjava-contrib/rxjava-debug/src/main/java/rx/operators/DebugSubscriber.java new file mode 100644 index 0000000000..276e245e74 --- /dev/null +++ b/rxjava-contrib/rxjava-debug/src/main/java/rx/operators/DebugSubscriber.java @@ -0,0 +1,69 @@ +package rx.operators; + +import rx.Observable.Operator; +import rx.Observer; +import rx.Subscriber; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.plugins.DebugNotification; + +public final class DebugSubscriber extends Subscriber { + private final Func1 onNextHook; + final Action1 events; + final Observer o; + Operator from = null; + Operator to = null; + + public DebugSubscriber( + Func1 onNextHook, + Action1 _events, + Subscriber _o, + Operator _out, + Operator _in) { + super(_o); + this.events = _events; + this.o = _o; + this.onNextHook = onNextHook; + this.from = _out; + this.to = _in; + this.add(new DebugSubscription(this)); + } + + @Override + public void onCompleted() { + events.call(DebugNotification.createOnCompleted(o, from, to)); + o.onCompleted(); + } + + @Override + public void onError(Throwable e) { + events.call(DebugNotification.createOnError(o, from, e, to)); + o.onError(e); + } + + @Override + public void onNext(T t) { + events.call(DebugNotification.createOnNext(o, from, t, to)); + o.onNext(onNextHook.call(t)); + } + + public Operator getFrom() { + return from; + } + + public void setFrom(Operator bind) { + this.from = bind; + } + + public Operator getTo() { + return to; + } + + public void setTo(Operator op) { + this.to = op; + } + + public Observer getActual() { + return o; + } +} \ No newline at end of file diff --git a/rxjava-contrib/rxjava-debug/src/main/java/rx/operators/DebugSubscription.java b/rxjava-contrib/rxjava-debug/src/main/java/rx/operators/DebugSubscription.java new file mode 100644 index 0000000000..e82960d5fe --- /dev/null +++ b/rxjava-contrib/rxjava-debug/src/main/java/rx/operators/DebugSubscription.java @@ -0,0 +1,23 @@ +package rx.operators; + +import rx.Subscription; +import rx.plugins.DebugNotification; + +final class DebugSubscription implements Subscription { + private final DebugSubscriber debugObserver; + + DebugSubscription(DebugSubscriber debugObserver) { + this.debugObserver = debugObserver; + } + + @Override + public void unsubscribe() { + debugObserver.events.call(DebugNotification. createUnsubscribe(debugObserver.o, debugObserver.from, debugObserver.to)); + debugObserver.unsubscribe(); + } + + @Override + public boolean isUnsubscribed() { + return debugObserver.isUnsubscribed(); + } +} \ No newline at end of file diff --git a/rxjava-contrib/rxjava-debug/src/main/java/rx/plugins/DebugHook.java b/rxjava-contrib/rxjava-debug/src/main/java/rx/plugins/DebugHook.java new file mode 100644 index 0000000000..31d54d87ce --- /dev/null +++ b/rxjava-contrib/rxjava-debug/src/main/java/rx/plugins/DebugHook.java @@ -0,0 +1,99 @@ +package rx.plugins; + +import rx.Observable; +import rx.Observable.OnSubscribe; +import rx.Observable.Operator; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action1; +import rx.functions.Actions; +import rx.functions.Func1; +import rx.functions.Functions; +import rx.operators.DebugSubscriber; + +/** + * Implements hooks into the {@link Observable} chain to emit a detailed account of all the events + * that happened. + * + * @author gscampbell + */ +public class DebugHook extends RxJavaObservableExecutionHook { + private final Func1 onNextHook; + private final Action1 events; + + /** + * Creates a new instance of the DebugHook RxJava plug-in that can be passed into + * {@link RxJavaPlugins} registerObservableExecutionHook(hook) method. + * + * @param onNextDataHook + * all of the onNext values are passed through this function to allow for + * manipulation of the values + * @param events + * This action is invoked as each notification is generated + */ + public DebugHook(Func1 onNextDataHook, Action1 events) { + this.onNextHook = onNextDataHook == null ? Functions.identity() : onNextDataHook; + this.events = events == null ? Actions.empty() : events; + } + + @Override + public OnSubscribe onSubscribeStart(Observable observableInstance, final OnSubscribe f) { + return new OnSubscribe() { + @Override + public void call(Subscriber o) { + events.call(DebugNotification.createSubscribe(o, f)); + f.call(wrapOutbound(null, o)); + } + }; + } + + @Override + public Subscription onSubscribeReturn(Observable observableInstance, Subscription subscription) { + return subscription; + } + + @Override + public OnSubscribe onCreate(final OnSubscribe f) { + return new OnSubscribe() { + @Override + public void call(Subscriber o) { + f.call(wrapInbound(null, o)); + } + }; + } + + @Override + public Operator onLift(final Operator bind) { + return new Operator() { + @Override + public Subscriber call(final Subscriber o) { + return wrapInbound(bind, bind.call(wrapOutbound(bind, o))); + } + }; + } + + @Override + public Subscription onAdd(Subscriber subscriber, Subscription s) { + return s; + } + + @SuppressWarnings("unchecked") + private Subscriber wrapOutbound(Operator bind, Subscriber o) { + if (o instanceof DebugSubscriber) { + if (bind != null) + ((DebugSubscriber) o).setFrom(bind); + return o; + } + return new DebugSubscriber(onNextHook, events, o, bind, null); + } + + @SuppressWarnings("unchecked") + private Subscriber wrapInbound(Operator bind, Subscriber o) { + if (o instanceof DebugSubscriber) { + if (bind != null) + ((DebugSubscriber) o).setTo(bind); + return o; + } + return new DebugSubscriber(onNextHook, events, o, null, bind); + } +} diff --git a/rxjava-contrib/rxjava-debug/src/main/java/rx/plugins/DebugNotification.java b/rxjava-contrib/rxjava-debug/src/main/java/rx/plugins/DebugNotification.java new file mode 100644 index 0000000000..f4144fb2b0 --- /dev/null +++ b/rxjava-contrib/rxjava-debug/src/main/java/rx/plugins/DebugNotification.java @@ -0,0 +1,108 @@ +package rx.plugins; + +import rx.Notification; +import rx.Observable.OnSubscribe; +import rx.Observable.Operator; +import rx.Observer; +import rx.observers.SafeSubscriber; +import rx.operators.DebugSubscriber; + +public class DebugNotification { + public static enum Kind { + OnNext, OnError, OnCompleted, Subscribe, Unsubscribe + } + + private final OnSubscribe source; + private final Operator from; + private final Kind kind; + private final Notification notification; + private final Operator to; + private final long nanoTime; + private final long threadId; + private Observer o; + + public static DebugNotification createSubscribe(Observer o, OnSubscribe source) { + Operator to = null; + Operator from = null; + if (o instanceof DebugSubscriber) { + to = ((DebugSubscriber) o).getTo(); + from = ((DebugSubscriber) o).getFrom(); + o = ((DebugSubscriber) o).getActual(); + } + return new DebugNotification(o, from, Kind.Subscribe, null, to, source); + } + + public static DebugNotification createOnNext(Observer o, Operator from, T t, Operator to) { + return new DebugNotification(o, from, Kind.OnNext, Notification.createOnNext(t), to, null); + } + + public static DebugNotification createOnError(Observer o, Operator from, Throwable e, Operator to) { + return new DebugNotification(o, from, Kind.OnError, Notification. createOnError(e), to, null); + } + + public static DebugNotification createOnCompleted(Observer o, Operator from, Operator to) { + return new DebugNotification(o, from, Kind.OnCompleted, Notification. createOnCompleted(), to, null); + } + + public static DebugNotification createUnsubscribe(Observer o, Operator from, Operator to) { + return new DebugNotification(o, from, Kind.Unsubscribe, null, to, null); + } + + private DebugNotification(Observer o, Operator from, Kind kind, Notification notification, Operator to, OnSubscribe source) { + this.o = (o instanceof SafeSubscriber) ? ((SafeSubscriber) o).getActual() : o; + this.from = from; + this.kind = kind; + this.notification = notification; + this.to = to; + this.source = source; + this.nanoTime = System.nanoTime(); + this.threadId = Thread.currentThread().getId(); + } + + public Operator getFrom() { + return from; + } + + public Notification getNotification() { + return notification; + } + + public Operator getTo() { + return to; + } + + public long getNanoTime() { + return nanoTime; + } + + public long getThreadId() { + return threadId; + } + + public Kind getKind() { + return kind; + } + + @Override + public String toString() { + final StringBuilder s = new StringBuilder("{"); + s.append(" \"nano\": ").append(nanoTime); + s.append(", \"thread\": ").append(threadId); + s.append(", \"observer\": \"").append(o.getClass().getName()).append("@").append(Integer.toHexString(o.hashCode())).append("\""); + s.append(", \"type\": \"").append(kind).append("\""); + if (notification != null) { + if (notification.hasValue()) + s.append(", \"value\": \"").append(notification.getValue()).append("\""); + if (notification.hasThrowable()) + s.append(", \"exception\": \"").append(notification.getThrowable().getMessage().replace("\\", "\\\\").replace("\"", "\\\"")).append("\""); + } + if (source != null) + s.append(", \"source\": \"").append(source.getClass().getName()).append("@").append(Integer.toHexString(source.hashCode())).append("\""); + if (from != null) + s.append(", \"from\": \"").append(from.getClass().getName()).append("@").append(Integer.toHexString(from.hashCode())).append("\""); + if (to != null) + s.append(", \"to\": \"").append(to.getClass().getName()).append("@").append(Integer.toHexString(to.hashCode())).append("\""); + s.append("}"); + return s.toString(); + } +} diff --git a/rxjava-contrib/rxjava-debug/src/test/java/rx/debug/DebugHookTest.java b/rxjava-contrib/rxjava-debug/src/test/java/rx/debug/DebugHookTest.java new file mode 100644 index 0000000000..92c6cdd0dd --- /dev/null +++ b/rxjava-contrib/rxjava-debug/src/test/java/rx/debug/DebugHookTest.java @@ -0,0 +1,130 @@ +package rx.debug; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.Arrays; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import rx.Notification; +import rx.Observable; +import rx.Observer; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.plugins.DebugHook; +import rx.plugins.DebugNotification; +import rx.plugins.PlugReset; +import rx.plugins.RxJavaPlugins; + +public class DebugHookTest { + @Before + @After + public void reset() { + PlugReset.reset(); + } + + @Test + @Ignore + public void testSimple() { + Action1 events = mock(Action1.class); + final DebugHook hook = new DebugHook(null, events); + RxJavaPlugins.getInstance().registerObservableExecutionHook(hook); + Observable.empty().subscribe(); + verify(events, times(1)).call(subscribe()); + verify(events, times(1)).call(onCompleted()); + } + + @Test + public void testOneOp() { + Action1 events = mock(Action1.class); + final DebugHook hook = new DebugHook(null, events); + RxJavaPlugins.getInstance().registerObservableExecutionHook(hook); + Observable.from(Arrays.asList(1, 3)).flatMap(new Func1>() { + @Override + public Observable call(Integer it) { + return Observable.from(Arrays.asList(it, it + 1)); + } + }).take(3).subscribe(new Observer() { + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onNext(Integer t) { + } + }); + verify(events, atLeast(3)).call(subscribe()); + verify(events, times(4)).call(onNext(1)); + // one less because it originates from the inner observable sent to merge + verify(events, times(3)).call(onNext(2)); + verify(events, times(4)).call(onNext(3)); + // because the take unsubscribes + verify(events, never()).call(onNext(4)); + } + + private static DebugNotification onNext(final T value) { + return argThat(new BaseMatcher>() { + @Override + public boolean matches(Object item) { + if (item instanceof DebugNotification) { + DebugNotification dn = (DebugNotification) item; + Notification n = dn.getNotification(); + return n != null && n.hasValue() && n.getValue().equals(value); + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("OnNext " + value); + } + }); + } + + private static DebugNotification subscribe() { + return argThat(new BaseMatcher() { + @Override + public boolean matches(Object item) { + if (item instanceof DebugNotification) { + DebugNotification dn = (DebugNotification) item; + return dn.getKind() == DebugNotification.Kind.Subscribe; + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("Subscribe"); + } + }); + } + + private static DebugNotification onCompleted() { + return argThat(new BaseMatcher() { + @Override + public boolean matches(Object item) { + if (item instanceof DebugNotification) { + DebugNotification dn = (DebugNotification) item; + Notification n = dn.getNotification(); + return n != null && n.isOnCompleted(); + } + return false; + } + + @Override + public void describeTo(Description description) { + description.appendText("onCompleted"); + } + }); + } +} diff --git a/rxjava-contrib/rxjava-debug/src/test/java/rx/plugins/PlugReset.java b/rxjava-contrib/rxjava-debug/src/test/java/rx/plugins/PlugReset.java new file mode 100644 index 0000000000..0292d1b5e3 --- /dev/null +++ b/rxjava-contrib/rxjava-debug/src/test/java/rx/plugins/PlugReset.java @@ -0,0 +1,7 @@ +package rx.plugins; + +public class PlugReset { + public static void reset() { + RxJavaPlugins.getInstance().reset(); + } +} diff --git a/rxjava-contrib/rxjava-string/build.gradle b/rxjava-contrib/rxjava-string/build.gradle index 5c578ae04d..a3423a5173 100644 --- a/rxjava-contrib/rxjava-string/build.gradle +++ b/rxjava-contrib/rxjava-string/build.gradle @@ -26,5 +26,6 @@ jar { 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/rxjava-contrib/rxjava-string/src/main/java/rx/observables/StringObservable.java b/rxjava-contrib/rxjava-string/src/main/java/rx/observables/StringObservable.java index dfe7ca3e68..16f410bf6d 100644 --- a/rxjava-contrib/rxjava-string/src/main/java/rx/observables/StringObservable.java +++ b/rxjava-contrib/rxjava-string/src/main/java/rx/observables/StringObservable.java @@ -1,5 +1,23 @@ +/** + * 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 java.io.IOException; +import java.io.InputStream; +import java.io.Reader; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; @@ -12,15 +30,108 @@ import java.util.regex.Pattern; import rx.Observable; -import rx.Observer; -import rx.Subscription; -import rx.Observable.OnSubscribeFunc; -import rx.util.functions.Func1; -import rx.util.functions.Func2; +import rx.Observable.OnSubscribe; +import rx.Observable.Operator; +import rx.Subscriber; +import rx.functions.Func1; +import rx.functions.Func2; public class StringObservable { /** - * Decodes a stream the multibyte chunks into a stream of strings that works on infinite streams and where handles when a multibyte character spans two chunks. + * Reads from the bytes from a source {@link InputStream} and outputs {@link Observable} of + * {@link byte[]}s + * + * @param i + * Source {@link InputStream} + * @return + */ + public static Observable from(final InputStream i) { + return from(i, 8 * 1024); + } + + /** + * Reads from the bytes from a source {@link InputStream} and outputs {@link Observable} of + * {@link byte[]}s + * + * @param i + * Source {@link InputStream} + * @param size + * internal buffer size + * @return + */ + public static Observable from(final InputStream i, final int size) { + return Observable.create(new OnSubscribe() { + @Override + public void call(Subscriber o) { + byte[] buffer = new byte[size]; + try { + if (o.isUnsubscribed()) + return; + int n = 0; + n = i.read(buffer); + while (n != -1 && !o.isUnsubscribed()) { + o.onNext(Arrays.copyOf(buffer, n)); + n = i.read(buffer); + } + } catch (IOException e) { + o.onError(e); + } + if (o.isUnsubscribed()) + return; + o.onCompleted(); + } + }); + } + + /** + * Reads from the characters from a source {@link Reader} and outputs {@link Observable} of + * {@link String}s + * + * @param i + * Source {@link Reader} + * @return + */ + public static Observable from(final Reader i) { + return from(i, 8 * 1024); + } + + /** + * Reads from the characters from a source {@link Reader} and outputs {@link Observable} of + * {@link String}s + * + * @param i + * Source {@link Reader} + * @param size + * internal buffer size + * @return + */ + public static Observable from(final Reader i, final int size) { + return Observable.create(new OnSubscribe() { + @Override + public void call(Subscriber o) { + char[] buffer = new char[size]; + try { + if (o.isUnsubscribed()) + return; + int n = 0; + n = i.read(buffer); + while (n != -1 && !o.isUnsubscribed()) { + o.onNext(new String(buffer, 0, n)); + n = i.read(buffer); + } + } catch (IOException e) { + o.onError(e); + } + if (o.isUnsubscribed()) + return; + o.onCompleted(); + } + }); + } + + /** + * Decodes a stream the multibyte chunks into a stream of strings that works on infinite streams + * and where handles when a multibyte character spans two chunks. * * @param src * @param charsetName @@ -31,7 +142,8 @@ public static Observable decode(Observable src, String charsetNa } /** - * Decodes a stream the multibyte chunks into a stream of strings that works on infinite streams and where handles when a multibyte character spans two chunks. + * Decodes a stream the multibyte chunks into a stream of strings that works on infinite streams + * and where handles when a multibyte character spans two chunks. * * @param src * @param charset @@ -42,7 +154,8 @@ public static Observable decode(Observable src, Charset charset) } /** - * Decodes a stream the multibyte chunks into a stream of strings that works on infinite streams and where handles when a multibyte character spans two chunks. + * Decodes a stream the multibyte chunks into a stream of strings that works on infinite streams + * and where it handles when a multibyte character spans two chunks. * This method allows for more control over how malformed and unmappable characters are handled. * * @param src @@ -50,22 +163,22 @@ public static Observable decode(Observable src, Charset charset) * @return */ public static Observable decode(final Observable src, final CharsetDecoder charsetDecoder) { - return Observable.create(new OnSubscribeFunc() { + return src.lift(new Operator() { @Override - public Subscription onSubscribe(final Observer observer) { - return src.subscribe(new Observer() { + public Subscriber call(final Subscriber o) { + return new Subscriber(o) { private ByteBuffer leftOver = null; @Override public void onCompleted() { if (process(null, leftOver, true)) - observer.onCompleted(); + o.onCompleted(); } @Override public void onError(Throwable e) { if (process(null, leftOver, true)) - observer.onError(e); + o.onError(e); } @Override @@ -74,6 +187,9 @@ public void onNext(byte[] bytes) { } public boolean process(byte[] next, ByteBuffer last, boolean endOfInput) { + if (o.isUnsubscribed()) + return false; + ByteBuffer bb; if (last != null) { if (next != null) { @@ -105,7 +221,7 @@ public boolean process(byte[] next, ByteBuffer last, boolean endOfInput) { cr.throwException(); } catch (CharacterCodingException e) { - observer.onError(e); + o.onError(e); return false; } } @@ -119,11 +235,11 @@ public boolean process(byte[] next, ByteBuffer last, boolean endOfInput) { String string = cb.toString(); if (!string.isEmpty()) - observer.onNext(string); + o.onNext(string); return true; } - }); + }; } }); } @@ -175,13 +291,15 @@ public byte[] call(String str) { } /** - * Gather up all of the strings in to one string to be able to use it as one message. Don't use this on infinite streams. + * Gather up all of the strings in to one string to be able to use it as one message. Don't use + * this on infinite streams. * * @param src * @return */ public static Observable stringConcat(Observable src) { - return src.aggregate(new Func2() { + return src.reduce(new Func2() { + @Override public String call(String a, String b) { return a + b; } @@ -191,8 +309,10 @@ public String call(String a, String b) { /** * Rechunks the strings based on a regex pattern and works on infinite stream. * - * resplit(["boo:an", "d:foo"], ":") --> ["boo", "and", "foo"] - * resplit(["boo:an", "d:foo"], "o") --> ["b", "", ":and:f", "", ""] + *
+     * split(["boo:an", "d:foo"], ":") --> ["boo", "and", "foo"]
+     * split(["boo:an", "d:foo"], "o") --> ["b", "", ":and:f", "", ""]
+     * 
* * See {@link Pattern} * @@ -202,22 +322,25 @@ public String call(String a, String b) { */ public static Observable split(final Observable src, String regex) { final Pattern pattern = Pattern.compile(regex); - return Observable.create(new OnSubscribeFunc() { + + return src.lift(new Operator() { @Override - public Subscription onSubscribe(final Observer observer) { - return src.subscribe(new Observer() { + public Subscriber call(final Subscriber o) { + return new Subscriber(o) { private String leftOver = null; @Override public void onCompleted() { output(leftOver); - observer.onCompleted(); + if (!o.isUnsubscribed()) + o.onCompleted(); } @Override public void onError(Throwable e) { output(leftOver); - observer.onError(e); + if (!o.isUnsubscribed()) + o.onError(e); } @Override @@ -234,8 +357,10 @@ public void onNext(String segment) { } private int emptyPartCount = 0; + /** * when limit == 0 trailing empty parts are not emitted. + * * @param part */ private void output(String part) { @@ -243,12 +368,136 @@ private void output(String part) { emptyPartCount++; } else { - for(; emptyPartCount>0; emptyPartCount--) - observer.onNext(""); - observer.onNext(part); + for (; emptyPartCount > 0; emptyPartCount--) + if (!o.isUnsubscribed()) + o.onNext(""); + if (!o.isUnsubscribed()) + o.onNext(part); } } - }); + }; + } + }); + } + + /** + * Concatenates the sequence of values by adding a separator + * between them and emitting the result once the source completes. + *

+ * The conversion from the value type to String is performed via + * {@link java.lang.String#valueOf(java.lang.Object)} calls. + *

+ * For example: + * + *

+     * Observable<Object> source = Observable.from("a", 1, "c");
+     * Observable<String> result = join(source, ", ");
+     * 
+ * + * will yield a single element equal to "a, 1, c". + * + * @param source + * the source sequence of CharSequence values + * @param separator + * the separator to a + * @return an Observable which emits a single String value having the concatenated + * values of the source observable with the separator between elements + */ + public static Observable join(final Observable source, final CharSequence separator) { + return source.lift(new Operator() { + @Override + public Subscriber call(final Subscriber o) { + return new Subscriber(o) { + boolean mayAddSeparator; + StringBuilder b = new StringBuilder(); + + @Override + public void onCompleted() { + String str = b.toString(); + b = null; + if (!o.isUnsubscribed()) + o.onNext(str); + if (!o.isUnsubscribed()) + o.onCompleted(); + } + + @Override + public void onError(Throwable e) { + b = null; + if (!o.isUnsubscribed()) + o.onError(e); + } + + @Override + public void onNext(Object t) { + if (mayAddSeparator) { + b.append(separator); + } + mayAddSeparator = true; + b.append(String.valueOf(t)); + } + }; + } + }); + } + + public final static class Line { + private final int number; + private final String text; + + public Line(int number, String text) { + this.number = number; + this.text = text; + } + + public int getNumber() { + return number; + } + + public String getText() { + return text; + } + + @Override + public int hashCode() { + int result = 31 + number; + result = 31 * result + (text == null ? 0 : text.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Line)) + return false; + Line other = (Line) obj; + if (number != other.number) + return false; + if (other.text == text) + return true; + if (text == null) + return false; + return text.equals(other.text); + } + + @Override + public String toString() { + return number + ":" + text; + } + } + + /** + * Splits the {@link Observable} of Strings by lines and numbers them (zero based index) + * + * @param source + * @return + */ + public static Observable byLine(Observable source) { + return split(source, System.getProperty("line.separator")).map(new Func1() { + int lineNumber = 0; + + @Override + public Line call(String text) { + return new Line(lineNumber++, text); } }); } diff --git a/rxjava-contrib/rxjava-string/src/test/java/rx/observables/StringObservableTest.java b/rxjava-contrib/rxjava-string/src/test/java/rx/observables/StringObservableTest.java index 8ced455f63..01dcc8f435 100644 --- a/rxjava-contrib/rxjava-string/src/test/java/rx/observables/StringObservableTest.java +++ b/rxjava-contrib/rxjava-string/src/test/java/rx/observables/StringObservableTest.java @@ -1,17 +1,42 @@ +/** + * 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 static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.io.NotSerializableException; +import java.io.StringReader; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.MalformedInputException; +import java.util.Arrays; +import java.util.List; + +import junit.framework.Assert; import org.junit.Test; import rx.Observable; -import rx.observables.BlockingObservable; -import rx.observables.StringObservable; +import rx.Observer; +import rx.observables.StringObservable.Line; +import rx.observers.TestObserver; import rx.util.AssertObservable; public class StringObservableTest { @@ -93,6 +118,7 @@ public void testEncode() { public void testSplitOnCollon() { testSplit("boo:and:foo", ":", 0, "boo", "and", "foo"); } + @Test public void testSplitOnOh() { testSplit("boo:and:foo", "o", 0, "b", "", ":and:f"); @@ -103,13 +129,126 @@ public void testSplit(String str, String regex, int limit, String... parts) { for (int i = 0; i < str.length(); i++) { String a = str.substring(0, i); String b = str.substring(i, str.length()); - testSplit(a+"|"+b, regex, limit, Observable.from(a, b), parts); + testSplit(a + "|" + b, regex, limit, Observable.from(a, b), parts); } } public void testSplit(String message, String regex, int limit, Observable src, String... parts) { Observable act = StringObservable.split(src, regex); Observable exp = Observable.from(parts); - AssertObservable.assertObservableEqualsBlocking("when input is "+message+" and limit = "+ limit, exp, act); + AssertObservable.assertObservableEqualsBlocking("when input is " + message + " and limit = " + limit, exp, act); + } + + @Test + public void testJoinMixed() { + Observable source = Observable. from(Arrays.asList("a", 1, "c")); + + Observable result = StringObservable.join(source, ", "); + + Observer observer = mock(Observer.class); + + result.subscribe(new TestObserver(observer)); + + verify(observer, times(1)).onNext("a, 1, c"); + verify(observer, times(1)).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void testJoinWithEmptyString() { + Observable source = Observable.from("", "b", "c"); + + Observable result = StringObservable.join(source, ", "); + + Observer observer = mock(Observer.class); + + result.subscribe(new TestObserver(observer)); + + verify(observer, times(1)).onNext(", b, c"); + verify(observer, times(1)).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void testJoinWithNull() { + Observable source = Observable.from("a", null, "c"); + + Observable result = StringObservable.join(source, ", "); + + Observer observer = mock(Observer.class); + + result.subscribe(new TestObserver(observer)); + + verify(observer, times(1)).onNext("a, null, c"); + verify(observer, times(1)).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void testJoinSingle() { + Observable source = Observable.from("a"); + + Observable result = StringObservable.join(source, ", "); + + Observer observer = mock(Observer.class); + + result.subscribe(new TestObserver(observer)); + + verify(observer, times(1)).onNext("a"); + verify(observer, times(1)).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void testJoinEmpty() { + Observable source = Observable.empty(); + + Observable result = StringObservable.join(source, ", "); + + Observer observer = mock(Observer.class); + + result.subscribe(new TestObserver(observer)); + + verify(observer, times(1)).onNext(""); + verify(observer, times(1)).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + } + + @Test + public void testJoinThrows() { + Observable source = Observable.concat(Observable.just("a"), Observable. error(new RuntimeException("Forced failure"))); + + Observable result = StringObservable.join(source, ", "); + + Observer observer = mock(Observer.class); + + result.subscribe(new TestObserver(observer)); + + verify(observer, never()).onNext("a"); + verify(observer, never()).onCompleted(); + verify(observer, times(1)).onError(any(Throwable.class)); + } + + @Test + public void testFromInputStream() { + final byte[] inBytes = "test".getBytes(); + final byte[] outBytes = StringObservable.from(new ByteArrayInputStream(inBytes)).toBlockingObservable().single(); + assertNotSame(inBytes, outBytes); + assertArrayEquals(inBytes, outBytes); + } + + @Test + public void testFromReader() { + final String inStr = "test"; + final String outStr = StringObservable.from(new StringReader(inStr)).toBlockingObservable().single(); + assertNotSame(inStr, outStr); + assertEquals(inStr, outStr); + } + + @Test + public void testByLine() { + List lines = StringObservable.byLine(Observable.from(Arrays.asList("qwer", "\nasdf\n", "zx", "cv"))).toList().toBlockingObservable().single(); + + assertEquals(Arrays.asList(new Line(0, "qwer"), new Line(1, "asdf"), new Line(2, "zxcv")), lines); } } diff --git a/rxjava-contrib/rxjava-swing/build.gradle b/rxjava-contrib/rxjava-swing/build.gradle index ea863813a2..87c04c3b53 100644 --- a/rxjava-contrib/rxjava-swing/build.gradle +++ b/rxjava-contrib/rxjava-swing/build.gradle @@ -25,5 +25,6 @@ jar { 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/rxjava-contrib/rxjava-swing/src/main/java/rx/concurrency/SwingScheduler.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/concurrency/SwingScheduler.java deleted file mode 100644 index f2d10d69b0..0000000000 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/concurrency/SwingScheduler.java +++ /dev/null @@ -1,274 +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.concurrency; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -import java.awt.EventQueue; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import javax.swing.SwingUtilities; -import javax.swing.Timer; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.InOrder; - -import rx.Scheduler; -import rx.Subscription; -import rx.subscriptions.CompositeSubscription; -import rx.subscriptions.Subscriptions; -import rx.util.functions.Action0; -import rx.util.functions.Func2; - -/** - * Executes work on the Swing UI thread. - * This scheduler should only be used with actions that execute quickly. - */ -public final class SwingScheduler extends Scheduler { - private static final SwingScheduler INSTANCE = new SwingScheduler(); - - public static SwingScheduler getInstance() { - return INSTANCE; - } - - private SwingScheduler() { - } - - @Override - public Subscription schedule(final T state, final Func2 action) { - final AtomicReference sub = new AtomicReference(); - EventQueue.invokeLater(new Runnable() { - @Override - public void run() { - sub.set(action.call(SwingScheduler.this, state)); - } - }); - return Subscriptions.create(new Action0() { - @Override - public void call() { - Subscription subscription = sub.get(); - if (subscription != null) { - subscription.unsubscribe(); - } - } - }); - } - - @Override - public Subscription schedule(final T state, final Func2 action, long dueTime, TimeUnit unit) { - final AtomicReference sub = new AtomicReference(); - long delay = unit.toMillis(dueTime); - assertThatTheDelayIsValidForTheSwingTimer(delay); - - class ExecuteOnceAction implements ActionListener { - private Timer timer; - - private void setTimer(Timer timer) { - this.timer = timer; - } - - @Override - public void actionPerformed(ActionEvent e) { - timer.stop(); - sub.set(action.call(SwingScheduler.this, state)); - } - } - - ExecuteOnceAction executeOnce = new ExecuteOnceAction(); - final Timer timer = new Timer((int) delay, executeOnce); - executeOnce.setTimer(timer); - timer.start(); - - return Subscriptions.create(new Action0() { - @Override - public void call() { - timer.stop(); - - Subscription subscription = sub.get(); - if (subscription != null) { - subscription.unsubscribe(); - } - } - }); - } - - @Override - public Subscription schedulePeriodically(T state, final Func2 action, long initialDelay, long period, TimeUnit unit) { - final AtomicReference timer = new AtomicReference(); - - final long delay = unit.toMillis(period); - assertThatTheDelayIsValidForTheSwingTimer(delay); - - final CompositeSubscription subscriptions = new CompositeSubscription(); - final Func2 initialAction = new Func2() { - @Override - public Subscription call(final Scheduler scheduler, final T state0) { - // start timer for periodic execution, collect subscriptions - timer.set(new Timer((int) delay, new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - subscriptions.add(action.call(scheduler, state0)); - } - })); - timer.get().start(); - - return action.call(scheduler, state0); - } - }; - subscriptions.add(schedule(state, initialAction, initialDelay, unit)); - - subscriptions.add(Subscriptions.create(new Action0() { - @Override - public void call() { - // in addition to all the individual unsubscriptions, stop the timer on unsubscribing - Timer maybeTimer = timer.get(); - if (maybeTimer != null) { - maybeTimer.stop(); - } - } - })); - - return subscriptions; - } - - private static void assertThatTheDelayIsValidForTheSwingTimer(long delay) { - if (delay < 0 || delay > Integer.MAX_VALUE) { - throw new IllegalArgumentException(String.format("The swing timer only accepts non-negative delays up to %d milliseconds.", Integer.MAX_VALUE)); - } - } - - public static class UnitTest { - @Rule - public ExpectedException exception = ExpectedException.none(); - - @Test - public void testInvalidDelayValues() { - final SwingScheduler scheduler = new SwingScheduler(); - final Action0 action = mock(Action0.class); - - exception.expect(IllegalArgumentException.class); - scheduler.schedulePeriodically(action, -1L, 100L, TimeUnit.SECONDS); - - exception.expect(IllegalArgumentException.class); - scheduler.schedulePeriodically(action, 100L, -1L, TimeUnit.SECONDS); - - exception.expect(IllegalArgumentException.class); - scheduler.schedulePeriodically(action, 1L + Integer.MAX_VALUE, 100L, TimeUnit.MILLISECONDS); - - exception.expect(IllegalArgumentException.class); - scheduler.schedulePeriodically(action, 100L, 1L + Integer.MAX_VALUE / 1000, TimeUnit.SECONDS); - } - - @Test - public void testPeriodicScheduling() throws Exception { - final SwingScheduler scheduler = new SwingScheduler(); - - final CountDownLatch latch = new CountDownLatch(4); - - final Action0 innerAction = mock(Action0.class); - final Action0 action = new Action0() { - @Override - public void call() { - try { - innerAction.call(); - assertTrue(SwingUtilities.isEventDispatchThread()); - } finally { - latch.countDown(); - } - } - }; - - Subscription sub = scheduler.schedulePeriodically(action, 50, 200, TimeUnit.MILLISECONDS); - - if (!latch.await(5000, TimeUnit.MILLISECONDS)) { - fail("timed out waiting for tasks to execute"); - } - - sub.unsubscribe(); - waitForEmptyEventQueue(); - verify(innerAction, times(4)).call(); - } - - @Test - public void testNestedActions() throws Exception { - final SwingScheduler scheduler = new SwingScheduler(); - - 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() { - assertTrue(SwingUtilities.isEventDispatchThread()); - firstStepStart.call(); - firstStepEnd.call(); - } - }; - final Action0 secondAction = new Action0() { - @Override - public void call() { - assertTrue(SwingUtilities.isEventDispatchThread()); - secondStepStart.call(); - scheduler.schedule(firstAction); - secondStepEnd.call(); - } - }; - final Action0 thirdAction = new Action0() { - @Override - public void call() { - assertTrue(SwingUtilities.isEventDispatchThread()); - thirdStepStart.call(); - scheduler.schedule(secondAction); - thirdStepEnd.call(); - } - }; - - InOrder inOrder = inOrder(firstStepStart, firstStepEnd, secondStepStart, secondStepEnd, thirdStepStart, thirdStepEnd); - - scheduler.schedule(thirdAction); - waitForEmptyEventQueue(); - - 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(); - } - - private static void waitForEmptyEventQueue() throws Exception { - EventQueue.invokeAndWait(new Runnable() { - @Override - public void run() { - // nothing to do, we're just waiting here for the event queue to be emptied - } - }); - } - } -} diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/observables/SwingObservable.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/observables/SwingObservable.java index 12200e7054..ab22538e2e 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/observables/SwingObservable.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/observables/SwingObservable.java @@ -25,13 +25,14 @@ import java.util.Set; import javax.swing.AbstractButton; +import javax.swing.SwingUtilities; import rx.Observable; +import rx.functions.Func1; import rx.swing.sources.AbstractButtonSource; import rx.swing.sources.ComponentEventSource; import rx.swing.sources.KeyEventSource; import rx.swing.sources.MouseEventSource; -import rx.util.functions.Func1; /** * Allows creating observables from various sources specific to Swing. @@ -140,4 +141,15 @@ public static Observable fromComponentEvents(Component component public static Observable fromResizing(Component component) { return ComponentEventSource.fromResizing(component); } + + /** + * Check if the current thead is the event dispatch thread. + * + * @throws IllegalStateException if the current thread is not the event dispatch thread. + */ + public static void assertEventDispatchThread() { + if (!SwingUtilities.isEventDispatchThread()) { + throw new IllegalStateException("Need to run in the event dispatch thread, but was " + Thread.currentThread()); + } + } } diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/schedulers/SwingScheduler.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/schedulers/SwingScheduler.java new file mode 100644 index 0000000000..1854db762b --- /dev/null +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/schedulers/SwingScheduler.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.schedulers; + +import java.awt.EventQueue; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import javax.swing.Timer; + +import rx.Scheduler; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.Subscriptions; + +/** + * Executes work on the Swing UI thread. + * This scheduler should only be used with actions that execute quickly. + */ +public final class SwingScheduler extends Scheduler { + private static final SwingScheduler INSTANCE = new SwingScheduler(); + + public static SwingScheduler getInstance() { + return INSTANCE; + } + + /* package for unit test */SwingScheduler() { + } + + @Override + public Subscription schedule(Action1 action) { + InnerSwingScheduler inner = new InnerSwingScheduler(); + inner.schedule(action); + return inner; + } + + @Override + public Subscription schedule(Action1 action, long delayTime, TimeUnit unit) { + long delay = unit.toMillis(delayTime); + assertThatTheDelayIsValidForTheSwingTimer(delay); + InnerSwingScheduler inner = new InnerSwingScheduler(); + inner.schedule(action, delayTime, unit); + return inner; + } + + private static class InnerSwingScheduler extends Inner { + + private final Inner _inner = this; + private final CompositeSubscription innerSubscription = new CompositeSubscription(); + + @Override + public void unsubscribe() { + innerSubscription.unsubscribe(); + } + + @Override + public boolean isUnsubscribed() { + return innerSubscription.isUnsubscribed(); + } + + @Override + public void schedule(final Action1 action, long delayTime, TimeUnit unit) { + final AtomicReference sub = new AtomicReference(); + long delay = unit.toMillis(delayTime); + assertThatTheDelayIsValidForTheSwingTimer(delay); + + final AtomicReference sf = new AtomicReference(); + class ExecuteOnceAction implements ActionListener { + private Timer timer; + + private void setTimer(Timer timer) { + this.timer = timer; + } + + @Override + public void actionPerformed(ActionEvent e) { + timer.stop(); + if (innerSubscription.isUnsubscribed()) { + return; + } + action.call(_inner); + Subscription s = sf.get(); + if (s != null) { + innerSubscription.remove(s); + } + } + } + + ExecuteOnceAction executeOnce = new ExecuteOnceAction(); + final Timer timer = new Timer((int) delay, executeOnce); + executeOnce.setTimer(timer); + timer.start(); + + Subscription s = Subscriptions.create(new Action0() { + @Override + public void call() { + timer.stop(); + + Subscription subscription = sub.get(); + if (subscription != null) { + subscription.unsubscribe(); + } + } + }); + + sf.set(s); + innerSubscription.add(s); + } + + @Override + public void schedule(final Action1 action) { + final AtomicReference sub = new AtomicReference(); + + final AtomicReference sf = new AtomicReference(); + EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + if (innerSubscription.isUnsubscribed()) { + return; + } + action.call(_inner); + Subscription s = sf.get(); + if (s != null) { + innerSubscription.remove(s); + } + } + }); + + Subscription s = Subscriptions.create(new Action0() { + @Override + public void call() { + Subscription subscription = sub.get(); + if (subscription != null) { + subscription.unsubscribe(); + } + } + }); + + sf.set(s); + innerSubscription.add(s); + } + + } + + private static void assertThatTheDelayIsValidForTheSwingTimer(long delay) { + if (delay < 0 || delay > Integer.MAX_VALUE) { + throw new IllegalArgumentException(String.format("The swing timer only accepts non-negative delays up to %d milliseconds.", Integer.MAX_VALUE)); + } + } +} diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/subscriptions/SwingSubscriptions.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/subscriptions/SwingSubscriptions.java new file mode 100644 index 0000000000..ce3c18e2d8 --- /dev/null +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/subscriptions/SwingSubscriptions.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.subscriptions; + +import javax.swing.SwingUtilities; + +import rx.Scheduler.Inner; +import rx.Subscription; +import rx.schedulers.SwingScheduler; +import rx.functions.Action0; +import rx.functions.Action1; + +public final class SwingSubscriptions { + + private SwingSubscriptions() { + // no instance + } + + /** + * Create an Subscription that always runs unsubscribe in the event dispatch thread. + * + * @param unsubscribe + * @return an Subscription that always runs unsubscribe in the event dispatch thread. + */ + public static Subscription unsubscribeInEventDispatchThread(final Action0 unsubscribe) { + return Subscriptions.create(new Action0() { + @Override + public void call() { + if (SwingUtilities.isEventDispatchThread()) { + unsubscribe.call(); + } else { + SwingScheduler.getInstance().schedule(new Action1() { + @Override + public void call(Inner inner) { + unsubscribe.call(); + } + }); + } + } + }); + } +} diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java index ef5c7db50a..037c9fa9e0 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java @@ -15,7 +15,10 @@ */ package rx.swing.sources; -import static org.mockito.Mockito.*; +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.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -26,12 +29,13 @@ import org.mockito.Matchers; import rx.Observable; -import rx.Observable.OnSubscribeFunc; -import rx.Observer; +import rx.Observable.OnSubscribe; +import rx.Subscriber; import rx.Subscription; -import rx.subscriptions.Subscriptions; -import rx.util.functions.Action0; -import rx.util.functions.Action1; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.observables.SwingObservable; +import rx.subscriptions.SwingSubscriptions; public enum AbstractButtonSource { ; // no instances @@ -39,63 +43,70 @@ public enum AbstractButtonSource { ; // no instances * @see rx.observables.SwingObservable#fromButtonAction */ public static Observable fromActionOf(final AbstractButton button) { - return Observable.create(new OnSubscribeFunc() { + return Observable.create(new OnSubscribe() { @Override - public Subscription onSubscribe(final Observer observer) { + public void call(final Subscriber subscriber) { + SwingObservable.assertEventDispatchThread(); final ActionListener listener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - observer.onNext(e); + subscriber.onNext(e); } }; button.addActionListener(listener); - - return Subscriptions.create(new Action0() { + subscriber.add(SwingSubscriptions.unsubscribeInEventDispatchThread(new Action0() { @Override public void call() { button.removeActionListener(listener); } - }); + })); } }); } public static class UnitTest { @Test - public void testObservingActionEvents() { - @SuppressWarnings("unchecked") - Action1 action = mock(Action1.class); - @SuppressWarnings("unchecked") - Action1 error = mock(Action1.class); - Action0 complete = mock(Action0.class); - - final ActionEvent event = new ActionEvent(this, 1, "command"); - - @SuppressWarnings("serial") - class TestButton extends AbstractButton { - void testAction() { - fireActionPerformed(event); + public void testObservingActionEvents() throws Throwable { + SwingTestHelper.create().runInEventDispatchThread(new Action0() { + + @Override + public void call() { + @SuppressWarnings("unchecked") + Action1 action = mock(Action1.class); + @SuppressWarnings("unchecked") + Action1 error = mock(Action1.class); + Action0 complete = mock(Action0.class); + + final ActionEvent event = new ActionEvent(this, 1, "command"); + + @SuppressWarnings("serial") + class TestButton extends AbstractButton { + void testAction() { + fireActionPerformed(event); + } + } + + TestButton button = new TestButton(); + Subscription sub = fromActionOf(button).subscribe(action, error, complete); + + verify(action, never()).call(Matchers. any()); + verify(error, never()).call(Matchers. any()); + verify(complete, never()).call(); + + button.testAction(); + verify(action, times(1)).call(Matchers. any()); + + button.testAction(); + verify(action, times(2)).call(Matchers. any()); + + sub.unsubscribe(); + button.testAction(); + verify(action, times(2)).call(Matchers. any()); + verify(error, never()).call(Matchers. any()); + verify(complete, never()).call(); } - } - - TestButton button = new TestButton(); - Subscription sub = fromActionOf(button).subscribe(action, error, complete); - - verify(action, never()).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); - verify(complete, never()).call(); - - button.testAction(); - verify(action, times(1)).call(Matchers.any()); - - button.testAction(); - verify(action, times(2)).call(Matchers.any()); - - sub.unsubscribe(); - button.testAction(); - verify(action, times(2)).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); - verify(complete, never()).call(); + + }).awaitTerminal(); } } } diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/ComponentEventSource.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/ComponentEventSource.java index 189b02b7a9..eb2b3a5c41 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/ComponentEventSource.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/ComponentEventSource.java @@ -15,7 +15,7 @@ */ package rx.swing.sources; -import static rx.swing.sources.ComponentEventSource.Predicate.*; +import static rx.swing.sources.ComponentEventSource.Predicate.RESIZED; import java.awt.Component; import java.awt.Dimension; @@ -23,13 +23,12 @@ import java.awt.event.ComponentListener; import rx.Observable; -import rx.Observable.OnSubscribeFunc; -import rx.Observer; -import rx.Subscription; +import rx.Observable.OnSubscribe; +import rx.Subscriber; +import rx.functions.Action0; +import rx.functions.Func1; import rx.observables.SwingObservable; -import rx.subscriptions.Subscriptions; -import rx.util.functions.Action0; -import rx.util.functions.Func1; +import rx.subscriptions.SwingSubscriptions; public enum ComponentEventSource { ; // no instances @@ -37,38 +36,38 @@ public enum ComponentEventSource { ; // no instances * @see rx.observables.SwingObservable#fromComponentEvents */ public static Observable fromComponentEventsOf(final Component component) { - return Observable.create(new OnSubscribeFunc() { + return Observable.create(new OnSubscribe() { @Override - public Subscription onSubscribe(final Observer observer) { + public void call(final Subscriber subscriber) { + SwingObservable.assertEventDispatchThread(); final ComponentListener listener = new ComponentListener() { @Override public void componentHidden(ComponentEvent event) { - observer.onNext(event); + subscriber.onNext(event); } @Override public void componentMoved(ComponentEvent event) { - observer.onNext(event); + subscriber.onNext(event); } @Override public void componentResized(ComponentEvent event) { - observer.onNext(event); + subscriber.onNext(event); } @Override public void componentShown(ComponentEvent event) { - observer.onNext(event); + subscriber.onNext(event); } }; component.addComponentListener(listener); - - return Subscriptions.create(new Action0() { + subscriber.add(SwingSubscriptions.unsubscribeInEventDispatchThread(new Action0() { @Override public void call() { component.removeComponentListener(listener); } - }); + })); } }); } @@ -88,7 +87,7 @@ public Dimension call(ComponentEvent event) { /** * Predicates that help with filtering observables for specific component events. */ - public enum Predicate implements rx.util.functions.Func1 { + public enum Predicate implements rx.functions.Func1 { RESIZED(ComponentEvent.COMPONENT_RESIZED), HIDDEN(ComponentEvent.COMPONENT_HIDDEN), MOVED(ComponentEvent.COMPONENT_MOVED), diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java index e3b37a7868..1b5107de08 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java @@ -15,8 +15,12 @@ */ package rx.swing.sources; -import static java.util.Arrays.*; -import static org.mockito.Mockito.*; +import static java.util.Arrays.asList; +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.awt.Component; import java.awt.event.KeyEvent; @@ -32,14 +36,15 @@ import org.mockito.Matchers; import rx.Observable; -import rx.Observable.OnSubscribeFunc; -import rx.Observer; +import rx.Observable.OnSubscribe; +import rx.Subscriber; import rx.Subscription; -import rx.subscriptions.Subscriptions; -import rx.util.functions.Action0; -import rx.util.functions.Action1; -import rx.util.functions.Func1; -import rx.util.functions.Func2; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.observables.SwingObservable; +import rx.subscriptions.SwingSubscriptions; public enum KeyEventSource { ; // no instances @@ -47,33 +52,34 @@ public enum KeyEventSource { ; // no instances * @see rx.observables.SwingObservable#fromKeyEvents(Component) */ public static Observable fromKeyEventsOf(final Component component) { - return Observable.create(new OnSubscribeFunc() { + return Observable.create(new OnSubscribe() { @Override - public Subscription onSubscribe(final Observer observer) { + public void call(final Subscriber subscriber) { + SwingObservable.assertEventDispatchThread(); final KeyListener listener = new KeyListener() { @Override public void keyPressed(KeyEvent event) { - observer.onNext(event); + subscriber.onNext(event); } - + @Override public void keyReleased(KeyEvent event) { - observer.onNext(event); + subscriber.onNext(event); } - + @Override public void keyTyped(KeyEvent event) { - observer.onNext(event); + subscriber.onNext(event); } }; component.addKeyListener(listener); - - return Subscriptions.create(new Action0() { + + subscriber.add(SwingSubscriptions.unsubscribeInEventDispatchThread(new Action0() { @Override public void call() { component.removeKeyListener(listener); } - }); + })); } }); } @@ -115,73 +121,87 @@ public static class UnitTest { private Component comp = new JPanel(); @Test - public void testObservingKeyEvents() { - @SuppressWarnings("unchecked") - Action1 action = mock(Action1.class); - @SuppressWarnings("unchecked") - Action1 error = mock(Action1.class); - Action0 complete = mock(Action0.class); - - final KeyEvent event = mock(KeyEvent.class); - - Subscription sub = fromKeyEventsOf(comp).subscribe(action, error, complete); - - verify(action, never()).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); - verify(complete, never()).call(); - - fireKeyEvent(event); - verify(action, times(1)).call(Matchers.any()); - - fireKeyEvent(event); - verify(action, times(2)).call(Matchers.any()); - - sub.unsubscribe(); - fireKeyEvent(event); - verify(action, times(2)).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); - verify(complete, never()).call(); + public void testObservingKeyEvents() throws Throwable { + SwingTestHelper.create().runInEventDispatchThread(new Action0(){ + + @Override + public void call() { + @SuppressWarnings("unchecked") + Action1 action = mock(Action1.class); + @SuppressWarnings("unchecked") + Action1 error = mock(Action1.class); + Action0 complete = mock(Action0.class); + + final KeyEvent event = mock(KeyEvent.class); + + Subscription sub = fromKeyEventsOf(comp).subscribe(action, error, complete); + + verify(action, never()).call(Matchers. any()); + verify(error, never()).call(Matchers. any()); + verify(complete, never()).call(); + + fireKeyEvent(event); + verify(action, times(1)).call(Matchers. any()); + + fireKeyEvent(event); + verify(action, times(2)).call(Matchers. any()); + + sub.unsubscribe(); + fireKeyEvent(event); + verify(action, times(2)).call(Matchers. any()); + verify(error, never()).call(Matchers. any()); + verify(complete, never()).call(); + } + + }).awaitTerminal(); } - + @Test - public void testObservingPressedKeys() { - @SuppressWarnings("unchecked") - Action1> action = mock(Action1.class); - @SuppressWarnings("unchecked") - Action1 error = mock(Action1.class); - Action0 complete = mock(Action0.class); - - Subscription sub = currentlyPressedKeysOf(comp).subscribe(action, error, complete); - - InOrder inOrder = inOrder(action); - inOrder.verify(action, times(1)).call(Collections.emptySet()); - verify(error, never()).call(Matchers.any()); - verify(complete, never()).call(); - - fireKeyEvent(keyEvent(1, KeyEvent.KEY_PRESSED)); - inOrder.verify(action, times(1)).call(new HashSet(asList(1))); - verify(error, never()).call(Matchers.any()); - verify(complete, never()).call(); - - fireKeyEvent(keyEvent(2, KeyEvent.KEY_PRESSED)); - fireKeyEvent(keyEvent(KeyEvent.VK_UNDEFINED, KeyEvent.KEY_TYPED)); - inOrder.verify(action, times(1)).call(new HashSet(asList(1, 2))); - - fireKeyEvent(keyEvent(2, KeyEvent.KEY_RELEASED)); - inOrder.verify(action, times(1)).call(new HashSet(asList(1))); - - fireKeyEvent(keyEvent(3, KeyEvent.KEY_RELEASED)); - inOrder.verify(action, times(1)).call(new HashSet(asList(1))); - - fireKeyEvent(keyEvent(1, KeyEvent.KEY_RELEASED)); - inOrder.verify(action, times(1)).call(Collections.emptySet()); - - sub.unsubscribe(); - - fireKeyEvent(keyEvent(1, KeyEvent.KEY_PRESSED)); - inOrder.verify(action, never()).call(Matchers.>any()); - verify(error, never()).call(Matchers.any()); - verify(complete, never()).call(); + public void testObservingPressedKeys() throws Throwable { + SwingTestHelper.create().runInEventDispatchThread(new Action0() { + + @Override + public void call() { + @SuppressWarnings("unchecked") + Action1> action = mock(Action1.class); + @SuppressWarnings("unchecked") + Action1 error = mock(Action1.class); + Action0 complete = mock(Action0.class); + + Subscription sub = currentlyPressedKeysOf(comp).subscribe(action, error, complete); + + InOrder inOrder = inOrder(action); + inOrder.verify(action, times(1)).call(Collections. emptySet()); + verify(error, never()).call(Matchers. any()); + verify(complete, never()).call(); + + fireKeyEvent(keyEvent(1, KeyEvent.KEY_PRESSED)); + inOrder.verify(action, times(1)).call(new HashSet(asList(1))); + verify(error, never()).call(Matchers. any()); + verify(complete, never()).call(); + + fireKeyEvent(keyEvent(2, KeyEvent.KEY_PRESSED)); + fireKeyEvent(keyEvent(KeyEvent.VK_UNDEFINED, KeyEvent.KEY_TYPED)); + inOrder.verify(action, times(1)).call(new HashSet(asList(1, 2))); + + fireKeyEvent(keyEvent(2, KeyEvent.KEY_RELEASED)); + inOrder.verify(action, times(1)).call(new HashSet(asList(1))); + + fireKeyEvent(keyEvent(3, KeyEvent.KEY_RELEASED)); + inOrder.verify(action, times(1)).call(new HashSet(asList(1))); + + fireKeyEvent(keyEvent(1, KeyEvent.KEY_RELEASED)); + inOrder.verify(action, times(1)).call(Collections. emptySet()); + + sub.unsubscribe(); + + fireKeyEvent(keyEvent(1, KeyEvent.KEY_PRESSED)); + inOrder.verify(action, never()).call(Matchers.> any()); + verify(error, never()).call(Matchers. any()); + verify(complete, never()).call(); + } + + }).awaitTerminal(); } private KeyEvent keyEvent(int keyCode, int id) { diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/MouseEventSource.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/MouseEventSource.java index f6589da054..d14ef8d471 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/MouseEventSource.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/MouseEventSource.java @@ -15,7 +15,11 @@ */ package rx.swing.sources; -import static org.mockito.Mockito.*; +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.awt.Component; import java.awt.Point; @@ -30,169 +34,156 @@ import org.mockito.Matchers; import rx.Observable; -import rx.Observable.OnSubscribeFunc; -import rx.Observer; +import rx.Observable.OnSubscribe; +import rx.Subscriber; import rx.Subscription; -import rx.subscriptions.Subscriptions; -import rx.util.functions.Action0; -import rx.util.functions.Action1; -import rx.util.functions.Func1; -import rx.util.functions.Func2; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func2; +import rx.observables.SwingObservable; +import rx.subscriptions.SwingSubscriptions; -public enum MouseEventSource { ; // no instances +public enum MouseEventSource { + ; // no instances /** * @see rx.observables.SwingObservable#fromMouseEvents */ public static Observable fromMouseEventsOf(final Component component) { - return Observable.create(new OnSubscribeFunc() { + return Observable.create(new OnSubscribe() { @Override - public Subscription onSubscribe(final Observer observer) { + public void call(final Subscriber subscriber) { + SwingObservable.assertEventDispatchThread(); final MouseListener listener = new MouseListener() { @Override public void mouseClicked(MouseEvent event) { - observer.onNext(event); + subscriber.onNext(event); } @Override public void mousePressed(MouseEvent event) { - observer.onNext(event); + subscriber.onNext(event); } @Override public void mouseReleased(MouseEvent event) { - observer.onNext(event); + subscriber.onNext(event); } @Override public void mouseEntered(MouseEvent event) { - observer.onNext(event); + subscriber.onNext(event); } @Override public void mouseExited(MouseEvent event) { - observer.onNext(event); + subscriber.onNext(event); } }; component.addMouseListener(listener); - - return Subscriptions.create(new Action0() { + + subscriber.add(SwingSubscriptions.unsubscribeInEventDispatchThread(new Action0() { @Override public void call() { component.removeMouseListener(listener); } - }); + })); } }); } - + /** * @see rx.observables.SwingObservable#fromMouseMotionEvents */ public static Observable fromMouseMotionEventsOf(final Component component) { - return Observable.create(new OnSubscribeFunc() { + return Observable.create(new OnSubscribe() { @Override - public Subscription onSubscribe(final Observer observer) { + public void call(final Subscriber subscriber) { + SwingObservable.assertEventDispatchThread(); final MouseMotionListener listener = new MouseMotionListener() { @Override public void mouseDragged(MouseEvent event) { - observer.onNext(event); + subscriber.onNext(event); } @Override public void mouseMoved(MouseEvent event) { - observer.onNext(event); + subscriber.onNext(event); } }; component.addMouseMotionListener(listener); - - return Subscriptions.create(new Action0() { + + subscriber.add(SwingSubscriptions.unsubscribeInEventDispatchThread(new Action0() { @Override public void call() { component.removeMouseMotionListener(listener); } - }); + })); } }); } - + /** * @see rx.observables.SwingObservable#fromRelativeMouseMotion */ public static Observable fromRelativeMouseMotion(final Component component) { - class OldAndRelative { - public final Point old; - public final Point relative; - - private OldAndRelative(Point old, Point relative) { - this.old = old; - this.relative = relative; - } - } - - class Relativize implements Func2 { + final Observable events = fromMouseMotionEventsOf(component); + return Observable.zip(events, events.skip(1), new Func2() { @Override - public OldAndRelative call(OldAndRelative last, MouseEvent event) { - Point current = new Point(event.getX(), event.getY()); - Point relative = new Point(current.x - last.old.x, current.y - last.old.y); - return new OldAndRelative(current, relative); + public Point call(MouseEvent ev1, MouseEvent ev2) { + return new Point(ev2.getX() - ev1.getX(), ev2.getY() - ev1.getY()); } - } - - class OnlyRelative implements Func1 { - @Override - public Point call(OldAndRelative oar) { - return oar.relative; - } - } - - return fromMouseMotionEventsOf(component) - .scan(new OldAndRelative(new Point(0, 0), new Point(0, 0)), new Relativize()) - .map(new OnlyRelative()) - .skip(2); // skip the useless initial value and the invalid first computation + }); } - + public static class UnitTest { private Component comp = new JPanel(); - + @Test - public void testRelativeMouseMotion() { - @SuppressWarnings("unchecked") - Action1 action = mock(Action1.class); - @SuppressWarnings("unchecked") - Action1 error = mock(Action1.class); - Action0 complete = mock(Action0.class); - - Subscription sub = fromRelativeMouseMotion(comp).subscribe(action, error, complete); - - InOrder inOrder = inOrder(action); - - verify(action, never()).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); - verify(complete, never()).call(); - - fireMouseEvent(mouseEvent(0, 0)); - verify(action, never()).call(Matchers.any()); - - fireMouseEvent(mouseEvent(10, -5)); - inOrder.verify(action, times(1)).call(new Point(10, -5)); - - fireMouseEvent(mouseEvent(6, 10)); - inOrder.verify(action, times(1)).call(new Point(-4, 15)); - - sub.unsubscribe(); - fireMouseEvent(mouseEvent(0, 0)); - inOrder.verify(action, never()).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); - verify(complete, never()).call(); + public void testRelativeMouseMotion() throws Throwable { + SwingTestHelper.create().runInEventDispatchThread(new Action0() { + + @Override + public void call() { + @SuppressWarnings("unchecked") + Action1 action = mock(Action1.class); + @SuppressWarnings("unchecked") + Action1 error = mock(Action1.class); + Action0 complete = mock(Action0.class); + + Subscription sub = fromRelativeMouseMotion(comp).subscribe(action, error, complete); + + InOrder inOrder = inOrder(action); + + verify(action, never()).call(Matchers. any()); + verify(error, never()).call(Matchers. any()); + verify(complete, never()).call(); + + fireMouseEvent(mouseEvent(0, 0)); + verify(action, never()).call(Matchers. any()); + + fireMouseEvent(mouseEvent(10, -5)); + inOrder.verify(action, times(1)).call(new Point(10, -5)); + + fireMouseEvent(mouseEvent(6, 10)); + inOrder.verify(action, times(1)).call(new Point(-4, 15)); + + sub.unsubscribe(); + fireMouseEvent(mouseEvent(0, 0)); + inOrder.verify(action, never()).call(Matchers. any()); + verify(error, never()).call(Matchers. any()); + verify(complete, never()).call(); + } + + }).awaitTerminal(); } - + private MouseEvent mouseEvent(int x, int y) { return new MouseEvent(comp, MouseEvent.MOUSE_MOVED, 1L, 0, x, y, 0, false); } - + private void fireMouseEvent(MouseEvent event) { - for (MouseMotionListener listener: comp.getMouseMotionListeners()) { + for (MouseMotionListener listener : comp.getMouseMotionListeners()) { listener.mouseMoved(event); } } diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/SwingTestHelper.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/SwingTestHelper.java new file mode 100644 index 0000000000..1b1053ec75 --- /dev/null +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/SwingTestHelper.java @@ -0,0 +1,68 @@ +/** + * 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.swing.sources; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import rx.Scheduler.Inner; +import rx.schedulers.SwingScheduler; +import rx.functions.Action0; +import rx.functions.Action1; + +/* package-private */ final class SwingTestHelper { // only for test + + private final CountDownLatch latch = new CountDownLatch(1); + private volatile Throwable error; + + private SwingTestHelper() { + } + + public static SwingTestHelper create() { + return new SwingTestHelper(); + } + + public SwingTestHelper runInEventDispatchThread(final Action0 action) { + SwingScheduler.getInstance().schedule(new Action1() { + + @Override + public void call(Inner inner) { + try { + action.call(); + } catch (Throwable e) { + error = e; + } + latch.countDown(); + } + }); + return this; + } + + public void awaitTerminal() throws Throwable { + latch.await(); + if (error != null) { + throw error; + } + } + + public void awaitTerminal(long timeout, TimeUnit unit) throws Throwable { + latch.await(timeout, unit); + if (error != null) { + throw error; + } + } + +} diff --git a/rxjava-contrib/rxjava-swing/src/test/java/rx/schedulers/SwingSchedulerTest.java b/rxjava-contrib/rxjava-swing/src/test/java/rx/schedulers/SwingSchedulerTest.java new file mode 100644 index 0000000000..afe6951e5b --- /dev/null +++ b/rxjava-contrib/rxjava-swing/src/test/java/rx/schedulers/SwingSchedulerTest.java @@ -0,0 +1,156 @@ +/** + * 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.schedulers; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.awt.EventQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.swing.SwingUtilities; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.InOrder; + +import rx.Scheduler.Inner; +import rx.Subscription; +import rx.functions.Action1; + +/** + * Executes work on the Swing UI thread. + * This scheduler should only be used with actions that execute quickly. + */ +public final class SwingSchedulerTest { + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Test + public void testInvalidDelayValues() { + final SwingScheduler scheduler = new SwingScheduler(); + final Action1 action = mock(Action1.class); + + exception.expect(IllegalArgumentException.class); + scheduler.schedulePeriodically(action, -1L, 100L, TimeUnit.SECONDS); + + exception.expect(IllegalArgumentException.class); + scheduler.schedulePeriodically(action, 100L, -1L, TimeUnit.SECONDS); + + exception.expect(IllegalArgumentException.class); + scheduler.schedulePeriodically(action, 1L + Integer.MAX_VALUE, 100L, TimeUnit.MILLISECONDS); + + exception.expect(IllegalArgumentException.class); + scheduler.schedulePeriodically(action, 100L, 1L + Integer.MAX_VALUE / 1000, TimeUnit.SECONDS); + } + + @Test + public void testPeriodicScheduling() throws Exception { + final SwingScheduler scheduler = new SwingScheduler(); + + final CountDownLatch latch = new CountDownLatch(4); + + final Action1 innerAction = mock(Action1.class); + final Action1 action = new Action1() { + @Override + public void call(Inner inner) { + try { + innerAction.call(inner); + assertTrue(SwingUtilities.isEventDispatchThread()); + } finally { + latch.countDown(); + } + } + }; + + Subscription sub = scheduler.schedulePeriodically(action, 50, 200, TimeUnit.MILLISECONDS); + + if (!latch.await(5000, TimeUnit.MILLISECONDS)) { + fail("timed out waiting for tasks to execute"); + } + + sub.unsubscribe(); + waitForEmptyEventQueue(); + verify(innerAction, times(4)).call(any(Inner.class)); + } + + @Test + public void testNestedActions() throws Exception { + final SwingScheduler scheduler = new SwingScheduler(); + + final Action1 firstStepStart = mock(Action1.class); + final Action1 firstStepEnd = mock(Action1.class); + + final Action1 secondStepStart = mock(Action1.class); + final Action1 secondStepEnd = mock(Action1.class); + + final Action1 thirdStepStart = mock(Action1.class); + final Action1 thirdStepEnd = mock(Action1.class); + + final Action1 firstAction = new Action1() { + @Override + public void call(Inner inner) { + assertTrue(SwingUtilities.isEventDispatchThread()); + firstStepStart.call(inner); + firstStepEnd.call(inner); + } + }; + final Action1 secondAction = new Action1() { + @Override + public void call(Inner inner) { + assertTrue(SwingUtilities.isEventDispatchThread()); + secondStepStart.call(inner); + scheduler.schedule(firstAction); + secondStepEnd.call(inner); + } + }; + final Action1 thirdAction = new Action1() { + @Override + public void call(Inner inner) { + assertTrue(SwingUtilities.isEventDispatchThread()); + thirdStepStart.call(inner); + scheduler.schedule(secondAction); + thirdStepEnd.call(inner); + } + }; + + InOrder inOrder = inOrder(firstStepStart, firstStepEnd, secondStepStart, secondStepEnd, thirdStepStart, thirdStepEnd); + + scheduler.schedule(thirdAction); + waitForEmptyEventQueue(); + + inOrder.verify(thirdStepStart, times(1)).call(any(Inner.class)); + inOrder.verify(thirdStepEnd, times(1)).call(any(Inner.class)); + inOrder.verify(secondStepStart, times(1)).call(any(Inner.class)); + inOrder.verify(secondStepEnd, times(1)).call(any(Inner.class)); + inOrder.verify(firstStepStart, times(1)).call(any(Inner.class)); + inOrder.verify(firstStepEnd, times(1)).call(any(Inner.class)); + } + + private static void waitForEmptyEventQueue() throws Exception { + EventQueue.invokeAndWait(new Runnable() { + @Override + public void run() { + // nothing to do, we're just waiting here for the event queue to be emptied + } + }); + } + +} diff --git a/rxjava-core/build.gradle b/rxjava-core/build.gradle index 1732de3017..8cd94496a6 100644 --- a/rxjava-core/build.gradle +++ b/rxjava-core/build.gradle @@ -1,5 +1,6 @@ apply plugin: 'maven' apply plugin: 'osgi' +apply plugin:'application' sourceCompatibility = JavaVersion.VERSION_1_6 targetCompatibility = JavaVersion.VERSION_1_6 @@ -31,3 +32,9 @@ jar { } } +task time(type:JavaExec) { + classpath = sourceSets.perf.runtimeClasspath + group 'Application' + description 'Execute the calipser benchmark timing of Rx' + main 'rx.operators.ObservableBenchmark' +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/Notification.java b/rxjava-core/src/main/java/rx/Notification.java index 996e217d50..df3e22b3a2 100644 --- a/rxjava-core/src/main/java/rx/Notification.java +++ b/rxjava-core/src/main/java/rx/Notification.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,12 +26,39 @@ public class Notification { private final Throwable throwable; private final T value; + private static final Notification ON_COMPLETED = new Notification(Kind.OnCompleted, null, null); + + public static Notification createOnNext(T t) { + return new Notification(Kind.OnNext, t, null); + } + + public static Notification createOnError(Throwable e) { + return new Notification(Kind.OnError, null, e); + } + + @SuppressWarnings("unchecked") + public static Notification createOnCompleted() { + return (Notification) ON_COMPLETED; + } + + @SuppressWarnings("unchecked") + public static Notification createOnCompleted(Class type) { + return (Notification) ON_COMPLETED; + } + + private Notification(Kind kind, T value, Throwable e) { + this.value = value; + this.throwable = e; + this.kind = kind; + } + /** * A constructor used to represent an onNext notification. * * @param value * The data passed to the onNext method. */ + @Deprecated public Notification(T value) { this.value = value; this.throwable = null; @@ -43,7 +70,9 @@ public Notification(T value) { * * @param exception * The exception passed to the onError notification. + * @deprecated Because type Throwable can't disambiguate the constructors if both onNext and onError are type "Throwable" */ + @Deprecated public Notification(Throwable exception) { this.throwable = exception; this.value = null; @@ -53,6 +82,7 @@ public Notification(Throwable exception) { /** * A constructor used to represent an onCompleted notification. */ + @Deprecated public Notification() { this.throwable = null; this.value = null; diff --git a/rxjava-core/src/main/java/rx/Observable.java b/rxjava-core/src/main/java/rx/Observable.java index cf92fb12cd..2224fa3c8e 100644 --- a/rxjava-core/src/main/java/rx/Observable.java +++ b/rxjava-core/src/main/java/rx/Observable.java @@ -1,21 +1,18 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 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. + * 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 rx.util.functions.Functions.*; +import static rx.functions.Functions.*; import java.util.ArrayList; import java.util.Arrays; @@ -27,6246 +24,8571 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import rx.concurrency.Schedulers; +import rx.exceptions.Exceptions; +import rx.exceptions.OnErrorThrowable; +import rx.exceptions.OnErrorNotImplementedException; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Action2; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.functions.Func3; +import rx.functions.Func4; +import rx.functions.Func5; +import rx.functions.Func6; +import rx.functions.Func7; +import rx.functions.Func8; +import rx.functions.Func9; +import rx.functions.FuncN; +import rx.functions.Function; +import rx.functions.Functions; import rx.joins.Pattern2; import rx.joins.Plan0; import rx.observables.BlockingObservable; import rx.observables.ConnectableObservable; import rx.observables.GroupedObservable; +import rx.observers.SafeSubscriber; +import rx.operators.OnSubscribeFromIterable; +import rx.operators.OnSubscribeRange; import rx.operators.OperationAll; import rx.operators.OperationAmb; import rx.operators.OperationAny; +import rx.operators.OperationAsObservable; 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.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.OperationFlatMap; import rx.operators.OperationGroupByUntil; import rx.operators.OperationGroupJoin; 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.OperationMergeMaxConcurrent; 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.OperationReplay; import rx.operators.OperationRetry; import rx.operators.OperationSample; -import rx.operators.OperationScan; import rx.operators.OperationSequenceEqual; +import rx.operators.OperationSingle; import rx.operators.OperationSkip; import rx.operators.OperationSkipLast; import rx.operators.OperationSkipUntil; import rx.operators.OperationSkipWhile; -import rx.operators.OperationSubscribeOn; import rx.operators.OperationSum; import rx.operators.OperationSwitch; import rx.operators.OperationSynchronize; -import rx.operators.OperationTake; import rx.operators.OperationTakeLast; +import rx.operators.OperationTakeTimed; import rx.operators.OperationTakeUntil; import rx.operators.OperationTakeWhile; import rx.operators.OperationThrottleFirst; import rx.operators.OperationTimeInterval; -import rx.operators.OperationTimeout; -import rx.operators.OperationTimestamp; +import rx.operators.OperationTimer; 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; -import rx.operators.SafeObserver; -import rx.plugins.RxJavaErrorHandler; +import rx.operators.OperatorCast; +import rx.operators.OperatorDoOnEach; +import rx.operators.OperatorFilter; +import rx.operators.OperatorGroupBy; +import rx.operators.OperatorMap; +import rx.operators.OperatorMerge; +import rx.operators.OperatorObserveOn; +import rx.operators.OperatorOnErrorResumeNextViaFunction; +import rx.operators.OperatorOnErrorFlatMap; +import rx.operators.OperatorParallel; +import rx.operators.OperatorRepeat; +import rx.operators.OperatorScan; +import rx.operators.OperatorSubscribeOn; +import rx.operators.OperatorTake; +import rx.operators.OperatorTimeout; +import rx.operators.OperatorTimeoutWithSelector; +import rx.operators.OperatorTimestamp; +import rx.operators.OperatorToObservableList; +import rx.operators.OperatorToObservableSortedList; +import rx.operators.OperatorUnsubscribeOn; +import rx.operators.OperatorZip; +import rx.operators.OperatorZipIterable; import rx.plugins.RxJavaObservableExecutionHook; import rx.plugins.RxJavaPlugins; +import rx.schedulers.Schedulers; +import rx.schedulers.TimeInterval; +import rx.schedulers.Timestamped; import rx.subjects.AsyncSubject; +import rx.subjects.BehaviorSubject; import rx.subjects.PublishSubject; import rx.subjects.ReplaySubject; import rx.subjects.Subject; import rx.subscriptions.Subscriptions; -import rx.util.OnErrorNotImplementedException; -import rx.util.Range; -import rx.util.TimeInterval; -import rx.util.Timestamped; -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 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; -import rx.util.functions.Functions; /** - * The Observable interface that implements the Reactive Pattern. + * The Observable class that implements the Reactive Pattern. *

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

- * The documentation for this interface makes use of marble diagrams. The - * following legend explains these diagrams: + * The documentation for this class 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 the type of the item emitted by the Observable + * @param + * the type of the items 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)} - * - * @param - */ - public static interface OnSubscribeFunc extends Function { - - public Subscription onSubscribe(Observer t1); - - } + final OnSubscribe f; /** * 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(OnSubscribe)} 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 f + * {@link OnSubscribe} to be executed when {@link #subscribe(Subscriber)} is called */ - protected Observable(OnSubscribeFunc onSubscribe) { - this.onSubscribe = onSubscribe; + protected Observable(OnSubscribe f) { + this.f = hook.onCreate(f); } 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. + * Returns an Observable that will execute the specified function when a {@link Subscriber} subscribes to + * it. *

- * 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} - */ - public Subscription subscribe(Observer observer) { - // allow the hook to intercept and/or decorate - OnSubscribeFunc onSubscribeFunction = hook.onSubscribeStart(this, onSubscribe); - // validate and proceed - if (observer == null) { - throw new IllegalArgumentException("observer can not be null"); - } - if (onSubscribeFunction == null) { - throw new IllegalStateException("onSubscribe function can not be null."); - // the subscribe function can also be overridden but generally that's not the appropriate approach so I won't mention that in the exception - } - try { - /** - * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator" - */ - if (isInternalImplementation(observer)) { - Subscription s = onSubscribeFunction.onSubscribe(observer); - if (s == null) { - // this generally shouldn't be the case on a 'trusted' onSubscribe but in case it happens - // we want to gracefully handle it the same as AtomicObservableSubscription does - return hook.onSubscribeReturn(this, Subscriptions.empty()); - } else { - return hook.onSubscribeReturn(this, s); - } - } else { - SafeObservableSubscription subscription = new SafeObservableSubscription(); - subscription.wrap(onSubscribeFunction.onSubscribe(new SafeObserver(subscription, observer))); - return hook.onSubscribeReturn(this, subscription); - } - } catch (OnErrorNotImplementedException e) { - // special handling when onError is not implemented ... we just rethrow - throw e; - } catch (Throwable e) { - // if an unhandled error occurs executing the onSubscribe we will propagate it - try { - observer.onError(hook.onSubscribeError(this, e)); - } catch (OnErrorNotImplementedException e2) { - // special handling when onError is not implemented ... we just rethrow - throw e2; - } catch (Throwable e2) { - // if this happens it means the onError itself failed (perhaps an invalid function implementation) - // so we are unable to propagate the error correctly and will just throw - RuntimeException r = new RuntimeException("Error occurred attempting to subscribe [" + e.getMessage() + "] and then again while trying to pass to onError.", e2); - hook.onSubscribeError(this, r); - throw r; - } - return Subscriptions.empty(); - } - } - - /** - * An {@link Observer} must call an Observable's {@code subscribe} method in - * order to receive items and notifications from the Observable. + * Write the function you pass to {@code create} so that it behaves as an Observable: It should invoke the + * Subscriber's {@link Subscriber#onNext onNext}, {@link Subscriber#onError onError}, and + * {@link Subscriber#onCompleted onCompleted} methods appropriately. *

- * 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. + * A well-formed Observable must invoke either the Subscriber's {@code onCompleted} method exactly once or + * its {@code onError} method exactly once. *

- * For more information see the - * RxJava Wiki + * See Rx Design Guidelines (PDF) for detailed + * information. * - * @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 + * the type of the items that this Observable emits + * @param f + * a function that accepts an {@code Subscriber}, and invokes its {@code onNext}, + * {@code onError}, and {@code onCompleted} methods as appropriate + * @return an Observable that, when a {@link Subscriber} subscribes to it, will execute the specified + * function + * @see RxJava Wiki: create() + * @see MSDN: Observable.Create */ - public Subscription subscribe(Observer observer, Scheduler scheduler) { - return subscribeOn(scheduler).subscribe(observer); + public final static Observable create(OnSubscribe f) { + return new Observable(f); } /** - * Protects against errors being thrown from Observer implementations and - * ensures onNext/onError/onCompleted contract compliance. - *

- * See https://github.com/Netflix/RxJava/issues/216 for a discussion on - * "Guideline 6.4: Protect calls to user code from within an operator" + * Invoked when Obserable.subscribe is called. */ - private Subscription protectivelyWrapAndSubscribe(Observer o) { - SafeObservableSubscription subscription = new SafeObservableSubscription(); - return subscription.wrap(subscribe(new SafeObserver(subscription, o))); + public static interface OnSubscribe extends Action1> { + // cover for generics insanity } - + /** - * Subscribe and ignore all events. - * - * @return + * Operator function for lifting into an Observable. */ - 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); - } + public interface Operator extends Func1, Subscriber> { + // cover for generics insanity + } - @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 + * @deprecated use {@link #create(OnSubscribe)} */ - public Subscription subscribe(final Action1 onNext) { - if (onNext == null) { - throw new IllegalArgumentException("onNext can not be null"); - } - - /** - * Wrapping since raw functions provided by the user are being invoked. - * - * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator" - */ - return protectivelyWrapAndSubscribe(new Observer() { - - @Override - public void onCompleted() { - // do nothing - } - - @Override - public void onError(Throwable e) { - handleError(e); - throw new OnErrorNotImplementedException(e); - } + @Deprecated + public final static Observable create(final OnSubscribeFunc f) { + return new Observable(new OnSubscribe() { @Override - public void onNext(T args) { - onNext.call(args); + public void call(Subscriber observer) { + Subscription s = f.onSubscribe(observer); + if (s != null && s != observer) { + observer.add(s); + } } }); } /** - * 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); + public static interface OnSubscribeFunc extends Function { + + public Subscription onSubscribe(Observer op); + } /** - * An {@link Observer} must call an Observable's {@code subscribe} method in - * order to receive items and notifications from the Observable. + * Lift a function to the current Observable and return a new Observable that when subscribed to will pass + * the values of the current Observable through the function. + *

+ * In other words, this allows chaining Observers together on an Observable for acting on the values within + * the Observable. + *

{@code + * observable.map(...).filter(...).take(5).lift(new ObserverA()).lift(new ObserverB(...)).subscribe() + * } * - * @param onNext - * @param onError - * @return + * @param bind + * @return an Observable that emits values that are the result of applying the bind function to the values + * of the current Observable */ - public Subscription subscribe(final Action1 onNext, final Action1 onError) { - if (onNext == null) { - throw new IllegalArgumentException("onNext can not be null"); - } - if (onError == null) { - throw new IllegalArgumentException("onError can not be null"); - } - - /** - * Wrapping since raw functions provided by the user are being invoked. - * - * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator" - */ - return protectivelyWrapAndSubscribe(new Observer() { - - @Override - public void onCompleted() { - // do nothing - } - - @Override - public void onError(Throwable e) { - handleError(e); - onError.call(e); - } - + public Observable lift(final Operator lift) { + return new Observable(new OnSubscribe() { @Override - public void onNext(T args) { - onNext.call(args); + public void call(Subscriber o) { + f.call(hook.onLift(lift).call(o)); } - }); } - /** - * 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 + /* ********************************************************************************************************* + * Observers Below Here + * ********************************************************************************************************* */ - 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. + * Mirror the one Observable in an Iterable of several Observables that first emits an item. + *

+ * * - * @param onNext - * @param onError - * @param onComplete - * @return + * @param sources + * an Iterable of Observable sources competing to react first + * @return an Observable that emits the same sequence of items as whichever of the source Observables first + * emitted an item + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb */ - public Subscription subscribe(final Action1 onNext, final Action1 onError, final Action0 onComplete) { - if (onNext == null) { - throw new IllegalArgumentException("onNext can not be null"); - } - if (onError == null) { - throw new IllegalArgumentException("onError can not be null"); - } - if (onComplete == null) { - throw new IllegalArgumentException("onComplete can not be null"); - } - - /** - * Wrapping since raw functions provided by the user are being invoked. - * - * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an operator" - */ - return protectivelyWrapAndSubscribe(new Observer() { - - @Override - public void onCompleted() { - onComplete.call(); - } - - @Override - public void onError(Throwable e) { - handleError(e); - onError.call(e); - } - - @Override - public void onNext(T args) { - onNext.call(args); - } - - }); + public final static Observable amb(Iterable> sources) { + return create(OperationAmb.amb(sources)); } /** - * An {@link Observer} must call an Observable's {@code subscribe} method in - * order to receive items and notifications from the Observable. + * Given two Observables, mirror the one that first emits an item. + *

+ * * - * @param onNext - * @param onError - * @param onComplete - * @param scheduler - * @return + * @param o1 + * an Observable competing to react first + * @param o2 + * an Observable competing to react first + * @return an Observable that emits the same sequence of items as whichever of the source Observables first + * emitted an item + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb */ - public Subscription subscribe(final Action1 onNext, final Action1 onError, final Action0 onComplete, Scheduler scheduler) { - return subscribeOn(scheduler).subscribe(onNext, onError, onComplete); + public final static Observable amb(Observable o1, Observable o2) { + return create(OperationAmb.amb(o1, o2)); } /** - * Returns a {@link ConnectableObservable} that upon connection causes the - * source Observable to push results into the specified subject. + * Given three Observables, mirror the one that first emits an item. + *

+ * * - * @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() + * @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 emits the same sequence of items as whichever of the source Observables first + * emitted an item + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb */ - public ConnectableObservable multicast(Subject subject) { - return OperationMulticast.multicast(this, subject); + public final static Observable amb(Observable o1, Observable o2, Observable o3) { + return create(OperationAmb.amb(o1, o2, o3)); } /** - * Allow the {@link RxJavaErrorHandler} to receive the exception from - * onError. + * Given four Observables, mirror the one that first emits an item. + *

+ * * - * @param e + * @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 emits the same sequence of items as whichever of the source Observables first + * emitted an item + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb */ - private void handleError(Throwable e) { - // onError should be rare so we'll only fetch when needed - RxJavaPlugins.getInstance().getErrorHandler().handleError(e); + public final static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4) { + return create(OperationAmb.amb(o1, o2, o3, o4)); } /** - * An Observable that never sends any information to an {@link Observer}. - * - * This Observable is useful primarily for testing purposes. + * Given five Observables, mirror the one that first emits an item. + *

+ * * - * @param the type of item emitted by the Observable + * @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 emits the same sequence of items as whichever of the source Observables first + * emitted an item + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb */ - private static class NeverObservable extends Observable { - public NeverObservable() { - super(new OnSubscribeFunc() { - - @Override - public Subscription onSubscribe(Observer t1) { - return Subscriptions.empty(); - } - - }); - } + public final static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5) { + return create(OperationAmb.amb(o1, o2, o3, o4, o5)); } /** - * An Observable that invokes {@link Observer#onError onError} when the - * {@link Observer} subscribes to it. + * Given six Observables, mirror the one that first emits an item. + *

+ * * - * @param the type of item emitted by the Observable + * @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 emits the same sequence of items as whichever of the source Observables first + * emitted an item + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb */ - private static class ThrowObservable extends Observable { - - public ThrowObservable(final Throwable exception) { - super(new OnSubscribeFunc() { - - /** - * Accepts an {@link Observer} and calls its - * {@link Observer#onError onError} method. - * - * @param observer an {@link Observer} of this Observable - * @return a reference to the subscription - */ - @Override - public Subscription onSubscribe(Observer observer) { - observer.onError(exception); - return Subscriptions.empty(); - } - - }); - } - + public final 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)); } /** - * 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. + * Given seven Observables, mirror the one that first emits an item. *

- * 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 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() + * @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 emits the same sequence of items as whichever of the source Observables first + * emitted an item + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb */ - public static Observable create(OnSubscribeFunc func) { - return new Observable(func); + public final 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)); } /** - * Returns an Observable that emits no data to the {@link Observer} and - * immediately invokes its {@link Observer#onCompleted onCompleted} method. + * Given eight Observables, mirror the one that first emits an item. *

- * + * * - * @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 + * @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 emits the same sequence of items as whichever of the source Observables first + * emitted an item + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb */ - public static Observable empty() { - return from(new ArrayList()); + public final 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)); } /** - * Returns an Observable that emits no data to the {@link Observer} and - * immediately invokes its {@link Observer#onCompleted onCompleted} method - * with the specified scheduler. + * Given nine Observables, mirror the one that first emits an item. *

- * + * * - * @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) + * @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 emits the same sequence of items as whichever of the source Observables first + * emitted an item + * @see RxJava Wiki: amb() + * @see MSDN: Observable.Amb */ - public static Observable empty(Scheduler scheduler) { - return Observable. empty().subscribeOn(scheduler); + public final 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)); } /** - * 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 - * @see RxJava Wiki: error() - * @see MSDN: Observable.Throw Method + * @deprecated use {@link #averageInteger} */ - public static Observable error(Throwable exception) { - return new ThrowObservable(exception); + @Deprecated + public final static Observable average(Observable source) { + return OperationAverage.average(source); } /** - * Returns an Observable that invokes an {@link Observer}'s - * {@link Observer#onError onError} method with the specified scheduler. + * Returns an Observable that emits the average of the Doubles emitted by the source Observable. *

- * + * * - * @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); + * @param source + * source Observable to compute the average of + * @return an Observable that emits a single item: the average of all the Doubles emitted by the source + * Observable + * @see RxJava Wiki: averageDouble() + * @see MSDN: Observable.Average + */ + public final static Observable averageDouble(Observable source) { + return OperationAverage.averageDoubles(source); } /** - * Converts an {@link Iterable} sequence into an Observable. - *

- * + * Returns an Observable that emits the average of the Floats emitted by the source 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 - * @see RxJava Wiki: from() + * @param source + * source Observable to compute the average of + * @return an Observable that emits a single item: the average of all the Floats emitted by the source + * Observable + * @see RxJava Wiki: averageFloat() + * @see MSDN: Observable.Average */ - public static Observable from(Iterable iterable) { - return create(OperationToObservableIterable.toObservableIterable(iterable)); + public final static Observable averageFloat(Observable source) { + return OperationAverage.averageFloats(source); } /** - * Converts an {@link Iterable} sequence into an Observable with the specified scheduler. + * Returns an Observable that emits the average of the Integers emitted by the source Observable. *

- * + * * - * @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 + * @param source + * source Observable to compute the average of + * @return an Observable that emits a single item: the average of all the Integers emitted by the source + * Observable + * @throws IllegalArgumentException + * if the source Observable emits no items + * @see RxJava Wiki: averageInteger() + * @see MSDN: Observable.Average */ - public static Observable from(Iterable iterable, Scheduler scheduler) { - return from(iterable).observeOn(scheduler); + public final static Observable averageInteger(Observable source) { + return OperationAverage.average(source); } /** - * Converts an Array into an Observable. - *

- * + * Returns an Observable that emits the average of the Longs emitted by the source 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 Array and the type of items to be - * emitted by the resulting Observable - * @return an Observable that emits each item in the source Array - * @see RxJava Wiki: from() + * @param source + * source Observable to compute the average of + * @return an Observable that emits a single item: the average of all the Longs emitted by the source + * Observable + * @see RxJava Wiki: averageLong() + * @see MSDN: Observable.Average */ - public static Observable from(T[] items) { - return create(OperationToObservableIterable.toObservableIterable(Arrays.asList(items))); + public final static Observable averageLong(Observable source) { + return OperationAverage.averageLongs(source); } /** - * Converts an item into an Observable that emits that item. + * Combines two source Observables by emitting an item that aggregates the latest values of each of the + * source Observables each time an item is received from either of the source Observables, where this + * aggregation is defined by a specified function. *

- * + * + * + * @param o1 + * the first source Observable + * @param o2 + * the second source Observable + * @param combineFunction + * the aggregation function used to combine the items emitted by the source Observables + * @return an Observable that emits items that are the result of combining the items emitted by the source + * Observables by means of the given aggregation function + * @see RxJava Wiki: combineLatest() + */ + public final static Observable combineLatest(Observable o1, Observable o2, Func2 combineFunction) { + return create(OperationCombineLatest.combineLatest(o1, o2, combineFunction)); + } + + /** + * Combines three source Observables by emitting an item that aggregates the latest values of each of the + * source Observables each time an item is received from any of the source Observables, where this + * aggregation is defined by a specified function. *

- * 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 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 - public static Observable from(T t1) { - return from(Arrays.asList(t1)); + * @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 items emitted by the source Observables + * @return an Observable that emits items that are the result of combining the items emitted by the source + * Observables by means of the given aggregation function + * @see RxJava Wiki: combineLatest() + */ + public final static Observable combineLatest(Observable o1, Observable o2, Observable o3, Func3 combineFunction) { + return create(OperationCombineLatest.combineLatest(o1, o2, o3, combineFunction)); } /** - * Converts a series of items into an Observable. + * Combines four source Observables by emitting an item that aggregates the latest values of each of the + * source Observables each time an item is received from any of the source Observables, where this + * aggregation is defined by a specified 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 items emitted by the source Observables + * @return an Observable that emits items that are the result of combining the items emitted by the source + * Observables by means of the given aggregation function + * @see RxJava Wiki: combineLatest() + */ + public final static Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, + Func4 combineFunction) { + return create(OperationCombineLatest.combineLatest(o1, o2, o3, o4, combineFunction)); + } + + /** + * Combines five source Observables by emitting an item that aggregates the latest values of each of the + * source Observables each time an item is received from any of the source Observables, where this + * aggregation is defined by a specified function. *

- * 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 the type of items, and the type of items to be emitted by the - * resulting Observable - * @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) { - return from(Arrays.asList(t1, t2)); + * @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 items emitted by the source Observables + * @return an Observable that emits items that are the result of combining the items emitted by the source + * Observables by means of the given aggregation function + * @see RxJava Wiki: combineLatest() + */ + public final static Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, + Func5 combineFunction) { + return create(OperationCombineLatest.combineLatest(o1, o2, o3, o4, o5, combineFunction)); } /** - * Converts a series of items into an Observable. + * Combines six source Observables by emitting an item that aggregates the latest values of each of the + * source Observables each time an item is received from any of the source Observables, where this + * aggregation is defined by a specified 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 combineFunction + * the aggregation function used to combine the items emitted by the source Observables + * @return an Observable that emits items that are the result of combining the items emitted by the source + * Observables by means of the given aggregation function + * @see RxJava Wiki: combineLatest() + */ + public final static Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, + Func6 combineFunction) { + return create(OperationCombineLatest.combineLatest(o1, o2, o3, o4, o5, o6, combineFunction)); + } + + /** + * Combines seven source Observables by emitting an item that aggregates the latest values of each of the + * source Observables each time an item is received from any of the source Observables, where this + * aggregation is defined by a specified function. *

- * 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 the type of items, and the type of items to be emitted by the - * resulting Observable - * @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) { - return from(Arrays.asList(t1, t2, t3)); + * @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 items emitted by the source Observables + * @return an Observable that emits items that are the result of combining the items emitted by the source + * Observables by means of the given aggregation function + * @see RxJava Wiki: combineLatest() + */ + public final static Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, + Func7 combineFunction) { + return create(OperationCombineLatest.combineLatest(o1, o2, o3, o4, o5, o6, o7, combineFunction)); } /** - * Converts a series of items into an Observable. + * Combines eight source Observables by emitting an item that aggregates the latest values of each of the + * source Observables each time an item is received from any of the source Observables, where this + * aggregation is defined by a specified 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 items emitted by the source Observables + * @return an Observable that emits items that are the result of combining the items emitted by the source + * Observables by means of the given aggregation function + * @see RxJava Wiki: combineLatest() + */ + public final static Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, + Func8 combineFunction) { + return create(OperationCombineLatest.combineLatest(o1, o2, o3, o4, o5, o6, o7, o8, combineFunction)); + } + + /** + * Combines nine source Observables by emitting an item that aggregates the latest values of each of the + * source Observables each time an item is received from any of the source Observables, where this + * aggregation is defined by a specified function. *

- * 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 the type of items, and the type of items to be emitted by the - * resulting Observable - * @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) { - return from(Arrays.asList(t1, t2, t3, t4)); + * @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 items emitted by the source Observables + * @return an Observable that emits items that are the result of combining the items emitted by the source + * Observables by means of the given aggregation function + * @see RxJava Wiki: combineLatest() + */ + public final static Observable combineLatest(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, + Observable o9, + Func9 combineFunction) { + return create(OperationCombineLatest.combineLatest(o1, o2, o3, o4, o5, o6, o7, o8, o9, combineFunction)); } /** - * Converts a series of items into an Observable. + * Returns an Observable that emits the items emitted by each of the Observables emitted by an Observable, + * one after the other, without interleaving them. *

- * + * + * + * @param observables + * an Observable that emits Observables + * @return an Observable that emits items all of the items emitted by the Observables emitted by + * {@code observables}, one after the other, without interleaving them + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat + */ + public final static Observable concat(Observable> observables) { + return create(OperationConcat.concat(observables)); + } + + /** + * Returns an Observable that emits the items emitted by two Observables, one after the other, without + * interleaving them. *

- * 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 - * @see RxJava Wiki: from() + * @param t1 + * an Observable to be concatenated + * @param t2 + * an Observable to be concatenated + * @return an Observable that emits items emitted by the two source Observables, one after the other, + * without interleaving them + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat */ @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) { - return from(Arrays.asList(t1, t2, t3, t4, t5)); + // suppress because the types are checked by the method signature before using a vararg + public final static Observable concat(Observable t1, Observable t2) { + return create(OperationConcat.concat(t1, t2)); } /** - * Converts a series of items into an Observable. + * Returns an Observable that emits the items emitted by three Observables, one after the other, without + * interleaving them. *

- * - *

- * 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 - * @see RxJava Wiki: from() + * @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 emitted by the three source Observables, one after the other, + * without interleaving them + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat */ @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) { - return from(Arrays.asList(t1, t2, t3, t4, t5, t6)); + // suppress because the types are checked by the method signature before using a vararg + public final static Observable concat(Observable t1, Observable t2, Observable t3) { + return create(OperationConcat.concat(t1, t2, t3)); } /** - * Converts a series of items into an Observable. - *

- * + * Returns an Observable that emits the items emitted by four Observables, one after the other, without + * interleaving them. *

- * 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 - * @see RxJava Wiki: from() + * @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 emitted by the four source Observables, one after the other, + * without interleaving them + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat */ @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) { - return from(Arrays.asList(t1, t2, t3, t4, t5, t6, t7)); + // suppress because the types are checked by the method signature before using a vararg + public final static Observable concat(Observable t1, Observable t2, Observable t3, Observable t4) { + return create(OperationConcat.concat(t1, t2, t3, t4)); } /** - * Converts a series of items into an Observable. + * Returns an Observable that emits the items emitted by five Observables, one after the other, without + * interleaving them. *

- * - *

- * 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 - * @see RxJava Wiki: from() + * @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 emitted by the five source Observables, one after the other, + * without interleaving them + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat */ @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) { - return from(Arrays.asList(t1, t2, t3, t4, t5, t6, t7, t8)); + // suppress because the types are checked by the method signature before using a vararg + public final static Observable concat(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5) { + return create(OperationConcat.concat(t1, t2, t3, t4, t5)); } /** - * Converts a series of items into an Observable. - *

- * + * Returns an Observable that emits the items emitted by six Observables, one after the other, without + * interleaving them. *

- * 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 - * @see RxJava Wiki: from() + * @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 emitted by the six source Observables, one after the other, + * without interleaving them + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat */ @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)); + // suppress because the types are checked by the method signature before using a vararg + public final static Observable concat(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6) { + return create(OperationConcat.concat(t1, t2, t3, t4, t5, t6)); } /** - * Converts a series of items into an Observable. + * Returns an Observable that emits the items emitted by seven Observables, one after the other, without + * interleaving them. *

- * - *

- * 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 - * @see RxJava Wiki: from() + * @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 emitted by the seven source Observables, one after the other, + * without interleaving them + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat */ @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, T t10) { - return from(Arrays.asList(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10)); + // suppress because the types are checked by the method signature before using a vararg + public final static Observable concat(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7) { + return create(OperationConcat.concat(t1, t2, t3, t4, t5, t6, t7)); } /** - * Generates an Observable that emits a sequence of integers within a - * specified range. - *

- * + * Returns an Observable that emits the items emitted by eight Observables, one after the other, without + * interleaving them. *

- * 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 RxJava Wiki: range() - * @see Observable.Range Method (Int32, Int32) + * @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 emitted by the eight source Observables, one after the other, + * without interleaving them + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat */ - public static Observable range(int start, int count) { - return from(Range.createWithCount(start, count)); + @SuppressWarnings("unchecked") + // suppress because the types are checked by the method signature before using a vararg + public final static Observable concat(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7, Observable t8) { + return create(OperationConcat.concat(t1, t2, t3, t4, t5, t6, t7, t8)); } /** - * Generates an Observable that emits a sequence of integers within a - * specified range with the specified scheduler. + * Returns an Observable that emits the items emitted by nine Observables, one after the other, without + * interleaving them. *

- * - * @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) + * + * + * @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 emitted by the nine source Observables, one after the other, + * without interleaving them + * @see RxJava Wiki: concat() + * @see MSDN: Observable.Concat */ - public static Observable range(int start, int count, Scheduler scheduler) { - return range(start, count).observeOn(scheduler); + @SuppressWarnings("unchecked") + // suppress because the types are checked by the method signature before using a vararg + public final static Observable concat(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7, Observable t8, Observable t9) { + return create(OperationConcat.concat(t1, t2, t3, t4, t5, t6, t7, t8, t9)); } /** - * 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. + * Returns an Observable that calls an Observable factory to create its Observable for each new Observer + * that subscribes. That is, for each subscriber, the actual Observable that subscriber observes 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 Observer 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 - * @see RxJava Wiki: defer() + * @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' subscriptions trigger an invocation of the given + * Observable factory function + * @see RxJava Wiki: defer() */ - public static Observable defer(Func0> observableFactory) { + public final static Observable defer(Func0> observableFactory) { return create(OperationDefer.defer(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. + * Returns an Observable that emits no items to the {@link Observer} and immediately invokes its + * {@link Observer#onCompleted onCompleted} 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. + * * - * @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() + * @param + * the type of the items (ostensibly) emitted by the Observable + * @return an Observable that emits no items to the {@link Observer} but immediately invokes the + * {@link Observer}'s {@link Observer#onCompleted() onCompleted} method + * @see RxJava Wiki: empty() + * @see MSDN: Observable.Empty */ - public static Observable just(T value) { - List list = new ArrayList(); - list.add(value); - - return from(list); + public final static Observable empty() { + return from(new ArrayList()); } /** - * Returns an Observable that emits a single item and then completes on a - * specified scheduler. + * Returns an Observable that emits no items to the {@link Observer} and immediately invokes its + * {@link Observer#onCompleted onCompleted} method on the 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() + * @param scheduler + * the Scheduler to use to call the {@link Observer#onCompleted onCompleted} method + * @param + * the type of the items (ostensibly) emitted by the Observable + * @return an Observable that emits no items to the {@link Observer} but immediately invokes the + * {@link Observer}'s {@link Observer#onCompleted() onCompleted} method with the specified + * {@code scheduler} + * @see RxJava Wiki: empty() + * @see MSDN: Observable.Empty Method (IScheduler) */ - public static Observable just(T value, Scheduler scheduler) { - return just(value).observeOn(scheduler); + public final static Observable empty(Scheduler scheduler) { + return Observable. empty().subscribeOn(scheduler); } /** - * Flattens a sequence of Observables emitted by an Observable into one - * Observable, without any transformation. - *

- * + * Returns an Observable that invokes an {@link Observer}'s {@link Observer#onError onError} method when the + * Observer subscribes to it. *

- * 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 RxJava Wiki: merge() - * @see MSDN: Observable.Merge Method + * @param exception + * the particular Throwable to pass to {@link Observer#onError onError} + * @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 */ - public static Observable merge(Observable> source) { - return create(OperationMerge.merge(source)); + public final static Observable error(Throwable exception) { + return new ThrowObservable(exception); } /** - * Flattens a series of Observables into one Observable, without any - * transformation. - *

- * + * Returns an Observable that invokes an {@link Observer}'s {@link Observer#onError onError} method on the + * specified Scheduler. *

- * 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 RxJava Wiki: merge() - * @see MSDN: Observable.Merge Method + * @param exception + * the particular Throwable to pass to {@link Observer#onError onError} + * @param scheduler + * the Scheduler on which to call {@link Observer#onError onError} + * @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, on + * the specified Scheduler + * @see RxJava Wiki: error() + * @see MSDN: Observable.Throw */ - @SuppressWarnings("unchecked") - // suppress because the types are checked by the method signature before using a vararg - public static Observable merge(Observable t1, Observable t2) { - return create(OperationMerge.merge(t1, t2)); + public final static Observable error(Throwable exception, Scheduler scheduler) { + return Observable. error(exception).subscribeOn(scheduler); } /** - * Flattens a series of Observables into one Observable, without any - * transformation. + * 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 combine items emitted by multiple Observables so that they act - * like a single Observable, by using the {@code merge} method. + * Important note: This Observable is blocking; you cannot unsubscribe from it. * - * @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 + * @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 {@link Future} + * @see RxJava Wiki: from() */ - @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)); + public final static Observable from(Future future) { + return create(OperationToObservableFuture.toObservableFuture(future)); } /** - * Flattens a series of Observables into one Observable, without any - * transformation. + * Converts a {@link Future} into an Observable, with a timeout on the Future. *

- * + * + *

+ * 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 combine items emitted by multiple Observables so that they act - * like a single Observable, by using the {@code merge} method. + * Important note: This Observable is blocking; you cannot unsubscribe from it. * - * @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 + * @param future + * the source {@link Future} + * @param timeout + * the maximum time to wait before calling {@code get()} + * @param unit + * the {@link TimeUnit} of the {@code 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() */ - @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)); + public final static Observable from(Future future, long timeout, TimeUnit unit) { + return create(OperationToObservableFuture.toObservableFuture(future, timeout, unit)); } /** - * Flattens a series of Observables into one Observable, without any - * transformation. + * Converts a {@link Future}, operating on a specified {@link Scheduler}, 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 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 + * @param future + * the source {@link Future} + * @param scheduler + * the {@link Scheduler} to wait for the Future on. Use a Scheduler such as + * {@link Schedulers#io()} 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 {@link Future} + * @see RxJava Wiki: from() */ - @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)); + public final static Observable from(Future future, Scheduler scheduler) { + return create(OperationToObservableFuture.toObservableFuture(future)).subscribeOn(scheduler); } /** - * Flattens a series of Observables into one Observable, without any - * transformation. - *

- * + * Converts an {@link Iterable} sequence into an Observable that emits the items in the sequence. *

- * 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 + * @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() */ - @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)); + public final static Observable from(Iterable iterable) { + return create(new OnSubscribeFromIterable(iterable)); } /** - * Flattens a series of Observables into one Observable, without any - * transformation. + * Converts an {@link Iterable} sequence into an Observable that operates on the specified Scheduler, + * emitting each item from the sequence. *

- * - *

- * 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 + * @param iterable + * the source {@link Iterable} sequence + * @param scheduler + * the Scheduler on which the Observable is 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, on the specified + * Scheduler + * @see RxJava Wiki: from() + * @see MSDN: Observable.ToObservable */ - @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)); + public final static Observable from(Iterable iterable, Scheduler scheduler) { + return create(new OnSubscribeFromIterable(iterable)).subscribeOn(scheduler); } /** - * Flattens a series of Observables into one Observable, without any - * transformation. - *

- * + * Converts an item into an Observable that emits that item. *

- * 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 + * + * + * @param t1 + * the item + * @param + * the type of the item + * @return an Observable that emits the item + * @see RxJava Wiki: from() */ - @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)); + // suppress unchecked because we are using varargs inside the method + public final static Observable from(T t1) { + return from(Arrays.asList(t1)); } /** - * Flattens a series of Observables into one Observable, without any - * transformation. + * Converts two items into an Observable that emits those items. *

- * - *

- * 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 + * + * + * @param t1 + * first item + * @param t2 + * second item + * @param + * the type of these items + * @return an Observable that emits each item + * @see RxJava Wiki: from() + * @deprecated use {@link #from(Iterable)} instead such as {@code from(Arrays.asList(t1,t2))} */ - @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, Observable t9) { - return create(OperationMerge.merge(t1, t2, t3, t4, t5, t6, t7, t8, t9)); + @Deprecated + // suppress unchecked because we are using varargs inside the method + public final static Observable from(T t1, T t2) { + return from(Arrays.asList(t1, t2)); } /** - * Returns an Observable that emits the items emitted by two or more - * Observables, one after the other. + * Converts three items into an Observable that emits those items. *

- * + * * - * @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 + * @param t1 + * first item + * @param t2 + * second item + * @param t3 + * third item + * @param + * the type of these items + * @return an Observable that emits each item + * @see RxJava Wiki: from() + * @deprecated use {@link #from(Iterable)} instead such as {@code from(Arrays.asList(t1,t2,t3))}. */ - public static Observable concat(Observable> observables) { - return create(OperationConcat.concat(observables)); + @Deprecated + // suppress unchecked because we are using varargs inside the method + public final static Observable from(T t1, T t2, T t3) { + return from(Arrays.asList(t1, t2, t3)); } /** - * Returns an Observable that emits the items emitted by two Observables, - * one after the other. + * Converts four items into an Observable that emits those items. *

- * + * * - * @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 + * @param t1 + * first item + * @param t2 + * second item + * @param t3 + * third item + * @param t4 + * fourth item + * @param + * the type of these items + * @return an Observable that emits each item + * @see RxJava Wiki: from() + * @deprecated use {@link #from(Iterable)} instead such as {@code from(Arrays.asList(t1,t2,t3,t4))}. */ - @SuppressWarnings("unchecked") - // suppress because the types are checked by the method signature before using a vararg - public static Observable concat(Observable t1, Observable t2) { - return create(OperationConcat.concat(t1, t2)); + @Deprecated + // suppress unchecked because we are using varargs inside the method + public final static Observable from(T t1, T t2, T t3, T t4) { + return from(Arrays.asList(t1, t2, t3, t4)); } /** - * Returns an Observable that emits the items emitted by three Observables, - * one after the other. + * Converts five items into an Observable that emits those items. *

- * + * * - * @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 + * @param t1 + * first item + * @param t2 + * second item + * @param t3 + * third item + * @param t4 + * fourth item + * @param t5 + * fifth item + * @param + * the type of these items + * @return an Observable that emits each item + * @see RxJava Wiki: from() + * @deprecated use {@link #from(Iterable)} instead such as {@code from(Arrays.asList(t1,t2,t3,t4,t5))}. */ - @SuppressWarnings("unchecked") - // suppress because the types are checked by the method signature before using a vararg - public static Observable concat(Observable t1, Observable t2, Observable t3) { - return create(OperationConcat.concat(t1, t2, t3)); + @Deprecated + // suppress unchecked because we are using varargs inside the method + public final static Observable from(T t1, T t2, T t3, T t4, T t5) { + return from(Arrays.asList(t1, t2, t3, t4, t5)); } /** - * Returns an Observable that emits the items emitted by four Observables, - * one after the other. + * Converts six items into an Observable that emits those items. *

- * + * * - * @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 + * @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 these items + * @return an Observable that emits each item + * @see RxJava Wiki: from() + * @deprecated use {@link #from(Iterable)} instead such as {@code from(Arrays.asList(t1,t2,t3,t4,t5,t6))}. */ - @SuppressWarnings("unchecked") - // suppress because the types are checked by the method signature before using a vararg - public static Observable concat(Observable t1, Observable t2, Observable t3, Observable t4) { - return create(OperationConcat.concat(t1, t2, t3, t4)); + @Deprecated + // suppress unchecked because we are using varargs inside the method + public final static Observable from(T t1, T t2, T t3, T t4, T t5, T t6) { + return from(Arrays.asList(t1, t2, t3, t4, t5, t6)); } /** - * Returns an Observable that emits the items emitted by five Observables, - * one after the other. + * Converts seven items into an Observable that emits those items. *

- * + * * - * @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 + * @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 these items + * @return an Observable that emits each item + * @see RxJava Wiki: from() + * @deprecated use {@link #from(Iterable)} instead such as {@code from(Arrays.asList(t1,t2,t3,t4,t5,t6,t7))}. */ - @SuppressWarnings("unchecked") - // suppress because the types are checked by the method signature before using a vararg - public static Observable concat(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5) { - return create(OperationConcat.concat(t1, t2, t3, t4, t5)); + @Deprecated + // suppress unchecked because we are using varargs inside the method + public final static Observable from(T t1, T t2, T t3, T t4, T t5, T t6, T t7) { + return from(Arrays.asList(t1, t2, t3, t4, t5, t6, t7)); } /** - * Returns an Observable that emits the items emitted by six Observables, - * one after the other. + * Converts eight items into an Observable that emits those items. *

- * + * * - * @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 + * @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 these items + * @return an Observable that emits each item + * @see RxJava Wiki: from() + * @deprecated use {@link #from(Iterable)} instead such as {@code from(Arrays.asList(t1,t2,t3,t4,t5,t6,t7,t8))}. */ - @SuppressWarnings("unchecked") - // suppress because the types are checked by the method signature before using a vararg - public static Observable concat(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6) { - return create(OperationConcat.concat(t1, t2, t3, t4, t5, t6)); + @Deprecated + // suppress unchecked because we are using varargs inside the method + public final static Observable from(T t1, T t2, T t3, T t4, T t5, T t6, T t7, T t8) { + return from(Arrays.asList(t1, t2, t3, t4, t5, t6, t7, t8)); } /** - * Returns an Observable that emits the items emitted by secven Observables, - * one after the other. + * Converts nine items into an Observable that emits those items. *

- * + * * - * @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 + * @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 these items + * @return an Observable that emits each item + * @see RxJava Wiki: from() + * @deprecated use {@link #from(Iterable)} instead such as {@code from(Arrays.asList(t1,t2,t3,t4,t5,t6,t7,t8,t9))}. */ - @SuppressWarnings("unchecked") - // suppress because the types are checked by the method signature before using a vararg - public static Observable concat(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7) { - return create(OperationConcat.concat(t1, t2, t3, t4, t5, t6, t7)); + @Deprecated + // suppress unchecked because we are using varargs inside the method + public final 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)); } /** - * Returns an Observable that emits the items emitted by eight Observables, - * one after the other. + * Converts ten items into an Observable that emits those items. + *

+ * *

- * * - * @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 + * @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 these items + * @return an Observable that emits each item + * @see RxJava Wiki: from() + * @deprecated use {@link #from(Iterable)} instead such as {@code from(Arrays.asList(t1,t2,t3,t4,t5,t6,t7,t8,t9,t10))}. */ - @SuppressWarnings("unchecked") - // suppress because the types are checked by the method signature before using a vararg - public static Observable concat(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7, Observable t8) { - return create(OperationConcat.concat(t1, t2, t3, t4, t5, t6, t7, t8)); + @Deprecated + // suppress unchecked because we are using varargs inside the method + public final static Observable from(T t1, T t2, T t3, T t4, T t5, T t6, T t7, T t8, T t9, T t10) { + return from(Arrays.asList(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10)); } /** - * Returns an Observable that emits the items emitted by nine Observables, - * one after the other. + * Converts an Array into an Observable that emits the items in the Array. *

- * + * * - * @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 + * @param items + * the source Array + * @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 + * @see RxJava Wiki: from() */ - @SuppressWarnings("unchecked") - // suppress because the types are checked by the method signature before using a vararg - public static Observable concat(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7, Observable t8, Observable t9) { - return create(OperationConcat.concat(t1, t2, t3, t4, t5, t6, t7, t8, t9)); + // @SafeVarargs // commenting out until we figure out if we can do Java7 compilation without breaking Android for just this feature + public final static Observable from(T... t1) { + return from(Arrays.asList(t1)); } /** - * 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. + * Converts an Array into an Observable that emits the items in the Array on a specified Scheduler. *

- * 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 RxJava Wiki: mergeDelayError() - * @see MSDN: Observable.Merge Method + * @param items + * the source Array + * @param scheduler + * the Scheduler on which the Observable emits the items of the Array + * @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 + * @see RxJava Wiki: from() */ - public static Observable mergeDelayError(Observable> source) { - return create(OperationMergeDelayError.mergeDelayError(source)); + public final static Observable from(T[] items, Scheduler scheduler) { + return from(Arrays.asList(items), scheduler); } /** - * 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. + * Returns an Observable that emits a sequential number every specified interval of time. *

- * - *

- * 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 - * @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 + * @param interval + * interval size in time units (see below) + * @param unit + * time units to use for the interval size + * @return an Observable that emits a sequential number each time interval + * @see RxJava Wiki: interval() + * @see MSDN: Observable.Interval */ - @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)); + public final static Observable interval(long interval, TimeUnit unit) { + return create(OperationInterval.interval(interval, unit)); } /** - * 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. + * Returns an Observable that emits a sequential number every specified interval of time, on a + * specified Scheduler. *

- * - *

- * 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 - * @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 + * @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 a sequential number each time interval + * @see RxJava Wiki: interval() + * @see MSDN: Observable.Interval */ - @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)); + public final static Observable interval(long interval, TimeUnit unit, Scheduler scheduler) { + return create(OperationInterval.interval(interval, unit, scheduler)); } /** - * 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. + * Returns an Observable that emits a single item and then completes. *

- * + * *

- * Even if multiple merged Observables send {@code onError} notifications, - * {@code mergeDelayError} will only invoke the {@code onError} method of - * its Observers once. + * To convert any object into an Observable that emits that object, pass that object into the {@code just} + * method. *

- * 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 is similar to the {@link #from(java.lang.Object[])} method, except that {@code 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 {@code just()} method converts an Iterable into an Observable that emits the entire + * Iterable as a single item. * - * @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 + * @param value + * the item to emit + * @param + * the type of that item + * @return an Observable that emits {@code value} as a single item and then completes + * @see RxJava Wiki: just() */ - @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)); + public final static Observable just(T value) { + return from(Arrays.asList(value)); } - + /** - * 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. - *

- * + * Returns an Observable that emits a single item and then completes, on a specified Scheduler. *

- * 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 is a scheduler version of {@link #just(Object)}. * - * @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 + * @param value + * the item to emit + * @param + * the type of that item + * @param scheduler + * the Scheduler to emit the single item on + * @return an Observable that emits {@code value} as a single item and then completes, on a specified + * Scheduler + * @see RxJava Wiki: just() + * @deprecated use {@link #from(T)} */ - @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)); + @Deprecated + public final static Observable just(T value, Scheduler scheduler) { + return from(Arrays.asList((value)), scheduler); } /** - * 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. + * Returns an Observable that emits the single item emitted by the source Observable with the maximum + * numeric value. If there is more than one item with the same maximum value, it emits the last-emitted of + * these. *

- * 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 + * @param source + * an Observable to scan for the maximum emitted item + * @return an Observable that emits this maximum item + * @throws IllegalArgumentException + * if the source is empty + * @see RxJava Wiki: max() + * @see MSDN: Observable.Max */ - @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)); + public final static > Observable max(Observable source) { + return OperationMinMax.max(source); } /** - * 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. + * Flattens an Iterable of Observables into one Observable, without any transformation. *

- * + * *

- * 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 + * You can combine the items emitted by multiple Observables so that they appear as a single Observable, by + * using the {@code merge} method. + * + * @param sequences + * the Iterable of Observables + * @return an Observable that emits items that are the result of flattening the items emitted by the + * Observables in the Iterable + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge */ - @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)); + public final static Observable merge(Iterable> sequences) { + return merge(from(sequences)); } /** - * 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. + * Flattens an Iterable of Observables into one Observable, without any transformation, while limiting the + * number of concurrent subscriptions to these Observables. *

- * + * *

- * 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 + * You can combine the items emitted by multiple Observables so that they appear as a single Observable, by + * using the {@code merge} method. + * + * @param sequences + * the Iterable of Observables + * @param maxConcurrent + * the maximum number of Observables that may be subscribed to concurrently + * @return an Observable that emits items that are the result of flattening the items emitted by the + * Observables in the Iterable + * @throws IllegalArgumentException + * if {@code maxConcurrent} is less than or equal to 0 + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge */ - @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)); + public final static Observable merge(Iterable> sequences, int maxConcurrent) { + return merge(from(sequences), maxConcurrent); } /** - * 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. + * Flattens an Iterable of Observables into one Observable, without any transformation, while limiting the + * number of concurrent subscriptions to these Observables, and subscribing to these Observables on a + * specified Scheduler. *

- * + * *

- * 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 + * You can combine the items emitted by multiple Observables so that they appear as a single Observable, by + * using the {@code merge} method. + * + * @param sequences + * the Iterable of Observables + * @param maxConcurrent + * the maximum number of Observables that may be subscribed to concurrently + * @param scheduler + * the Scheduler on which to traverse the Iterable of Observables + * @return an Observable that emits items that are the result of flattening the items emitted by the + * Observables in the Iterable + * @throws IllegalArgumentException + * if {@code maxConcurrent} is less than or equal to 0 + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge */ - @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, Observable t9) { - return create(OperationMergeDelayError.mergeDelayError(t1, t2, t3, t4, t5, t6, t7, t8, t9)); + public final static Observable merge(Iterable> sequences, int maxConcurrent, Scheduler scheduler) { + return merge(from(sequences, scheduler), maxConcurrent); } /** - * Returns an Observable that never sends any items or notifications to an - * {@link Observer}. + * Flattens an Iterable of Observables into one Observable, without any transformation, subscribing to these + * Observables on a specified Scheduler. *

- * + * *

- * This Observable is useful primarily for testing purposes. + * You can combine the items emitted by multiple Observables so that they appear as a single Observable, by + * using the {@code merge} method. * - * @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() + * @param sequences + * the Iterable of Observables + * @param scheduler + * the Scheduler on which to traverse the Iterable of Observables + * @return an Observable that emits items that are the result of flattening the items emitted by the + * Observables in the Iterable + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge */ - public static Observable never() { - return new NeverObservable(); + public final static Observable merge(Iterable> sequences, Scheduler scheduler) { + return merge(from(sequences, scheduler)); } /** - * Given an Observable that emits Observables, creates a single Observable - * that emits the items emitted by the most recently published of those - * Observables. + * Flattens an Observable that emits Observables into a single Observable that emits the items emitted by + * those Observables, without any transformation. *

- * + * + *

+ * You can combine the items emitted by multiple Observables so that they appear as a single Observable, by + * using the {@code merge} method. * - * @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} + * @param source + * an Observable that emits Observables + * @return an Observable that emits items that are the result of flattening the Observables emitted by the + * {@code source} Observable + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge */ - @Deprecated - public static Observable switchDo(Observable> sequenceOfSequences) { - return create(OperationSwitch.switchDo(sequenceOfSequences)); + public final static Observable merge(Observable> source) { + return source.lift(new OperatorMerge()); } /** - * Given an Observable that emits Observables, creates a single Observable - * that emits the items emitted by the most recently published of those - * Observables. + * Flattens an Observable that emits Observables into a single Observable that emits the items emitted by + * those Observables, without any transformation, while limiting the maximum number of concurrent + * subscriptions to these Observables. *

- * + * + *

+ * You can combine the items emitted by multiple Observables so that they appear as a single Observable, by + * using the {@code merge} method. * - * @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() + * @param source + * an Observable that emits Observables + * @param maxConcurrent + * the maximum number of Observables that may be subscribed to concurrently + * @return an Observable that emits items that are the result of flattening the Observables emitted by the + * {@code source} Observable + * @throws IllegalArgumentException + * if {@code maxConcurrent} is less than or equal to 0 + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge */ - public static Observable switchOnNext(Observable> sequenceOfSequences) { - return create(OperationSwitch.switchDo(sequenceOfSequences)); + public final static Observable merge(Observable> source, int maxConcurrent) { + return Observable.create(OperationMergeMaxConcurrent.merge(source, maxConcurrent)); } /** - * Accepts an Observable and wraps it in another Observable that ensures - * that the resulting Observable is chronologically well-behaved. + * Flattens two Observables into a single Observable, without any transformation. *

- * + * *

- * 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. - * - * @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() { - return create(OperationSynchronize.synchronize(this)); + * You can combine items emitted by multiple Observables so that they appear as 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 all of the items emitted by the source Observables + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge + */ + public final static Observable merge(Observable t1, Observable t2) { + return merge(from(Arrays.asList(t1, t2))); } /** - * 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. + * Flattens three Observables into a single Observable, without any transformation. *

- * + * *

- * 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)); + * You can combine items emitted by multiple Observables so that they appear as 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 all of the items emitted by the source Observables + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge + */ + public final static Observable merge(Observable t1, Observable t2, Observable t3) { + return merge(from(Arrays.asList(t1, t2, t3))); } /** - * @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). + * Flattens four Observables into a single Observable, without any transformation. *

- * - * - * @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). + * *

- * + * You can combine items emitted by multiple Observables so that they appear as a single Observable, by + * using the {@code merge} method. * - * @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 + * @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 all of the items emitted by the source Observables + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge */ - public static Observable interval(long interval, TimeUnit unit, Scheduler scheduler) { - return create(OperationInterval.interval(interval, unit, scheduler)); + public final static Observable merge(Observable t1, Observable t2, Observable t3, Observable t4) { + return merge(from(Arrays.asList(t1, t2, t3, t4))); } /** - * 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. - *

- * + * Flattens five Observables into a single Observable, without any transformation. *

- * Information on debounce vs throttle: + * *

- *

+ * You can combine items emitted by multiple Observables so that they appear as a single Observable, by + * using the {@code merge} method. * - * @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) + * @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 all of the items emitted by the source Observables + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge */ - public Observable debounce(long timeout, TimeUnit unit) { - return create(OperationDebounce.debounce(this, timeout, unit)); + public final static Observable merge(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5) { + return merge(from(Arrays.asList(t1, t2, t3, t4, t5))); } /** - * 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. - *

- * + * Flattens six Observables into a single Observable, without any transformation. *

- * Information on debounce vs throttle: + * *

- *

+ * You can combine items emitted by multiple Observables so that they appear as a single Observable, by + * using the {@code merge} method. * - * @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) + * @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 all of the items emitted by the source Observables + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge */ - public Observable debounce(long timeout, TimeUnit unit, Scheduler scheduler) { - return create(OperationDebounce.debounce(this, timeout, unit, scheduler)); + public final static Observable merge(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6) { + return merge(from(Arrays.asList(t1, t2, t3, t4, t5, t6))); } /** - * 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. - *

- * + * Flattens seven Observables into a single Observable, without any transformation. *

- * Information on debounce vs throttle: + * *

- *

+ * You can combine items emitted by multiple Observables so that they appear as a single Observable, by + * using the {@code merge} method. * - * @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) + * @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 all of the items emitted by the source Observables + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge */ - public Observable throttleWithTimeout(long timeout, TimeUnit unit) { - return create(OperationDebounce.debounce(this, timeout, unit)); + public final static Observable merge(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7) { + return merge(from(Arrays.asList(t1, t2, t3, t4, t5, t6, t7))); } /** - * 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. - *

- * + * Flattens eight Observables into a single Observable, without any transformation. *

- * 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 - * @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) + * You can combine items emitted by multiple Observables so that they appear as 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 + * @return an Observable that emits all of the items emitted by the source Observables + * @see MSDN: Observable.Merge */ - public Observable throttleWithTimeout(long timeout, TimeUnit unit, Scheduler scheduler) { - return create(OperationDebounce.debounce(this, timeout, unit, scheduler)); + public final static Observable merge(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7, Observable t8) { + return merge(from(Arrays.asList(t1, t2, t3, t4, t5, t6, t7, t8))); } /** - * Throttles by skipping items until "skipDuration" passes and then emits - * the next received item. + * Flattens nine Observables into a single Observable, without any transformation. *

- * 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 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() + * You can combine items emitted by multiple Observables so that they appear as 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 all of the items emitted by the source Observables + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge */ - public Observable throttleFirst(long windowDuration, TimeUnit unit) { - return create(OperationThrottleFirst.throttleFirst(this, windowDuration, unit)); + // suppress because the types are checked by the method signature before using a vararg + public final static Observable merge(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7, Observable t8, Observable t9) { + return merge(from(Arrays.asList(t1, t2, t3, t4, t5, t6, t7, t8, t9))); } /** - * Throttles by skipping items until "skipDuration" passes and then emits - * the next received item. + * Flattens an Array of Observables into one Observable, without any transformation. *

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

- * + * You can combine items emitted by multiple Observables so that they appear as a single Observable, by + * using the {@code merge} method. * - * @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() + * @param sequences + * the Array of Observables + * @return an Observable that emits all of the items emitted by the Observables in the Array + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge */ - public Observable throttleFirst(long skipDuration, TimeUnit unit, Scheduler scheduler) { - return create(OperationThrottleFirst.throttleFirst(this, skipDuration, unit, scheduler)); + public final static Observable merge(Observable[] sequences) { + return merge(from(sequences)); } /** - * Throttles by emitting the last item in each interval defined by - * intervalDuration. + * Flattens an Array of Observables into one Observable, without any transformation, traversing the array on + * a specified Scheduler. *

- * 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. + * *

- * + * You can combine items emitted by multiple Observables so that they appear as a single Observable, by + * using the {@code merge} method. * - * @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) + * @param sequences + * the Array of Observables + * @param scheduler + * the Scheduler on which to traverse the Array + * @return an Observable that emits all of the items emitted by the Observables in the Array + * @see RxJava Wiki: merge() + * @see MSDN: Observable.Merge */ - public Observable throttleLast(long intervalDuration, TimeUnit unit) { - return sample(intervalDuration, unit); + public final static Observable merge(Observable[] sequences, Scheduler scheduler) { + return merge(from(sequences, scheduler)); } /** - * Throttles by emitting the last item in each interval defined by - * intervalDuration. + * A version of merge that 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 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 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. * - * @param intervalDuration duration of windows within which the last item - * will be emitted - * @param unit the unit of time for the specified interval - * @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: throttleLast() - * @see #sample(long, TimeUnit, Scheduler) + * @param source + * an Observable that emits Observables + * @return an Observable that emits all of the items emitted by the Observables emitted by the + * {@code source} Observable + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge */ - public Observable throttleLast(long intervalDuration, TimeUnit unit, Scheduler scheduler) { - return sample(intervalDuration, unit, scheduler); + public final static Observable mergeDelayError(Observable> source) { + return create(OperationMergeDelayError.mergeDelayError(source)); } /** - * Wraps each item emitted by a source Observable in a {@link Timestamped} - * object. + * A version of merge that 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 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 both merged Observables send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Observers once. * - * @return an Observable that emits timestamped items from the source - * Observable - * @see RxJava Wiki: timestamp() - * @see MSDN: Observable.Timestamp + * @param t1 + * an Observable to be merged + * @param t2 + * an Observable to be merged + * @return an Observable that emits all of the items that are emitted by the two source Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge */ - public Observable> timestamp() { - return create(OperationTimestamp.timestamp(this)); + @SuppressWarnings("unchecked") + // suppress because the types are checked by the method signature before using a vararg + public final static Observable mergeDelayError(Observable t1, Observable t2) { + return create(OperationMergeDelayError.mergeDelayError(t1, t2)); } /** - * Wraps each item emitted by a source Observable in a {@link Timestamped} - * object with timestamps provided by the given Scheduler. + * A version of merge that 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 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. * - * @param scheduler the {@link Scheduler} to use as a time source. - * @return an Observable that emits timestamped items from the source - * Observable with timestamps provided by the given Scheduler - * @see RxJava Wiki: timestamp() - * @see MSDN: Observable.Timestamp + * @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 all of the items that are emitted by the source Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge */ - public Observable> timestamp(Scheduler scheduler) { - return create(OperationTimestamp.timestamp(this, scheduler)); + @SuppressWarnings("unchecked") + // suppress because the types are checked by the method signature before using a vararg + public final static Observable mergeDelayError(Observable t1, Observable t2, Observable t3) { + return create(OperationMergeDelayError.mergeDelayError(t1, t2, t3)); } /** - * Converts a {@link Future} into an Observable. + * A version of merge that 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 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. *

- * 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. - * - * @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() + * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Observers once. + * + * @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 all of the items that are emitted by the source Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge */ - public static Observable from(Future future) { - return create(OperationToObservableFuture.toObservableFuture(future)); + @SuppressWarnings("unchecked") + // suppress because the types are checked by the method signature before using a vararg + public final static Observable mergeDelayError(Observable t1, Observable t2, Observable t3, Observable t4) { + return create(OperationMergeDelayError.mergeDelayError(t1, t2, t3, t4)); } /** - * Converts a {@link Future} into an Observable. + * A version of merge that 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 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. *

- * 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 - * @return an Observable that emits the item from the source Future - * @see RxJava Wiki: from() + * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Observers once. + * + * @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 all of the items that are emitted by the source Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge */ - public static Observable from(Future future, Scheduler scheduler) { - return create(OperationToObservableFuture.toObservableFuture(future)).subscribeOn(scheduler); + @SuppressWarnings("unchecked") + // suppress because the types are checked by the method signature before using a vararg + public final static Observable mergeDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5) { + return create(OperationMergeDelayError.mergeDelayError(t1, t2, t3, t4, t5)); } /** - * Converts a {@link Future} into an Observable with timeout. + * A version of merge that 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 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. *

- * 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. - * - * @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() + * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Observers once. + * + * @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 all of the items that are emitted by the source Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge */ - public static Observable from(Future future, long timeout, TimeUnit unit) { - return create(OperationToObservableFuture.toObservableFuture(future, timeout, unit)); + @SuppressWarnings("unchecked") + // suppress because the types are checked by the method signature before using a vararg + public final 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)); } /** - * Returns an Observable that emits a Boolean value that indicate - * whether two sequences are equal by comparing the elements pairwise. + * A version of merge that 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 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 a Boolean value that indicate - * whether two sequences are equal by comparing the elements pairwise. - * @see RxJava Wiki: sequenceEqual() + * 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. + * + * @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 all of the items that are emitted by the source Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge */ - public static Observable sequenceEqual(Observable first, Observable second) { - return sequenceEqual(first, second, new Func2() { - @Override - public Boolean call(T first, T second) { - if(first == null) { - return second == null; - } - return first.equals(second); - } - }); + @SuppressWarnings("unchecked") + // suppress because the types are checked by the method signature before using a vararg + public final 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)); } /** - * Returns an Observable that emits a Boolean value that indicate - * whether two sequences are equal by comparing the elements pairwise - * based on the results of a specified equality function. + * A version of merge that 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 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 a Boolean value that indicate - * whether two sequences are equal by comparing the elements pairwise. - * @see RxJava Wiki: sequenceEqual() + * 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. + * + * @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 all of the items that are emitted by the source Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge */ - public static Observable sequenceEqual(Observable first, Observable second, Func2 equality) { - return OperationSequenceEqual.sequenceEqual(first, second, equality); + @SuppressWarnings("unchecked") + // suppress because the types are checked by the method signature before using a vararg + public final 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)); } /** - * 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. + * A version of merge that 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 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. *

- * {@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() + * Even if multiple merged Observables send {@code onError} notifications, {@code mergeDelayError} will only + * invoke the {@code onError} method of its Observers once. + * + * @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 all of the items that are emitted by the source Observables + * @see RxJava Wiki: mergeDelayError() + * @see MSDN: Observable.Merge */ - public static Observable zip(Observable o1, Observable o2, Func2 zipFunction) { - return create(OperationZip.zip(o1, o2, zipFunction)); + @SuppressWarnings("unchecked") + // suppress because the types are checked by the method signature before using a vararg + public final static Observable mergeDelayError(Observable t1, Observable t2, Observable t3, Observable t4, Observable t5, Observable t6, Observable t7, Observable t8, Observable t9) { + return create(OperationMergeDelayError.mergeDelayError(t1, t2, t3, t4, t5, t6, t7, t8, t9)); } /** - * 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 single numerically minimum item emitted by the source Observable. + * If there is more than one such item, it returns the last-emitted one. *

- * {@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() + * @param source + * an Observable to determine the minimum item of + * @return an Observable that emits the minimum item emitted by the source Observable + * @throws IllegalArgumentException + * if the source is empty + * @see MSDN: Observable.Min */ - public static Observable zip(Observable o1, Observable o2, Observable o3, Func3 zipFunction) { - return create(OperationZip.zip(o1, o2, o3, zipFunction)); + public final static > Observable min(Observable source) { + return OperationMinMax.min(source); } /** - * 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. + * Convert the current {@code Observable} into an {@code 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() + * @return an Observable that emits a single item: the source Observable */ - public static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Func4 zipFunction) { - return create(OperationZip.zip(o1, o2, o3, o4, zipFunction)); + public final Observable> nest() { + return from(this); } - + /** - * 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 never sends any items or notifications to an {@link Observer}. *

- * + * *

- * {@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. + * This Observable is useful primarily for testing purposes. * - * @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() + * @param + * the type of items (not) emitted by the Observable + * @return an Observable that never emits any items or sends any notifications to an {@link Observer} + * @see RxJava Wiki: never() */ - 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)); + public final static Observable never() { + return new NeverObservable(); } /** - * 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. + * Converts an {@code Observable>} into another {@code Observable>} whose + * emitted Observables emit the same items, but the number of such Observables is restricted by + * {@code parallelObservables}. *

- * + * For example, if the original {@code Observable>} emits 100 Observables and + * {@code parallelObservables} is 8, the items emitted by the 100 original Observables will be distributed + * among 8 Observables 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. + * This is a mechanism for efficiently processing n number of Observables on a smaller m + * number of resources (typically CPU cores). * - * @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() + * @param parallelObservables + * the number of Observables to merge into + * @return an Observable of Observables constrained in number by {@code parallelObservables} + * @see RxJava Wiki: parallelMerge() */ - public static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, - Func6 zipFunction) { - return create(OperationZip.zip(o1, o2, o3, o4, o5, o6, zipFunction)); + public final static Observable> parallelMerge(Observable> source, int parallelObservables) { + return OperationParallelMerge.parallelMerge(source, parallelObservables); } /** - * Returns an Observable that emits the results of a function of your - * choosing applied to combinations of seven items emitted, in sequence, by - * seven other Observables. + * Converts an {@code Observable>} into another {@code Observable>} whose + * emitted Observables emit the same items, but the number of such Observables is restricted by + * {@code parallelObservables}, and each runs on a defined Scheduler. *

- * + * For example, if the original {@code Observable>} emits 100 Observables and + * {@code parallelObservables} is 8, the items emitted by the 100 original Observables will be distributed + * among 8 Observables 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. + * This is a mechanism for efficiently processing n number of Observables on a smaller m + * number of resources (typically CPU cores). * - * @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() + * @param parallelObservables + * the number of Observables to merge into + * @param scheduler + * the {@link Scheduler} to run each Observable on + * @return an Observable of Observables constrained in number by {@code parallelObservables} + * @see RxJava Wiki: parallelMerge() */ - public static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, - Func7 zipFunction) { - return create(OperationZip.zip(o1, o2, o3, o4, o5, o6, o7, zipFunction)); + public final static Observable> parallelMerge(Observable> source, int parallelObservables, Scheduler scheduler) { + return OperationParallelMerge.parallelMerge(source, parallelObservables, scheduler); } /** - * Returns an Observable that emits the results of a function of your - * choosing applied to combinations of eight items emitted, in sequence, by - * eight 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 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. + * Returns an Observable that emits a sequence of Integers within a specified range. *

- * 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) { - return create(OperationZip.zip(o1, o2, o3, o4, o5, o6, o7, o8, zipFunction)); + * @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 + * @throws IllegalArgumentException + * if {@code count} is less than zero + * @throws IllegalArgumentException + * if {@code start} + {@code count} exceeds {@code Integer.MAX_VALUE} + * @see RxJava Wiki: range() + * @see MSDN: Observable.Range + */ + public final static Observable range(int start, int count) { + if (count < 0) { + throw new IllegalArgumentException("Count can not be negative"); + } + if ((start + count) > Integer.MAX_VALUE) { + throw new IllegalArgumentException("start + count can not exceed Integer.MAX_VALUE"); + } + return Observable.create(new OnSubscribeRange(start, start + (count - 1))); } /** - * Returns an Observable that emits the results of a function of your - * choosing applied to combinations of nine items emitted, in sequence, by - * nine 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 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. + * Returns an Observable that emits a sequence of Integers within a specified range, on a specified + * Scheduler. *

- * 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() + * @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 MSDN: Observable.Range */ - public static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, - Observable o9, Func9 zipFunction) { - return create(OperationZip.zip(o1, o2, o3, o4, o5, o6, o7, o8, o9, zipFunction)); + public final static Observable range(int start, int count, Scheduler scheduler) { + return range(start, count).subscribeOn(scheduler); } /** - * 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. + * Returns an Observable that emits a Boolean value that indicates whether two Observable sequences are the + * same by comparing the items emitted by each Observable pairwise. *

- * + * * - * @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() + * @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 a Boolean value that indicates whether the two sequences are the same + * @see RxJava Wiki: sequenceEqual() */ - public static Observable combineLatest(Observable o1, Observable o2, Func2 combineFunction) { - return create(OperationCombineLatest.combineLatest(o1, o2, combineFunction)); + public final static Observable sequenceEqual(Observable first, Observable second) { + return sequenceEqual(first, second, new Func2() { + @Override + public final Boolean call(T first, T second) { + if (first == null) { + return second == null; + } + return first.equals(second); + } + }); } /** - * 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. + * Returns an Observable that emits a Boolean value that indicates whether two Observable sequences are the + * same by comparing the items emitted by each Observable pairwise based on the results of a specified + * equality 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() + * @param first + * the first Observable to compare + * @param second + * the second Observable to compare + * @param equality + * a function used to compare items emitted by each Observable + * @param + * the type of items emitted by each Observable + * @return an Observable that emits a Boolean value that indicates whether the two Observable two sequences + * are the same according to the specified function + * @see RxJava Wiki: sequenceEqual() */ - public static Observable combineLatest(Observable o1, Observable o2, Observable o3, Func3 combineFunction) { - return create(OperationCombineLatest.combineLatest(o1, o2, o3, combineFunction)); + public final static Observable sequenceEqual(Observable first, Observable second, Func2 equality) { + return OperationSequenceEqual.sequenceEqual(first, second, equality); } /** - * 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. + * Returns an Observable that emits the sum of all the Doubles emitted by the source 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 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) { - return create(OperationCombineLatest.combineLatest(o1, o2, o3, o4, combineFunction)); + * @param source + * the source Observable to compute the sum of + * @return an Observable that emits a single item: the sum of all the Doubles emitted by the source + * Observable + * @see RxJava Wiki: sumDouble() + * @see MSDN: Observable.Sum + */ + public final static Observable sumDouble(Observable source) { + return OperationSum.sumDoubles(source); } /** - * 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. + * Returns an Observable that emits the sum of all the Floats emitted by the source 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) { - return create(OperationCombineLatest.combineLatest(o1, o2, o3, o4, o5, combineFunction)); + * @param source + * the source Observable to compute the sum of + * @return an Observable that emits a single item: the sum of all the Floats emitted by the source + * Observable + * @see RxJava Wiki: sumFloat() + * @see MSDN: Observable.Sum + */ + public final static Observable sumFloat(Observable source) { + return OperationSum.sumFloats(source); } /** - * 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. + * Returns an Observable that emits the sum of all the Integers emitted by the source 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) { - return create(OperationCombineLatest.combineLatest(o1, o2, o3, o4, o5, o6, combineFunction)); + * @param source + * source Observable to compute the sum of + * @return an Observable that emits a single item: the sum of all the Integers emitted by the source + * Observable + * @see RxJava Wiki: sumInteger() + * @see MSDN: Observable.Sum + */ + public final static Observable sumInteger(Observable source) { + return OperationSum.sumIntegers(source); } /** - * 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. + * Returns an Observable that emits the sum of all the Longs emitted by the source 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 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) { - return create(OperationCombineLatest.combineLatest(o1, o2, o3, o4, o5, o6, o7, combineFunction)); + * @param source + * source Observable to compute the sum of + * @return an Observable that emits a single item: the sum of all the Longs emitted by the + * source Observable + * @see RxJava Wiki: sumLong() + * @see MSDN: Observable.Sum + */ + public final static Observable sumLong(Observable source) { + return OperationSum.sumLongs(source); } /** - * 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. + * Given an Observable that emits Observables, returns an Observable that emits the items emitted by the + * most recently emitted of those Observables. *

- * - * - * @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) { - return create(OperationCombineLatest.combineLatest(o1, o2, o3, o4, o5, o6, o7, o8, combineFunction)); - } - - /** - * 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. + * *

- * + * {@code switchDo()} subscribes to an Observable that emits Observables. Each time it observes one of these + * emitted Observables, the Observable returned by {@code switchDo()} begins emitting the items emitted by + * that Observable. When a new Observable is emitted, {@code switchDo()} stops emitting items from the + * earlier-emitted Observable and begins emitting items from the new one. * - * @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) { - return create(OperationCombineLatest.combineLatest(o1, o2, o3, o4, o5, o6, o7, o8, o9, combineFunction)); + * @param sequenceOfSequences + * the source Observable that emits Observables + * @return an Observable that emits the items emitted by the Observable most recently emitted by the source + * Observable + * @see RxJava Wiki: switchOnNext() + * @deprecated use {@link #switchOnNext} + */ + @Deprecated + public final static Observable switchDo(Observable> sequenceOfSequences) { + return create(OperationSwitch.switchDo(sequenceOfSequences)); } -/** - * Creates an Observable that produces buffers of collected items. + /** + * Given an Observable that emits Observables, returns an Observable that emits the items emitted by the + * most recently emitted of those Observables. *

- * + * *

- * 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 an - * object. The bufferClosingSelector - * will then be used to create a new Observable to listen for the end of - * the next buffer. + * {@code switchLatest()} subscribes to an Observable that emits Observables. Each time it observes one of + * these emitted Observables, the Observable returned by {@code switchLatest()} begins emitting the items + * emitted by that Observable. When a new Observable is emitted, {@code switchLatest()} stops emitting items + * from the earlier-emitted Observable and begins emitting items from the new one. * - * @param bufferClosingSelector the {@link Func0} which is used to produce - * an {@link Observable} for every buffer - * created. When this {@link Observable} - * produces an 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 an - * object - * @see RxJava Wiki: buffer() - */ - public Observable> buffer(Func0> bufferClosingSelector) { - return create(OperationBuffer.buffer(this, bufferClosingSelector)); + * @param sequenceOfSequences + * the source Observable that emits Observables + * @return an Observable that emits the items emitted by the Observable most recently emitted by the source + * Observable + * @see RxJava Wiki: switchOnNext() + * @see {@link #switchOnNext(Observable)} + */ + public final static Observable switchLatest(Observable> sequenceOfSequences) { + return create(OperationSwitch.switchDo(sequenceOfSequences)); } /** - * Creates an Observable which produces buffers of collected values. + * Given an Observable that emits Observables, returns an Observable that emits the items emitted by the + * most recently emitted of those Observables. *

- * + * *

- * This Observable produces buffers. Buffers are created when the specified - * bufferOpenings Observable produces an - * object. Additionally the bufferClosingSelector argument is - * used to create an Observable which produces - * objects. When this Observable produces such an object, the associated - * buffer is emitted. - * - * @param bufferOpenings the {@link Observable} that, when it produces an - * 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 an 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)); + * {@code switchOnNext()} subscribes to an Observable that emits Observables. Each time it observes one of + * these emitted Observables, the Observable returned by {@code switchOnNext()} begins emitting the items + * emitted by that Observable. When a new Observable is emitted, {@code switchOnNext()} stops emitting items + * from the earlier-emitted Observable and begins emitting items from the new one. + * + * @param sequenceOfSequences + * the source Observable that emits Observables + * @return an Observable that emits the items emitted by the Observable most recently emitted by the source + * Observable + * @see RxJava Wiki: switchOnNext() + */ + public final static Observable switchOnNext(Observable> sequenceOfSequences) { + return create(OperationSwitch.switchDo(sequenceOfSequences)); } /** - * 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} that produces connected, non-overlapping - * buffers containing at most "count" items - * @see RxJava Wiki: buffer() + * @deprecated use {@link #synchronize()} or {@link #synchronize(Object)} */ - public Observable> buffer(int count) { - return create(OperationBuffer.buffer(this, count)); + @Deprecated + public final static Observable synchronize(Observable source) { + return create(OperationSynchronize.synchronize(source)); } /** - * Creates an Observable which produces buffers of collected items. - *

- * + * Return an Observable that emits a 0L after the {@code initialDelay} and ever increasing numbers after + * each {@code period} of time thereafter. *

- * 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 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)); + * + * + * @param initialDelay + * the initial delay time to wait before emitting the first value of 0L + * @param period + * the period of time between emissions of the subsequent numbers + * @param unit + * the time unit for both {@code initialDelay} and {@code period} + * @return an Observable that emits a 0L after the {@code initialDelay} and ever increasing numbers after + * each {@code period} of time thereafter + * @see RxJava Wiki: timer() + * @see MSDN: Observable.Timer + */ + public final static Observable timer(long initialDelay, long period, TimeUnit unit) { + return timer(initialDelay, period, unit, Schedulers.computation()); } /** - * Creates an Observable that produces buffers of collected values. - *

- * + * Return an Observable that emits a 0L after the {@code initialDelay} and ever increasing numbers after + * each {@code period} of time thereafter, on a specified Scheduler. *

- * 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 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() + * @param initialDelay + * the initial delay time to wait before emitting the first value of 0L + * @param period + * the period of time between emissions of the subsequent numbers + * @param unit + * the time unit for both {@code initialDelay} and {@code period} + * @param scheduler + * the Scheduler on which the waiting happens and items are emitted + * @return an Observable that emits a 0L after the {@code initialDelay} and ever increasing numbers after + * each {@code period} of time thereafter, while running on the given Scheduler + * @see RxJava Wiki: timer() + * @see MSDN: Observable.Timer */ - public Observable> buffer(long timespan, TimeUnit unit) { - return create(OperationBuffer.buffer(this, timespan, unit)); + public final static Observable timer(long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { + return create(new OperationTimer.TimerPeriodically(initialDelay, period, unit, scheduler)); } /** - * Creates an Observable that produces buffers of collected values. - *

- * + * Returns an Observable that emits one item after a specified delay, and then completes. *

- * 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 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)); + * + * + * @param delay + * the initial delay before emitting a single 0L + * @param unit + * time units to use for {@code delay} + * @see RxJava wiki: timer() + */ + public final static Observable timer(long delay, TimeUnit unit) { + return timer(delay, unit, Schedulers.computation()); } /** - * 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. + * Returns an Observable that emits one item after a specified delay, on a specified Scheduler, and then + * completes. *

- * + * * - * @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)); + * @param delay + * the initial delay before emitting a single 0L + * @param unit + * time units to use for {@code delay} + * @param scheduler + * the Scheduler to use for scheduling the item + * @see RxJava wiki: timer() + */ + public final static Observable timer(long delay, TimeUnit unit, Scheduler scheduler) { + return create(new OperationTimer.TimerOnce(delay, unit, scheduler)); } /** - * 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. + * Constructs an Observable that creates a dependent resource object. *

- * + * * - * @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)); + * @param resourceFactory + * the factory function to create 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 final static Observable using(Func0 resourceFactory, Func1> observableFactory) { + return create(OperationUsing.using(resourceFactory, observableFactory)); } /** - * 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. + * Joins together the results from several patterns via their plans. *

- * + * * - * @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)); + * @param plans + * a series of plans created by use of the {@link #then} Observer on patterns + * @return an Observable that emits the results from matching several patterns + * @throws NullPointerException + * if {@code plans} is null + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + public final static Observable when(Iterable> plans) { + if (plans == null) { + throw new NullPointerException("plans"); + } + return create(OperationJoinPatterns.when(plans)); } /** - * 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. + * Joins together the results from several patterns via their plans. *

- * + * * - * @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)); + * @param plans + * a series of plans created by use of the {@link #then} Observer on patterns + * @return an Observable that emits the results from matching several patterns + * @throws NullPointerException + * if {@code plans} is null + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + public final static Observable when(Plan0... plans) { + return create(OperationJoinPatterns.when(plans)); } /** - * 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 an - * object. The closingSelector will - * then be used to create a new Observable to listen for the end of the next - * window. + * Joins the results from a pattern via its plan. *

- * + * * - * @param closingSelector the {@link Func0} used to produce an - * {@link Observable} for every window created. When this - * {@link Observable} emits an 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 an - * object. - * @see RxJava Wiki: window() - */ - public Observable> window(Func0> closingSelector) { - return create(OperationWindow.window(this, closingSelector)); + * @param p1 + * the plan to join, created by use of the {@link #then} Observer on a pattern + * @return an Observable that emits the results from matching a pattern + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + @SuppressWarnings("unchecked") + public final static Observable when(Plan0 p1) { + return create(OperationJoinPatterns.when(p1)); } /** - * Creates an Observable that produces windows of collected items. This - * Observable produces windows. Chunks are created when the - * windowOpenings Observable produces an - * object. Additionally the closingSelector argument creates an - * Observable that produces objects. When this - * Observable produces such an object, the associated window is emitted. + * Joins together the results from two patterns via their plans. *

- * + * * - * @param windowOpenings the {@link Observable} that, when it produces an - * 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 an - * 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)); + * @param p1 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p2 + * a plan, created by use of the {@link #then} Observer on a pattern + * @return an Observable that emits the results from matching two patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + @SuppressWarnings("unchecked") + public final static Observable when(Plan0 p1, Plan0 p2) { + return create(OperationJoinPatterns.when(p1, p2)); } - + /** - * 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. + * Joins together the results from three patterns via their plans. *

- * + * * - * @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() + * @param p1 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p2 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p3 + * a plan, created by use of the {@link #then} Observer on a pattern + * @return an Observable that emits the results from matching three patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When */ - public Observable> window(int count) { - return create(OperationWindow.window(this, count)); + @SuppressWarnings("unchecked") + public final static Observable when(Plan0 p1, Plan0 p2, Plan0 p3) { + return create(OperationJoinPatterns.when(p1, p2, p3)); } /** - * 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. + * Joins together the results from four patterns via their plans. *

- * + * * - * @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() + * @param p1 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p2 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p3 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p4 + * a plan, created by use of the {@link #then} Observer on a pattern + * @return an Observable that emits the results from matching four patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When */ - public Observable> window(int count, int skip) { - return create(OperationWindow.window(this, count, skip)); + @SuppressWarnings("unchecked") + public final static Observable when(Plan0 p1, Plan0 p2, Plan0 p3, Plan0 p4) { + return create(OperationJoinPatterns.when(p1, p2, p3, p4)); } /** - * 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. + * Joins together the results from five patterns via their plans. *

- * + * * - * @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() + * @param p1 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p2 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p3 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p4 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p5 + * a plan, created by use of the {@link #then} Observer on a pattern + * @return an Observable that emits the results from matching five patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When */ - public Observable> window(long timespan, TimeUnit unit) { - return create(OperationWindow.window(this, timespan, unit)); + @SuppressWarnings("unchecked") + public final static Observable when(Plan0 p1, Plan0 p2, Plan0 p3, Plan0 p4, Plan0 p5) { + return create(OperationJoinPatterns.when(p1, p2, p3, p4, p5)); } /** - * 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. + * Joins together the results from six patterns via their plans. *

- * + * * - * @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)); + * @param p1 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p2 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p3 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p4 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p5 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p6 + * a plan, created by use of the {@link #then} Observer on a pattern + * @return an Observable that emits the results from matching six patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + @SuppressWarnings("unchecked") + public final 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)); } /** - * 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. + * Joins together the results from seven patterns via their plans. *

- * + * * - * @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)); + * @param p1 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p2 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p3 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p4 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p5 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p6 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p7 + * a plan, created by use of the {@link #then} Observer on a pattern + * @return an Observable that emits the results from matching seven patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + @SuppressWarnings("unchecked") + public final 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)); } /** - * 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. + * Joins together the results from eight patterns via their plans. *

- * + * * - * @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)); + * @param p1 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p2 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p3 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p4 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p5 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p6 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p7 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p8 + * a plan, created by use of the {@link #then} Observer on a pattern + * @return an Observable that emits the results from matching eight patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + @SuppressWarnings("unchecked") + public final 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)); } /** - * 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. + * Joins together the results from nine patterns via their plans. *

- * + * * - * @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)); + * @param p1 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p2 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p3 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p4 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p5 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p6 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p7 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p8 + * a plan, created by use of the {@link #then} Observer on a pattern + * @param p9 + * a plan, created by use of the {@link #then} Observer on a pattern + * @return an Observable that emits the results from matching nine patterns + * @see RxJava Wiki: when() + * @see MSDN: Observable.When + */ + @SuppressWarnings("unchecked") + public final 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)); } /** - * 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. + * Returns an Observable that emits the results of a function of your choosing applied to combinations of + * items emitted, in sequence, by an Iterable 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 each 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 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)); + * @param ws + * an Iterable 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 final static Observable zip(Iterable> ws, FuncN zipFunction) { + List> os = new ArrayList>(); + for (Observable o : ws) { + os.add(o); + } + return Observable.just(os.toArray(new Observable[os.size()])).lift(new OperatorZip(zipFunction)); } /** - * 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. + * Returns an Observable that emits the results of a function of your choosing applied to combinations of + * n items emitted, in sequence, by the n Observables emitted by a specified 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 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. + * {@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 each of the Observables emitted + * by the 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 {@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 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 Observables emitted by + * {@code ws}, results in an item that will be emitted by the resulting Observable * @return an Observable that emits the zipped results - * @see RxJava Wiki: zip() + * @see RxJava Wiki: zip() */ - public static Observable zip(Observable> ws, final FuncN zipFunction) { - return ws.toList().mapMany(new Func1>, Observable>() { + public final static Observable zip(Observable> ws, final FuncN zipFunction) { + return ws.toList().map(new Func1>, Observable[]>() { + @Override - public Observable call(List> wsList) { - return create(OperationZip.zip(wsList, zipFunction)); + public Observable[] call(List> o) { + return o.toArray(new Observable[o.size()]); } - }); + + }).lift(new OperatorZip(zipFunction)); } /** - * 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. + * 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 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. + * {@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 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 o1 + * the first source Observable + * @param o2 + * a second 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() + * @see RxJava Wiki: zip() */ - public static Observable zip(Iterable> ws, FuncN zipFunction) { - return create(OperationZip.zip(ws, zipFunction)); + public final static Observable zip(Observable o1, Observable o2, final Func2 zipFunction) { + return just(new Observable[] { o1, o2 }).lift(new OperatorZip(zipFunction)); } /** - * Filter items emitted by an Observable. + * 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 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 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() + * @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 Observable filter(Func1 predicate) { - return create(OperationFilter.filter(this, predicate)); + public final static Observable zip(Observable o1, Observable o2, Observable o3, Func3 zipFunction) { + return just(new Observable[] { o1, o2, o3 }).lift(new OperatorZip(zipFunction)); } /** - * Returns an Observable that forwards all sequentially distinct items - * emitted from the source Observable. + * 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 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. * - * @return an Observable of sequentially distinct items - * @see RxJava Wiki: distinctUntilChanged() - * @see MSDN: Observable.distinctUntilChanged + * @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 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 Observable distinctUntilChanged() { - return create(OperationDistinctUntilChanged.distinctUntilChanged(this)); + public final static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Func4 zipFunction) { + return just(new Observable[] { o1, o2, o3, o4 }).lift(new OperatorZip(zipFunction)); } /** - * 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 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 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 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 RxJava Wiki: distinctUntilChanged() - * @see MSDN: Observable.distinctUntilChanged + * @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 Observable distinctUntilChanged(Func1 keySelector) { - return create(OperationDistinctUntilChanged.distinctUntilChanged(this, keySelector)); + public final static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Func5 zipFunction) { + return just(new Observable[] { o1, o2, o3, o4, o5 }).lift(new OperatorZip(zipFunction)); } /** - * Returns an Observable that emits all distinct items emitted from the - * source Observable. + * 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 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. * - * @return an Observable of distinct items - * @see RxJava Wiki: distinct() - * @see MSDN: Observable.distinct + * @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 Observable distinct() { - return create(OperationDistinct.distinct(this)); + public final static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, + Func6 zipFunction) { + return just(new Observable[] { o1, o2, o3, o4, o5, o6 }).lift(new OperatorZip(zipFunction)); } /** - * Returns an Observable that emits all items emitted from the source - * Observable that are distinct according to a key selector function. + * Returns an Observable that emits the results of a function of your choosing applied to combinations of + * seven items emitted, in sequence, by seven 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 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 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 + * @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 Observable distinct(Func1 keySelector) { - return create(OperationDistinct.distinct(this, keySelector)); + public final static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, + Func7 zipFunction) { + return just(new Observable[] { o1, o2, o3, o4, o5, o6, o7 }).lift(new OperatorZip(zipFunction)); } /** - * Returns the item at a specified index in a sequence. + * Returns an Observable that emits the results of a function of your choosing applied to combinations of + * eight items emitted, in sequence, by eight 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 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 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() + * @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 Observable elementAt(int index) { - return create(OperationElementAt.elementAt(this, index)); + public final static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, + Func8 zipFunction) { + return just(new Observable[] { o1, o2, o3, o4, o5, o6, o7, o8 }).lift(new OperatorZip(zipFunction)); } /** - * Returns the item at a specified index in a sequence or the default item - * if the index is out of range. + * Returns an Observable that emits the results of a function of your choosing applied to combinations of + * nine items emitted, in sequence, by nine 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 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 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() + * @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 Observable elementAtOrDefault(int index, T defaultValue) { - return create(OperationElementAt.elementAtOrDefault(this, index, defaultValue)); + public final static Observable zip(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5, Observable o6, Observable o7, Observable o8, + Observable o9, Func9 zipFunction) { + return just(new Observable[] { o1, o2, o3, o4, o5, o6, o7, o8, o9 }).lift(new OperatorZip(zipFunction)); } /** - * 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. + * Synonymous with {@code reduce()}. *

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

- * + * * - * @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. + * @see RxJava Wiki: reduce() + * @see #reduce(Func2) + * @deprecated use {@link #reduce(Func2)} */ - public Observable exists(Func1 predicate) { - return create(OperationAny.exists(this, predicate)); + @Deprecated + public final Observable aggregate(Func2 accumulator) { + return reduce(accumulator); } /** - * Determines whether an Observable sequence contains a specified item. + * Synonymous with {@code reduce()}. *

- * + * * - * @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 + * @see RxJava Wiki: reduce() + * @see #reduce(Object, Func2) + * @deprecated use {@link #reduce(Object, Func2)} */ - public Observable contains(final T element) { - return exists(new Func1() { - public Boolean call(T t1) { - return element == null ? t1 == null : element.equals(t1); - } - }); + @Deprecated + public final Observable aggregate(R initialValue, Func2 accumulator) { + return reduce(initialValue, accumulator); } /** - * Registers an {@link Action0} to be called when this Observable invokes - * {@link Observer#onCompleted onCompleted} or - * {@link Observer#onError onError}. + * Returns an Observable that emits a Boolean that indicates whether all of the items emitted by the source + * Observable satisfy a condition. *

- * + * * - * @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 + * @param predicate + * a function that evaluates an item and returns a Boolean + * @return an Observable that emits {@code true} if all items emitted by the source Observable satisfy the + * predicate; otherwise, {@code false} + * @see RxJava Wiki: all() */ - public Observable finallyDo(Action0 action) { - return create(OperationFinally.finallyDo(this, action)); + public final Observable all(Func1 predicate) { + return create(OperationAll.all(this, predicate)); } /** - * 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 a Pattern that matches when both Observables emit an item. *

- * - *

- * 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. - * @see RxJava Wiki: flatMap() - * @see #mapMany(Func1) + * @param right + * an Observable to match with the source Observable + * @return a Pattern object that matches when both Observables emit an item + * @throws NullPointerException + * if {@code right} is null + * @see RxJava Wiki: and() + * @see MSDN: Observable.And */ - public Observable flatMap(Func1> func) { - return mapMany(func); + public final Pattern2 and(Observable right) { + return OperationJoinPatterns.and(this, right); } /** - * Filter items emitted by an Observable. - *

- * + * Hides the identity of this Observable. Useful for instance when you have an implementation of a subclass + * of Observable but you want to hide the properties and methods of this subclass from whomever you are + * passing the Observable to. * - * @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) + * @return an Observable that hides the identity of this Observable */ - public Observable where(Func1 predicate) { - return filter(predicate); + public final Observable asObservable() { + return create(new OperationAsObservable(this)); } /** - * Returns an Observable that applies the given function to each item - * emitted by an Observable and emits the result. + * Returns an Observable that transforms items emitted by the source Observable into Doubles by using a + * function you provide and then emits the Double average of the complete sequence of transformed values. *

- * + * * - * @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 + * @param valueExtractor + * the function to transform an item emitted by the source Observable into a Double + * @return an Observable that emits a single item: the Double average of the complete sequence of items + * emitted by the source Observable when transformed into Doubles by the specified function + * @see RxJava Wiki: averageDouble() + * @see MSDN: Observable.Average */ - public Observable map(Func1 func) { - return create(OperationMap.map(this, func)); + public final Observable averageDouble(Func1 valueExtractor) { + return create(new OperationAverage.AverageDoubleExtractor(this, valueExtractor)); } /** - * Returns an Observable that applies the given function to each item - * emitted by an Observable and emits the result. + * Returns an Observable that transforms items emitted by the source Observable into Floats by using a + * function you provide and then emits the Float average of the complete sequence of transformed values. *

- * + * * - * @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 + * @param valueExtractor + * the function to transform an item emitted by the source Observable into a Float + * @return an Observable that emits a single item: the Float average of the complete sequence of items + * emitted by the source Observable when transformed into Floats by the specified function + * @see RxJava Wiki: averageFloat() + * @see MSDN: Observable.Average */ - public Observable mapWithIndex(Func2 func) { - return create(OperationMap.mapWithIndex(this, func)); + public final Observable averageFloat(Func1 valueExtractor) { + return create(new OperationAverage.AverageFloatExtractor(this, valueExtractor)); } /** - * 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 transforms items emitted by the source Observable into Integers by using a + * function you provide and then emits the Integer average of the complete sequence of transformed values. *

- * 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. - * @see RxJava Wiki: mapMany() - * @see #flatMap(Func1) + * @param valueExtractor + * the function to transform an item emitted by the source Observable into an Integer + * @return an Observable that emits a single item: the Integer average of the complete sequence of items + * emitted by the source Observable when transformed into Integers by the specified function + * @see RxJava Wiki: averageInteger() + * @see MSDN: Observable.Average */ - public Observable mapMany(Func1> func) { - return create(OperationMap.mapMany(this, func)); + public final Observable averageInteger(Func1 valueExtractor) { + return create(new OperationAverage.AverageIntegerExtractor(this, valueExtractor)); } /** - * 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. + * Returns an Observable that transforms items emitted by the source Observable into Longs by using a + * function you provide and then emits the Long average of the complete sequence of transformed values. *

- * + * * - * @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 + * @param valueExtractor + * the function to transform an item emitted by the source Observable into a Long + * @return an Observable that emits a single item: the Long average of the complete sequence of items + * emitted by the source Observable when transformed into Longs by the specified function + * @see RxJava Wiki: averageLong() + * @see MSDN: Observable.Average */ - public Observable> materialize() { - return create(OperationMaterialize.materialize(this)); + public final Observable averageLong(Func1 valueExtractor) { + return create(new OperationAverage.AverageLongExtractor(this, valueExtractor)); } /** - * Asynchronously subscribes and unsubscribes Observers on the specified - * {@link Scheduler}. + * Returns an Observable that emits buffers of items it collects from the source Observable. The resulting + * Observable emits connected, non-overlapping buffers. It emits the current buffer and replaces it with a + * new buffer when the Observable produced by the specified {@code bufferClosingSelector} emits an item. It + * then uses the {@code bufferClosingSelector} to create a new Observable to observe to indicate the end of + * the next buffer. *

- * + * * - * @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() + * @param bufferClosingSelector + * a {@link Func0} that produces an Observable for each buffer created. When this + * {@code Observable} emits an item, {@code buffer()} emits the associated buffer and replaces it + * with a new one + * @return an Observable that emits a connected, non-overlapping buffer of items from the source Observable + * each time the current Observable created with the {@code bufferClosingSelector} argument emits an + * item + * @see RxJava Wiki: buffer() */ - public Observable subscribeOn(Scheduler scheduler) { - return create(OperationSubscribeOn.subscribeOn(this, scheduler)); + public final Observable> buffer(Func0> bufferClosingSelector) { + return create(OperationBuffer.buffer(this, bufferClosingSelector)); } /** - * Asynchronously notify {@link Observer}s on the specified - * {@link Scheduler}. + * Returns an Observable that emits buffers of items it collects from the source Observable. The resulting + * Observable emits connected, non-overlapping buffers, each containing {@code count} items. When the source + * Observable completes or encounters an error, the resulting Observable emits the current buffer and + * propagates the notification from the source Observable. *

- * + * * - * @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() + * @param count + * the maximum number of items in each buffer before it should be emitted + * @return an Observable that emits connected, non-overlapping buffers, each containing at most + * {@code count} items from the source Observable + * @see RxJava Wiki: buffer() */ - public Observable observeOn(Scheduler scheduler) { - return create(OperationObserveOn.observeOn(this, scheduler)); + public final Observable> buffer(int count) { + return create(OperationBuffer.buffer(this, count)); } /** - * 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 emits buffers of items it collects from the source Observable. The resulting + * Observable emits buffers every {@code skip} items, each containing {@code count} items. When the source + * Observable completes or encounters an error, the resulting Observable emits the current buffer and + * propagates the notification from the source 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() { - return create(OperationDematerialize.dematerialize((Observable>) this)); + * @param count + * the maximum size of each buffer before it should be emitted + * @param skip + * how many items emitted by the source Observable should be skipped before starting a new + * buffer. Note that when {@code skip} and {@code count} are equal, this is the same operation as + * {@link #buffer(int)}. + * @return an Observable that emits buffers for every {@code skip} item from the source Observable and + * containing at most {@code count} items + * @see RxJava Wiki: buffer() + */ + public final Observable> buffer(int count, int skip) { + return create(OperationBuffer.buffer(this, count, skip)); } /** - * Instruct an Observable to pass control to another Observable rather than - * invoking {@link Observer#onError onError} if it encounters an error. - *

- * + * Returns an Observable that emits buffers of items it collects from the source Observable. The resulting + * Observable starts a new buffer periodically, as determined by the {@code timeshift} argument. It emits + * each buffer after a fixed timespan, specified by the {@code timespan} argument. When the source + * Observable completes or encounters an error, the resulting Observable emits the current buffer and + * propagates the notification from the source Observable. *

- * 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)); + * + * + * @param timespan + * the period of time each buffer collects items before it is 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 {@code timespan} and {@code timeshift} arguments + * @return an Observable that emits new buffers of items emitted by the source Observable periodically after + * a fixed timespan has elapsed + * @see RxJava Wiki: buffer() + */ + public final Observable> buffer(long timespan, long timeshift, TimeUnit unit) { + return create(OperationBuffer.buffer(this, timespan, timeshift, unit)); } /** - * Instruct an Observable to pass control to another Observable rather than - * invoking {@link Observer#onError onError} if it encounters an error. + * Returns an Observable that emits buffers of items it collects from the source Observable. The resulting + * Observable starts a new buffer periodically, as determined by the {@code timeshift} argument, and on the + * specified {@code scheduler}. It emits each buffer after a fixed timespan, specified by the + * {@code timespan} argument. When the source Observable completes or encounters an error, the resulting + * Observable emits the current buffer and propagates the notification from the source Observable. *

- * - *

- * 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 - * @return the original Observable, with appropriately modified behavior - * @see RxJava Wiki: onErrorResumeNext() + * + * + * @param timespan + * the period of time each buffer collects items before it is 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 {@code timespan} and {@code timeshift} arguments + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a buffer + * @return an Observable that emits new buffers of items emitted by the source Observable periodically after + * a fixed timespan has elapsed + * @see RxJava Wiki: buffer() */ - public Observable onErrorResumeNext(final Observable resumeSequence) { - return create(OperationOnErrorResumeNextViaObservable.onErrorResumeNextViaObservable(this, resumeSequence)); + public final Observable> buffer(long timespan, long timeshift, TimeUnit unit, Scheduler scheduler) { + return create(OperationBuffer.buffer(this, timespan, timeshift, unit, scheduler)); } /** - * 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}. + * Returns an Observable that emits buffers of items it collects from the source Observable. The resulting + * Observable emits connected, non-overlapping buffers, each of a fixed duration specified by the + * {@code timespan} argument. When the source Observable completes or encounters an error, the resulting + * Observable emits the current buffer and propagates the notification from the source Observable. *

- * 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 - * 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 - * @return the original Observable, with appropriately modified behavior - * @see RxJava Wiki: onExceptionResumeNextViaObservable() + * + * + * @param timespan + * the period of time each buffer collects items before it is emitted and replaced with a new + * buffer + * @param unit + * the unit of time that applies to the {@code timespan} argument + * @return an Observable that emits connected, non-overlapping buffers of items emitted by the source + * Observable within a fixed duration + * @see RxJava Wiki: buffer() */ - public Observable onExceptionResumeNext(final Observable resumeSequence) { - return create(OperationOnExceptionResumeNextViaObservable.onExceptionResumeNextViaObservable(this, resumeSequence)); + public final Observable> buffer(long timespan, TimeUnit unit) { + return create(OperationBuffer.buffer(this, timespan, unit)); } /** - * Instruct an Observable to emit an item (returned by a specified function) - * rather than invoking {@link Observer#onError onError} if it encounters an - * error. + * Returns an Observable that emits buffers of items it collects from the source Observable. The resulting + * Observable emits connected, non-overlapping buffers, each of a fixed duration specified by the + * {@code timespan} argument or a maximum size specified by the {@code count} argument (whichever is reached + * first). When the source Observable completes or encounters an error, the resulting Observable emits the + * current buffer and propagates the notification from the source Observable. *

- * - *

- * 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. - * - * @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)); + * + * + * @param timespan + * the period of time each buffer collects items before it is emitted and replaced with a new + * buffer + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param count + * the maximum size of each buffer before it is emitted + * @return an Observable that emits connected, non-overlapping buffers of items emitted by the source + * Observable, after a fixed duration or when the buffer reaches maximum capacity (whichever occurs + * first) + * @see RxJava Wiki: buffer() + */ + public final Observable> buffer(long timespan, TimeUnit unit, int count) { + return create(OperationBuffer.buffer(this, timespan, unit, count)); } /** - * 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 emits buffers of items it collects from the source Observable. The resulting + * Observable emits connected, non-overlapping buffers, each of a fixed duration specified by the + * {@code timespan} argument as measured on the specified {@code scheduler}, or a maximum size specified by + * the {@code count} argument (whichever is reached first). When the source Observable completes or + * encounters an error, the resulting Observable emits the current buffer and propagates the notification + * from the source Observable. *

- * - *

- * 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) + * @param timespan + * the period of time each buffer collects items before it is emitted and replaced with a new + * buffer + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param count + * the maximum size of each buffer before it is emitted + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a buffer + * @return an Observable that emits connected, non-overlapping buffers of items emitted by the source + * Observable after a fixed duration or when the buffer reaches maximum capacity (whichever occurs + * first) + * @see RxJava Wiki: buffer() */ - public Observable reduce(Func2 accumulator) { - /* - * 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(); + public final Observable> buffer(long timespan, TimeUnit unit, int count, Scheduler scheduler) { + return create(OperationBuffer.buffer(this, timespan, unit, count, scheduler)); } /** - * Returns an Observable that counts the total number of items in the - * source Observable. + * Returns an Observable that emits buffers of items it collects from the source Observable. The resulting + * Observable emits connected, non-overlapping buffers, each of a fixed duration specified by the + * {@code timespan} argument and on the specified {@code scheduler}. When the source Observable completes or + * encounters an error, the resulting Observable emits the current buffer and propagates the notification + * from 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 - * @see #longCount() + * @param timespan + * the period of time each buffer collects items before it is emitted and replaced with a new + * buffer + * @param unit + * the unit of time which applies to the {@code timespan} argument + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a buffer + * @return an Observable that emits connected, non-overlapping buffers of items emitted by the source + * Observable within a fixed duration + * @see RxJava Wiki: buffer() */ - public Observable count() { - return reduce(0, new Func2() { - @Override - public Integer call(Integer t1, T t2) { - return t1 + 1; - } - }); + public final Observable> buffer(long timespan, TimeUnit unit, Scheduler scheduler) { + return create(OperationBuffer.buffer(this, timespan, unit, scheduler)); } /** - * Returns an Observable that sums up the integers emitted by the source - * Observable. + * Returns an Observable that emits buffers of items it collects from the source Observable. The resulting + * Observable emits buffers that it creates when the specified {@code bufferOpenings} Observable emits an + * item, and closes when the Observable returned from {@code bufferClosingSelector} emits an item. *

- * + * * - * @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 + * @param bufferOpenings + * the Observable that, when it emits an item, causes a new buffer to be created + * @param bufferClosingSelector + * the {@link Func1} that is used to produce an Observable for every buffer created. When this + * Observable emits an item, the associated buffer is emitted. + * @return an Observable that emits buffers, containing items from the source Observable, that are created + * and closed when the specified Observables emit items + * @see RxJava Wiki: buffer() */ - public static Observable sum(Observable source) { - return OperationSum.sum(source); + public final Observable> buffer(Observable bufferOpenings, Func1> bufferClosingSelector) { + return create(OperationBuffer.buffer(this, bufferOpenings, bufferClosingSelector)); } /** - * Returns an Observable that sums up the longs emitted by the source - * Observable. + * Returns an Observable that emits non-overlapping buffered items from the source Observable each time the + * specified boundary Observable emits an item. *

- * + * + *

+ * Completion of either the source or the boundary Observable causes the returned Observable to emit the + * latest buffer and complete. * - * @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 + * @param + * the boundary value type (ignored) + * @param boundary + * the boundary Observable + * @return an Observable that emits buffered items from the source Observable when the boundary Observable + * emits an item + * @see #buffer(rx.Observable, int) + * @see RxJava Wiki: buffer() */ - public static Observable sumLongs(Observable source) { - return OperationSum.sumLongs(source); + public final Observable> buffer(Observable boundary) { + return create(OperationBuffer.bufferWithBoundaryObservable(this, boundary)); } /** - * Returns an Observable that sums up the floats emitted by the source - * Observable. + * Returns an Observable that emits non-overlapping buffered items from the source Observable each time the + * specified boundary Observable emits an item. *

- * + * + *

+ * Completion of either the source or the boundary Observable causes the returned Observable to emit the + * latest buffer and complete. * - * @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 + * @param + * the boundary value type (ignored) + * @param boundary + * the boundary Observable + * @param initialCapacity + * the initial capacity of each buffer chunk + * @return an Observable that emits buffered items from the source Observable when the boundary Observable + * emits an item + * @see RxJava Wiki: buffer() + * @see #buffer(rx.Observable, int) */ - public static Observable sumFloats(Observable source) { - return OperationSum.sumFloats(source); + public final Observable> buffer(Observable boundary, int initialCapacity) { + return create(OperationBuffer.bufferWithBoundaryObservable(this, boundary, initialCapacity)); } /** - * Returns an Observable that sums up the doubles emitted by the source - * Observable. + * 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 Subscriber}s. + *

+ * When you call {@code cache()}, it does not yet subscribe to the source Observable. This only happens when + * {@code subscribe} is called the first time on the Observable returned by {@code cache()}. + *

+ * + * + * + * Note: You sacrifice the ability to unsubscribe from the origin when you use the {@code cache()} + * Observer so be careful not to use this Observer on Observables that emit an infinite or very large number + * of items that will use up memory. * - * @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 + * @return an Observable that, when first subscribed to, caches all of its items and notifications for the + * benefit of subsequent observers + * @see RxJava Wiki: cache() */ - public static Observable sumDoubles(Observable source) { - return OperationSum.sumDoubles(source); + public final Observable cache() { + return create(OperationCache.cache(this)); } /** - * Returns an Observable that computes the average of the integers emitted - * by the source Observable. + * Returns an Observable that emits the items emitted by the source Observable, converted to the specified + * type. *

- * + * * - * @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 + * @param klass + * the target class type that the items emitted by the source Observable will be converted to + * before being emitted by the resulting Observable + * @return an Observable that emits each item from the source Observable after converting it to the + * specified type + * @see RxJava Wiki: cast() + * @see MSDN: Observable.Cast */ - public static Observable average(Observable source) { - return OperationAverage.average(source); + public final Observable cast(final Class klass) { + return lift(new OperatorCast(klass)); } /** - * Returns an Observable that computes the average of the longs emitted by - * the source Observable. + * Collect values into a single mutable data structure. + *

+ * This is a simplified version of {@code reduce} that does not need to return the state on each pass. *

- * * - * @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 + * @param state + * FIXME FIXME FIXME + * @param collector + * FIXME FIXME FIXME + * @return FIXME FIXME FIXME */ - public static Observable averageLongs(Observable source) { - return OperationAverage.averageLongs(source); + public final Observable collect(R state, final Action2 collector) { + Func2 accumulator = new Func2() { + + @Override + public final R call(R state, T value) { + collector.call(state, value); + return state; + } + + }; + return reduce(state, accumulator); } /** - * Returns an Observable that computes the average of the floats emitted by - * the source Observable. + * Returns a new Observable that emits items resulting from applying a function that you supply to each item + * emitted by the source Observable, where that function returns an Observable, and then emitting the items + * that result from concatinating those resulting Observables. *

- * + * * - * @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 + * @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 concatinating the Observables obtained from this transformation */ - public static Observable averageFloats(Observable source) { - return OperationAverage.averageFloats(source); + public final Observable concatMap(Func1> func) { + return concat(map(func)); } /** - * Returns an Observable that computes the average of the doubles emitted - * by the source Observable. + * Returns an Observable that emits a Boolean that indicates whether the source Observable emitted a + * specified item. *

- * + * * - * @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 + * @param element + * the item to search for in the emissions from the source Observable + * @return an Observable that emits {@code true} if the specified item is emitted by the source Observable, + * or {@code false} if the source Observable completes without emitting that item + * @see RxJava Wiki: contains() + * @see MSDN: Observable.Contains */ - public static Observable averageDoubles(Observable source) { - return OperationAverage.averageDoubles(source); + public final Observable contains(final T element) { + return exists(new Func1() { + public final Boolean call(T t1) { + return element == null ? t1 == null : element.equals(t1); + } + }); } /** - * Returns the minimum item emitted by an Observable. If there are more than - * one minimum items, its returns the last one. + * Returns an Observable that emits the count of the total number of items emitted by the source Observable. *

- * - * - * @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 + * + * + * @return an Observable that emits a single item: the number of elements emitted by the source Observable + * @see RxJava Wiki: count() + * @see MSDN: Observable.Count + * @see #longCount() */ - public static > Observable min(Observable source) { - return OperationMinMax.min(source); + public final Observable count() { + return reduce(0, new Func2() { + @Override + public final Integer call(Integer t1, T t2) { + return t1 + 1; + } + }); } /** - * 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. + * Return an Observable that mirrors the source Observable, except that it drops items emitted by the source + * Observable that are followed by another item within a computed debounce duration. *

- * - * - * @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 + * + * + * @param + * the debounce value type (ignored) + * @param debounceSelector + * function to retrieve a sequence that indicates the throttle duration for each item + * @return an Observable that omits items emitted by the source Observable that are followed by another item + * within a computed debounce duration */ - public Observable min(Comparator comparator) { - return OperationMinMax.min(this, comparator); + public final Observable debounce(Func1> debounceSelector) { + return create(OperationDebounce.debounceSelector(this, debounceSelector)); } /** - * 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. + * Return an Observable that mirrors the source Observable, except that it drops items emitted by the source + * Observable that are followed by newer items before a timeout value expires. The timer resets on each + * emission. *

- * - * - * @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 + * Note: If items keep being emitted by the source Observable faster than the timeout then no items + * will be emitted by the resulting Observable. + *

+ * + *

+ * Information on debounce vs throttle: + *

+ *

+ * + * @param timeout + * the time each item has to be "the most recent" of those emitted by the source Observable to + * ensure that it's not dropped + * @param unit + * the {@link TimeUnit} for the timeout + * @return an Observable that filters out items from the source Observable that are too quickly followed by + * newer items + * @see RxJava Wiki: debounce() + * @see #throttleWithTimeout(long, TimeUnit) */ - public > Observable> minBy(Func1 selector) { - return OperationMinMax.minBy(this, selector); + public final Observable debounce(long timeout, TimeUnit unit) { + return create(OperationDebounce.debounce(this, timeout, unit)); } /** - * 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. + * Return an Observable that mirrors the source Observable, except that it drops items emitted by the source + * Observable that are followed by newer items before a timeout value expires on a specified Scheduler. The + * timer resets on each emission. *

- * - * - * @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 + * Note: If items keep being emitted by the source Observable faster than the timeout then no items + * will be emitted by the resulting Observable. + *

+ * + *

+ * Information on debounce vs throttle: + *

+ *

+ * + * @param timeout + * the time each item has to be "the most recent" of those emitted by the source 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 + * item + * @return an Observable that filters out items from the source Observable that are too quickly followed by + * newer items + * @see RxJava Wiki: debounce() + * @see #throttleWithTimeout(long, TimeUnit, Scheduler) */ - public Observable> minBy(Func1 selector, Comparator comparator) { - return OperationMinMax.minBy(this, selector, comparator); + public final Observable debounce(long timeout, TimeUnit unit, Scheduler scheduler) { + return create(OperationDebounce.debounce(this, timeout, unit, scheduler)); } /** - * Returns the maximum item emitted by an Observable. If there is more - * than one maximum item, it returns the last one. + * Returns an Observable that emits the items emitted by the source Observable or a specified default item + * if the source Observable is empty. *

- * - * - * @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 + * + * + * @param defaultValue + * the item to emit if the source Observable emits no items + * @return an Observable that emits either the specified default item if the source Observable emits no + * items, or the items emitted by the source Observable + * @see RxJava Wiki: defaultIfEmpty() + * @see MSDN: Observable.DefaultIfEmpty */ - public static > Observable max(Observable source) { - return OperationMinMax.max(source); + public final Observable defaultIfEmpty(T defaultValue) { + return create(OperationDefaultIfEmpty.defaultIfEmpty(this, defaultValue)); } /** - * 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. + * Returns an Observable that delays the subscription to and emissions from the souce Observable via another + * Observable on a per-item basis. *

- * - * - * @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 + * + *

+ * Note: the resulting Observable will immediately propagate any {@code onError} notification + * from the source Observable. + * + * @param + * the subscription delay value type (ignored) + * @param + * the item delay value type (ignored) + * @param subscriptionDelay + * a function that returns an Observable that triggers the subscription to the source Observable + * once it emits any item + * @param itemDelay + * a function that returns an Observable for each item emitted by the source Observable, which is + * then used to delay the emission of that item by the resulting Observable until the Observable + * returned from {@code itemDelay} emits an item + * @return an Observable that delays the subscription and emissions of the source Observable via another + * Observable on a per-item basis */ - public Observable max(Comparator comparator) { - return OperationMinMax.max(this, comparator); + public final Observable delay( + Func0> subscriptionDelay, + Func1> itemDelay) { + return create(OperationDelay.delay(this, subscriptionDelay, itemDelay)); } /** - * 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. + * Returns an Observable that delays the emissions of the source Observable via another Observable on a + * per-item basis. *

- * - * - * @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 + * + *

+ * Note: the resulting Observable will immediately propagate any {@code onError} notification + * from the source Observable. + * + * @param + * the item delay value type (ignored) + * @param itemDelay + * a function that returns an Observable for each item emitted by the source Observable, which is + * then used to delay the emission of that item by the resulting Observable until the Observable + * returned from {@code itemDelay} emits an item + * @return an Observable that delays the emissions of the source Observable via another Observable on a + * per-item basis */ - public > Observable> maxBy(Func1 selector) { - return OperationMinMax.maxBy(this, selector); + public final Observable delay(Func1> itemDelay) { + return create(OperationDelay.delay(this, itemDelay)); } /** - * 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. + * Returns an Observable that emits the items emitted by the source Observable shifted forward in time by a + * specified delay. Error notifications from the source Observable are not delayed. *

- * - * - * @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 + * + * + * @param delay + * the delay to shift the source by + * @param unit + * the {@link TimeUnit} in which {@code period} is defined + * @return the source Observable shifted in time by the specified delay + * @see RxJava Wiki: delay() + * @see MSDN: Observable.Delay */ - public Observable> maxBy(Func1 selector, Comparator comparator) { - return OperationMinMax.maxBy(this, selector, comparator); + public final Observable delay(long delay, TimeUnit unit) { + return OperationDelay.delay(this, delay, unit, Schedulers.computation()); } /** - * 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 an Observable that emits the items emitted by the source Observable shifted forward in time by a + * specified delay. Error notifications from the source Observable are not delayed. *

- * + * * - * @return a {@link ConnectableObservable} that upon connection causes the - * source Observable to emit items to its {@link Observer}s - * @see RxJava Wiki: replay() + * @param delay + * the delay to shift the source by + * @param unit + * the time unit of {@code delay} + * @param scheduler + * the {@link Scheduler} to use for delaying + * @return the source Observable shifted in time by the specified delay + * @see RxJava Wiki: delay() + * @see MSDN: Observable.Delay */ - public ConnectableObservable replay() { - return OperationMulticast.multicast(this, ReplaySubject. create()); + public final Observable delay(long delay, TimeUnit unit, Scheduler scheduler) { + return OperationDelay.delay(this, delay, unit, scheduler); } /** - * 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. + * Return an Observable that delays the subscription to the source Observable by a given amount of time. *

- * 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 an Observable with retry logic - * @see RxJava Wiki: retry() + * @param delay + * the time to delay the subscription + * @param unit + * the time unit of {@code delay} + * @return an Observable that delays the subscription to the source Observable by the given amount */ - public Observable retry(int retryCount) { - return create(OperationRetry.retry(this, retryCount)); + public final Observable delaySubscription(long delay, TimeUnit unit) { + return delaySubscription(delay, unit, Schedulers.computation()); } /** - * Retry subscription to origin Observable whenever onError is - * called (infinite retry count). + * Return an Observable that delays the subscription to the source Observable by a given amount of time, + * both waiting and subscribing on a given Scheduler. *

- * - *

- * 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. - *

- * 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() + * @param delay + * the time to delay the subscription + * @param unit + * the time unit of {@code delay} + * @param scheduler + * the Scheduler on which the waiting and subscription will happen + * @return an Observable that delays the subscription to the source Observable by a given + * amount, waiting and subscribing on the given Scheduler */ - public Observable retry() { - return create(OperationRetry.retry(this)); + public final Observable delaySubscription(long delay, TimeUnit unit, Scheduler scheduler) { + return create(OperationDelay.delaySubscription(this, delay, unit, scheduler)); } /** - * This method has similar behavior to {@link #replay} except that this - * auto-subscribes to the source Observable rather than returning a - * {@link ConnectableObservable}. - *

- * + * 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. *

- * 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. - *

- * When you call {@code cache()}, it does not yet subscribe to the - * source Observable. This only happens when {@code subscribe} is called - * the first time on the Observable returned by {@code cache()}. - *

- * 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. - * @see RxJava Wiki: cache() + * @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 */ - public Observable cache() { - return create(OperationCache.cache(this)); + @SuppressWarnings("unchecked") + public final Observable dematerialize() { + return create(OperationDematerialize.dematerialize((Observable>) this)); } /** - * Perform work in parallel by sharding an {@code Observable} on a - * {@link Schedulers#threadPoolForComputation()} {@link Scheduler} and - * return an {@code Observable} with the output. + * Returns an Observable that emits all items emitted by the source Observable that are distinct. *

- * + * * - * @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() + * @return an Observable that emits only those items emitted by the source Observable that are distinct from + * each other + * @see RxJava Wiki: distinct() + * @see MSDN: Observable.distinct */ - public Observable parallel(Func1, Observable> f) { - return OperationParallel.parallel(this, f); + public final Observable distinct() { + return create(OperationDistinct.distinct(this)); } /** - * Perform work in parallel by sharding an {@code Observable} on a - * {@link Scheduler} and return an {@code Observable} with the output. + * Returns an Observable that emits all items emitted by the source Observable that are distinct according + * to a key selector function. *

- * + * * - * @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() + * @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 those items emitted by the source Observable that have distinct keys + * @see RxJava Wiki: distinct() + * @see MSDN: Observable.distinct */ - public Observable parallel(final Func1, Observable> f, final Scheduler s) { - return OperationParallel.parallel(this, f, s); + public final Observable distinct(Func1 keySelector) { + return create(OperationDistinct.distinct(this, keySelector)); } - /** - * 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. + * Returns an Observable that emits all items emitted by the source Observable that are distinct from their + * immediate predecessors. *

- * 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() + * @return an Observable that emits those items from the source Observable that are distinct from their + * immediate predecessors + * @see RxJava Wiki: distinctUntilChanged() + * @see MSDN: Observable.distinctUntilChanged */ - public static Observable> parallelMerge(Observable> source, int parallelObservables) { - return OperationParallelMerge.parallelMerge(source, parallelObservables); + public final Observable distinctUntilChanged() { + return create(OperationDistinctUntilChanged.distinctUntilChanged(this)); } - + /** - * 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. + * Returns an Observable that emits all items emitted by the source Observable that are distinct from their + * immediate predecessors, according to a key selector function. *

- * 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 - * @param scheduler - * @return an Observable of Observables constrained to number defined by - * parallelObservables. - * @see RxJava Wiki: parallelMerge() + * @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 those items from the source Observable whose keys are distinct from + * those of their immediate predecessors + * @see RxJava Wiki: distinctUntilChanged() + * @see MSDN: Observable.distinctUntilChanged */ - public static Observable> parallelMerge(Observable> source, int parallelObservables, Scheduler scheduler) { - return OperationParallelMerge.parallelMerge(source, parallelObservables, scheduler); + public final Observable distinctUntilChanged(Func1 keySelector) { + return create(OperationDistinctUntilChanged.distinctUntilChanged(this, keySelector)); } - + /** - * 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. + * Modifies an Observable so that it invokes an action when it calls {@code onCompleted}. *

- * + * * - * @return a {@link ConnectableObservable} that upon connection causes the - * source Observable to emit items to its {@link Observer}s - * @see RxJava Wiki: publish() + * @param onCompleted + * the action to invoke when the source Observable calls {@code onCompleted} + * @return the source Observable with the side-effecting behavior applied + * @see RxJava Wiki: doOnCompleted() + * @see MSDN: Observable.Do */ - public ConnectableObservable publish() { - return OperationMulticast.multicast(this, PublishSubject. create()); + public final Observable doOnCompleted(final Action0 onCompleted) { + Observer observer = new Observer() { + @Override + public final void onCompleted() { + onCompleted.call(); + } + + @Override + public final void onError(Throwable e) { + } + + @Override + public final void onNext(T args) { + } + + }; + + return lift(new OperatorDoOnEach(observer)); } /** - * Returns a {@link ConnectableObservable} that shares a single subscription - * that contains the last notification only. + * Modifies an Observable so that it invokes an action for each item it emits. *

- * + * * - * @return a {@link ConnectableObservable} - * @see RxJava Wiki: publishLast() + * @param observer + * the action to invoke for each item emitted by the source Observable + * @return the source Observable with the side-effecting behavior applied + * @see RxJava Wiki: doOnEach() + * @see MSDN: Observable.Do */ - public ConnectableObservable publishLast() { - return OperationMulticast.multicast(this, AsyncSubject. create()); - } + public final Observable doOnEach(final Action1> onNotification) { + Observer observer = new Observer() { + @Override + public final void onCompleted() { + onNotification.call(new Notification()); + } - /** - * Synonymous with reduce(). - *

- * - * - * @see RxJava Wiki: aggregate() - * @see #reduce(Func2) - */ - public Observable aggregate(Func2 accumulator) { - return reduce(accumulator); + @Override + public final void onError(Throwable e) { + onNotification.call(new Notification(e)); + } + + @Override + public final void onNext(T v) { + onNotification.call(new Notification(v)); + } + + }; + + return lift(new OperatorDoOnEach(observer)); } /** - * 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. + * Modifies an Observable so that it notifies an Observer for each item it emits. *

- * - *

- * 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) + * @param observer + * the action to invoke for each item emitted by the source Observable + * @return the source Observable with the side-effecting behavior applied + * @see RxJava Wiki: doOnEach() + * @see MSDN: Observable.Do */ - public Observable reduce(R initialValue, Func2 accumulator) { - return create(OperationScan.scan(this, initialValue, accumulator)).takeLast(1); + public final Observable doOnEach(Observer observer) { + return lift(new OperatorDoOnEach(observer)); } /** - * Synonymous with reduce(). + * Modifies an Observable so that it invokes an action if it calls {@code onError}. *

- * + * * - * @see RxJava Wiki: aggregate() - * @see #reduce(Object, Func2) + * @param onError + * the action to invoke if the source Observable calls {@code onError} + * @return the source Observable with the side-effecting behavior applied + * @see RxJava Wiki: doOnError() + * @see MSDN: Observable.Do */ - public Observable aggregate(R initialValue, Func2 accumulator) { - return reduce(initialValue, accumulator); + public final Observable doOnError(final Action1 onError) { + Observer observer = new Observer() { + @Override + public final void onCompleted() { + } + + @Override + public final void onError(Throwable e) { + onError.call(e); + } + + @Override + public final void onNext(T args) { + } + + }; + + return lift(new OperatorDoOnEach(observer)); } /** - * 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. + * Modifies an Observable so that it invokes an action when it calls {@code onNext}. *

- * 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 RxJava Wiki: scan() - * @see MSDN: Observable.Scan + * @param onNext + * the action to invoke when the source Observable calls {@code onNext} + * @return the source Observable with the side-effecting behavior applied + * @see RxJava Wiki: doOnNext() + * @see MSDN: Observable.Do */ - public Observable scan(Func2 accumulator) { - return create(OperationScan.scan(this, accumulator)); + public final Observable doOnNext(final Action1 onNext) { + Observer observer = new Observer() { + @Override + public final void onCompleted() { + } + + @Override + public final void onError(Throwable e) { + } + + @Override + public final void onNext(T args) { + onNext.call(args); + } + + }; + + return lift(new OperatorDoOnEach(observer)); } /** - * 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 single item at a specified index in a sequence of emissions from a + * source Observbable. *

- * + * * - * @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)); + * @param index + * the zero-based index of the item to retrieve + * @return an Observable that emits a single item: the item at the specified position in the sequence of + * those emitted by the source Observable + * @throws IndexOutOfBoundsException + * if {@code index} is greater than or equal to the number of items emitted by the source + * Observable + * @throws IndexOutOfBoundsException + * if {@code index} is less than 0 + * @see RxJava Wiki: elementAt() + */ + public final Observable elementAt(int index) { + return create(OperationElementAt.elementAt(this, index)); } /** - * 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 item found at a specified index in a sequence of emissions from a + * source Observable, or a default item if that index is out of range. *

- * - * - * @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)); - } - - /** - * Return an Observable that emits the results of sampling the items - * emitted by this Observable when the sampler - * Observable produces an item or completes. - * - * @param sampler the Observable to use for sampling this + * * - * @return an Observable that emits the results of sampling the items - * emitted by this Observable when the sampler - * Observable produces an item or completes. - */ - public Observable sample(Observable sampler) { - return create(new OperationSample.SampleWithObservable(this, sampler)); + * @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 sequence emitted by the source + * Observable, or the default item if that index is outside the bounds of the source sequence + * @throws IndexOutOfBoundsException + * if {@code index} is less than 0 + * @see RxJava Wiki: elementAtOrDefault() + */ + public final Observable elementAtOrDefault(int index, T defaultValue) { + return create(OperationElementAt.elementAtOrDefault(this, index, defaultValue)); } - + /** - * 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 emits {@code true} if any item emitted by the source Observable satisfies a + * specified condition, otherwise {@code false}. Note: this always emits {@code false} if the + * source Observable is empty. *

- * 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. + * In Rx.Net this is the {@code any} Observer but we renamed it in RxJava to better match Java naming + * idioms. * - * @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 + * @param predicate + * the condition to test items emitted by the source Observable + * @return an Observable that emits a Boolean that indicates whether any item emitted by the source + * Observable satisfies the {@code predicate} + * @see RxJava Wiki: exists() + * @see MSDN: Observable.Any Note: the description in this page was wrong at the time of this writing. */ - public Observable scan(R initialValue, Func2 accumulator) { - return create(OperationScan.scan(this, initialValue, accumulator)); + public final Observable exists(Func1 predicate) { + return create(OperationAny.exists(this, predicate)); } /** - * Returns an Observable that emits a Boolean that indicates whether all of - * the items emitted by the source Observable satisfy a condition. + * Filter items emitted by an Observable. *

- * + * * - * @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() + * @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 emitted by the source Observable that the filter + * evaluates as {@code true} + * @see RxJava Wiki: filter() */ - public Observable all(Func1 predicate) { - return create(OperationAll.all(this, predicate)); + public final Observable filter(Func1 predicate) { + return lift(new OperatorFilter(predicate)); } /** - * Returns an Observable that skips the first num items emitted - * by the source Observable and emits the remainder. + * Registers an {@link Action0} to be called when this Observable invokes either + * {@link Observer#onCompleted onCompleted} or {@link Observer#onError onError}. *

- * - *

- * 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 - * @see RxJava Wiki: skip() + * @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 */ - public Observable skip(int num) { - return create(OperationSkip.skip(this, num)); + public final Observable finallyDo(Action0 action) { + return create(OperationFinally.finallyDo(this, action)); } /** - * 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, or raises an + * {@code IllegalArgumentException} if the source Observable is empty. *

* * - * @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 + * @return an Observable that emits only the very first item emitted by the source Observable, or raises an + * {@code IllegalArgumentException} if the source Observable is empty + * @see RxJava Wiki: first() + * @see MSDN: {@code Observable.firstAsync()} */ - public Observable first() { - return take(1); + public final Observable first() { + return take(1).single(); } /** - * 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 specified condition, or raises an {@code IllegalArgumentException} if no such items are emitted. *

* * - * @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 + * @param predicate + * the condition that an item emitted by the source Observable has to satisfy + * @return an Observable that emits only the very first item emitted by the source Observable that satisfies + * the {@code predicate}, or raises an {@code IllegalArgumentException} if no such items are emitted + * @see RxJava Wiki: first() + * @see MSDN: {@code Observable.firstAsync()} */ - public Observable first(Func1 predicate) { - return skipWhile(not(predicate)).take(1); + public final Observable first(Func1 predicate) { + return takeFirst(predicate).single(); } /** - * 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 + * item if the source Observable completes without emitting anything. *

* * - * @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 + * @param defaultValue + * the default item 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 item if the + * source Observable completes without emitting any items + * @see RxJava Wiki: firstOrDefault() + * @see MSDN: {@code Observable.firstOrDefaultAsync()} */ - public Observable firstOrDefault(T defaultValue) { - return create(OperationFirstOrDefault.firstOrDefault(this, defaultValue)); + public final Observable firstOrDefault(T defaultValue) { + return take(1).singleOrDefault(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 specified condition, or a default item if the source Observable emits no such items. *

* * - * @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 + * @param predicate + * the condition any item emitted by the source Observable has to satisfy + * @param defaultValue + * the default item to emit if the source Observable doesn't emit anything that satisfies the + * {@code predicate} + * @return an Observable that emits only the very first item emitted by the source Observable that satisfies + * the {@code predicate}, or a default item if the source Observable emits no such items + * @see RxJava Wiki: firstOrDefault() + * @see MSDN: {@code Observable.firstOrDefaultAsync()} */ - public Observable firstOrDefault(Func1 predicate, T defaultValue) { - return create(OperationFirstOrDefault.firstOrDefault(this, predicate, defaultValue)); + public final Observable firstOrDefault(T defaultValue, Func1 predicate) { + return takeFirst(predicate).singleOrDefault(defaultValue); } /** - * Returns the elements of the specified sequence or the specified default - * value in a singleton sequence if the sequence is empty. + * Returns an Observable that emits items based on 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. *

- * + * * - * @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 + * @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() */ - public Observable defaultIfEmpty(T defaultValue) { - return create(OperationDefaultIfEmpty.defaultIfEmpty(this, defaultValue)); + public final Observable flatMap(Func1> func) { + return mergeMap(func); } /** - * Returns an Observable that emits only the first num items - * emitted by the source Observable. + * Groups the items emitted by an Observable according to a specified criterion, and emits these grouped + * items as {@link GroupedObservable}s, one {@code GroupedObservable} per group. *

- * - *

- * 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 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() + * @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 each of which emits those items from the source Observable that share that key value + * @see RxJava Wiki: groupBy */ - public Observable take(final int num) { - return create(OperationTake.take(this, num)); + public final Observable> groupBy(final Func1 keySelector) { + return lift(new OperatorGroupBy(keySelector)); } /** - * Returns an Observable that emits items emitted by the source Observable - * so long as a specified condition is true. + * Groups the items emitted by an Observable according to a specified key selector function until the + * duration Observable expires for the key. *

- * + * * - * @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() + * @param keySelector + * a function to extract the key for each item + * @param durationSelector + * a function to signal the expiration of a group + * @return an Observable that emits {@link GroupedObservable}s, each of which corresponds to a key value and + * each of which emits all items emitted by the source Observable during that key's duration that + * share that same key value + * @see RxJava Wiki: groupByUntil() + * @see MSDN: Observable.GroupByUntil */ - public Observable takeWhile(final Func1 predicate) { - return create(OperationTakeWhile.takeWhile(this, predicate)); + public final Observable> groupByUntil(Func1 keySelector, Func1, ? extends Observable> durationSelector) { + return groupByUntil(keySelector, Functions. identity(), durationSelector); } /** - * 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. + * Groups the items emitted by an Observable (transformed by a selector) according to a specified key + * selector function until the duration Observable expires for the key. *

- * + * * - * @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() + * @param keySelector + * a function to extract the key for each item + * @param valueSelector + * a function to map each item emitted by the source Observable to an item emitted by one of the + * resulting {@link GroupedObservable}s + * @param durationSelector + * a function to signal the expiration of a group + * @return an Observable that emits {@link GroupedObservable}s, each of which corresponds to a key value and + * each of which emits all items emitted by the source Observable during that key's duration that + * share that same key value, transformed by the value selector + * @see RxJava Wiki: groupByUntil() + * @see MSDN: Observable.GroupByUntil */ - public Observable takeWhileWithIndex(final Func2 predicate) { - return create(OperationTakeWhile.takeWhileWithIndex(this, predicate)); + public final Observable> groupByUntil(Func1 keySelector, Func1 valueSelector, Func1, ? extends Observable> durationSelector) { + return create(new OperationGroupByUntil(this, keySelector, valueSelector, durationSelector)); } /** - * Returns an Observable that emits only the very first item emitted by the - * source Observable. + * Return an Observable that correlates two Observables when they overlap in time and groups the results. *

- * + * * - * @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() + * @param right + * the other Observable to correlate items from the source Observable with + * @param leftDuration + * a function that returns an Observable whose emissions indicate the duration of the values of + * the source Observable + * @param rightDuration + * a function that returns an Observable whose emissions indicate the duration of the values of + * the {@code right} Observable + * @param resultSelector + * a function that takes an item emitted by each Observable and returns the value to be emitted + * by the resulting Observable + * @return an Observable that emits items based on combining those items emitted by the source Observables + * whose durations overlap + * @see RxJava Wiiki: groupJoin + * @see MSDN: Observable.GroupJoin */ - public Observable takeFirst() { - return first(); + public final Observable groupJoin(Observable right, Func1> leftDuration, + Func1> rightDuration, + Func2, ? extends R> resultSelector) { + return create(new OperationGroupJoin(this, right, leftDuration, rightDuration, resultSelector)); } /** - * Returns an Observable that emits only the very first item emitted by the - * source Observable that satisfies a given condition. + * Ignores all items emitted by the source Observable and only calls {@code onCompleted} or {@code onError}. *

- * + * * - * @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) + * @return an empty Observable that only calls {@code onCompleted} or {@code onError}, based on which one is + * called by the source Observable + * @see RxJava Wiki: ignoreElements() + * @see MSDN: Observable.IgnoreElements */ - public Observable takeFirst(Func1 predicate) { - return first(predicate); + public final Observable ignoreElements() { + return filter(alwaysFalse()); } /** - * Returns an Observable that emits only the last count items - * emitted by the source Observable. + * Returns an Observable that emits {@code true} if the source Observable is empty, otherwise {@code false}. *

- * + * In Rx.Net this is negated as the {@code any} Observer but we renamed this in RxJava to better match Java + * naming idioms. + *

+ * * - * @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() + * @return an Observable that emits a Boolean + * @see RxJava Wiki: isEmpty() + * @see MSDN: Observable.Any */ - public Observable takeLast(final int count) { - return create(OperationTakeLast.takeLast(this, count)); + public final Observable isEmpty() { + return create(OperationAny.isEmpty(this)); } /** - * Returns an Observable that emits the items from the source Observable - * only until the other Observable emits an item. + * Correlates the items emitted by two Observables based on overlapping durations. *

- * + * * - * @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() + * @param right + * the second Observable to join items from + * @param leftDurationSelector + * a function to select a duration for each item emitted by the source Observable, used to + * determine overlap + * @param rightDurationSelector + * a function to select a duration for each item emitted by the {@code right} Observable, used to + * determine overlap + * @param resultSelector + * a function that computes an item to be emitted by the resulting Observable for any two + * overlapping items emitted by the two Observables + * @return an Observable that emits items correlating to items emitted by the source Observables that have + * overlapping durations + * @see RxJava Wiki: join() + * @see MSDN: Observable.Join */ - public Observable takeUntil(Observable other) { - return OperationTakeUntil.takeUntil(this, other); + public final Observable join(Observable right, Func1> leftDurationSelector, + Func1> rightDurationSelector, + Func2 resultSelector) { + return create(new OperationJoin(this, right, leftDurationSelector, rightDurationSelector, resultSelector)); } /** - * 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. + * Returns an Observable that emits the last item emitted by the source Observable or notifies observers of + * an {@code IllegalArgumentException} if the source Observable is empty. *

- * + * * - * @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 + * @return an Observable that emits the last item from the source Observable or notifies observers of an + * error + * @see RxJava Wiki: last() + * @see MSDN: {@code Observable.lastAsync()} */ - public Observable skipWhileWithIndex(Func2 predicate) { - return create(OperationSkipWhile.skipWhileWithIndex(this, predicate)); + public final Observable last() { + return takeLast(1).single(); } /** - * 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. + * Returns an Observable that emits only the last item emitted by the source Observable that satisfies a + * given condition, or an {@code IllegalArgumentException} if no such items are emitted. *

- * + * * - * @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 + * @param predicate + * the condition any source emitted item has to satisfy + * @return an Observable that emits only the last item satisfying the given condition from the source, or an + * {@code IllegalArgumentException} if no such items are emitted + * @throws IllegalArgumentException + * if no items that match the predicate are emitted by the source Observable + * @see RxJava Wiki: last() + * @see MSDN: {@code Observable.lastAsync()} */ - public Observable skipWhile(Func1 predicate) { - return create(OperationSkipWhile.skipWhile(this, predicate)); + public final Observable last(Func1 predicate) { + return filter(predicate).takeLast(1).single(); } /** - * 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. + * Returns an Observable that emits only the last item emitted by the source Observable, or a default item + * if the source Observable completes without emitting any items. *

- * + * * - * @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 + * @param defaultValue + * the default item to emit if the source Observable is empty + * @return an Observable that emits only the last item emitted by the source Observable, or a default item + * if the source Observable is empty + * @see RxJava Wiki: lastOrDefault() + * @see MSDN: {@code Observable.lastOrDefaultAsync()} */ - public Observable skipLast(int count) { - return create(OperationSkipLast.skipLast(this, count)); + public final Observable lastOrDefault(T defaultValue) { + return takeLast(1).singleOrDefault(defaultValue); } /** - * Returns an Observable that emits a single item, a list composed of all - * the items emitted by the source Observable. + * Returns an Observable that emits only the last item emitted by the source Observable that satisfies a + * specified condition, or a default item if no such item is 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. - *

- * 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. - * @see RxJava Wiki: toList() + * @param defaultValue + * the default item to emit if the source Observable doesn't emit anything that satisfies the + * specified {@code predicate} + * @param predicate + * the condition any item emitted by the source Observable has to satisfy + * @return an Observable that emits only the last item emitted by the source Observable that satisfies the + * given condition, or a default item if no such item is emitted by the source Observable + * @see RxJava Wiki: lastOrDefault() + * @see MSDN: {@code Observable.lastOrDefaultAsync()} */ - public Observable> toList() { - return create(OperationToObservableList.toObservableList(this)); + public final Observable lastOrDefault(T defaultValue, Func1 predicate) { + return filter(predicate).takeLast(1).singleOrDefault(defaultValue); } /** - * 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). + * Returns an Observable that counts the total number of items emitted by the source Observable and emits + * this count as a 64-bit Long. *

- * + * * - * @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() + * @return an Observable that emits a single item: the number of items emitted by the source Observable as a + * 64-bit Long item + * @see RxJava Wiki: count() + * @see MSDN: Observable.LongCount + * @see #count() */ - public Observable> toSortedList() { - return create(OperationToObservableSortedList.toSortedList(this)); + public final Observable longCount() { + return reduce(0L, new Func2() { + @Override + public final Long call(Long t1, T t2) { + return t1 + 1; + } + }); } /** - * Return an Observable that emits the items emitted by the source - * Observable, in a sorted order based on a specified comparison function + * Returns an Observable that applies a specified function to each item emitted by the source Observable and + * emits the results of these function applications. *

- * + * * - * @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() + * @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 specified + * function + * @see RxJava Wiki: map() + * @see MSDN: Observable.Select */ - public Observable> toSortedList(Func2 sortFunction) { - return create(OperationToObservableSortedList.toSortedList(this, sortFunction)); + public final Observable map(Func1 func) { + return lift(new OperatorMap(func)); } /** - * Emit a specified set of items before beginning to emit items from the - * source Observable. + * Returns 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 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() + * @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 these + * transformations + * @see RxJava Wiki: mapMany() + * @see #flatMap(Func1) + * @deprecated use {@link #flatMap(Func1)} */ - public Observable startWith(Iterable values) { - return concat(Observable. from(values), this); + @Deprecated + public final Observable mapMany(Func1> func) { + return mergeMap(func); } /** - * Emit a specified set of items with the specified scheduler before - * beginning to emit items from the source Observable. + * Turns all of the emissions and notifications from a source Observable into emissions marked with their + * original types within {@link Notification} objects. *

- * + * * - * @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 + * @return an Observable that emits items that are the result of materializing the items and notifications + * of the source Observable + * @see RxJava Wiki: materialize() + * @see MSDN: Observable.materialize */ - public Observable startWith(Iterable values, Scheduler scheduler) { - return concat(from(values, scheduler), this); + public final Observable> materialize() { + return create(OperationMaterialize.materialize(this)); } /** - * Emit a specified array of items with the specified scheduler before - * beginning to emit items from the source Observable. + * Returns an Observable that emits the maximum item emitted by the source Observable, according to the + * specified comparator. If there is more than one item with the same maximum value, it emits the + * last-emitted of these. *

- * - * - * @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 + * + * + * @param comparator + * the comparer used to compare items + * @return an Observable that emits the maximum item emitted by the source Observable, according to the + * specified comparator + * @throws IllegalArgumentException + * if the source is empty + * @see RxJava Wiki: max() + * @see MSDN: Observable.Max */ - public Observable startWith(T[] values, Scheduler scheduler) { - return startWith(Arrays.asList(values), scheduler); + public final Observable max(Comparator comparator) { + return OperationMinMax.max(this, comparator); } /** - * Emit a specified item before beginning to emit items from the source - * Observable. + * Returns an Observable that emits a List of items emitted by the source Observable that have the maximum + * key value. For a source Observable that emits no items, the resulting Observable emits an empty List. *

- * + * * - * @param t1 item to emit - * @return an Observable that exhibits the modified behavior - * @see RxJava Wiki: startWith() + * @param selector + * this function accepts an item emitted by the source Observable and returns a key + * @return an Observable that emits a List of those items emitted by the source Observable that had the + * largest key value of all of the emitted items + * @see RxJava Wiki: maxBy() + * @see MSDN: Observable.MaxBy */ - public Observable startWith(T t1) { - return concat(Observable. from(t1), this); + public final > Observable> maxBy(Func1 selector) { + return OperationMinMax.maxBy(this, selector); } /** - * Emit a specified set of items before beginning to emit items from the - * source Observable. + * Returns an Observable that emits a List of items emitted by the source Observable that have the maximum + * key value according to a specified comparator. For a source Observable that emits no items, the resulting + * Observable emits an empty List. *

- * + * * - * @param t1 first item to emit - * @param t2 second item to emit - * @return an Observable that exhibits the modified behavior - * @see RxJava Wiki: startWith() + * @param selector + * this function accepts an item emitted by the source Observable and returns a key + * @param comparator + * the comparator used to compare key values + * @return an Observable that emits a List of those items emitted by the source Observable that had the + * largest key value of all of the emitted items according to the specified comparator + * @see RxJava Wiki: maxBy() + * @see MSDN: Observable.MaxBy */ - public Observable startWith(T t1, T t2) { - return concat(Observable. from(t1, t2), this); + public final Observable> maxBy(Func1 selector, Comparator comparator) { + return OperationMinMax.maxBy(this, selector, comparator); } /** - * Emit a specified set of items before beginning to emit items from the - * source Observable. + * Returns an Observable that emits the results of applying a specified function 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. *

- * + * * - * @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() + * @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 these + * transformations + * @see RxJava Wiki: flatMap() + * @see #flatMap(Func1) */ - public Observable startWith(T t1, T t2, T t3) { - return concat(Observable. from(t1, t2, t3), this); + public final Observable mergeMap(Func1> func) { + return merge(map(func)); } /** - * Emit a specified set of items before beginning to emit items from the - * source Observable. + * Returns an Observable that applies a function to each item emitted or notification raised by the source + * Observable and then flattens the Observables returned from these functions and emits the resulting items. *

- * + * * - * @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() + * @param + * the result type + * @param onNext + * a function that returns an Observable to merge for each item emitted by the source Observable + * @param onError + * a function that returns an Observable to merge for an onError notification from the source + * Observable + * @param onCompleted + * a function that returns an Observable to merge for an onCompleted notification from the source + * Observable + * @return an Observable that emits the results of merging the Observables returned from applying the + * specified functions to the emissions and notifications of the source Observable */ - public Observable startWith(T t1, T t2, T t3, T t4) { - return concat(Observable. from(t1, t2, t3, t4), this); + public final Observable mergeMap( + Func1> onNext, + Func1> onError, + Func0> onCompleted) { + return create(OperationFlatMap.flatMap(this, onNext, onError, onCompleted)); } /** - * Emit a specified set of items before beginning to emit items from the - * source Observable. + * Returns an Observable that emits the results of a specified function to the pair of values emitted by the + * source Observable and a specified collection Observable. *

- * + * * - * @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() + * @param + * the type of items emitted by the collection Observable + * @param + * the type of items emitted by the resulting Observable + * @param collectionSelector + * a function that returns an Observable for each item emitted by the source Observable + * @param resultSelector + * a function that combines one item emitted by each of the source and collection Observables and + * returns an item to be emitted by the resulting Observable + * @return an Observable that emits the results of applying a function to a pair of values emitted by the + * source Observable and the collection Observable */ - public Observable startWith(T t1, T t2, T t3, T t4, T t5) { - return concat(Observable. from(t1, t2, t3, t4, t5), this); + public final Observable mergeMap(Func1> collectionSelector, + Func2 resultSelector) { + return create(OperationFlatMap.flatMap(this, collectionSelector, resultSelector)); } /** - * Emit a specified set of items before beginning to emit items from the - * source Observable. + * Returns an Observable that merges each item emitted by the source Observable with the values in an + * Iterable corresponding to that item that is generated by a selector. *

- * + * * - * @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() + * @param + * the type of item emitted by the resulting Observable + * @param collectionSelector + * a function that returns an Iterable sequence of values for when given an item emitted by the + * source Observable + * @return an Observable that emits the results of merging the items emitted by the source Observable with + * the values in the Iterables corresponding to those items, as generated by + * {@code collectionSelector} */ - 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); + public final Observable mergeMapIterable(Func1> collectionSelector) { + return merge(map(OperationFlatMap.flatMapIterableFunc(collectionSelector))); } /** - * Emit a specified set of items before beginning to emit items from the - * source Observable. - *

- * - * - * @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. + * Returns an Observable that emits the results of applying a function to the pair of values from the source + * Observable and an Iterable corresponding to that item that is generated by a selector. *

- * + * * - * @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); + * @param + * the collection element type + * @param + * the type of item emited by the resulting Observable + * @param collectionSelector + * a function that returns an Iterable sequence of values for each item emitted by the source + * Observable + * @param resultSelector + * a function that returns an item based on the item emitted by the source Observable and the + * Iterable returned for that item by the {@code collectionSelector} + * @return an Observable that emits the items returned by {@code resultSelector} for each item in the source + * Observable + */ + public final Observable mergeMapIterable(Func1> collectionSelector, + Func2 resultSelector) { + return mergeMap(OperationFlatMap.flatMapIterableFunc(collectionSelector), resultSelector); } /** - * Emit a specified set of items before beginning to emit items from the - * source Observable. + * Returns an Observable that emits the minimum item emitted by the source Observable, according to a + * specified comparator. If there is more than one such item, it returns the last-emitted one. *

- * + * * - * @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); + * @param comparator + * the comparer used to compare elements + * @return an Observable that emits the minimum item emitted by the source Observable according to the + * specified comparator + * @throws IllegalArgumentException + * if the source is empty + * @see RxJava Wiki: min() + * @see MSDN: Observable.Min + */ + public final Observable min(Comparator comparator) { + return OperationMinMax.min(this, comparator); } /** - * 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. + * Returns an Observable that emits a List of items emitted by the source Observable that have the minimum + * key value. For a source Observable that emits no items, the resulting Observable emits an empty List. *

- * + * * - * @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 + * @param selector + * the key selector function + * @return an Observable that emits a List of all of the items from the source Observable that had the + * lowest key value of any items emitted by the source Observable + * @see RxJava Wiki: minBy() + * @see MSDN: Observable.MinBy */ - public Observable> groupBy(final Func1 keySelector, final Func1 elementSelector) { - return create(OperationGroupBy.groupBy(this, keySelector, elementSelector)); + public final > Observable> minBy(Func1 selector) { + return OperationMinMax.minBy(this, selector); } /** - * 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. + * Returns an Observable that emits a List of items emitted by the source Observable that have the minimum + * key value according to a given comparator function. For a source Observable that emits no items, the + * resulting Observable emits an empty List. *

- * + * * - * @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 + * @param selector + * the key selector function + * @param comparator + * the comparator used to compare key values + * @return an Observable that emits a List of all of the items from the source Observable that had the + * lowest key value of any items emitted by the source Observable according to the specified + * comparator + * @see RxJava Wiki: minBy() + * @see MSDN: Observable.MinBy */ - public Observable> groupBy(final Func1 keySelector) { - return create(OperationGroupBy.groupBy(this, keySelector)); + public final Observable> minBy(Func1 selector, Comparator comparator) { + return OperationMinMax.minBy(this, selector, comparator); } /** - * Return an Observable which correlates two sequences when they overlap and groups the results. - *

- * + * Returns an Observable that emits items produced by multicasting the source Observable within a selector + * function. * - * @param right the other Observable to correlate values of this observable to - * @param leftDuration function that returns an Observable which indicates the duration of - * the values of this Observable - * @param rightDuration function that returns an Observable which indicates the duration of - * the values of the right Observable - * @param resultSelector function that takes a left value, the right observable and returns the - * value to be emitted - * @return an Observable that emits grouped values based on overlapping durations from this and - * another Observable - * - * @see RxJava Wiiki: groupJoin - * @see MSDN: Observable.GroupJoin + * @param subjectFactory + * the {@link Subject} factory + * @param selector + * the selector function, which can use the multicasted source Observable subject to the policies + * enforced by the created {@code Subject} + * @return an Observable that emits the items produced by multicasting the source Observable within a + * selector function + * @see RxJava: Observable.publish() and Observable.multicast() + * @see MSDN: Observable.Multicast */ - public Observable groupJoin(Observable right, Func1> leftDuration, - Func1> rightDuration, - Func2, ? extends R> resultSelector) { - return create(new OperationGroupJoin(this, right, leftDuration, rightDuration, resultSelector)); + public final Observable multicast( + final Func0> subjectFactory, + final Func1, ? extends Observable> selector) { + return OperationMulticast.multicast(this, subjectFactory, selector); } - + /** - * 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. - *

- * + * Returns a {@link ConnectableObservable} that upon connection causes the source Observable to push results + * into the specified subject. * - * @return an Observable that emits a Boolean - * @see RxJava Wiki: isEmpty() - * @see MSDN: Observable.Any + * @param subject + * the {@link Subject} for the {@link ConnectableObservable} to push source items into + * @param + * the type of items emitted by the resulting {@code ConnectableObservable} + * @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 Observable isEmpty() { - return create(OperationAny.isEmpty(this)); + public final ConnectableObservable multicast(Subject subject) { + return OperationMulticast.multicast(this, subject); } - + /** - * Returns an {@link Observable} that emits the last item emitted by the - * source or an IllegalArgumentException if the source - * {@link Observable} is empty. + * Move notifications to the specified {@link Scheduler} asynchronously with an unbounded buffer. *

- * + * * - * @return - * @see RxJava Wiki: last() + * @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 last() { - return create(OperationLast.last(this)); + public final Observable observeOn(Scheduler scheduler) { + return lift(new OperatorObserveOn(scheduler)); } -/** - * Returns an Observable that counts the total number of items in the - * source Observable as a 64 bit long. + /** + * Filters the items emitted by an Observable, only emitting those of the specified type. *

- * + * * - * @return an Observable that emits the number of counted elements of the - * source Observable as its single, 64 bit long item - * @see RxJava Wiki: count() - * @see MSDN: Observable.LongCount - * @see #count() + * @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 {@code klass} + * @see RxJava Wiki: ofType() + * @see MSDN: Observable.OfType */ - public Observable longCount() { - return reduce(0L, new Func2() { - @Override - public Long call(Long t1, T t2) { - return t1 + 1; + public final Observable ofType(final Class klass) { + return filter(new Func1() { + public final Boolean call(T t) { + return klass.isInstance(t); } - }); + }).cast(klass); } - + /** - * Converts an Observable into a {@link BlockingObservable} (an Observable - * with blocking operators). - * - * @return - * @see RxJava Wiki: Blocking Observable Operators + * 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 {@code onError} method, and then quits + * without invoking any more of its Observer's methods. The {@code onErrorResumeNext} method changes this + * behavior. If you pass a function that returns an Observable ({@code resumeFunction}) to + * {@code onErrorResumeNext}, if the original Observable encounters an error, instead of invoking its + * Observer's {@code onError} method, it will instead relinquish control to the Observable returned from + * {@code 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 {@code 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 BlockingObservable toBlockingObservable() { - return BlockingObservable.from(this); + public final Observable onErrorResumeNext(final Func1> resumeFunction) { + return lift(new OperatorOnErrorResumeNextViaFunction(resumeFunction)); } /** - * Converts the items emitted by an Observable to the specified type. + * Instruct an Observable to pass control to another Observable rather than invoking + * {@link Observer#onError onError} if it encounters an error. *

- * - * - * @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 + * + *

+ * 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 {@code onError} method, and then quits + * without invoking any more of its Observer's methods. The {@code onErrorResumeNext} method changes this + * behavior. If you pass another Observable ({@code resumeSequence}) to an Observable's + * {@code onErrorResumeNext} method, if the original Observable encounters an error, instead of invoking its + * Observer's {@code onError} method, it will instead relinquish control to {@code 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 {@code 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 cast(final Class klass) { - return create(OperationCast.cast(this, klass)); + public final Observable onErrorResumeNext(final Observable resumeSequence) { + return create(OperationOnErrorResumeNextViaObservable.onErrorResumeNextViaObservable(this, resumeSequence)); } /** - * Filters the items emitted by an Observable based on the specified type. + * 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 {@code onError} method, and then quits + * without invoking any more of its Observer's methods. The {@code onErrorReturn} method changes this + * behavior. If you pass a function ({@code resumeFunction}) to an Observable's {@code onErrorReturn} + * method, if the original Observable encounters an error, instead of invoking its Observer's + * {@code onError} method, it will instead emit the return value of {@code resumeFunction}. + *

+ * You can use this to prevent errors from propagating or to supply fallback data should errors be + * encountered. * - * @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 + * @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 ofType(final Class klass) { - return filter(new Func1() { - public Boolean call(T t) { - return klass.isInstance(t); - } - }).cast(klass); + public final Observable onErrorReturn(Func1 resumeFunction) { + return create(OperationOnErrorReturn.onErrorReturn(this, resumeFunction)); } /** - * 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 + * Allows inserting onNext events into a stream when onError events are received + * and continuing the original sequence instead of terminating. Thus it allows a sequence + * with multiple onError events. */ - public Observable ignoreElements() { - return filter(alwaysFalse()); + public final Observable onErrorFlatMap(final Func1> resumeFunction) { + return lift(new OperatorOnErrorFlatMap(resumeFunction)); } /** - * 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. + * Instruct an Observable to pass control to another Observable rather than invoking + * {@link Observer#onError onError} if it encounters an {@link java.lang.Exception}. *

- * - * - * @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 + * 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 exception that prevents it from emitting the expected item + * to its {@link Observer}, the Observable invokes its Observer's {@code onError} method, and then quits + * without invoking any more of its Observer's methods. The {@code onExceptionResumeNext} method changes + * this behavior. If you pass another Observable ({@code resumeSequence}) to an Observable's + * {@code onExceptionResumeNext} method, if the original Observable encounters an exception, instead of + * invoking its Observer's {@code onError} method, it will instead relinquish control to + * {@code 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 {@code onError}, the Observer + * may never know that an exception happened. + *

+ * You can use this to prevent exceptions from propagating or to supply fallback data should exceptions be + * encountered. + * + * @param resumeSequence + * a function that returns an Observable that will take over if the source Observable encounters + * an exception + * @return the original Observable, with appropriately modified behavior + * @see RxJava Wiki: onExceptionResumeNext() */ - public Observable timeout(long timeout, TimeUnit timeUnit) { - return create(OperationTimeout.timeout(this, timeout, timeUnit)); + public final Observable onExceptionResumeNext(final Observable resumeSequence) { + return create(OperationOnExceptionResumeNextViaObservable.onExceptionResumeNextViaObservable(this, resumeSequence)); } /** - * 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. + * Perform work on the source {@code Observable} in parallel by sharding it on a + * {@link Schedulers#computation()} {@link Scheduler}, and return the resulting {@code Observable}. *

- * - * - * @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 + * + * + * @param f + * a {@link Func1} that applies Observable Observers to {@code Observable} in parallel and + * returns an {@code Observable} + * @return an Observable that emits the results of applying {@link f} to the items emitted by the source + * Observable + * @see RxJava Wiki: parallel() */ - public Observable timeout(long timeout, TimeUnit timeUnit, Observable other) { - return create(OperationTimeout.timeout(this, timeout, timeUnit, other)); + public final Observable parallel(Func1, Observable> f) { + // TODO move this back to Schedulers.computation() again once that is properly using eventloops + // see https://github.com/Netflix/RxJava/issues/713 for why this was changed + return lift(new OperatorParallel(f, Schedulers.newThread())); } /** - * 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. + * Perform work on the source {@code Observable} in parallel by sharding it on a {@link Scheduler}, and + * return the resulting {@code Observable}. *

- * - * - * @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 + * + * + * @param f + * a {@link Func1} that applies Observable Observers to {@code Observable} in parallel and + * returns an {@code Observable} + * @param s + * a {@link Scheduler} to perform the work on + * @return an Observable that emits the results of applying {@link f} to the items emitted by the source + * Observable + * @see RxJava Wiki: parallel() */ - public Observable timeout(long timeout, TimeUnit timeUnit, Scheduler scheduler) { - return create(OperationTimeout.timeout(this, timeout, timeUnit, scheduler)); + public final Observable parallel(final Func1, Observable> f, final Scheduler s) { + return lift(new OperatorParallel(f, s)); } /** - * 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. + * Protects against errors being thrown from Observer implementations and ensures + * onNext/onError/onCompleted contract compliance. *

- * - * - * @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 + * See https://github.com/Netflix/RxJava/issues/216 for a discussion on "Guideline 6.4: Protect calls to + * user code from within an Observer" */ - public Observable timeout(long timeout, TimeUnit timeUnit, Observable other, Scheduler scheduler) { - return create(OperationTimeout.timeout(this, timeout, timeUnit, other, scheduler)); + private Subscription protectivelyWrapAndSubscribe(Subscriber o) { + return subscribe(new SafeSubscriber(o)); } /** - * Records the time interval between consecutive items emitted by an - * Observable. + * 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 an Observable that emits time interval information items - * @see RxJava Wiki: timeInterval() - * @see MSDN: Observable.TimeInterval + * @return a {@link ConnectableObservable} that upon connection causes the source Observable to emit items + * to its {@link Observer}s + * @see RxJava Wiki: publish() */ - public Observable> timeInterval() { - return create(OperationTimeInterval.timeInterval(this)); + public final ConnectableObservable publish() { + return OperationMulticast.multicast(this, PublishSubject. create()); } /** - * Records the time interval between consecutive items emitted by an - * Observable, using the specified Scheduler to compute time intervals. + * Returns an Observable that emits the results of invoking a specified selector on items emitted by a + * {@link ConnectableObservable} that shares a single subscription to the underlying sequence. *

- * + * * - * @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 + * @param + * the type of items emitted by the resulting Observable + * @param selector + * a function that can use the multicasted source sequence as many times as needed, without + * causing multiple subscriptions to the source sequence. Subscribers to the given source will + * receive all notifications of the source from the time of the subscription forward. + * @return an Observable that emits the results of invoking the selector on the items emitted by a + * {@link ConnectableObservable} that shares a single subscription to the underlying sequence */ - public Observable> timeInterval(Scheduler scheduler) { - return create(OperationTimeInterval.timeInterval(this, scheduler)); + public final Observable publish(Func1, ? extends Observable> selector) { + return multicast(new Func0>() { + @Override + public final Subject call() { + return PublishSubject.create(); + } + }, selector); } /** - * Constructs an Observable that depends on a resource object. + * Returns an Observable that emits {@code initialValue} followed by the results of invoking a specified + * selector on items emitted by a {@link ConnectableObservable} that shares a single subscription to the + * source Observable. *

- * - * - * @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)); + * + * + * @param + * the type of items emitted by the resulting Observable + * @param selector + * a function that can use the multicasted source sequence as many times as needed, without + * causing multiple subscriptions to the source Observable. Subscribers to the source will + * receive all notifications of the source from the time of the subscription forward + * @param initialValue + * the initial value of the underlying {@link BehaviorSubject} + * @return an Observable that emits {@code initialValue} followed by the results of invoking the selector + * on a {@ConnectableObservable} that shares a single subscription to the underlying Observable + */ + public final Observable publish(Func1, ? extends Observable> selector, final T initialValue) { + return multicast(new Func0>() { + @Override + public final Subject call() { + return BehaviorSubject.create(initialValue); + } + }, selector); } /** - * Propagates the Observable sequence that reacts first. + * Returns an Observable that emits {@code initialValue} followed by the items emitted by a + * {@link ConnectableObservable} that shares a single subscription to the source Observable. *

- * - * - * @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 + * + * + * @param initialValue + * the initial value to be emitted by the resulting Observable + * @return a {@link ConnectableObservable} that shares a single subscription to the underlying Observable + * and starts with {@code initialValue} */ - public static Observable amb(Observable o1, Observable o2) { - return create(OperationAmb.amb(o1, o2)); + public final ConnectableObservable publish(T initialValue) { + return OperationMulticast.multicast(this, BehaviorSubject. create(initialValue)); } /** - * Propagates the Observable sequence that reacts first. + * Returns a {@link ConnectableObservable} that emits only the last item emitted by the source Observable. *

- * - * - * @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 + * + * + * @return a {@link ConnectableObservable} that emits only the last item emitted by the source Observable + * @see RxJava Wiki: publishLast() */ - public static Observable amb(Observable o1, Observable o2, Observable o3) { - return create(OperationAmb.amb(o1, o2, o3)); + public final ConnectableObservable publishLast() { + return OperationMulticast.multicast(this, AsyncSubject. create()); } /** - * Propagates the observable sequence that reacts first. + * Returns an Observable that emits an item that results from invoking a specified selector on the last item + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source Observable. *

- * - * - * @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 + * + * + * @param + * the type of items emitted by the resulting Observable + * @param selector + * a function that can use the multicasted source sequence as many times as needed, without + * causing multiple subscriptions to the source Observable. Subscribers to the source will only + * receive the last item emitted by the source. + * @return an Observable that emits an item that is the result of invoking the selector on a + * {@link ConnectableObservable} that shares a single subscription to the source Observable */ - public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4) { - return create(OperationAmb.amb(o1, o2, o3, o4)); + public final Observable publishLast(Func1, ? extends Observable> selector) { + return multicast(new Func0>() { + @Override + public final Subject call() { + return AsyncSubject.create(); + } + }, selector); } /** - * Propagates the Observable sequence that reacts first. + * 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. *

- * - * - * @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 + * + *

+ * This technique, which is called "reduce" here, is sometimes called "aggregate," "fold," "accumulate," + * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code 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 items emitted by + * the source Observable + * @throws IllegalArgumentException + * if the source Observable emits no items + * @see RxJava Wiki: reduce() + * @see MSDN: Observable.Aggregate + * @see Wikipedia: Fold (higher-order function) */ - public static Observable amb(Observable o1, Observable o2, Observable o3, Observable o4, Observable o5) { - return create(OperationAmb.amb(o1, o2, o3, o4, o5)); + public final Observable reduce(Func2 accumulator) { + /* + * 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 scan(accumulator).last(); } /** - * Propagates the observable sequence that reacts first. + * Returns an Observable that applies a function of your choosing to the first item emitted by a source + * Observable and a specified seed value, 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. *

- * - * - * @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 + * + *

+ * This technique, which is called "reduce" here, is sometimec called "aggregate," "fold," "accumulate," + * "compress," or "inject" in other programming contexts. Groovy, for instance, has an {@code 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 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)); + public final Observable reduce(R initialValue, Func2 accumulator) { + return scan(initialValue, accumulator).takeLast(1); } /** - * Propagates the observable sequence that reacts first. + * Returns an Observable that repeats the sequence of items emitted by the source Observable indefinitely. *

- * - * - * @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 + * + * + * @return an Observable that emits the items emitted by the source Observable repeatedly and in sequence + * @see RxJava Wiki: repeat() + * @see MSDN: Observable.Repeat */ - 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)); + public final Observable repeat() { + return nest().lift(new OperatorRepeat()); } /** - * Propagates the observable sequence that reacts first. + * Returns an Observable that repeats the sequence of items emitted by the source Observable indefinitely, + * on a particular Scheduler. *

- * - * - * @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 + * + * + * @param scheduler + * the Scheduler to emit the items on + * @return an Observable that emits the items emitted by the source Observable repeatedly and in sequence + * @see RxJava Wiki: repeat() + * @see MSDN: Observable.Repeat */ - 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)); + public final Observable repeat(Scheduler scheduler) { + return nest().lift(new OperatorRepeat(scheduler)); } /** - * Propagates the observable sequence that reacts first. + * Returns an Observable that repeats the sequence of items emitted by the source Observable at most + * {@code count} times. *

- * - * - * @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)); + * + * + * @param count + * the number of times the source Observable items are repeated, a count of 0 will yield an empty + * sequence + * @return an Observable that repeats the sequence of items emitted by the source Observable at most + * {@code count} times + * @throws IllegalArgumentException + * if {@code count} is less than zero + * @see RxJava Wiki: repeat() + * @see MSDN: Observable.Repeat + */ + public final Observable repeat(long count) { + if (count < 0) { + throw new IllegalArgumentException("count >= 0 expected"); + } + return nest().lift(new OperatorRepeat(count)); } /** - * Propagates the observable sequence that reacts first. + * Returns an Observable that repeats the sequence of items emitted by the source Observable at most + * {@code count} times, on a particular Scheduler. *

- * - * - * @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 + * + * + * @param count + * the number of times the source Observable items are repeated, a count of 0 will yield an empty + * sequence + * @param scheduler + * the {@link Scheduler} to emit the items on + * @return an Observable that repeats the sequence of items emitted by the source Observable at most + * {@code count} times on a particular Scheduler + * @see RxJava Wiki: repeat() + * @see MSDN: Observable.Repeat */ - public static Observable amb(Iterable> sources) { - return create(OperationAmb.amb(sources)); + public final Observable repeat(long count, Scheduler scheduler) { + return nest().lift(new OperatorRepeat(count, scheduler)); } /** - * Invokes an action for each item emitted by the Observable. + * 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}. *

- * - * - * @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 + * + * + * @return a {@link ConnectableObservable} that upon connection causes the source Observable to emit its + * items to its {@link Observer}s + * @see RxJava Wiki: replay() */ - public Observable doOnEach(Observer observer) { - return create(OperationDoOnEach.doOnEach(this, observer)); + public final ConnectableObservable replay() { + return OperationMulticast.multicast(this, ReplaySubject. create()); } /** - * Invokes an action for each item emitted by an Observable. + * Returns an Observable that emits items that are the results of invoking a specified selector on the items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source 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 + * + * + * @param + * the type of items emitted by the resulting Observable + * @param selector + * the selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Observable + * @return an Observable that emits items that are the results of invoking the selector on a + * {@link ConnectableObservable} that shares a single subscription to the source Observable + * @see RxJava Wiki: replay() + * @see MSDN: Observable.Replay */ - public Observable doOnEach(final Action1 onNext) { - Observer observer = new Observer() { + public final Observable replay(Func1, ? extends Observable> selector) { + return OperationMulticast.multicast(this, new Func0>() { @Override - public void onCompleted() {} + public final Subject call() { + return ReplaySubject.create(); + } + }, selector); + } + /** + * Returns an Observable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source Observable, + * replaying {@code bufferSize} notifications. + *

+ * + * + * @param + * the type of items emitted by the resulting Observable + * @param selector + * the selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Observable + * @param bufferSize + * the buffer size that limits the number of items the connectable observable can replay + * @return an Observable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableObservable} that shares a single subscription to the source Observable + * replaying no more than {@code bufferSize} items + * @see RxJava Wiki: replay() + * @see MSDN: Observable.Replay + */ + public final Observable replay(Func1, ? extends Observable> selector, final int bufferSize) { + return OperationMulticast.multicast(this, new Func0>() { @Override - public void onError(Throwable e) {} - + public final Subject call() { + return OperationReplay.replayBuffered(bufferSize); + } + }, selector); + } + + /** + * Returns an Observable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source Observable, + * replaying no more than {@code bufferSize} items that were emitted within a specified time window. + *

+ * + * + * @param + * the type of items emitted by the resulting Observable + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Observable + * @param bufferSize + * the buffer size that limits the number of items the connectable observable can replay + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @return an Observable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableObservable} that shares a single subscription to the source Observable, and + * replays no more than {@code bufferSize} items that were emitted within the window defined by + * {@code time} + * @see RxJava Wiki: replay() + * @see MSDN: Observable.Replay + */ + public final Observable replay(Func1, ? extends Observable> selector, int bufferSize, long time, TimeUnit unit) { + return replay(selector, bufferSize, time, unit, Schedulers.computation()); + } + + /** + * Returns an Observable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source Observable, + * replaying no more than {@code bufferSize} items that were emitted within a specified time window. + *

+ * + * + * @param + * the type of items emitted by the resulting Observable + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Observable + * @param bufferSize + * the buffer size that limits the number of items the connectable observable can replay + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that is the time source for the window + * @return an Observable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableObservable} that shares a single subscription to the source Observable, and + * replays no more than {@code bufferSize} items that were emitted within the window defined by + * {@code time} + * @throws IllegalArgumentException + * if {@code bufferSize} is less than zero + * @see RxJava Wiki: replay() + * @see MSDN: Observable.Replay + */ + public final Observable replay(Func1, ? extends Observable> selector, final int bufferSize, final long time, final TimeUnit unit, final Scheduler scheduler) { + if (bufferSize < 0) { + throw new IllegalArgumentException("bufferSize < 0"); + } + return OperationMulticast.multicast(this, new Func0>() { @Override - public void onNext(T args) { - onNext.call(args); + public final Subject call() { + return OperationReplay.replayWindowed(time, unit, bufferSize, scheduler); } - - }; - - - return create(OperationDoOnEach.doOnEach(this, observer)); + }, selector); } - + /** - * Invokes an action if onError is called from the Observable. + * Returns an Observable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source Observable, + * replaying a maximum of {@code bufferSize} items. *

- * - * - * @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() { + * + * + * @param + * the type of items emitted by the resulting Observable + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Observable + * @param bufferSize + * the buffer size that limits the number of items the connectable observable can replay + * @param scheduler + * the Scheduler on which the replay is observed + * @return an Observable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableObservable} that shares a single subscription to the source Observable, + * replaying no more than {@code bufferSize} notifications + * @see RxJava Wiki: replay() + * @see MSDN: Observable.Replay + */ + public final Observable replay(Func1, ? extends Observable> selector, final int bufferSize, final Scheduler scheduler) { + return OperationMulticast.multicast(this, new Func0>() { @Override - public void onCompleted() {} + public final Subject call() { + return OperationReplay. createScheduledSubject(OperationReplay. replayBuffered(bufferSize), scheduler); + } + }, selector); + } + /** + * Returns an Observable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source Observable, + * replaying all items that were emitted within a specified time window. + *

+ * + * + * @param + * the type of items emitted by the resulting Observable + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Observable + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @return an Observable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableObservable} that shares a single subscription to the source Observable, + * replaying all items that were emitted within the window defined by {@code time} + * @see RxJava Wiki: replay() + * @see MSDN: Observable.Replay + */ + public final Observable replay(Func1, ? extends Observable> selector, long time, TimeUnit unit) { + return replay(selector, time, unit, Schedulers.computation()); + } + + /** + * Returns an Observable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source Observable, + * replaying all items that were emitted within a specified time window. + *

+ * + * + * @param + * the type of items emitted by the resulting Observable + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Observable + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @param scheduler + * the scheduler that is the time source for the window + * @return an Observable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableObservable} that shares a single subscription to the source Observable, + * replaying all items that were emitted within the window defined by {@code time} + * @see RxJava Wiki: replay() + * @see MSDN: Observable.Replay + */ + public final Observable replay(Func1, ? extends Observable> selector, final long time, final TimeUnit unit, final Scheduler scheduler) { + return OperationMulticast.multicast(this, new Func0>() { @Override - public void onError(Throwable e) { - onError.call(e); + public final Subject call() { + return OperationReplay.replayWindowed(time, unit, -1, scheduler); } + }, selector); + } + /** + * Returns an Observable that emits items that are the results of invoking a specified selector on items + * emitted by a {@link ConnectableObservable} that shares a single subscription to the source Observable. + *

+ * + * + * @param + * the type of items emitted by the resulting Observable + * @param selector + * a selector function, which can use the multicasted sequence as many times as needed, without + * causing multiple subscriptions to the Observable + * @param scheduler + * the Scheduler where the replay is observed + * @return an Observable that emits items that are the results of invoking the selector on items emitted by + * a {@link ConnectableObservable} that shares a single subscription to the source Observable, + * replaying all items + * @see RxJava Wiki: replay() + * @see MSDN: Observable.Replay + */ + public final Observable replay(Func1, ? extends Observable> selector, final Scheduler scheduler) { + return OperationMulticast.multicast(this, new Func0>() { @Override - public void onNext(T args) { } + public final Subject call() { + return OperationReplay.createScheduledSubject(ReplaySubject. create(), scheduler); + } + }, selector); + } - }; + /** + * Returns a {@link ConnectableObservable} that shares a single subscription to the source Observable that + * replays at most {@code bufferSize} items emitted by that Observable. + *

+ * + * + * @param bufferSize + * the buffer size that limits the number of items that can be replayed + * @return a {@link ConnectableObservable} that shares a single subscription to the source Observable and + * replays at most {@code bufferSize} items emitted by that Observable + * @see RxJava Wiki: replay() + * @see MSDN: Observable.Replay + */ + public final ConnectableObservable replay(int bufferSize) { + return OperationMulticast.multicast(this, OperationReplay. replayBuffered(bufferSize)); + } + /** + * Returns a {@link ConnectableObservable} that shares a single subscription to the source Observable and + * replays at most {@code bufferSize} items that were emitted during a specified time window. + *

+ * + * + * @param bufferSize + * the buffer size that limits the number of items that can be replayed + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @return a {@link ConnectableObservable} that shares a single subscription to the source Observable and + * replays at most {@code bufferSize} items that were emitted during the window defined by + * {@code time} + * @see RxJava Wiki: replay() + * @see MSDN: Observable.Replay + */ + public final ConnectableObservable replay(int bufferSize, long time, TimeUnit unit) { + return replay(bufferSize, time, unit, Schedulers.computation()); + } - return create(OperationDoOnEach.doOnEach(this, observer)); + /** + * Returns a {@link ConnectableObservable} that shares a single subscription to the source Observable and + * that replays a maximum of {@code bufferSize} items that are emitted within a specified time window. + *

+ * + * + * @param bufferSize + * the buffer size that limits the number of items that can be replayed + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @param scheduler + * the scheduler that is used as a time source for the window + * @return a {@link ConnectableObservable} that shares a single subscription to the source Observable and + * replays at most {@code bufferSize} items that were emitted during the window defined by + * {@code time} + * @throws IllegalArgumentException + * if {@code bufferSize} is less than zero + * @see RxJava Wiki: replay() + * @see MSDN: Observable.Replay + */ + public final ConnectableObservable replay(int bufferSize, long time, TimeUnit unit, Scheduler scheduler) { + if (bufferSize < 0) { + throw new IllegalArgumentException("bufferSize < 0"); + } + return OperationMulticast.multicast(this, OperationReplay. replayWindowed(time, unit, bufferSize, scheduler)); } - + /** - * Invokes an action when onCompleted is called by the - * Observable. + * Returns a {@link ConnectableObservable} that shares a single subscription to the source Observable and + * replays at most {@code bufferSize} items emitted by that 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 + * + * + * @param bufferSize + * the buffer size that limits the number of items that can be replayed + * @param scheduler + * the scheduler on which the Observers will observe the emitted items + * @return a {@link ConnectableObservable} that shares a single subscription to the source Observable and + * replays at most {@code bufferSize} items that were emitted by the Observable + * @see RxJava Wiki: replay() + * @see MSDN: Observable.Replay */ - public Observable doOnCompleted(final Action0 onCompleted) { - Observer observer = new Observer() { - @Override - public void onCompleted() { - onCompleted.call(); - } + public final ConnectableObservable replay(int bufferSize, Scheduler scheduler) { + return OperationMulticast.multicast(this, + OperationReplay.createScheduledSubject( + OperationReplay. replayBuffered(bufferSize), scheduler)); + } - @Override - public void onError(Throwable e) { } + /** + * Returns a {@link ConnectableObservable} that shares a single subscription to the source Observable and + * replays all items emitted by that Observable within a specified time window. + *

+ * + * + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @return a {@link ConnectableObservable} that shares a single subscription to the source Observable and + * replays the items that were emitted during the window defined by {@code time} + * @see RxJava Wiki: replay() + * @see MSDN: Observable.Replay + */ + public final ConnectableObservable replay(long time, TimeUnit unit) { + return replay(time, unit, Schedulers.computation()); + } - @Override - public void onNext(T args) { } + /** + * Returns a {@link ConnectableObservable} that shares a single subscription to the source Observable and + * replays all items emitted by that Observable within a specified time window. + *

+ * + * + * @param time + * the duration of the window in which the replayed items must have been emitted + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that is the time source for the window + * @return a {@link ConnectableObservable} that shares a single subscription to the source Observable and + * replays the items that were emitted during the window defined by {@code time} + * @see RxJava Wiki: replay() + * @see MSDN: Observable.Replay + */ + public final ConnectableObservable replay(long time, TimeUnit unit, Scheduler scheduler) { + return OperationMulticast.multicast(this, OperationReplay. replayWindowed(time, unit, -1, scheduler)); + } - }; + /** + * Returns a {@link ConnectableObservable} that shares a single subscription to the source Observable that + * will replay all of its items and notifications to any future {@link Observer} on the given + * {@link Scheduler}. + *

+ * + * + * @param scheduler + * the Scheduler on which the Observers will observe the emitted items + * @return a {@link ConnectableObservable} that shares a single subscription to the source Observable that + * will replay all of its items and notifications to any future {@link Observer} on the given + * {@link Scheduler} + * @see RxJava Wiki: replay() + * @see MSDN: Observable.Replay + */ + public final ConnectableObservable replay(Scheduler scheduler) { + return OperationMulticast.multicast(this, OperationReplay.createScheduledSubject(ReplaySubject. create(), scheduler)); + } + /** + * Return an Observable that mirrors the source Observable, resubscribing to it if it calls {@code onError} + * (infinite retry count). + *

+ * + *

+ * If the source Observable calls {@link Observer#onError}, this method will resubscribe to the source + * Observable rather than propagating the {@code onError} call. + *

+ * Any and all items emitted by the source Observable will be emitted by the resulting Observable, even + * those emitted during failed subscriptions. For example, if an Observable fails at first but emits + * {@code [1, 2]} then succeeds the second time and emits {@code [1, 2, 3, 4, 5]} then the complete sequence + * of emissions and notifications would be {@code [1, 2, 1, 2, 3, 4, 5, onCompleted]}. + * + * @return the source Observable modified with retry logic + * @see RxJava Wiki: retry() + */ + public final Observable retry() { + return create(OperationRetry.retry(this)); + } - return create(OperationDoOnEach.doOnEach(this, observer)); + /** + * Return an Observable that mirrors the source Observable, resubscribing to it if it calls {@code onError} + * up to a specified number of retries. + *

+ * + *

+ * If the source Observable calls {@link Observer#onError}, this method will resubscribe to the source + * Observable for a maximum of {@code retryCount} resubscriptions rather than propagating the + * {@code onError} call. + *

+ * Any and all items emitted by the source Observable will be emitted by the resulting Observable, even + * those emitted during failed subscriptions. For example, if an Observable fails at first but emits + * {@code [1, 2]} then succeeds the second time and emits {@code [1, 2, 3, 4, 5]} then the complete sequence + * of emissions and notifications would be {@code [1, 2, 1, 2, 3, 4, 5, onCompleted]}. + * + * @param retryCount + * number of retry attempts before failing + * @return the source Observable modified with retry logic + * @see RxJava Wiki: retry() + */ + public final Observable retry(int retryCount) { + return create(OperationRetry.retry(this, retryCount)); } /** - * Invokes an action for each item emitted by an Observable. + * Returns an Observable that emits the results of sampling the items emitted by the source Observable at a + * specified time interval. *

- * - * - * @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() {} + * + * + * @param period + * the sampling rate + * @param unit + * the {@link TimeUnit} in which {@code 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 final Observable sample(long period, TimeUnit unit) { + return create(OperationSample.sample(this, period, unit)); + } - @Override - public void onError(Throwable e) { - onError.call(e); - } + /** + * 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 {@code 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 final Observable sample(long period, TimeUnit unit, Scheduler scheduler) { + return create(OperationSample.sample(this, period, unit, scheduler)); + } - @Override - public void onNext(T args) { - onNext.call(args); - } + /** + * Return an Observable that emits the results of sampling the items emitted by the source Observable + * whenever the specified {@code sampler} Observable emits an item or completes. + *

+ * + * + * @param sampler + * the Observable to use for sampling the source Observable + * @return an Observable that emits the results of sampling the items emitted by this Observable whenever + * the {@code sampler} Observable emits an item or completes + * @see RxJava Wiki: sample() + */ + public final Observable sample(Observable sampler) { + return create(new OperationSample.SampleWithObservable(this, sampler)); + } - }; + /** + * 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, + * emitting the result of each of these iterations. + *

+ * + *

+ * This sort of function is sometimes called an accumulator. + * + * @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 final Observable scan(Func2 accumulator) { + return lift(new OperatorScan(accumulator)); + } + /** + * Returns an Observable that applies a function of your choosing to the first item emitted by a source + * Observable and a seed value, 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, emitting the result of each of these iterations. + *

+ * + *

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

+ * Note that the Observable that results from this method will emit {@code initialValue} as its first + * emitted item. + * + * @param initialValue + * the initial (seed) accumulator 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 {@code initialValue} followed by the results of each call to the + * accumulator function + * @see RxJava Wiki: scan() + * @see MSDN: Observable.Scan + */ + public final Observable scan(R initialValue, Func2 accumulator) { + return lift(new OperatorScan(initialValue, accumulator)); + } - return create(OperationDoOnEach.doOnEach(this, observer)); + /** + * If the source Observable completes after emitting a single item, return an Observable that emits that + * item. If the source Observable emits more than one item or no items, throw an + * {@code IllegalArgumentException}. + *

+ * + * + * @return an Observable that emits the single item emitted by the source Observable + * @throws IllegalArgumentException + * if the source emits more than one item or no items + * @see RxJava Wiki: single() + * @see MSDN: {@code Observable.singleAsync()} + */ + public final Observable single() { + return create(OperationSingle. single(this)); } /** - * Invokes an action for each item emitted by an Observable. + * If the Observable completes after emitting a single item that matches a specified predicate, return an + * Observable that emits that item. If the source Observable emits more than one such item or no such items, + * throw an {@code IllegalArgumentException}. *

- * - * - * @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(); - } + * + * + * @param predicate + * a predicate function to evaluate items emitted by the source Observable + * @return an Observable that emits the single item emitted by the source Observable that matches the + * predicate + * @throws IllegalArgumentException + * if the source Observable emits either more than one item that matches the predicate or no + * items that match the predicate + * @see RxJava Wiki: single() + * @see MSDN: {@code Observable.singleAsync()} + */ + public final Observable single(Func1 predicate) { + return filter(predicate).single(); + } - @Override - public void onError(Throwable e) { - onError.call(e); - } + /** + * If the source Observable completes after emitting a single item, return an Observable that emits that + * item; if the source Observable is empty, return an Observable that emits a default item. If the source + * Observable emits more than one item, throw an {@code IllegalArgumentException.} + *

+ * + * + * @param defaultValue + * a default value to emit if the source Observable emits no item + * @return an Observable that emits the single item emitted by the source Observable, or a default item if + * the source Observable is empty + * @throws IllegalArgumentException + * if the source Observable emits more than one item + * @see RxJava Wiki: single() + * @see MSDN: {@code Observable.singleOrDefaultAsync()} + */ + public final Observable singleOrDefault(T defaultValue) { + return create(OperationSingle. singleOrDefault(this, defaultValue)); + } - @Override - public void onNext(T args) { - onNext.call(args); - } + /** + * If the Observable completes after emitting a single item that matches a predicate, return an Observable + * that emits that item; if the source Observable emits no such item, return an Observable that emits a + * default item. If the source Observable emits more than one such item, throw an + * {@code IllegalArgumentException}. + *

+ * + * + * @param defaultValue + * a default item to emit if the source Observable emits no matching items + * @param predicate + * a predicate function to evaluate items emitted by the source Observable + * @return an Observable that emits the single item emitted by the source Observable that matches the + * predicate, or the default item if no emitted item matches the predicate + * @throws IllegalArgumentException + * if the source Observable emits more than one item that matches the predicate + * @see RxJava Wiki: single() + * @see MSDN: {@code Observable.singleOrDefaultAsync()} + */ + public final Observable singleOrDefault(T defaultValue, Func1 predicate) { + return filter(predicate).singleOrDefault(defaultValue); + } - }; + /** + * Returns an Observable that skips the first {@code num} items emitted by the source Observable and emits + * the remainder. + *

+ * + * + * @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 + * {@code num} items that the source Observable emits + * @see RxJava Wiki: skip() + */ + public final Observable skip(int num) { + return create(OperationSkip.skip(this, num)); + } + + /** + * Returns an Observable that skips values emitted by the source Observable before a specified time window + * elapses. + *

+ * + * + * @param time + * the length of the time window to skip + * @param unit + * the time unit of {@code time} + * @return an Observable that skips values emitted by the source Observable before the time window defined + * by {@code time} elapses and the emits the remainder + * @see RxJava Wiki: skip() + */ + public final Observable skip(long time, TimeUnit unit) { + return skip(time, unit, Schedulers.computation()); + } + + /** + * Returns an Observable that skips values emitted by the source Observable before a specified time window + * on a specified {@link Scheduler} elapses. + *

+ * + * + * @param time + * the length of the time window to skip + * @param unit + * the time unit of {@code time} + * @param scheduler + * the {@link Scheduler} on which the timed wait happens + * @return an Observable that skips values emitted by the source Observable before the time window defined + * by {@code time} and {@code scheduler} elapses, and then emits the remainder + * @see RxJava Wiki: skip() + */ + public final Observable skip(long time, TimeUnit unit, Scheduler scheduler) { + return create(new OperationSkip.SkipTimed(this, time, unit, scheduler)); + } - return create(OperationDoOnEach.doOnEach(this, observer)); + /** + * Returns an Observable that drops a specified number of items from the end of the sequence emitted by the + * source Observable. + *

+ * + *

+ * This Observer accumulates a queue long enough to store the first {@code count} items. As more items are + * received, items are taken from the front of the queue and emitted by the returned Observable. This causes + * such items to be delayed. + * + * @param count + * number of items to drop from the end of the source sequence + * @return an Observable that emits the items emitted by the source Observable except for the dropped ones + * at the end + * @throws IndexOutOfBoundsException + * if {@code count} is less than zero + * @see RxJava Wiki: skipLast() + * @see MSDN: Observable.SkipLast + */ + public final Observable skipLast(int count) { + return create(OperationSkipLast.skipLast(this, count)); } /** - * Whether a given {@link Function} is an internal implementation inside - * rx.* packages or not. + * Returns an Observable that drops items emitted by the source Observable during a specified time window + * before the source completes. *

- * 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 time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @return an Observable that drops those items emitted by the source Observable in a time window before the + * source completes defined by {@code time} + * @see RxJava Wiki: skipLast() + * @see MSDN: Observable.SkipLast + */ + public final Observable skipLast(long time, TimeUnit unit) { + return skipLast(time, unit, Schedulers.computation()); + } + + /** + * Returns an Observable that drops items emitted by the source Observable during a specified time window + * (defined on a specified scheduler) before the source completes. + *

+ * * - * @param o - * @return {@code true} if the given function is an internal implementation, - * and {@code false} otherwise. + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the scheduler used as the time source + * @return an Observable that drops those items emitted by the source Observable in a time window before the + * source completes defined by {@code time} and {@code scheduler} + * @see RxJava Wiki: skipLast() + * @see MSDN: Observable.SkipLast */ - private boolean isInternalImplementation(Object o) { - if (o == null) { - return true; - } - // prevent double-wrapping (yeah it happens) - if (o instanceof SafeObserver) { - return true; - } + public final Observable skipLast(long time, TimeUnit unit, Scheduler scheduler) { + return create(new OperationSkipLast.SkipLastTimed(this, time, unit, scheduler)); + } - 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; - } + /** + * Returns an Observable that skips items emitted by the source Observable until a second Observable emits + * an item. + *

+ * + * + * @param other + * the second Observable that has to emit an item before the source Observable's elements begin + * to be mirrored by the resulting Observable + * @return an Observable that skips items from the source Observable until the second Observable emits an + * item, then emits the remaining items + * @see RxJava Wiki: skipUntil() + * @see MSDN: Observable.SkipUntil + */ + public final Observable skipUntil(Observable other) { + return create(new OperationSkipUntil(this, other)); + } + + /** + * Returns an Observable that skips all items emitted by the source Observable as long as a 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 + * @return an Observable that begins emitting items emitted by the source Observable when the specified + * predicate becomes false + * @see RxJava Wiki: skipWhile() + * @see MSDN: Observable.SkipWhile + */ + public final Observable skipWhile(Func1 predicate) { + return create(OperationSkipWhile.skipWhile(this, predicate)); + } + + /** + * Returns an Observable that skips all items emitted by the source Observable as long as a 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. It takes the emitted item as + * the first parameter and the sequential index of the emitted item as a second parameter. + * @return an Observable that begins emitting items emitted by the source Observable when the specified + * predicate becomes false + * @see RxJava Wiki: skipWhileWithIndex() + * @see MSDN: Observable.SkipWhile + */ + public final Observable skipWhileWithIndex(Func2 predicate) { + return create(OperationSkipWhile.skipWhileWithIndex(this, predicate)); + } + + /** + * Returns an Observable that emits the items in a specified {@link Iterable} before it begins to emit items + * emitted by the source Observable. + *

+ * + * + * @param values + * an Iterable that contains the items you want the modified Observable to emit first + * @return an Observable that emits the items in the specified {@link Iterable} and then emits the items + * emitted by the source Observable + * @see RxJava Wiki: startWith() + */ + public final Observable startWith(Iterable values) { + return concat(Observable. from(values), this); + } + + /** + * Returns an Observable that emits the items in a specified {@link Iterable}, on a specified + * {@link Scheduler}, before it begins to emit items emitted by the source Observable. + *

+ * + * + * @param values + * an Iterable that contains the items you want the modified Observable to emit first + * @param scheduler + * the Scheduler to emit the prepended values on + * @return an Observable that emits the items in the specified {@link Iterable} and then emits the items + * emitted by the source Observable + * @see RxJava Wiki: startWith() + * @see MSDN: Observable.StartWith + */ + public final Observable startWith(Iterable values, Scheduler scheduler) { + return concat(from(values, scheduler), this); + } + + /** + * Returns an Observable that emits a specified item before it begins to emit items emitted by the source + * Observable. + *

+ * + * + * @param t1 + * the item to emit + * @return an Observable that emits the specified item before it begins to emit items emitted by the source + * Observable + * @see RxJava Wiki: startWith() + */ + public final Observable startWith(T t1) { + return concat(Observable. from(t1), this); + } + + /** + * Returns an Observable that emits the specified items before it begins to emit items emitted by the source + * Observable. + *

+ * + * + * @param t1 + * the first item to emit + * @param t2 + * the second item to emit + * @return an Observable that emits the specified items before it begins to emit items emitted by the source + * Observable + * @see RxJava Wiki: startWith() + */ + public final Observable startWith(T t1, T t2) { + return concat(Observable. from(t1, t2), this); + } + + /** + * Returns an Observable that emits the specified items before it begins to emit items emitted by the source + * Observable. + *

+ * + * + * @param t1 + * the first item to emit + * @param t2 + * the second item to emit + * @param t3 + * the third item to emit + * @return an Observable that emits the specified items before it begins to emit items emitted by the source + * Observable + * @see RxJava Wiki: startWith() + */ + public final Observable startWith(T t1, T t2, T t3) { + return concat(Observable. from(t1, t2, t3), this); + } + + /** + * Returns an Observable that emits the specified items before it begins to emit items emitted by the source + * Observable. + *

+ * + * + * @param t1 + * the first item to emit + * @param t2 + * the second item to emit + * @param t3 + * the third item to emit + * @param t4 + * the fourth item to emit + * @return an Observable that emits the specified items before it begins to emit items emitted by the source + * Observable + * @see RxJava Wiki: startWith() + */ + public final Observable startWith(T t1, T t2, T t3, T t4) { + return concat(Observable. from(t1, t2, t3, t4), this); + } + + /** + * Returns an Observable that emits the specified items before it begins to emit items emitted by the source + * Observable. + *

+ * + * + * @param t1 + * the first item to emit + * @param t2 + * the second item to emit + * @param t3 + * the third item to emit + * @param t4 + * the fourth item to emit + * @param t5 + * the fifth item to emit + * @return an Observable that emits the specified items before it begins to emit items emitted by the source + * Observable + * @see RxJava Wiki: startWith() + */ + public final Observable startWith(T t1, T t2, T t3, T t4, T t5) { + return concat(Observable. from(t1, t2, t3, t4, t5), this); + } + + /** + * Returns an Observable that emits the specified items before it begins to emit items emitted by the source + * Observable. + *

+ * + * + * @param t1 + * the first item to emit + * @param t2 + * the second item to emit + * @param t3 + * the third item to emit + * @param t4 + * the fourth item to emit + * @param t5 + * the fifth item to emit + * @param t6 + * the sixth item to emit + * @return an Observable that emits the specified items before it begins to emit items emitted + * by the source Observable + * @see RxJava Wiki: startWith() + */ + public final 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); + } + + /** + * Returns an Observable that emits the specified items before it begins to emit items emitted by the source + * Observable. + *

+ * + * + * @param t1 + * the first item to emit + * @param t2 + * the second item to emit + * @param t3 + * the third item to emit + * @param t4 + * the fourth item to emit + * @param t5 + * the fifth item to emit + * @param t6 + * the sixth item to emit + * @param t7 + * the seventh item to emit + * @return an Observable that emits the specified items before it begins to emit items emitted by the source + * Observable + * @see RxJava Wiki: startWith() + */ + public final 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); + } + + /** + * Returns an Observable that emits the specified items before it begins to emit items emitted by the source + * Observable. + *

+ * + * + * @param t1 + * the first item to emit + * @param t2 + * the second item to emit + * @param t3 + * the third item to emit + * @param t4 + * the fourth item to emit + * @param t5 + * the fifth item to emit + * @param t6 + * the sixth item to emit + * @param t7 + * the seventh item to emit + * @param t8 + * the eighth item to emit + * @return an Observable that emits the specified items before it begins to emit items emitted by the source + * Observable + * @see RxJava Wiki: startWith() + */ + public final 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); + } + + /** + * Returns an Observable that emits the specified items before it begins to emit items emitted by the source + * Observable. + *

+ * + * + * @param t1 + * the first item to emit + * @param t2 + * the second item to emit + * @param t3 + * the third item to emit + * @param t4 + * the fourth item to emit + * @param t5 + * the fifth item to emit + * @param t6 + * the sixth item to emit + * @param t7 + * the seventh item to emit + * @param t8 + * the eighth item to emit + * @param t9 + * the ninth item to emit + * @return an Observable that emits the specified items before it begins to emit items emitted by the source + * Observable + * @see RxJava Wiki: startWith() + */ + public final 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); + } + + /** + * Returns an Observable that emits the items from a specified array, on a specified Scheduler, before it + * begins to emit items emitted by 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 emits the items from {@code values}, on {@code scheduler}, before it begins to + * emit items emitted by the source Observable. + * @see RxJava Wiki: startWith() + * @see MSDN: Observable.StartWith + */ + public final Observable startWith(T[] values, Scheduler scheduler) { + return startWith(Arrays.asList(values), scheduler); + } + + /** + * Subscribe and ignore all events. + * + * @return a {@link Subscription} reference with which the {@link Observer} can stop receiving items before + * the Observable has finished sending them + * @throws OnErrorNotImplementedException + * if the Observable tries to call {@code onError} + */ + public final Subscription subscribe() { + return protectivelyWrapAndSubscribe(new Subscriber() { + + @Override + public final void onCompleted() { + // do nothing + } + + @Override + public final void onError(Throwable e) { + throw new OnErrorNotImplementedException(e); + } + + @Override + public final 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 + * FIXME FIXME FIXME + * @return a {@link Subscription} reference with which the {@link Observer} can stop receiving items before + * the Observable has finished sending them + * @throws IllegalArgumentException + * if {@code onNext} is null + * @throws OnErrorNotImplementedException + * if the Observable tries to call {@code onError} + * @see RxJava Wiki: onNext, onCompleted, and onError + */ + public final Subscription subscribe(final Action1 onNext) { + if (onNext == null) { + throw new IllegalArgumentException("onNext can not be null"); + } + + /** + * Wrapping since raw functions provided by the user are being invoked. + * + * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to + * user code from within an Observer" + */ + return protectivelyWrapAndSubscribe(new Subscriber() { + + @Override + public final void onCompleted() { + // do nothing + } + + @Override + public final void onError(Throwable e) { + throw new OnErrorNotImplementedException(e); + } + + @Override + public final void onNext(T args) { + onNext.call(args); + } + + }); + } + + /** + * An {@link Observer} must call an Observable's {@code subscribe} method in order to receive items and + * notifications from the Observable. + * + * @param onNext + * FIXME FIXME FIXME + * @param onError + * FIXME FIXME FIXME + * @return a {@link Subscription} reference with which the {@link Observer} can stop receiving items before + * the Observable has finished sending them + * @see RxJava Wiki: onNext, onCompleted, and onError + * @throws IllegalArgumentException + * if {@code onNext} is null + * @throws IllegalArgumentException + * if {@code onError} is null + */ + public final Subscription subscribe(final Action1 onNext, final Action1 onError) { + if (onNext == null) { + throw new IllegalArgumentException("onNext can not be null"); + } + if (onError == null) { + throw new IllegalArgumentException("onError can not be null"); + } + + /** + * Wrapping since raw functions provided by the user are being invoked. + * + * See https://github.com/Netflix/RxJava/issues/216 for discussion on + * "Guideline 6.4: Protect calls to user code from within an Observer" + */ + return protectivelyWrapAndSubscribe(new Subscriber() { + + @Override + public final void onCompleted() { + // do nothing + } + + @Override + public final void onError(Throwable e) { + onError.call(e); + } + + @Override + public final void onNext(T args) { + onNext.call(args); + } + + }); + } + + /** + * An {@link Observer} must call an Observable's {@code subscribe} method in order to receive items and + * notifications from the Observable. + * + * @param onNext + * FIXME FIXME FIXME + * @param onError + * FIXME FIXME FIXME + * @param onComplete + * FIXME FIXME FIXME + * @return a {@link Subscription} reference with which the {@link Observer} can stop receiving items before + * the Observable has finished sending them + * @throws IllegalArgumentException + * if {@code onNext} is null + * @throws IllegalArgumentException + * if {@code onError} is null + * @throws IllegalArgumentException + * if {@code onComplete} is null + * @see RxJava Wiki: onNext, onCompleted, and onError + */ + public final Subscription subscribe(final Action1 onNext, final Action1 onError, final Action0 onComplete) { + if (onNext == null) { + throw new IllegalArgumentException("onNext can not be null"); + } + if (onError == null) { + throw new IllegalArgumentException("onError can not be null"); + } + if (onComplete == null) { + throw new IllegalArgumentException("onComplete can not be null"); + } + + /** + * Wrapping since raw functions provided by the user are being invoked. + * + * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls to user code from within an Observer" + */ + return protectivelyWrapAndSubscribe(new Subscriber() { + + @Override + public final void onCompleted() { + onComplete.call(); + } + + @Override + public final void onError(Throwable e) { + onError.call(e); + } + + @Override + public final void onNext(T args) { + onNext.call(args); + } + + }); + } + + /** + * An {@link Observer} must call an Observable's {@code subscribe} method in order to receive items and + * notifications from the Observable. + * + * @param onNext + * FIXME FIXME FIXME + * @param onError + * FIXME FIXME FIXME + * @param onComplete + * FIXME FIXME FIXME + * @param scheduler + * FIXME FIXME FIXME + * @return a {@link Subscription} reference with which the {@link Observer} can stop receiving items before + * the Observable has finished sending them + * @see RxJava Wiki: onNext, onCompleted, and onError + */ + public final Subscription subscribe(final Action1 onNext, final Action1 onError, final Action0 onComplete, Scheduler scheduler) { + return subscribeOn(scheduler).subscribe(onNext, onError, onComplete); + } + + /** + * An {@link Observer} must call an Observable's {@code subscribe} method in order to receive items and + * notifications from the Observable. + * + * @param onNext + * FIXME FIXME FIXME + * @param onError + * FIXME FIXME FIXME + * @param scheduler + * FIXME FIXME FIXME + * @return a {@link Subscription} reference with which the {@link Observer} can stop receiving items before + * the Observable has finished sending them + * @see RxJava Wiki: onNext, onCompleted, and onError + */ + public final 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 + * FIXME FIXME FIXME + * @param scheduler + * FIXME FIXME FIXME + * @return a {@link Subscription} reference with which the {@link Observer} can stop receiving items before + * the Observable has finished sending them + * @see RxJava Wiki: onNext, onCompleted, and onError + */ + public final Subscription subscribe(final Action1 onNext, Scheduler scheduler) { + return subscribeOn(scheduler).subscribe(onNext); + } + + /** + * An {@link Observer} must subscribe to an Observable in order to receive items and notifications from the + * Observable. + * + * @param observer + * FIXME FIXME FIXME + * @param scheduler + * FIXME FIXME FIXME + * @return a {@link Subscription} reference with which the {@link Observer} can stop receiving items before + * the Observable has finished sending them + * @see RxJava Wiki: onNext, onCompleted, and onError + */ + public final Subscription subscribe(final Observer observer, Scheduler scheduler) { + return subscribeOn(scheduler).subscribe(observer); + } + + /** + * An {@link Observer} must subscribe to an Observable in order to receive items and notifications from the + * Observable. + * + * @param observer + * FIXME FIXME FIXME + * @return a {@link Subscription} reference with which the {@link Observer} can stop receiving items before + * the Observable has finished sending them + * @see RxJava Wiki: onNext, onCompleted, and onError + */ + public final Subscription subscribe(final Observer observer) { + return subscribe(new Subscriber() { + + @Override + public void onCompleted() { + observer.onCompleted(); + } + + @Override + public void onError(Throwable e) { + observer.onError(e); + } + + @Override + public void onNext(T t) { + observer.onNext(t); + } + + }); + } + + /** + * A {@link Subscriber} 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 Subscriber 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 + * Subscribers. Unless the documentation for a particular {@code Observable} implementation indicates + * otherwise, Subscriber should make no assumptions about the order in which multiple Subscribers will + * receive their notifications. + *

+ * For more information see the + * RxJava Wiki + * + * @param observer + * the {@link Subscriber} + * @return a {@link Subscription} reference with which Subscribers that are {@link Observer}s can + * unsubscribe from the Observable + * @throws IllegalStateException + * if {@code subscribe()} is unable to obtain an {@code OnSubscribe<>} function + * @throws IllegalArgumentException + * if the {@link Subscriber} provided as the argument to {@code subscribe()} is {@code null} + * @throws OnErrorNotImplementedException + * if the {@link Subscriber}'s {@code onError} method is null + * @throws RuntimeException + * if the {@link Subscriber}'s {@code onError} method itself threw a {@code Throwable} + */ + public final Subscription subscribe(Subscriber observer) { + // allow the hook to intercept and/or decorate + OnSubscribe onSubscribeFunction = hook.onSubscribeStart(this, f); + // validate and proceed + if (observer == null) { + throw new IllegalArgumentException("observer can not be null"); + } + if (onSubscribeFunction == null) { + throw new IllegalStateException("onSubscribe function can not be null."); + /* + * the subscribe function can also be overridden but generally that's not the appropriate approach + * so I won't mention that in the exception + */ + } + try { + /* + * See https://github.com/Netflix/RxJava/issues/216 for discussion on "Guideline 6.4: Protect calls + * to user code from within an Observer" + */ + if (isInternalImplementation(observer)) { + onSubscribeFunction.call(observer); + } else { + // assign to `observer` so we return the protected version + observer = new SafeSubscriber(observer); + onSubscribeFunction.call(observer); + } + final Subscription returnSubscription = hook.onSubscribeReturn(this, observer); + // we return it inside a Subscription so it can't be cast back to Subscriber + return Subscriptions.create(new Action0() { + + @Override + public void call() { + returnSubscription.unsubscribe(); + } + + }); + } catch (Throwable e) { + // special handling for certain Throwable/Error/Exception types + Exceptions.throwIfFatal(e); + // if an unhandled error occurs executing the onSubscribe we will propagate it + try { + observer.onError(hook.onSubscribeError(this, e)); + } catch (OnErrorNotImplementedException e2) { + // special handling when onError is not implemented ... we just rethrow + throw e2; + } catch (Throwable e2) { + // if this happens it means the onError itself failed (perhaps an invalid function implementation) + // so we are unable to propagate the error correctly and will just throw + RuntimeException r = new RuntimeException("Error occurred attempting to subscribe [" + e.getMessage() + "] and then again while trying to pass to onError.", e2); + hook.onSubscribeError(this, r); + throw r; + } + return Subscriptions.empty(); + } + } + + /** + * A {@link Subscriber} 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 Subscriber 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 + * Subscribers. Unless the documentation for a particular {@code Observable} implementation indicates + * otherwise, Subscribers should make no assumptions about the order in which multiple Subscribers will + * receive their notifications. + *

+ * For more information see the + * RxJava Wiki + * + * @param observer + * the {@link Subscriber} + * @param scheduler + * the {@link Scheduler} on which Subscribers subscribe to the Observable + * @return a {@link Subscription} reference with which Subscribers that are {@link Observer}s can + * unsubscribe from the Observable + * @throws IllegalArgumentException + * if an argument to {@code subscribe()} is {@code null} + */ + public final Subscription subscribe(Subscriber observer, Scheduler scheduler) { + return subscribeOn(scheduler).subscribe(observer); + } + + /** + * Asynchronously subscribes Observers to this Observable on the specified + * {@link Scheduler}. + *

+ * + * + * @param scheduler + * the {@link Scheduler} to perform subscription actions on + * @return the source Observable modified so that its subscriptions happen on the + * specified {@link Scheduler} + * @see RxJava Wiki: subscribeOn() + * @see #subscribeOn(rx.Scheduler, int) + */ + public final Observable subscribeOn(Scheduler scheduler) { + return nest().lift(new OperatorSubscribeOn(scheduler)); + } + + /** + * Returns an Observable that extracts a Double from each of the items emitted by the source Observable via + * a function you specify, and then emits the sum of these Doubles. + *

+ * + * + * @param valueExtractor + * the function to extract a Double from each item emitted by the source Observable + * @return an Observable that emits the Double sum of the Double values corresponding to the items emitted + * by the source Observable as transformed by the provided function + * @see RxJava Wiki: sumDouble() + * @see MSDN: Observable.Sum + */ + public final Observable sumDouble(Func1 valueExtractor) { + return OperationSum.sumAtLeastOneDoubles(map(valueExtractor)); + } + + /** + * Returns an Observable that extracts a Float from each of the items emitted by the source Observable via + * a function you specify, and then emits the sum of these Floats. + *

+ * + * + * @param valueExtractor + * the function to extract a Float from each item emitted by the source Observable + * @return an Observable that emits the Float sum of the Float values corresponding to the items emitted by + * the source Observable as transformed by the provided function + * @see RxJava Wiki: sumFloat() + * @see MSDN: Observable.Sum + */ + public final Observable sumFloat(Func1 valueExtractor) { + return OperationSum.sumAtLeastOneFloats(map(valueExtractor)); + } + + /** + * Returns an Observable that extracts an Integer from each of the items emitted by the source Observable + * via a function you specify, and then emits the sum of these Integers. + *

+ * + * + * @param valueExtractor + * the function to extract an Integer from each item emitted by the source Observable + * @return an Observable that emits the Integer sum of the Integer values corresponding to the items emitted + * by the source Observable as transformed by the provided function + * @see RxJava Wiki: sumInteger() + * @see MSDN: Observable.Sum + */ + public final Observable sumInteger(Func1 valueExtractor) { + return OperationSum.sumAtLeastOneIntegers(map(valueExtractor)); + } + + /** + * Returns an Observable that extracts a Long from each of the items emitted by the source Observable via a + * function you specify, and then emits the sum of these Longs. + *

+ * + * + * @param valueExtractor + * the function to extract a Long from each item emitted by the source Observable + * @return an Observable that emits the Long sum of the Long values corresponding to the items emitted by + * the source Observable as transformed by the provided function + * @see RxJava Wiki: sumLong() + * @see MSDN: Observable.Sum + */ + public final Observable sumLong(Func1 valueExtractor) { + return OperationSum.sumAtLeastOneLongs(map(valueExtractor)); + } + + /** + * Returns a new Observable by applying a function that you supply to each item emitted by the source + * Observable that returns an Observable, and then emitting the items emitted by the most recently emitted + * of these Observables. + *

+ * + * + * @param func + * a function that, when applied to an item emitted by the source Observable, returns an + * Observable + * @return an Observable that emits the items emitted by the Observable returned from applying {@code func} + * to the most recently emitted item emitted by the source Observable + */ + public final Observable switchMap(Func1> func) { + return switchOnNext(map(func)); + } + + /** + * Wraps the source 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 either {@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. + * + * @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 final Observable synchronize() { + return create(OperationSynchronize.synchronize(this)); + } + + /** + * Wraps the source Observable in another Observable that ensures that the resulting Observable is + * chronologically well-behaved by acquiring a mutual-exclusion lock for the object provided as the + * {@code 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 either {@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 final Observable synchronize(Object lock) { + return create(OperationSynchronize.synchronize(this, lock)); + } + + /** + * Returns an Observable that emits only the first {@code 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 {@code num} times before invoking + * {@link Observer#onCompleted onCompleted}. + * + * @param num + * the maximum number of items to emit + * @return an Observable that emits only the first {@code num} items emitted by the source Observable, or + * all of the items from the source Observable if that Observable emits fewer than {@code num} items + * @see RxJava Wiki: take() + */ + public final Observable take(final int num) { + return lift(new OperatorTake(num)); + } + + /** + * Returns an Observable that emits those items emitted by source Observable before a specified time runs + * out. + *

+ * + * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @return an Observable that emits those items emitted by the source Observable before the time runs out + * @see RxJava Wiki: take() + */ + public final Observable take(long time, TimeUnit unit) { + return take(time, unit, Schedulers.computation()); + } + + /** + * Returns an Observable that emits those items emitted by source Observable before a specified time (on a + * specified Scheduler) runs out. + *

+ * + * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler used for time source + * @return an Observable that emits those items emitted by the source Observable before the time runs out, + * according to the specified Scheduler + * @see RxJava Wiki: take() + */ + public final Observable take(long time, TimeUnit unit, Scheduler scheduler) { + return create(new OperationTakeTimed.TakeTimed(this, time, unit, scheduler)); + } + + /** + * 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 emitted by the source Observable, or an empty + * Observable if the source Observable completes without emitting a single item + * @see RxJava Wiki: first() + * @see MSDN: {@code Observable.firstAsync()} + * @deprecated use {@code take(1)} directly + */ + @Deprecated + public final Observable takeFirst() { + return take(1); + } + + /** + * Returns an Observable that emits only the very first item emitted by the source Observable that satisfies + * a specified condition. + *

+ * + * + * @param predicate + * the condition any item emitted by the source Observable has to satisfy + * @return an Observable that emits only the very first item emitted by the source Observable that satisfies + * the given condition, or that completes without emitting anything if the source Observable + * completes without emitting a single condition-satisfying item + * @see RxJava Wiki: first() + * @see MSDN: {@code Observable.firstAsync()} + */ + public final Observable takeFirst(Func1 predicate) { + return filter(predicate).take(1); + } + + /** + * Returns an Observable that emits only the last {@code count} items emitted by the source Observable. + *

+ * + * + * @param count + * the number of items to emit from the end of the sequence of items emitted by the source + * Observable + * @return an Observable that emits only the last {@code count} items emitted by the source Observable + * @see RxJava Wiki: takeLast() + */ + public final Observable takeLast(final int count) { + return create(OperationTakeLast.takeLast(this, count)); + } + + /** + * Return an Observable that emits at most a specified number of items from the source Observable that were + * emitted in a specified window of time before the Observable completed. + *

+ * + * + * @param count + * the maximum number of items to emit + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @return an Observable that emits at most {@code count} items from the source Observable that were emitted + * in a specified window of time before the Observable completed + */ + public final Observable takeLast(int count, long time, TimeUnit unit) { + return takeLast(count, time, unit, Schedulers.computation()); + } + + /** + * Return an Observable that emits at most a specified number of items from the source Observable that were + * emitted in a specified window of time before the Observable completed, where the timing information is + * provided by a given Scheduler. + *

+ * + * + * @param count + * the maximum number of items to emit + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that provides the timestamps for the observed items + * @return an Observable that emits at most {@code count} items from the source Observable that were emitted + * in a specified window of time before the Observable completed, where the timing information is + * provided by the given {@code scheduler} + * @throws IllegalArgumentException + * if {@code count} is less than zero + */ + public final Observable takeLast(int count, long time, TimeUnit unit, Scheduler scheduler) { + if (count < 0) { + throw new IllegalArgumentException("count >= 0 required"); + } + return create(OperationTakeLast.takeLast(this, count, time, unit, scheduler)); + } + + /** + * Return an Observable that emits the items from the source Observable that were emitted in a specified + * window of time before the Observable completed. + *

+ * + * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @return an Observable that emits the items from the source Observable that were emitted in the window of + * time before the Observable completed specified by {@code time} + */ + public final Observable takeLast(long time, TimeUnit unit) { + return takeLast(time, unit, Schedulers.computation()); + } + + /** + * Return an Observable that emits the items from the source Observable that were emitted in a specified + * window of time before the Observable completed, where the timing information is provided by a specified + * Scheduler. + *

+ * + * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that provides the timestamps for the Observed items + * @return an Observable that emits the items from the source Observable that were emitted in the window of + * time before the Observable completed specified by {@code time}, where the timing information is + * provided by {@code scheduler} + */ + public final Observable takeLast(long time, TimeUnit unit, Scheduler scheduler) { + return create(OperationTakeLast.takeLast(this, time, unit, scheduler)); + } + + /** + * Return an Observable that emits a single List containing the last {@code count} elements emitted by the + * source Observable. + *

+ * + * + * @param count + * the number of items to emit in the list + * @return an Observable that emits a single list containing the last {@code count} elements emitted by the + * source Observable + */ + public final Observable> takeLastBuffer(int count) { + return takeLast(count).toList(); + } + + /** + * Return an Observable that emits a single List containing at most {@code count} items from the source + * Observable that were emitted during a specified window of time before the source Observable completed. + *

+ * + * + * @param count + * the maximum number of items to emit + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @return an Observable that emits a single List containing at most {@code count} items emitted by the + * source Observable during the time window defined by {@code time} before the source Observable + * completed + */ + public final Observable> takeLastBuffer(int count, long time, TimeUnit unit) { + return takeLast(count, time, unit).toList(); + } + + /** + * Return an Observable that emits a single List containing at most {@code count} items from the source + * Observable that were emitted during a specified window of time (on a specified Scheduler) before the + * source Observable completed. + *

+ * + * + * @param count + * the maximum number of items to emit + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that provides the timestamps for the observed items + * @return an Observable that emits a single List containing at most {@code count} items emitted by the + * source Observable during the time window defined by {@code time} before the source Observable + * completed + */ + public final Observable> takeLastBuffer(int count, long time, TimeUnit unit, Scheduler scheduler) { + return takeLast(count, time, unit, scheduler).toList(); + } + + /** + * Return an Observable that emits a single List containing those items from the source Observable that were + * emitted during a specified window of time before the source Observable completed. + *

+ * + * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @return an Observable that emits a single List containing the items emitted by the source Observable + * during the time window defined by {@code time} before the source Observable completed + */ + public final Observable> takeLastBuffer(long time, TimeUnit unit) { + return takeLast(time, unit).toList(); + } + + /** + * Return an Observable that emits a single List containing those items from the source Observable that were + * emitted during a specified window of time before the source Observable completed, where the timing + * information is provided by the given Scheduler. + *

+ * + * + * @param time + * the length of the time window + * @param unit + * the time unit of {@code time} + * @param scheduler + * the Scheduler that provides the timestamps for the observed items + * @return an Observable that emits a single List containing the items emitted by the source Observable + * during the time window defined by {@code time} before the source Observable completed, where the + * timing information is provided by {@code scheduler} + */ + public final Observable> takeLastBuffer(long time, TimeUnit unit, Scheduler scheduler) { + return takeLast(time, unit, scheduler).toList(); + } + + /** + * Returns an Observable that emits the items emitted by the source Observable until a second Observable + * emits an item. + *

+ * + * + * @param other + * the Observable whose first emitted item will cause {@code takeUntil} to stop emitting items + * from the source Observable + * @param + * the type of items emitted by {@code other} + * @return an Observable that emits the items emitted by the source Observable until such time as + * {@code other} emits its first item + * @see RxJava Wiki: takeUntil() + */ + public final Observable takeUntil(Observable other) { + return OperationTakeUntil.takeUntil(this, other); + } + + /** + * Returns an Observable that emits items emitted by the source Observable so long as each item satisfied a + * specified condition, and then completes as soon as this condition is not satisfied. + *

+ * + * + * @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 {@code predicate}, then completes + * @see RxJava Wiki: takeWhile() + */ + public final 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 operates on both the item and its index relative to the complete + * sequence of emitted items. + *

+ * + * + * @param predicate + * a function to test each item emitted by the source Observable for a condition; the second + * parameter of the function represents the sequential index of the source item; it returns a + * Boolean + * @return an Observable that emits items from the source Observable so long as the predicate continues to + * return {@code true} for each item, then completes + * @see RxJava Wiki: takeWhileWithIndex() + */ + public final Observable takeWhileWithIndex(final Func2 predicate) { + return create(OperationTakeWhile.takeWhileWithIndex(this, predicate)); + } + + /** + * Matches when the Observable has an available item and projects the item by invoking the selector + * function. + *

+ * + * + * @param selector + * selector that will be invoked for items emitted by the source Observable + * @return a {@link Plan} that produces the projected results, to be fed (with other Plans) to the + * {@link #when} Observer + * @throws NullPointerException + * if {@code selector} is null + * @see RxJava Wiki: then() + * @see MSDN: Observable.Then + */ + public final Plan0 then(Func1 selector) { + return OperationJoinPatterns.then(this, selector); + } + + /** + * Returns an Observable that emits only the first item emitted by the source Observable during sequential + * time windows of a specified duration. + *

+ * 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 emitting another item after emitting the last item + * @param unit + * the unit of time of {@code windowDuration} + * @return an Observable that performs the throttle operation + * @see RxJava Wiki: throttleFirst() + */ + public final Observable throttleFirst(long windowDuration, TimeUnit unit) { + return create(OperationThrottleFirst.throttleFirst(this, windowDuration, unit)); + } + + /** + * Returns an Observable that emits only the first item emitted by the source Observable during sequential + * time windows of a specified duration, where the windows are managed by a specified Scheduler. + *

+ * 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 emitting another item after emitting the last item + * @param unit + * the unit of time of {@code skipDuration} + * @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 final Observable throttleFirst(long skipDuration, TimeUnit unit, Scheduler scheduler) { + return create(OperationThrottleFirst.throttleFirst(this, skipDuration, unit, scheduler)); + } + + /** + * Returns an Observable that emits only the last item emitted by the source Observable during sequential + * time windows of a specified duration. + *

+ * 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 which the last item emitted by the source Observable will be + * emitted + * @param unit + * the unit of time of {@code intervalDuration} + * @return an Observable that performs the throttle operation + * @see RxJava Wiki: throttleLast() + * @see #sample(long, TimeUnit) + */ + public final Observable throttleLast(long intervalDuration, TimeUnit unit) { + return sample(intervalDuration, unit); + } + + /** + * Returns an Observable that emits only the last item emitted by the source Observable during sequential + * time windows of a specified duration, where the duration is governed by a specified Scheduler. + *

+ * 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 which the last item emitted by the source Observable will be + * emitted + * @param unit + * the unit of time of {@code intervalDuration} + * @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: throttleLast() + * @see #sample(long, TimeUnit, Scheduler) + */ + public final Observable throttleLast(long intervalDuration, TimeUnit unit, Scheduler scheduler) { + return sample(intervalDuration, unit, scheduler); + } + + /** + * Returns an Observable that only emits those items emitted by the source Observable that are not followed + * by another emitted item within a specified time window. + *

+ * Note: If the source Observable keeps emitting items more frequently than the length of the time + * window then no items will be emitted by the resulting Observable. + *

+ * + *

+ * Information on debounce vs throttle: + *

+ *

+ * + * @param timeout + * the length of the window of time that must pass after the emission of an item from the source + * Observable in which that Observable emits no items in order for the item to be emitted by the + * resulting Observable + * @param unit + * the {@link TimeUnit} of {@code timeout} + * @return an Observable that filters out items that are too quickly followed by newer items + * @see RxJava Wiki: throttleWithTimeout() + * @see #debounce(long, TimeUnit) + */ + public final Observable throttleWithTimeout(long timeout, TimeUnit unit) { + return create(OperationDebounce.debounce(this, timeout, unit)); + } + + /** + * Returns an Observable that only emits those items emitted by the source Observable that are not followed + * by another emitted item within a specified time window, where the time window is governed by a specified + * Scheduler. + *

+ * Note: If the source Observable keeps emitting items more frequently than the length of the time + * window then no items will be emitted by the resulting Observable. + *

+ * + *

+ * Information on debounce vs throttle: + *

+ *

+ * + * @param timeout + * the length of the window of time that must pass after the emission of an item from the source + * Observable in which that Observable emits no items in order for the item to be emitted by the + * resulting Observable + * @param unit + * the {@link TimeUnit} of {@code timeout} + * @param scheduler + * the {@link Scheduler} to use internally to manage the timers that handle the timeout for each + * item + * @return an Observable that filters out items that are too quickly followed by newer items + * @see RxJava Wiki: throttleWithTimeout() + * @see #debounce(long, TimeUnit, Scheduler) + */ + public final Observable throttleWithTimeout(long timeout, TimeUnit unit, Scheduler scheduler) { + return create(OperationDebounce.debounce(this, timeout, unit, scheduler)); + } + + /** + * Returns an Observable that emits records of the time interval between consecutive items emitted by the + * source Observable. + *

+ * + * + * @return an Observable that emits time interval information items + * @see RxJava Wiki: timeInterval() + * @see MSDN: Observable.TimeInterval + */ + public final Observable> timeInterval() { + return create(OperationTimeInterval.timeInterval(this)); + } + + /** + * Returns an Observable that emits records of the time interval between consecutive items emitted by the + * source Observable, where this interval is computed on a specified Scheduler. + *

+ * + * + * @param scheduler + * the {@link Scheduler} used to compute time intervals + * @return an Observable that emits time interval information items + * @see RxJava Wiki: timeInterval() + * @see MSDN: Observable.TimeInterval + */ + public final Observable> timeInterval(Scheduler scheduler) { + return create(OperationTimeInterval.timeInterval(this, scheduler)); + } + + /** + * Returns an Observable that mirrors the source Observable, but notifies observers of a TimeoutException if + * either the first item emitted by the source Observable or any subsequent item don't arrive within time + * windows defined by other Observables. + *

+ * + * + * @param + * the first timeout value type (ignored) + * @param + * the subsequent timeout value type (ignored) + * @param firstTimeoutSelector + * a function that returns an Observable that determines the timeout window for the first source + * item + * @param timeoutSelector + * a function that returns an Observable for each item emitted by the source Observable and that + * determines the timeout window in which the subsequent source item must arrive in order to + * continue the sequence + * @return an Observable that mirrors the source Observable, but notifies observers of a TimeoutException if + * either the first item or any subsequent item doesn't arrive within the time windows specified by + * the timeout selectors + */ + public final Observable timeout(Func0> firstTimeoutSelector, Func1> timeoutSelector) { + return timeout(firstTimeoutSelector, timeoutSelector, null); + } + + /** + * Returns an Observable that mirrors the source Observable, but switches to a fallback Observable if either + * the first item emitted by the source Observable or any subsequent item don't arrive within time windows + * defined by other Observables. + *

+ * + * + * @param + * the first timeout value type (ignored) + * @param + * the subsequent timeout value type (ignored) + * @param firstTimeoutSelector + * a function that returns an Observable which determines the timeout window for the first source + * item + * @param timeoutSelector + * a function that returns an Observable for each item emitted by the source Observable and that + * determines the timeout window in which the subsequent source item must arrive in order to + * continue the sequence + * @param other + * the fallback Observable to switch to if the source Observable times out + * @return an Observable that mirrors the source Observable, but switches to the {@code other} Observable if + * either the first item emitted by the source Observable or any subsequent item don't arrive within + * time windows defined by the timeout selectors + * @throws NullPointerException + * if {@code timeoutSelector} is null + */ + public final Observable timeout(Func0> firstTimeoutSelector, Func1> timeoutSelector, Observable other) { + if(timeoutSelector == null) { + throw new NullPointerException("timeoutSelector is null"); + } + return lift(new OperatorTimeoutWithSelector(firstTimeoutSelector, timeoutSelector, other)); + } + + /** + * Returns an Observable that mirrors the source Observable, but notifies observers of a TimeoutException if + * an item emitted by the source Observable doesn't arrive within a window of time after the emission of the + * previous item, where that period of time is measured by an Observable that is a function of the previous + * item. + *

+ * + *

+ * Note: The arrival of the first source item is never timed out. + * + * @param + * the timeout value type (ignored) + * @param timeoutSelector + * a function that returns an observable for each item emitted by the source + * Observable and that determines the timeout window for the subsequent item + * @return an Observable that mirrors the source Observable, but notifies observers of a TimeoutException if + * an item emitted by the source Observable takes longer to arrive than the time window defined by + * the selector for the previously emitted item + */ + public final Observable timeout(Func1> timeoutSelector) { + return timeout(null, timeoutSelector, null); + } + + /** + * Returns an Observable that mirrors the source Observable, but that switches to a fallback Observable if + * an item emitted by the source Observable doesn't arrive within a window of time after the emission of the + * previous item, where that period of time is measured by an Observable that is a function of the previous + * item. + *

+ * + *

+ * Note: The arrival of the first source item is never timed out. + * + * @param + * the timeout value type (ignored) + * @param timeoutSelector + * a function that returns an Observable, for each item emitted by the source Observable, that + * determines the timeout window for the subsequent item + * @param other + * the fallback Observable to switch to if the source Observable times out + * @return an Observable that mirrors the source Observable, but switches to mirroring a fallback Observable + * if an item emitted by the source Observable takes longer to arrive than the time window defined + * by the selector for the previously emitted item + */ + public final Observable timeout(Func1> timeoutSelector, Observable other) { + return timeout(null, timeoutSelector, other); + } + + /** + * Returns an Observable that mirrors the source Observable but applies a timeout policy for each emitted + * item. If the next item isn't emitted within the specified timeout duration starting from its predecessor, + * the resulting Observable terminates and notifies observers of a {@code TimeoutException}. + *

+ * + * + * @param timeout + * maximum duration between emitted items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument. + * @return the source Observable modified to notify observers of a {@code TimeoutException} in case of a + * timeout + * @see RxJava Wiki: timeout() + * @see MSDN: Observable.Timeout + */ + public final Observable timeout(long timeout, TimeUnit timeUnit) { + return timeout(timeout, timeUnit, null, Schedulers.computation()); + } + + /** + * Returns an Observable that mirrors the source Observable but applies a timeout policy for each emitted + * item. If the next item isn't emitted within the specified timeout duration starting from its predecessor, + * the resulting Observable begins instead to mirror a fallback Observable. + *

+ * + * + * @param timeout + * maximum duration between items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument + * @param other + * the fallback Observable to use in case of a timeout + * @return the source Observable modified to switch to the fallback Observable in case of a timeout + * @see RxJava Wiki: timeout() + * @see MSDN: Observable.Timeout + */ + public final Observable timeout(long timeout, TimeUnit timeUnit, Observable other) { + return timeout(timeout, timeUnit, other, Schedulers.computation()); + } + + /** + * Returns an Observable that mirrors the source Observable but applies a timeout policy for each emitted + * item using a specified Scheduler. If the next item isn't emitted within the specified timeout duration + * starting from its predecessor, the resulting Observable begins instead to mirror a fallback Observable. + *

+ * + * + * @param timeout + * maximum duration between items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument + * @param other + * the Observable to use as the fallback in case of a timeout + * @param scheduler + * the {@link Scheduler} to run the timeout timers on + * @return the source Observable modified so that it will switch to the fallback Observable in case of a + * timeout + * @see RxJava Wiki: timeout() + * @see MSDN: Observable.Timeout + */ + public final Observable timeout(long timeout, TimeUnit timeUnit, Observable other, Scheduler scheduler) { + return lift(new OperatorTimeout(timeout, timeUnit, other, scheduler)); + } + + /** + * Returns an Observable that mirrors the source Observable but applies a timeout policy for each emitted + * item, where this policy is governed on a specified Scheduler. If the next item isn't emitted within the + * specified timeout duration starting from its predecessor, the resulting Observable terminates and + * notifies observers of a {@code TimeoutException}. + *

+ * + * + * @param timeout + * maximum duration between items before a timeout occurs + * @param timeUnit + * the unit of time that applies to the {@code timeout} argument + * @param scheduler + * the Scheduler to run the timeout timers on + * @return the source Observable modified to notify observers of a {@code TimeoutException} in case of a + * timeout + * @see RxJava Wiki: timeout() + * @see MSDN: Observable.Timeout + */ + public final Observable timeout(long timeout, TimeUnit timeUnit, Scheduler scheduler) { + return timeout(timeout, timeUnit, null, scheduler); + } + + /** + * Returns an Observable that emits each item emitted by the source Observable, wrapped in a + * {@link Timestamped} object. + *

+ * + * + * @return an Observable that emits timestamped items from the source Observable + * @see RxJava Wiki: timestamp() + * @see MSDN: Observable.Timestamp + */ + public final Observable> timestamp() { + return timestamp(Schedulers.immediate()); + } + + /** + * Returns an Observable that emits each item emitted by the source Observable, wrapped in a + * {@link Timestamped} object whose timestamps are provided by a specified Scheduler. + *

+ * + * + * @param scheduler + * the {@link Scheduler} to use as a time source + * @return an Observable that emits timestamped items from the source Observable with timestamps provided by + * the {@code scheduler} + * @see RxJava Wiki: timestamp() + * @see MSDN: Observable.Timestamp + */ + public final Observable> timestamp(Scheduler scheduler) { + return lift(new OperatorTimestamp(scheduler)); + } + + /** + * Converts an Observable into a {@link BlockingObservable} (an Observable with blocking operators). + * + * @return a {@code BlockingObservable} version of this Observable + * @see RxJava Wiki: Blocking Observable Observers + */ + public final BlockingObservable toBlockingObservable() { + return BlockingObservable.from(this); + } + + /** + * 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 {@code onNext} + * function once, passing it the entire list, by calling the Observable's {@code 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. + * + * @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 final Observable> toList() { + return lift(new OperatorToObservableList()); + } + + /** + * Return an Observable that emits a single HashMap containing all items emitted by the source Observable, + * mapped by the keys returned by a specified {@code keySelector} function. + *

+ * + *

+ * If more than one 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 a source item to be used in the HashMap + * @return an Observable that emits a single item: a HashMap containing the mapped items from the source + * Observable + * @see RxJava Wiki: toMap() + * @see MSDN: Observable.ToDictionary + */ + public final Observable> toMap(Func1 keySelector) { + return create(OperationToMap.toMap(this, keySelector)); } /** - * Creates a pattern that matches when both Observable sequences have an - * available item. + * Return an Observable that emits a single HashMap containing values corresponding to items emitted by the + * source Observable, mapped by the keys returned by a specified {@code keySelector} function. *

- * - * - * @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 + * + *

+ * If more than one source item maps to the same key, the HashMap will contain a single entry that + * corresponds to the latest of those items. + * + * @param keySelector + * the function that extracts the key from a source item to be used in the HashMap + * @param valueSelector + * the function that extracts the value from a source item to be used in the HashMap + * @return an Observable that emits a single item: a HashMap containing the mapped items from the source + * Observable + * @see RxJava Wiki: toMap() + * @see MSDN: Observable.ToDictionary */ - public Pattern2 and(Observable right) { - return OperationJoinPatterns.and(this, right); + public final Observable> toMap(Func1 keySelector, Func1 valueSelector) { + return create(OperationToMap.toMap(this, keySelector, valueSelector)); } /** - * Matches when the Observable sequence has an available item and - * projects the item by invoking the selector function. + * Return an Observable that emits a single Map, returned by a specified {@code mapFactory} function, that + * contains keys and values extracted from the items emitted by the source Observable. *

- * - * - * @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); + * + * + * @param keySelector + * the function that extracts the key from a source item to be used 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 a Map instance to be used + * @return an Observable that emits a single item: a Map that contains the mapped items emitted by the + * source Observable + * @see RxJava Wiki: toMap() + */ + public final Observable> toMap(Func1 keySelector, Func1 valueSelector, Func0> mapFactory) { + return create(OperationToMap.toMap(this, keySelector, valueSelector, mapFactory)); } /** - * Joins together the results from several patterns. + * Return an Observable that emits a single HashMap that contains an ArrayList of items emitted by the + * source Observable keyed by a specified {@code keySelector} function. *

- * - * - * @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 + * + * + * @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 item: a HashMap that contains an ArrayList of items mapped from + * the source Observable + * @see RxJava Wiki: toMap() + * @see MSDN: Observable.ToLookup */ - public static Observable when(Plan0... plans) { - return create(OperationJoinPatterns.when(plans)); + public final Observable>> toMultimap(Func1 keySelector) { + return create(OperationToMultimap.toMultimap(this, keySelector)); } /** - * Joins together the results from several patterns. + * Return an Observable that emits a single HashMap that contains an ArrayList of values extracted by a + * specified {@code valueSelector} function from items emitted by the source Observable, keyed by a + * specified {@code keySelector} function. *

- * - * - * @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 + * + * + * @param keySelector + * the function that extracts a key from the source items to be used as key in the HashMap + * @param valueSelector + * the function that extracts a value from the source items to be used as value in the HashMap + * @return an Observable that emits a single item: a HashMap that contains an ArrayList of items mapped from + * the source Observable + * @see RxJava Wiki: toMap() + * @see MSDN: Observable.ToLookup */ - public static Observable when(Iterable> plans) { - if (plans == null) { - throw new NullPointerException("plans"); - } - return create(OperationJoinPatterns.when(plans)); + public final Observable>> toMultimap(Func1 keySelector, Func1 valueSelector) { + return create(OperationToMultimap.toMultimap(this, keySelector, valueSelector)); } /** - * Joins the results from a pattern. + * Return an Observable that emits a single Map, returned by a specified {@code mapFactory} function, that + * contains an ArrayList of values, extracted by a specified {@code valueSelector} function from items + * emitted by the source Observable and keyed by the {@code keySelector} function. *

- * - * - * @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 + * + * + * @param keySelector + * the function that extracts a key from the source items to be used as the key in the Map + * @param valueSelector + * the function that extracts a value from the source items to be used as the value in the Map + * @param mapFactory + * the function that returns a Map instance to be used + * @return an Observable that emits a single item: a Map that contains a list items mapped from the source + * Observable + * @see RxJava Wiki: toMap() */ - @SuppressWarnings("unchecked") - public static Observable when(Plan0 p1) { - return create(OperationJoinPatterns.when(p1)); + public final Observable>> toMultimap(Func1 keySelector, Func1 valueSelector, Func0>> mapFactory) { + return create(OperationToMultimap.toMultimap(this, keySelector, valueSelector, mapFactory)); } /** - * Joins together the results from several patterns. + * Return an Observable that emits a single Map, returned by a specified {@code mapFactory} function, that + * contains a custom collection of values, extracted by a specified {@code valueSelector} function from + * items emitted by the source Observable, and keyed by the {@code keySelector} function. *

- * - * - * @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)); + * + * + * @param keySelector + * the function that extracts a key from the source items to be used as the key in the Map + * @param valueSelector + * the function that extracts a value from the source items to be used as the value in the Map + * @param mapFactory + * the function that returns a 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 item: a Map that contains the collection of mapped items from + * the source Observable + * @see RxJava Wiki: toMap() + */ + public final Observable>> toMultimap(Func1 keySelector, Func1 valueSelector, Func0>> mapFactory, Func1> collectionFactory) { + return create(OperationToMultimap.toMultimap(this, keySelector, valueSelector, mapFactory, collectionFactory)); } /** - * Joins together the results from several patterns. + * Returns an Observable that emits a list that contains 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. *

- * - * - * @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 + * + * + * @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 a list that contains the items emitted by the source Observable in + * sorted order + * @see RxJava Wiki: toSortedList() */ - @SuppressWarnings("unchecked") - public static Observable when(Plan0 p1, Plan0 p2, Plan0 p3) { - return create(OperationJoinPatterns.when(p1, p2, p3)); + public final Observable> toSortedList() { + return lift(new OperatorToObservableSortedList()); } /** - * Joins together the results from several patterns. + * Returns an Observable that emits a list that contains the items emitted by the source Observable, in a + * sorted order based on a specified comparison function. *

- * - * - * @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 + * + * + * @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 a list that contains the items emitted by the source Observable in + * sorted order + * @see RxJava Wiki: toSortedList() */ - @SuppressWarnings("unchecked") - public static Observable when(Plan0 p1, Plan0 p2, Plan0 p3, Plan0 p4) { - return create(OperationJoinPatterns.when(p1, p2, p3, p4)); + public final Observable> toSortedList(Func2 sortFunction) { + return lift(new OperatorToObservableSortedList(sortFunction)); } /** - * Joins together the results from several patterns. + * Asynchronously unsubscribes 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 unsubscriptions happen on the specified {@link Scheduler} + */ + public final Observable unsubscribeOn(Scheduler scheduler) { + return lift(new OperatorUnsubscribeOn(scheduler)); + } + + /** + * Returns an Observable that represents a filtered version of the source Observable. *

- * - * - * @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 + * + * + * @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 source Observable that the filter + * evaluates as {@code true} + * @see RxJava Wiki: where() + * @see #filter(Func1) + * @deprecated use {@link #filter(Func1)} */ - @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)); + @Deprecated + public final Observable where(Func1 predicate) { + return filter(predicate); } /** - * Joins together the results from several patterns. + * Returns an Observable that emits windows of items it collects from the source Observable. The resulting + * Observable emits connected, non-overlapping windows. It emits the current window and opens a new one when + * the Observable produced by the specified {@code closingSelector} emits an item. The + * {@code closingSelector} then creates a new Observable to generate the closer of the next window. *

- * - * - * @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 + * + * + * @param closingSelector + * a {@link Func0} that produces an Observable for every window created. When this Observable + * emits an item, {@code window()} emits the associated window and begins a new one. + * @return an Observable that emits connected, non-overlapping windows of items from the source Observable + * when {@code closingSelector} emits an item + * @see RxJava Wiki: window() */ - @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)); + public final Observable> window(Func0> closingSelector) { + return create(OperationWindow.window(this, closingSelector)); } /** - * Joins together the results from several patterns. + * Returns an Observable that emits windows of items it collects from the source Observable. The resulting + * Observable emits connected, non-overlapping windows, each containing {@code count} items. When the source + * Observable completes or encounters an error, the resulting Observable emits the current window and + * propagates the notification from the source Observable. *

- * - * - * @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 + * + * + * @param count + * the maximum size of each window before it should be emitted + * @return an Observable that emits connected, non-overlapping windows, each containing at most + * {@code count} items from the source Observable + * @see RxJava Wiki: window() */ - @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)); + public final Observable> window(int count) { + return create(OperationWindow.window(this, count)); } /** - * Joins together the results from several patterns. + * Returns an Observable that emits windows of items it collects from the source Observable. The resulting + * Observable emits windows every {@code skip} items, each containing no more than {@code count} items. When + * the source Observable completes or encounters an error, the resulting Observable emits the current window + * and propagates the notification from the source Observable. *

- * - * - * @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 + * + * + * @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 {@code skip} and + * {@code count} are equal this is the same operation as {@link #window(int)}. + * @return an Observable that emits windows every {@code skip} items containing at most {@code count} items + * from the source Observable + * @see RxJava Wiki: window() */ - @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)); + public final Observable> window(int count, int skip) { + return create(OperationWindow.window(this, count, skip)); } /** - * Joins together the results from several patterns. + * Returns an Observable that emits windows of items it collects from the source Observable. The resulting + * Observable starts a new window periodically, as determined by the {@code timeshift} argument. It emits + * each window after a fixed timespan, specified by the {@code timespan} argument. When the source + * Observable completes or Observable completes or encounters an error, the resulting Observable emits the + * current window and propagates the notification from the source Observable. *

- * - * - * @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 + * + * + * @param timespan + * the period of time each window collects items 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 {@code timespan} and {@code timeshift} arguments + * @return an Observable that emits new windows periodically as a fixed timespan elapses + * @see RxJava Wiki: window() */ - @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)); + public final Observable> window(long timespan, long timeshift, TimeUnit unit) { + return create(OperationWindow.window(this, timespan, timeshift, unit)); } /** - * Correlates the elements of two sequences based on overlapping durations. + * Returns an Observable that emits windows of items it collects from the source Observable. The resulting + * Observable starts a new window periodically, as determined by the {@code timeshift} argument. It emits + * each window after a fixed timespan, specified by the {@code timespan} argument. When the source + * Observable completes or Observable completes or encounters an error, the resulting Observable emits the + * current window and propagates the notification from the source Observable. *

- * - * - * @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 + * + * + * @param timespan + * the period of time each window collects items 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 {@code timespan} and {@code timeshift} arguments + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a window + * @return an Observable that emits new windows periodically as a fixed timespan elapses + * @see RxJava Wiki: window() */ - public Observable join(Observable right, Func1> leftDurationSelector, - Func1> rightDurationSelector, - Func2 resultSelector) { - return create(new OperationJoin(this, right, leftDurationSelector, rightDurationSelector, resultSelector)); - } - + public final Observable> window(long timespan, long timeshift, TimeUnit unit, Scheduler scheduler) { + return create(OperationWindow.window(this, timespan, timeshift, unit, scheduler)); + } + /** - * 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. + * Returns an Observable that emits windows of items it collects from the source Observable. The resulting + * Observable emits connected, non-overlapping windows, each of a fixed duration specified by the + * {@code timespan} argument. When the source Observable completes or encounters an error, the resulting + * Observable emits the current window and propagates the notification from 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 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 + * @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 {@code timespan} argument + * @return an Observable that emits connected, non-overlapping windows represending items emitted by the + * source Observable during fixed, consecutive durations + * @see RxJava Wiki: window() */ - public Observable> toMap(Func1 keySelector) { - return create(OperationToMap.toMap(this, keySelector)); + public final Observable> window(long timespan, TimeUnit unit) { + return create(OperationWindow.window(this, timespan, unit)); } - + /** - * Return an Observable that emits a single HashMap containing elements with - * key and value extracted from the values emitted by the source Observable. + * Returns an Observable that emits windows of items it collects from the source Observable. The resulting + * Observable emits connected, non-overlapping windows, each of a fixed duration as specified by the + * {@code timespan} argument or a maximum size as specified by the {@code count} argument (whichever is + * reached first). When the source Observable completes or encounters an error, the resulting Observable + * emits the current window and propagates the notification from the source Observable. *

- * + * + * + * @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 {@code timespan} argument + * @param count + * the maximum size of each window before it should be emitted + * @return an Observable that emits connected, non-overlapping windows of items from the source Observable + * that were emitted during a fixed duration of time or when the window has reached maximum capacity + * (whichever occurs first) + * @see RxJava Wiki: window() + */ + public final Observable> window(long timespan, TimeUnit unit, int count) { + return create(OperationWindow.window(this, timespan, unit, count)); + } + + /** + * Returns an Observable that emits windows of items it collects from the source Observable. The resulting + * Observable emits connected, non-overlapping windows, each of a fixed duration specified by the + * {@code timespan} argument or a maximum size specified by the {@code count} argument (whichever is reached + * first). When the source Observable completes or encounters an error, the resulting Observable emits the + * current window and propagates the notification from 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 + * @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 {@code 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 Observable that emits connected, non-overlapping windows of items from the source Observable + * that were emitted during a fixed duration of time or when the window has reached maximum capacity + * (whichever occurs first) + * @see RxJava Wiki: window() */ - public Observable> toMap(Func1 keySelector, Func1 valueSelector) { - return create(OperationToMap.toMap(this, keySelector, valueSelector)); + public final Observable> window(long timespan, TimeUnit unit, int count, Scheduler scheduler) { + return create(OperationWindow.window(this, timespan, unit, count, scheduler)); } - + /** - * 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. + * Returns an Observable that emits windows of items it collects from the source Observable. The resulting + * Observable emits connected, non-overlapping windows, each of a fixed duration as specified by the + * {@code timespan} argument. When the source Observable completes or encounters an error, the resulting + * Observable emits the current window and propagates the notification from 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() + * @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 {@code timespan} argument + * @param scheduler + * the {@link Scheduler} to use when determining the end and start of a window + * @return an Observable that emits connected, non-overlapping windows containing items emitted by the + * source Observable within a fixed duration + * @see RxJava Wiki: window() */ - public Observable> toMap(Func1 keySelector, Func1 valueSelector, Func0> mapFactory) { - return create(OperationToMap.toMap(this, keySelector, valueSelector, mapFactory)); + public final Observable> window(long timespan, TimeUnit unit, Scheduler scheduler) { + return create(OperationWindow.window(this, timespan, unit, scheduler)); } - + /** - * Return an Observable that emits a single HashMap containing an ArrayList - * of elements, emitted by the source Observable and keyed by the - * keySelector function. + * Returns an Observable that emits windows of items it collects from the source Observable. The resulting + * Observable emits windows that contain those items emitted by the source Observable between the time when + * the {@code windowOpenings} Observable emits an item and when the Observable returned by + * {@code closingSelector} emits an item. *

- * + * * - * @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 + * @param windowOpenings + * an Observable that, when it emits an item, causes another window to be created + * @param closingSelector + * a {@link Func1} that produces an Observable for every window created. When this Observable + * emits an item, the associated window is closed and emitted + * @return an Observable that emits windows of items emitted by the source Observable that are governed by + * the specified window-governing Observables + * @see RxJava Wiki: window() */ - public Observable>> toMultimap(Func1 keySelector) { - return create(OperationToMultimap.toMultimap(this, keySelector)); + public final Observable> window(Observable windowOpenings, Func1> closingSelector) { + return create(OperationWindow.window(this, windowOpenings, closingSelector)); } - + /** - * 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. + * Returns an Observable that emits non-overlapping windows of items it collects from the source Observable + * where the boundary of each window is determined by the items emitted from a specified boundary-governing + * Observable. *

- * + * * - * @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 + * @param + * the window element type (ignored) + * @param boundary + * an Observable whose emitted items close and open windows + * @return an Observable that emits non-overlapping windows of items it collects from the source Observable + * where the boundary of each window is determined by the items emitted from the {@code boundary} + * Observable */ - public Observable>> toMultimap(Func1 keySelector, Func1 valueSelector) { - return create(OperationToMultimap.toMultimap(this, keySelector, valueSelector)); + public final Observable> window(Observable boundary) { + return create(OperationWindow.window(this, boundary)); } - + /** - * 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. + * Returns an Observable that emits items that are the result of applying a specified function to pairs of + * values, one each from the source Observable and a specified Iterable sequence. *

- * + * + *

+ * Note that the {@code other} Iterable is evaluated as items are observed from the source Observable; it is + * not pre-consumed. This allows you to zip infinite streams on either side. * - * @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() + * @param + * the type of items in the {@code other} Iterable + * @param + * the type of items emitted by the resulting Observable + * @param other + * the Iterable sequence + * @param zipFunction + * a function that combines the pairs of items from the Observable and the Iterable to generate + * the items to be emitted by the resulting Observable + * @return an Observable that pairs up values from the source Observable and the {@code other} Iterable + * sequence and emits the results of {@code zipFunction} applied to these pairs */ - public Observable>> toMultimap(Func1 keySelector, Func1 valueSelector, Func0>> mapFactory) { - return create(OperationToMultimap.toMultimap(this, keySelector, valueSelector, mapFactory)); + public final Observable zip(Iterable other, Func2 zipFunction) { + return lift(new OperatorZipIterable(other, zipFunction)); } /** - * 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. + * Returns an Observable that emits items that are the result of applying a specified function to pairs of + * values, one each from the source Observable and another specified 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 - * @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)); - } - + * @param + * the type of items emitted by the {@code other} Observable + * @param + * the type of items emitted by the resulting Observable + * @param other + * the other Observable + * @param zipFunction + * a function that combines the pairs of items from the two Observables to generate the items to + * be emitted by the resulting Observable + * @return an Observable that pairs up values from the source Observable and the {@code other} Observable + * and emits the results of {@code zipFunction} applied to these pairs + */ + public final Observable zip(Observable other, Func2 zipFunction) { + return zip(this, other, zipFunction); + } + /** - * Return an Observable that skips elements from the source Observable until - * the secondary Observable emits an element. - *

- * + * An Observable that never sends any information to an {@link Observer}. * - * @param other the other Observable that has to emit an element before this - * Observable's elements are relayed - * @return an Observable that skips elements from the source Observable - * until the secondary Observable emits an element. - * @see RxJava Wiki: skipUntil() - * @see MSDN: Observable.SkipUntil + * This Observable is useful primarily for testing purposes. + * + * @param + * the type of item (not) emitted by the Observable */ - public Observable skipUntil(Observable other) { - return create(new OperationSkipUntil(this, other)); + private static class NeverObservable extends Observable { + public NeverObservable() { + super(new OnSubscribe() { + + @Override + public void call(Subscriber observer) { + // do nothing + } + + }); + } } /** - * Groups the items emitted by an Observable according to a specified key - * selector function until the duration Observable expires for the key. - *

- * - * - * @param keySelector a function to extract the key for each item - * @param durationSelector a function to signal the expiration of a group - * @return a sequence of Observable groups, each of which corresponds to a - * unique key value, containing all items that share that same - * key value - * @see RxJava Wiki: groupByUntil() - * @see MSDN: Observable.GroupByUntil + * An Observable that invokes {@link Observer#onError onError} when the {@link Observer} subscribes to it. + * + * @param + * the type of item (ostensibly) emitted by the Observable */ - public Observable> groupByUntil(Func1 keySelector, Func1, ? extends Observable> durationSelector) { - return groupByUntil(keySelector, Functions.identity(), durationSelector); + private static class ThrowObservable extends Observable { + + public ThrowObservable(final Throwable exception) { + super(new OnSubscribe() { + + /** + * Accepts an {@link Observer} and calls its {@link Observer#onError onError} method. + * + * @param observer + * an {@link Observer} of this Observable + * @return a reference to the subscription + */ + @Override + public void call(Subscriber observer) { + observer.onError(exception); + } + + }); + } } - + + @SuppressWarnings("rawtypes") + private final static ConcurrentHashMap internalClassMap = new ConcurrentHashMap(); + /** - * Groups the items emitted by an Observable according to specified key and - * value selector functions until the duration Observable expires for the - * key. + * Whether a given {@link Function} is an internal implementation inside rx.* packages or not. *

- * - * - * @param keySelector a function to extract the key for each item - * @param valueSelector a function to map each source element to an item - * emitted by an Observable group - * @param durationSelector a function to signal the expiration of a group - * @return a sequence of Observable groups, each of which corresponds to a - * unique key value, containing all items that share that same key - * value - * @see RxJava Wiki: groupByUntil() - * @see MSDN: Observable.GroupByUntil + * 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 Observer" + *

+ * 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. + * + * @return {@code true} if the given function is an internal implementation, and {@code false} otherwise */ - public Observable> groupByUntil(Func1 keySelector, Func1 valueSelector, Func1, ? extends Observable> durationSelector) { - return create(new OperationGroupByUntil(this, keySelector, valueSelector, durationSelector)); + private boolean isInternalImplementation(Object o) { + if (o == null) { + return true; + } + // prevent double-wrapping (yeah it happens) + if (o instanceof SafeSubscriber) { + return true; + } + + 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; + } } } diff --git a/rxjava-core/src/main/java/rx/Observer.java b/rxjava-core/src/main/java/rx/Observer.java index 3d35066ba4..a2de94be24 100644 --- a/rxjava-core/src/main/java/rx/Observer.java +++ b/rxjava-core/src/main/java/rx/Observer.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,9 +18,9 @@ /** * 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 - * call an Observer's onCompleted closure exactly once or the Observer's onError closure exactly once. + * 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 * @@ -33,7 +33,7 @@ public interface Observer { *

* The {@link Observable} will not call this closure if it calls onError. */ - public void onCompleted(); + public abstract void onCompleted(); /** * Notifies the Observer that the {@link Observable} has experienced an error condition. @@ -42,7 +42,7 @@ public interface Observer { * * @param e */ - public void onError(Throwable e); + public abstract void onError(Throwable e); /** * Provides the Observer with new data. @@ -53,5 +53,6 @@ public interface Observer { * * @param args */ - public void onNext(T args); + public abstract void onNext(T t); + } diff --git a/rxjava-core/src/main/java/rx/Scheduler.java b/rxjava-core/src/main/java/rx/Scheduler.java index b01872f226..21d4791f44 100644 --- a/rxjava-core/src/main/java/rx/Scheduler.java +++ b/rxjava-core/src/main/java/rx/Scheduler.java @@ -15,16 +15,9 @@ */ package rx; -import java.util.Date; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import rx.subscriptions.CompositeSubscription; -import rx.subscriptions.MultipleAssignmentSubscription; -import rx.subscriptions.Subscriptions; -import rx.util.functions.Action0; -import rx.util.functions.Action1; -import rx.util.functions.Func2; +import rx.functions.Action1; /** * Represents an object that schedules units of work. @@ -48,30 +41,24 @@ public abstract class Scheduler { /** - * Schedules a cancelable action to be executed. + * Schedules an Action on a new Scheduler instance (typically another thread) for execution. * - * @param state - * State to pass into the action. * @param action * Action to schedule. * @return a subscription to be able to unsubscribe from action. */ - public abstract Subscription schedule(T state, Func2 action); + + public abstract Subscription schedule(Action1 action); /** - * Schedules a cancelable action to be executed in delayTime. + * Schedules an Action on a new Scheduler instance (typically another thread) for execution at some point in the future. * - * @param state - * State to pass into the action. * @param action - * Action to schedule. * @param delayTime - * Time the action is to be delayed before executing. * @param unit - * Time unit of the delay time. - * @return a subscription to be able to unsubscribe from action. + * @return */ - public abstract Subscription schedule(T state, Func2 action, long delayTime, TimeUnit unit); + public abstract Subscription schedule(final Action1 action, final long delayTime, final TimeUnit unit); /** * Schedules a cancelable action to be executed periodically. @@ -90,152 +77,109 @@ public abstract class Scheduler { * 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) { + public Subscription schedulePeriodically(final Action1 action, long initialDelay, long period, TimeUnit unit) { final long periodInNanos = unit.toNanos(period); - final AtomicBoolean complete = new AtomicBoolean(); - final Func2 recursiveAction = new Func2() { + final Action1 recursiveAction = new Action1() { @Override - public Subscription call(Scheduler scheduler, T state0) { - if (!complete.get()) { + public void call(Inner inner) { + if (!inner.isUnsubscribed()) { long startedAt = now(); - final Subscription sub1 = action.call(scheduler, state0); + action.call(inner); long timeTakenByActionInNanos = TimeUnit.MILLISECONDS.toNanos(now() - startedAt); - final Subscription sub2 = schedule(state0, this, periodInNanos - timeTakenByActionInNanos, TimeUnit.NANOSECONDS); - return Subscriptions.create(new Action0() { - @Override - public void call() { - sub1.unsubscribe(); - sub2.unsubscribe(); - } - }); + inner.schedule(this, periodInNanos - timeTakenByActionInNanos, TimeUnit.NANOSECONDS); } - return Subscriptions.empty(); } }; - final Subscription sub = schedule(state, recursiveAction, initialDelay, unit); - return Subscriptions.create(new Action0() { + return schedule(recursiveAction, initialDelay, unit); + } + + public final Subscription scheduleRecursive(final Action1 action) { + return schedule(new Action1() { + @Override - public void call() { - complete.set(true); - sub.unsubscribe(); + public void call(Inner inner) { + action.call(new Recurse(inner, action)); } - }); - } - /** - * 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. - */ - public Subscription schedule(T state, Func2 action, Date dueTime) { - long scheduledTime = dueTime.getTime(); - long timeInFuture = scheduledTime - now(); - if (timeInFuture <= 0) { - return schedule(state, action); - } else { - return schedule(state, action, timeInFuture, TimeUnit.MILLISECONDS); - } + }); } - /** - * Schedules an action and receives back an action for recursive execution. - * - * @param action - * action - * @return a subscription to be able to unsubscribe from action. - */ - public Subscription schedule(final Action1 action) { - final CompositeSubscription parentSubscription = new CompositeSubscription(); - final MultipleAssignmentSubscription childSubscription = new MultipleAssignmentSubscription(); - parentSubscription.add(childSubscription); - - final Func2 parentAction = new Func2() { + public static final class Recurse { + private final Action1 action; + private final Inner inner; - @Override - public Subscription call(final Scheduler scheduler, final Func2 parentAction) { - action.call(new Action0() { + private Recurse(Inner inner, Action1 action) { + this.inner = inner; + this.action = action; + } - @Override - public void call() { - if (!parentSubscription.isUnsubscribed()) { - childSubscription.setSubscription(scheduler.schedule(parentAction, parentAction)); - } - } + /** + * Schedule the current function for execution immediately. + */ + public final void schedule() { + final Recurse self = this; + inner.schedule(new Action1() { - }); - return childSubscription; - } - }; + @Override + public void call(Inner _inner) { + action.call(self); + } - parentSubscription.add(schedule(parentAction, parentAction)); + }); + } - return parentSubscription; - } + /** + * Schedule the current function for execution in the future. + */ + public final void schedule(long delay, TimeUnit unit) { + final Recurse self = this; + inner.schedule(new Action1() { - /** - * Schedules an action to be executed. - * - * @param action - * action - * @return a subscription to be able to unsubscribe from action. - */ - public Subscription schedule(final Action0 action) { - return schedule(null, new Func2() { + @Override + public void call(Inner _inner) { + action.call(self); + } - @Override - public Subscription call(Scheduler scheduler, Void state) { - action.call(); - return Subscriptions.empty(); - } - }); + }, delay, unit); + } } - /** - * Schedules an action to be executed in delayTime. - * - * @param action - * action - * @return a subscription to be able to unsubscribe from action. - */ - public Subscription schedule(final Action0 action, long delayTime, TimeUnit unit) { - return schedule(null, new Func2() { - - @Override - public Subscription call(Scheduler scheduler, Void state) { - action.call(); - return Subscriptions.empty(); - } - }, delayTime, unit); + public abstract static class Inner implements Subscription { + + /** + * Schedules an action to be executed in delayTime. + * + * @param delayTime + * Time the action is to be delayed before executing. + * @param unit + * Time unit of the delay time. + */ + public abstract void schedule(Action1 action, long delayTime, TimeUnit unit); + + /** + * Schedules a cancelable action to be executed in delayTime. + * + */ + public abstract void schedule(Action1 action); + + /** + * @return the scheduler's notion of current absolute time in milliseconds. + */ + public long now() { + return System.currentTimeMillis(); + } } /** - * Schedules an action to be executed periodically. + * 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. * - * @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. - * @param unit - * The time unit the interval above is given in. - * @return A subscription to be able to unsubscribe from action. + * @return the scheduler's available degree of parallelism. */ - public Subscription schedulePeriodically(final Action0 action, long initialDelay, long period, TimeUnit unit) { - return schedulePeriodically(null, new Func2() { - @Override - public Subscription call(Scheduler scheduler, Void state) { - action.call(); - return Subscriptions.empty(); - } - }, initialDelay, period, unit); + public int degreeOfParallelism() { + return Runtime.getRuntime().availableProcessors(); } /** @@ -245,14 +189,4 @@ public long now() { return System.currentTimeMillis(); } - /** - * 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/Subscriber.java b/rxjava-core/src/main/java/rx/Subscriber.java new file mode 100644 index 0000000000..3ad986813c --- /dev/null +++ b/rxjava-core/src/main/java/rx/Subscriber.java @@ -0,0 +1,65 @@ +/** + * Copyright 2014 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 rx.subscriptions.CompositeSubscription; + +/** + * 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 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 abstract class Subscriber implements Observer, Subscription { + + private final CompositeSubscription cs; + + protected Subscriber(CompositeSubscription cs) { + if (cs == null) { + throw new IllegalArgumentException("The CompositeSubscription can not be null"); + } + this.cs = cs; + } + + protected Subscriber() { + this(new CompositeSubscription()); + } + + protected Subscriber(Subscriber op) { + this(op.cs); + } + + /** + * Used to register an unsubscribe callback. + */ + public final void add(Subscription s) { + cs.add(s); + } + + @Override + public final void unsubscribe() { + cs.unsubscribe(); + } + + public final boolean isUnsubscribed() { + return cs.isUnsubscribed(); + } +} diff --git a/rxjava-core/src/main/java/rx/Subscription.java b/rxjava-core/src/main/java/rx/Subscription.java index ceb8510f00..20a11ad664 100644 --- a/rxjava-core/src/main/java/rx/Subscription.java +++ b/rxjava-core/src/main/java/rx/Subscription.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import rx.subscriptions.Subscriptions; /** - * Subscription returns from {@link Observable#subscribe(Observer)} to allow unsubscribing. + * Subscription returns from {@link Observable#subscribe(Subscriber)} to allow unsubscribing. *

* See utilities in {@link Subscriptions} and implementations in the {@code rx.subscriptions} package. *

@@ -27,10 +27,12 @@ public interface Subscription { /** - * Stop receiving notifications on the {@link Observer} that was registered when this Subscription was received. + * Stop receiving notifications on the {@link Subscriber} that was registered when this Subscription was received. *

- * This allows unregistering an {@link Observer} before it has finished receiving all events (ie. before onCompleted is called). + * This allows unregistering an {@link Subscriber} before it has finished receiving all events (ie. before onCompleted is called). */ public void unsubscribe(); + + public boolean isUnsubscribed(); } diff --git a/rxjava-core/src/main/java/rx/concurrency/CurrentThreadScheduler.java b/rxjava-core/src/main/java/rx/concurrency/CurrentThreadScheduler.java deleted file mode 100644 index 9bf1f6ad91..0000000000 --- a/rxjava-core/src/main/java/rx/concurrency/CurrentThreadScheduler.java +++ /dev/null @@ -1,99 +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.concurrency; - -import java.util.PriorityQueue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import rx.Scheduler; -import rx.Subscription; -import rx.util.functions.Func2; - -/** - * Schedules work on the current thread but does not execute immediately. Work is put in a queue and executed after the current unit of work is completed. - */ -public class CurrentThreadScheduler extends Scheduler { - private static final CurrentThreadScheduler INSTANCE = new CurrentThreadScheduler(); - - public static CurrentThreadScheduler getInstance() { - return INSTANCE; - } - - private static final ThreadLocal> QUEUE = new ThreadLocal>(); - - /* package accessible for unit tests */CurrentThreadScheduler() { - } - - private final AtomicInteger counter = new AtomicInteger(0); - - @Override - public Subscription schedule(T state, Func2 action) { - DiscardableAction discardableAction = new DiscardableAction(state, action); - enqueue(discardableAction, now()); - return discardableAction; - } - - @Override - public Subscription schedule(T state, Func2 action, long dueTime, TimeUnit unit) { - long execTime = now() + unit.toMillis(dueTime); - - DiscardableAction discardableAction = new DiscardableAction(state, new SleepingAction(action, this, execTime)); - enqueue(discardableAction, execTime); - return discardableAction; - } - - private void enqueue(DiscardableAction action, long execTime) { - PriorityQueue queue = QUEUE.get(); - boolean exec = queue == null; - - if (exec) { - queue = new PriorityQueue(); - QUEUE.set(queue); - } - - queue.add(new TimedAction(action, execTime, counter.incrementAndGet())); - - if (exec) { - while (!queue.isEmpty()) { - queue.poll().action.call(this); - } - - QUEUE.set(null); - } - } - - private static class TimedAction implements Comparable { - final DiscardableAction action; - final Long execTime; - final Integer count; // In case if time between enqueueing took less than 1ms - - private TimedAction(DiscardableAction action, Long execTime, Integer count) { - this.action = action; - this.execTime = execTime; - this.count = count; - } - - @Override - public int compareTo(TimedAction that) { - int result = execTime.compareTo(that.execTime); - if (result == 0) { - return count.compareTo(that.count); - } - return result; - } - } -} diff --git a/rxjava-core/src/main/java/rx/concurrency/DiscardableAction.java b/rxjava-core/src/main/java/rx/concurrency/DiscardableAction.java deleted file mode 100644 index 94d04075c5..0000000000 --- a/rxjava-core/src/main/java/rx/concurrency/DiscardableAction.java +++ /dev/null @@ -1,57 +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.concurrency; - -import java.util.concurrent.atomic.AtomicBoolean; - -import rx.Scheduler; -import rx.Subscription; -import rx.operators.SafeObservableSubscription; -import rx.util.functions.Func1; -import rx.util.functions.Func2; - -/** - * Combines standard {@link Subscription#unsubscribe()} functionality with ability to skip execution if an unsubscribe occurs before the {@link #call()} method is invoked. - */ -/* package */class DiscardableAction implements Func1, Subscription { - private final Func2 underlying; - private final T state; - - private final SafeObservableSubscription wrapper = new SafeObservableSubscription(); - private final AtomicBoolean ready = new AtomicBoolean(true); - - public DiscardableAction(T state, Func2 underlying) { - this.state = state; - this.underlying = underlying; - } - - @Override - public Subscription call(Scheduler scheduler) { - if (ready.compareAndSet(true, false)) { - Subscription subscription = underlying.call(scheduler, state); - wrapper.wrap(subscription); - return subscription; - } - return wrapper; - } - - @Override - public void unsubscribe() { - ready.set(false); - wrapper.unsubscribe(); - } - -} diff --git a/rxjava-core/src/main/java/rx/concurrency/ExecutorScheduler.java b/rxjava-core/src/main/java/rx/concurrency/ExecutorScheduler.java deleted file mode 100644 index 1e35735c67..0000000000 --- a/rxjava-core/src/main/java/rx/concurrency/ExecutorScheduler.java +++ /dev/null @@ -1,147 +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.concurrency; - -import java.util.concurrent.Executor; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import rx.Scheduler; -import rx.Subscription; -import rx.subscriptions.CompositeSubscription; -import rx.subscriptions.Subscriptions; -import rx.util.functions.Func2; - -/** - * A {@link Scheduler} implementation that uses an {@link Executor} or {@link ScheduledExecutorService} implementation. - *

- * Note that if an {@link Executor} implementation is used instead of {@link ScheduledExecutorService} then a system-wide Timer will be used to handle delayed events. - */ -public class ExecutorScheduler extends Scheduler { - private final Executor executor; - - public ExecutorScheduler(Executor executor) { - this.executor = executor; - } - - public ExecutorScheduler(ScheduledExecutorService executor) { - this.executor = executor; - } - - @Override - 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() { - Subscription s = action.call(ExecutorScheduler.this, state); - subscriptions.add(s); - } - }, initialDelay, period, unit); - - 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); - final Scheduler _scheduler = this; - // all subscriptions that may need to be unsubscribed - final CompositeSubscription subscription = new CompositeSubscription(discardableAction); - - if (executor instanceof ScheduledExecutorService) { - // we are a ScheduledExecutorService so can do proper scheduling - ScheduledFuture f = ((ScheduledExecutorService) executor).schedule(new Runnable() { - @Override - public void run() { - // when the delay has passed we now do the work on the actual scheduler - Subscription s = discardableAction.call(_scheduler); - // 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)); - } else { - // we are not a ScheduledExecutorService so can't directly schedule - if (delayTime == 0) { - // no delay so put on the thread-pool right now - Subscription s = schedule(state, action); - // add the subscription to the CompositeSubscription so it is unsubscribed - subscription.add(s); - } else { - // there is a delay and this isn't a ScheduledExecutorService so we'll use a system-wide ScheduledExecutorService - // to handle the scheduling and once it's ready then execute on this Executor - ScheduledFuture f = GenericScheduledExecutorService.getInstance().schedule(new Runnable() { - - @Override - public void run() { - // now execute on the real Executor (by using the other overload that schedules for immediate execution) - 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) { - final DiscardableAction discardableAction = new DiscardableAction(state, action); - final Scheduler _scheduler = this; - // all subscriptions that may need to be unsubscribed - final CompositeSubscription subscription = new CompositeSubscription(discardableAction); - - // work to be done on a thread - Runnable r = new Runnable() { - @Override - public void run() { - Subscription s = discardableAction.call(_scheduler); - // add the subscription to the CompositeSubscription so it is unsubscribed - subscription.add(s); - } - }; - - // submit for immediate execution - if (executor instanceof ExecutorService) { - // 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.from(f)); - } else { - // we are the lowest common denominator so can't unsubscribe once we execute - executor.execute(r); - } - - return subscription; - - } - -} diff --git a/rxjava-core/src/main/java/rx/concurrency/ImmediateScheduler.java b/rxjava-core/src/main/java/rx/concurrency/ImmediateScheduler.java deleted file mode 100644 index c5a4cef9d4..0000000000 --- a/rxjava-core/src/main/java/rx/concurrency/ImmediateScheduler.java +++ /dev/null @@ -1,49 +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.concurrency; - -import java.util.concurrent.TimeUnit; - -import rx.Scheduler; -import rx.Subscription; -import rx.util.functions.Func2; - -/** - * Executes work immediately on the current thread. - */ -public final class ImmediateScheduler extends Scheduler { - private static final ImmediateScheduler INSTANCE = new ImmediateScheduler(); - - public static ImmediateScheduler getInstance() { - return INSTANCE; - } - - /* package accessible for unit tests */ImmediateScheduler() { - } - - @Override - public Subscription schedule(T state, Func2 action) { - return action.call(this, state); - } - - @Override - public Subscription schedule(T state, Func2 action, long dueTime, TimeUnit unit) { - // since we are executing immediately on this thread we must cause this thread to sleep - long execTime = now() + unit.toMillis(dueTime); - - return schedule(state, new SleepingAction(action, this, execTime)); - } -} diff --git a/rxjava-core/src/main/java/rx/concurrency/NewThreadScheduler.java b/rxjava-core/src/main/java/rx/concurrency/NewThreadScheduler.java deleted file mode 100644 index 036ba62127..0000000000 --- a/rxjava-core/src/main/java/rx/concurrency/NewThreadScheduler.java +++ /dev/null @@ -1,136 +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.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.subscriptions.CompositeSubscription; -import rx.subscriptions.Subscriptions; -import rx.util.functions.Func2; - -/** - * Schedules work on a new thread. - */ -public class NewThreadScheduler extends Scheduler { - - private final static NewThreadScheduler INSTANCE = new NewThreadScheduler(); - private final static AtomicLong count = new AtomicLong(); - - public static NewThreadScheduler getInstance() { - return INSTANCE; - } - - private NewThreadScheduler() { - - } - - private static class EventLoopScheduler extends Scheduler { - private final ExecutorService executor; - - 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 - public Subscription schedule(final T state, final Func2 action, long delay, 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); - } - } - }, delay, 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; - } -} diff --git a/rxjava-core/src/main/java/rx/concurrency/TestScheduler.java b/rxjava-core/src/main/java/rx/concurrency/TestScheduler.java deleted file mode 100644 index 04b8c1a2c5..0000000000 --- a/rxjava-core/src/main/java/rx/concurrency/TestScheduler.java +++ /dev/null @@ -1,120 +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.concurrency; - -import java.util.Comparator; -import java.util.PriorityQueue; -import java.util.Queue; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import rx.Scheduler; -import rx.Subscription; -import rx.util.functions.Func2; - -public class TestScheduler extends Scheduler { - private final Queue> queue = new PriorityQueue>(11, new CompareActionsByTime()); - - private static class TimedAction { - - private final long time; - private final Func2 action; - private final T state; - private final TestScheduler scheduler; - private final AtomicBoolean isCancelled = new AtomicBoolean(false); - - private TimedAction(TestScheduler scheduler, long time, Func2 action, T state) { - this.time = time; - this.action = action; - this.state = state; - this.scheduler = scheduler; - } - - public void cancel() { - isCancelled.set(true); - } - - @Override - public String toString() { - return String.format("TimedAction(time = %d, action = %s)", time, action.toString()); - } - } - - private static class CompareActionsByTime implements Comparator> { - @Override - public int compare(TimedAction action1, TimedAction action2) { - return Long.valueOf(action1.time).compareTo(Long.valueOf(action2.time)); - } - } - - // Storing time in nanoseconds internally. - private long time; - - @Override - public long now() { - return TimeUnit.NANOSECONDS.toMillis(time); - } - - public void advanceTimeBy(long delayTime, TimeUnit unit) { - advanceTimeTo(time + unit.toNanos(delayTime), TimeUnit.NANOSECONDS); - } - - public void advanceTimeTo(long delayTime, TimeUnit unit) { - long targetTime = unit.toNanos(delayTime); - triggerActions(targetTime); - } - - public void triggerActions() { - triggerActions(time); - } - - @SuppressWarnings("unchecked") - private void triggerActions(long targetTimeInNanos) { - while (!queue.isEmpty()) { - TimedAction current = queue.peek(); - if (current.time > targetTimeInNanos) { - break; - } - time = current.time; - queue.remove(); - - // Only execute if the TimedAction has not yet been cancelled - if (!current.isCancelled.get()) { - // because the queue can have wildcards we have to ignore the type T for the state - ((Func2) current.action).call(current.scheduler, current.state); - } - } - time = targetTimeInNanos; - } - - @Override - public Subscription schedule(T state, Func2 action) { - return schedule(state, action, 0, TimeUnit.MILLISECONDS); - } - - @Override - public Subscription schedule(T state, Func2 action, long delayTime, TimeUnit unit) { - final TimedAction timedAction = new TimedAction(this, time + unit.toNanos(delayTime), action, state); - queue.add(timedAction); - - return new Subscription() { - @Override - public void unsubscribe() { - timedAction.cancel(); - } - }; - } -} diff --git a/rxjava-core/src/main/java/rx/exceptions/CompositeException.java b/rxjava-core/src/main/java/rx/exceptions/CompositeException.java new file mode 100644 index 0000000000..af2dfa1948 --- /dev/null +++ b/rxjava-core/src/main/java/rx/exceptions/CompositeException.java @@ -0,0 +1,120 @@ +/** + * Copyright 2014 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.exceptions; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Exception that is a composite of 1 or more other exceptions. + *

+ * The getMessage() will return a concatenation of the composite exceptions. + */ +public final class CompositeException extends RuntimeException { + + private static final long serialVersionUID = 3026362227162912146L; + + private final List exceptions; + private final String message; + private final Throwable cause; + + public CompositeException(String messagePrefix, Collection errors) { + List _exceptions = new ArrayList(); + CompositeExceptionCausalChain _cause = new CompositeExceptionCausalChain(); + int count = 0; + for (Throwable e : errors) { + count++; + attachCallingThreadStack(_cause, e); + _exceptions.add(e); + } + this.exceptions = Collections.unmodifiableList(_exceptions); + this.message = count + " exceptions occurred. See them in causal chain below."; + this.cause = _cause; + } + + public CompositeException(Collection errors) { + this(null, errors); + } + + public List getExceptions() { + return exceptions; + } + + @Override + public String getMessage() { + return message; + } + + @Override + public synchronized Throwable getCause() { + return cause; + } + + @SuppressWarnings("unused") + // useful when debugging but don't want to make part of publicly supported API + private static String getStackTraceAsString(StackTraceElement[] stack) { + StringBuilder s = new StringBuilder(); + boolean firstLine = true; + for (StackTraceElement e : stack) { + if (e.toString().startsWith("java.lang.Thread.getStackTrace")) { + // we'll ignore this one + continue; + } + if (!firstLine) { + s.append("\n\t"); + } + s.append(e.toString()); + firstLine = false; + } + return s.toString(); + } + + /* package-private */ static void attachCallingThreadStack(Throwable e, Throwable cause) { + Set seenCauses = new HashSet(); + + while (e.getCause() != null) { + e = e.getCause(); + if (seenCauses.contains(e.getCause())) { + break; + } else { + seenCauses.add(e.getCause()); + } + } + // we now have 'e' as the last in the chain + try { + e.initCause(cause); + } catch (Throwable t) { + // ignore + // the javadocs say that some Throwables (depending on how they're made) will never + // let me call initCause without blowing up even if it returns null + } + } + + /* package-private */ final static class CompositeExceptionCausalChain extends RuntimeException { + private static final long serialVersionUID = 3875212506787802066L; + /* package-private */ static String MESSAGE = "Chain of Causes for CompositeException In Order Received =>"; + + @Override + public String getMessage() { + return MESSAGE; + } + } + +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/exceptions/Exceptions.java b/rxjava-core/src/main/java/rx/exceptions/Exceptions.java new file mode 100644 index 0000000000..5fccfb2a61 --- /dev/null +++ b/rxjava-core/src/main/java/rx/exceptions/Exceptions.java @@ -0,0 +1,100 @@ +/** + * Copyright 2014 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.exceptions; + +import java.util.HashSet; +import java.util.Set; + +public class Exceptions { + private Exceptions() { + + } + + public static RuntimeException propagate(Throwable t) { + /** + * The return type of RuntimeException is a trick for code to be like this: + * + * throw Exceptions.propagate(e); + * + * Even though nothing will return and throw via that 'throw', it allows the code to look like it + * so it's easy to read and understand that it will always result in a throw. + */ + if (t instanceof RuntimeException) { + throw (RuntimeException) t; + } else if (t instanceof Error) { + throw (Error) t; + } else { + throw new RuntimeException(t); + } + } + + public static void throwIfFatal(Throwable t) { + if (t instanceof OnErrorNotImplementedException) { + throw (OnErrorNotImplementedException) t; + } + // values here derived from https://github.com/Netflix/RxJava/issues/748#issuecomment-32471495 + else if (t instanceof StackOverflowError) { + throw (StackOverflowError) t; + } else if (t instanceof VirtualMachineError) { + throw (VirtualMachineError) t; + } else if (t instanceof ThreadDeath) { + throw (ThreadDeath) t; + } else if (t instanceof LinkageError) { + throw (LinkageError) t; + } + } + + private static final int MAX_DEPTH = 25; + + public static void addCause(Throwable e, Throwable cause) { + Set seenCauses = new HashSet(); + + int i = 0; + while (e.getCause() != null) { + if (i++ >= MAX_DEPTH) { + // stack too deep to associate cause + return; + } + e = e.getCause(); + if (seenCauses.contains(e.getCause())) { + break; + } else { + seenCauses.add(e.getCause()); + } + } + // we now have 'e' as the last in the chain + try { + e.initCause(cause); + } catch (Throwable t) { + // ignore + // the javadocs say that some Throwables (depending on how they're made) will never + // let me call initCause without blowing up even if it returns null + } + } + + public static Throwable getFinalCause(Throwable e) { + int i = 0; + while (e.getCause() != null) { + if (i++ >= MAX_DEPTH) { + // stack too deep to get final cause + return new RuntimeException("Stack too deep to get final cause"); + } + e = e.getCause(); + } + return e; + } + +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/util/OnErrorNotImplementedException.java b/rxjava-core/src/main/java/rx/exceptions/OnErrorNotImplementedException.java similarity index 87% rename from rxjava-core/src/main/java/rx/util/OnErrorNotImplementedException.java rename to rxjava-core/src/main/java/rx/exceptions/OnErrorNotImplementedException.java index 29c5718877..15ca62a036 100644 --- a/rxjava-core/src/main/java/rx/util/OnErrorNotImplementedException.java +++ b/rxjava-core/src/main/java/rx/exceptions/OnErrorNotImplementedException.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package rx.util; +package rx.exceptions; -import rx.Observer; +import rx.Subscriber; /** - * Used for re-throwing {@link Observer#onError(Throwable)} when an implementation doesn't exist. + * Used for re-throwing {@link Subscriber#onError(Throwable)} when an implementation doesn't exist. * * https://github.com/Netflix/RxJava/issues/198 * diff --git a/rxjava-core/src/main/java/rx/exceptions/OnErrorThrowable.java b/rxjava-core/src/main/java/rx/exceptions/OnErrorThrowable.java new file mode 100644 index 0000000000..7044dd37ca --- /dev/null +++ b/rxjava-core/src/main/java/rx/exceptions/OnErrorThrowable.java @@ -0,0 +1,81 @@ +/** + * Copyright 2014 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.exceptions; + +public class OnErrorThrowable extends RuntimeException { + + private static final long serialVersionUID = -569558213262703934L; + + private final boolean hasValue; + private final Object value; + + private OnErrorThrowable(Throwable exception) { + super(exception); + hasValue = false; + this.value = null; + } + + private OnErrorThrowable(Throwable exception, Object value) { + super(exception); + hasValue = true; + this.value = value; + } + + public Object getValue() { + return value; + } + + public boolean isValueNull() { + return hasValue; + } + + public static OnErrorThrowable from(Throwable t) { + Throwable cause = Exceptions.getFinalCause(t); + if (cause instanceof OnErrorThrowable.OnNextValue) { + return new OnErrorThrowable(t, ((OnNextValue) cause).getValue()); + } else { + return new OnErrorThrowable(t); + } + } + + /** + * Adds the given value as the final cause of the given Throwable wrapped in OnNextValue RuntimeException.. + * + * @param e + * @param value + * @return Throwable e passed in + */ + public static Throwable addValueAsLastCause(Throwable e, Object value) { + Exceptions.addCause(e, new OnNextValue(value)); + return e; + } + + public static class OnNextValue extends RuntimeException { + + private static final long serialVersionUID = -3454462756050397899L; + private final Object value; + + public OnNextValue(Object value) { + super("OnError while emitting onNext value: " + value); + this.value = value; + } + + public Object getValue() { + return value; + } + + } +} diff --git a/rxjava-core/src/main/java/rx/functions/Action.java b/rxjava-core/src/main/java/rx/functions/Action.java new file mode 100644 index 0000000000..277533e325 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Action.java @@ -0,0 +1,25 @@ +/** + * Copyright 2014 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.functions; + +/** + * All Action interfaces extend from this. + *

+ * Marker interface to allow instanceof checks. + */ +public interface Action extends Function { + +} diff --git a/rxjava-core/src/test/java/rx/operators/TakeWhileTest.java b/rxjava-core/src/main/java/rx/functions/Action0.java similarity index 73% rename from rxjava-core/src/test/java/rx/operators/TakeWhileTest.java rename to rxjava-core/src/main/java/rx/functions/Action0.java index 25e17ba13b..2a79857f18 100644 --- a/rxjava-core/src/test/java/rx/operators/TakeWhileTest.java +++ b/rxjava-core/src/main/java/rx/functions/Action0.java @@ -1,22 +1,20 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 - * + * + * 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; +package rx.functions; -import org.junit.Ignore; - -@Ignore("WIP") -public class TakeWhileTest { -} +public interface Action0 extends Action { + public void call(); +} \ No newline at end of file diff --git a/rxjava-core/src/test/java/rx/operators/OperatorTesterTest.java b/rxjava-core/src/main/java/rx/functions/Action1.java similarity index 72% rename from rxjava-core/src/test/java/rx/operators/OperatorTesterTest.java rename to rxjava-core/src/main/java/rx/functions/Action1.java index 645cea8e3b..588cd9e66f 100644 --- a/rxjava-core/src/test/java/rx/operators/OperatorTesterTest.java +++ b/rxjava-core/src/main/java/rx/functions/Action1.java @@ -1,22 +1,20 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 - * + * + * 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; +package rx.functions; -import org.junit.Ignore; - -@Ignore("WIP") -public class OperatorTesterTest { -} +public interface Action1 extends Action { + public void call(T1 t1); +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/functions/Action2.java b/rxjava-core/src/main/java/rx/functions/Action2.java new file mode 100644 index 0000000000..370c4650cf --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Action2.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 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.functions; + +public interface Action2 extends Action { + public void call(T1 t1, T2 t2); +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/functions/Action3.java b/rxjava-core/src/main/java/rx/functions/Action3.java new file mode 100644 index 0000000000..e48e9bb92a --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Action3.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 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.functions; + +public interface Action3 extends Action { + public void call(T1 t1, T2 t2, T3 t3); +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/functions/Action4.java b/rxjava-core/src/main/java/rx/functions/Action4.java new file mode 100644 index 0000000000..c0e2999bd4 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Action4.java @@ -0,0 +1,24 @@ +/** + * Copyright 2014 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.functions; + +/** + * A four-argument action. + */ +public interface Action4 extends Action { + void call(T1 t1, T2 t2, T3 t3, T4 t4); +} diff --git a/rxjava-core/src/main/java/rx/functions/Action5.java b/rxjava-core/src/main/java/rx/functions/Action5.java new file mode 100644 index 0000000000..78d5237e6b --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Action5.java @@ -0,0 +1,24 @@ +/** + * Copyright 2014 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.functions; + +/** + * A five-argument action. + */ +public interface Action5 extends Action { + void call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5); +} diff --git a/rxjava-core/src/main/java/rx/functions/Action6.java b/rxjava-core/src/main/java/rx/functions/Action6.java new file mode 100644 index 0000000000..3908969444 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Action6.java @@ -0,0 +1,24 @@ +/** + * Copyright 2014 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.functions; + +/** + * A six-argument action. + */ +public interface Action6 extends Action { + void call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6); +} diff --git a/rxjava-core/src/main/java/rx/functions/Action7.java b/rxjava-core/src/main/java/rx/functions/Action7.java new file mode 100644 index 0000000000..60bec4b877 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Action7.java @@ -0,0 +1,23 @@ +/** + * Copyright 2014 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.functions; + +/** + * A seven-argument action. + */ +public interface Action7 extends Action { + void call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7); +} diff --git a/rxjava-core/src/main/java/rx/functions/Action8.java b/rxjava-core/src/main/java/rx/functions/Action8.java new file mode 100644 index 0000000000..43da8ed309 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Action8.java @@ -0,0 +1,23 @@ +/** + * Copyright 2014 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.functions; + +/** + * An eight-argument action. + */ +public interface Action8 extends Action { + void call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8); +} diff --git a/rxjava-core/src/main/java/rx/functions/Action9.java b/rxjava-core/src/main/java/rx/functions/Action9.java new file mode 100644 index 0000000000..012fe3040c --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Action9.java @@ -0,0 +1,23 @@ +/** + * Copyright 2014 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.functions; + +/** + * A nine-argument action. + */ +public interface Action9 extends Action { + void call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9); +} diff --git a/rxjava-core/src/main/java/rx/functions/ActionN.java b/rxjava-core/src/main/java/rx/functions/ActionN.java new file mode 100644 index 0000000000..ccc714c341 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/ActionN.java @@ -0,0 +1,23 @@ +/** + * Copyright 2014 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.functions; + +/** + * A vector-argument action. + */ +public interface ActionN extends Action { + void call(Object... args); +} diff --git a/rxjava-core/src/main/java/rx/functions/Actions.java b/rxjava-core/src/main/java/rx/functions/Actions.java new file mode 100644 index 0000000000..9954ce5119 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Actions.java @@ -0,0 +1,464 @@ +/** + * Copyright 2014 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.functions; + +import rx.Observer; + +/** + * Utility class for the Action interfaces. + */ +public final class Actions { + private Actions() { + throw new IllegalStateException("No instances!"); + } + + public static final EmptyAction empty() { + return EMPTY_ACTION; + } + + private static final EmptyAction EMPTY_ACTION = new EmptyAction(); + + private static final class EmptyAction implements Action0, Action1, Action2, Action3, Action4, Action5, Action6, Action7, Action8, Action9, ActionN { + @Override + public void call() { + } + + @Override + public void call(Object t1) { + } + + @Override + public void call(Object t1, Object t2) { + } + + @Override + public void call(Object t1, Object t2, Object t3) { + } + + @Override + public void call(Object t1, Object t2, Object t3, Object t4) { + } + + @Override + public void call(Object t1, Object t2, Object t3, Object t4, Object t5) { + } + + @Override + public void call(Object t1, Object t2, Object t3, Object t4, Object t5, Object t6) { + } + + @Override + public void call(Object t1, Object t2, Object t3, Object t4, Object t5, Object t6, Object t7) { + } + + @Override + public void call(Object t1, Object t2, Object t3, Object t4, Object t5, Object t6, Object t7, Object t8) { + } + + @Override + public void call(Object t1, Object t2, Object t3, Object t4, Object t5, Object t6, Object t7, Object t8, Object t9) { + } + + @Override + public void call(Object... args) { + } + } + + /** + * 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(); + } + }; + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @return {@link Func0} + */ + public static Func0 toFunc(final Action0 action) { + return toFunc(action, (Void) null); + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @return {@link Func0} + */ + public static Func1 toFunc(final Action1 action) { + return toFunc(action, (Void) null); + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @return {@link Func0} + */ + public static Func2 toFunc(final Action2 action) { + return toFunc(action, (Void) null); + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @return {@link Func0} + */ + public static Func3 toFunc(final Action3 action) { + return toFunc(action, (Void) null); + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @return {@link Func0} + */ + public static Func4 toFunc(final Action4 action) { + return toFunc(action, (Void) null); + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @return {@link Func0} + */ + public static Func5 toFunc( + final Action5 action) { + return toFunc(action, (Void) null); + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @return {@link Func0} + */ + public static Func6 toFunc( + final Action6 action) { + return toFunc(action, (Void) null); + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @return {@link Func0} + */ + public static Func7 toFunc( + final Action7 action) { + return toFunc(action, (Void) null); + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @return {@link Func0} + */ + public static Func8 toFunc( + final Action8 action) { + return toFunc(action, (Void) null); + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @return {@link Func0} + */ + public static Func9 toFunc( + final Action9 action) { + return toFunc(action, (Void) null); + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @return {@link Func0} + */ + public static FuncN toFunc( + final ActionN action) { + return toFunc(action, (Void) null); + } + + /** + * Convert an action to a function which calls + * the action returns the given result. + * + * @param action + * @param result + * @return {@link Func0} + */ + public static Func0 toFunc(final Action0 action, final R result) { + return new Func0() { + @Override + public R call() { + action.call(); + return result; + } + }; + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @param result + * @return {@link Func0} + */ + public static Func1 toFunc(final Action1 action, final R result) { + return new Func1() { + @Override + public R call(T1 t1) { + action.call(t1); + return result; + } + }; + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @param result + * @return {@link Func0} + */ + public static Func2 toFunc(final Action2 action, final R result) { + return new Func2() { + @Override + public R call(T1 t1, T2 t2) { + action.call(t1, t2); + return result; + } + }; + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @param result + * @return {@link Func0} + */ + public static Func3 toFunc(final Action3 action, final R result) { + return new Func3() { + @Override + public R call(T1 t1, T2 t2, T3 t3) { + action.call(t1, t2, t3); + return result; + } + }; + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @param result + * @return {@link Func0} + */ + public static Func4 toFunc(final Action4 action, final R result) { + return new Func4() { + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4) { + action.call(t1, t2, t3, t4); + return result; + } + }; + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @param result + * @return {@link Func0} + */ + public static Func5 toFunc( + final Action5 action, final R result) { + return new Func5() { + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) { + action.call(t1, t2, t3, t4, t5); + return result; + } + }; + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @param result + * @return {@link Func0} + */ + public static Func6 toFunc( + final Action6 action, final R result) { + return new Func6() { + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) { + action.call(t1, t2, t3, t4, t5, t6); + return result; + } + }; + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @param result + * @return {@link Func0} + */ + public static Func7 toFunc( + final Action7 action, final R result) { + return new Func7() { + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) { + action.call(t1, t2, t3, t4, t5, t6, t7); + return result; + } + }; + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @param result + * @return {@link Func0} + */ + public static Func8 toFunc( + final Action8 action, final R result) { + return new Func8() { + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) { + action.call(t1, t2, t3, t4, t5, t6, t7, t8); + return result; + } + }; + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @param result + * @return {@link Func0} + */ + public static Func9 toFunc( + final Action9 action, final R result) { + return new Func9() { + @Override + public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9) { + action.call(t1, t2, t3, t4, t5, t6, t7, t8, t9); + return result; + } + }; + } + + /** + * Convert an action to a function which calls + * the action returns Void (null). + * + * @param action + * @param result + * @return {@link Func0} + */ + public static FuncN toFunc( + final ActionN action, final R result) { + return new FuncN() { + @Override + public R call(Object... args) { + action.call(args); + return result; + } + }; + } +} diff --git a/rxjava-core/src/main/java/rx/functions/Func0.java b/rxjava-core/src/main/java/rx/functions/Func0.java new file mode 100644 index 0000000000..ee8178bb33 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Func0.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 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.functions; + +public interface Func0 extends Function { + public R call(); +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/functions/Func1.java b/rxjava-core/src/main/java/rx/functions/Func1.java new file mode 100644 index 0000000000..1f22c557c1 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Func1.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 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.functions; + +public interface Func1 extends Function { + public R call(T1 t1); +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/functions/Func2.java b/rxjava-core/src/main/java/rx/functions/Func2.java new file mode 100644 index 0000000000..cbc6eb0515 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Func2.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 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.functions; + +public interface Func2 extends Function { + public R call(T1 t1, T2 t2); +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/functions/Func3.java b/rxjava-core/src/main/java/rx/functions/Func3.java new file mode 100644 index 0000000000..7a187626ab --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Func3.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 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.functions; + +public interface Func3 extends Function { + public R call(T1 t1, T2 t2, T3 t3); +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/functions/Func4.java b/rxjava-core/src/main/java/rx/functions/Func4.java new file mode 100644 index 0000000000..2d37860fc0 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Func4.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 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.functions; + +public interface Func4 extends Function { + public R call(T1 t1, T2 t2, T3 t3, T4 t4); +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/functions/Func5.java b/rxjava-core/src/main/java/rx/functions/Func5.java new file mode 100644 index 0000000000..c2ec077dfe --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Func5.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 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.functions; + +public interface Func5 extends Function { + public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5); +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/functions/Func6.java b/rxjava-core/src/main/java/rx/functions/Func6.java new file mode 100644 index 0000000000..08dd563996 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Func6.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 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.functions; + +public interface Func6 extends Function { + public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6); +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/functions/Func7.java b/rxjava-core/src/main/java/rx/functions/Func7.java new file mode 100644 index 0000000000..f36cbd9c21 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Func7.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 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.functions; + +public interface Func7 extends Function { + public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7); +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/functions/Func8.java b/rxjava-core/src/main/java/rx/functions/Func8.java new file mode 100644 index 0000000000..499e687183 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Func8.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 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.functions; + +public interface Func8 extends Function { + public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8); +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/functions/Func9.java b/rxjava-core/src/main/java/rx/functions/Func9.java new file mode 100644 index 0000000000..d96731ed01 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Func9.java @@ -0,0 +1,20 @@ +/** + * Copyright 2014 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.functions; + +public interface Func9 extends Function { + public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9); +} \ No newline at end of file diff --git a/rxjava-core/src/test/java/rx/operators/TimeIntervalObserverTest.java b/rxjava-core/src/main/java/rx/functions/FuncN.java similarity index 72% rename from rxjava-core/src/test/java/rx/operators/TimeIntervalObserverTest.java rename to rxjava-core/src/main/java/rx/functions/FuncN.java index 29337619ff..2d4c9d3911 100644 --- a/rxjava-core/src/test/java/rx/operators/TimeIntervalObserverTest.java +++ b/rxjava-core/src/main/java/rx/functions/FuncN.java @@ -1,22 +1,20 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 - * + * + * 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; +package rx.functions; -import org.junit.Ignore; - -@Ignore("WIP") -public class TimeIntervalObserverTest { +public interface FuncN extends Function { + public R call(Object... args); } diff --git a/rxjava-core/src/main/java/rx/functions/Function.java b/rxjava-core/src/main/java/rx/functions/Function.java new file mode 100644 index 0000000000..cbe7bd1a37 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Function.java @@ -0,0 +1,25 @@ +/** + * Copyright 2014 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.functions; + +/** + * All Func and Action interfaces extend from this. + *

+ * Marker interface to allow instanceof checks. + */ +public interface Function { + +} diff --git a/rxjava-core/src/main/java/rx/functions/Functions.java b/rxjava-core/src/main/java/rx/functions/Functions.java new file mode 100644 index 0000000000..b30ad4e4d2 --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Functions.java @@ -0,0 +1,361 @@ +/** + * Copyright 2014 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.functions; + +public class Functions { + + /** + * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. + * + * @param f + * @return {@link FuncN} + */ + public static FuncN fromFunc(final Func0 f) { + return new FuncN() { + + @Override + public R call(Object... args) { + if (args.length != 0) { + throw new RuntimeException("Func0 expecting 0 arguments."); + } + return f.call(); + } + + }; + } + + /** + * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. + * + * @param f + * @return {@link FuncN} + */ + public static FuncN fromFunc(final Func1 f) { + return new FuncN() { + + @SuppressWarnings("unchecked") + @Override + public R call(Object... args) { + if (args.length != 1) { + throw new RuntimeException("Func1 expecting 1 argument."); + } + return f.call((T0) args[0]); + } + + }; + } + + /** + * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. + * + * @param f + * @return {@link FuncN} + */ + public static FuncN fromFunc(final Func2 f) { + return new FuncN() { + + @SuppressWarnings("unchecked") + @Override + public R call(Object... args) { + if (args.length != 2) { + throw new RuntimeException("Func2 expecting 2 arguments."); + } + return f.call((T0) args[0], (T1) args[1]); + } + + }; + } + + /** + * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. + * + * @param f + * @return {@link FuncN} + */ + public static FuncN fromFunc(final Func3 f) { + return new FuncN() { + + @SuppressWarnings("unchecked") + @Override + public R call(Object... args) { + if (args.length != 3) { + throw new RuntimeException("Func3 expecting 3 arguments."); + } + return f.call((T0) args[0], (T1) args[1], (T2) args[2]); + } + + }; + } + + /** + * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. + * + * @param f + * @return {@link FuncN} + */ + public static FuncN fromFunc(final Func4 f) { + return new FuncN() { + + @SuppressWarnings("unchecked") + @Override + public R call(Object... args) { + if (args.length != 4) { + throw new RuntimeException("Func4 expecting 4 arguments."); + } + return f.call((T0) args[0], (T1) args[1], (T2) args[2], (T3) args[3]); + } + + }; + } + + /** + * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. + * + * @param f + * @return {@link FuncN} + */ + public static FuncN fromFunc(final Func5 f) { + return new FuncN() { + + @SuppressWarnings("unchecked") + @Override + public R call(Object... args) { + if (args.length != 5) { + throw new RuntimeException("Func5 expecting 5 arguments."); + } + return f.call((T0) args[0], (T1) args[1], (T2) args[2], (T3) args[3], (T4) args[4]); + } + + }; + } + + /** + * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. + * + * @param f + * @return {@link FuncN} + */ + public static FuncN fromFunc(final Func6 f) { + return new FuncN() { + + @SuppressWarnings("unchecked") + @Override + public R call(Object... args) { + if (args.length != 6) { + throw new RuntimeException("Func6 expecting 6 arguments."); + } + return f.call((T0) args[0], (T1) args[1], (T2) args[2], (T3) args[3], (T4) args[4], (T5) args[5]); + } + + }; + } + + /** + * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. + * + * @param f + * @return {@link FuncN} + */ + public static FuncN fromFunc(final Func7 f) { + return new FuncN() { + + @SuppressWarnings("unchecked") + @Override + public R call(Object... args) { + if (args.length != 7) { + throw new RuntimeException("Func7 expecting 7 arguments."); + } + return f.call((T0) args[0], (T1) args[1], (T2) args[2], (T3) args[3], (T4) args[4], (T5) args[5], (T6) args[6]); + } + + }; + } + + /** + * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. + * + * @param f + * @return {@link FuncN} + */ + public static FuncN fromFunc(final Func8 f) { + return new FuncN() { + + @SuppressWarnings("unchecked") + @Override + public R call(Object... args) { + if (args.length != 8) { + throw new RuntimeException("Func8 expecting 8 arguments."); + } + return f.call((T0) args[0], (T1) args[1], (T2) args[2], (T3) args[3], (T4) args[4], (T5) args[5], (T6) args[6], (T7) args[7]); + } + + }; + } + + /** + * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. + * + * @param f + * @return {@link FuncN} + */ + public static FuncN fromFunc(final Func9 f) { + return new FuncN() { + + @SuppressWarnings("unchecked") + @Override + public R call(Object... args) { + if (args.length != 9) { + throw new RuntimeException("Func9 expecting 9 arguments."); + } + return f.call((T0) args[0], (T1) args[1], (T2) args[2], (T3) args[3], (T4) args[4], (T5) args[5], (T6) args[6], (T7) args[7], (T8) args[8]); + } + + }; + } + + /** + * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. + * + * @param f + * @return {@link FuncN} + */ + public static FuncN fromAction(final Action0 f) { + return new FuncN() { + + @Override + public Void call(Object... args) { + if (args.length != 0) { + throw new RuntimeException("Action0 expecting 0 arguments."); + } + f.call(); + return null; + } + + }; + } + + /** + * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. + * + * @param f + * @return {@link FuncN} + */ + public static FuncN fromAction(final Action1 f) { + return new FuncN() { + + @SuppressWarnings("unchecked") + @Override + public Void call(Object... args) { + if (args.length != 1) { + throw new RuntimeException("Action1 expecting 1 argument."); + } + f.call((T0) args[0]); + return null; + } + + }; + } + + /** + * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. + * + * @param f + * @return {@link FuncN} + */ + public static FuncN fromAction(final Action2 f) { + return new FuncN() { + + @SuppressWarnings("unchecked") + @Override + public Void call(Object... args) { + if (args.length != 2) { + throw new RuntimeException("Action3 expecting 2 arguments."); + } + f.call((T0) args[0], (T1) args[1]); + return null; + } + + }; + } + + /** + * Convert a function to FuncN to allow heterogeneous handling of functions with different arities. + * + * @param f + * @return {@link FuncN} + */ + public static FuncN fromAction(final Action3 f) { + return new FuncN() { + + @SuppressWarnings("unchecked") + @Override + public Void call(Object... args) { + if (args.length != 3) { + throw new RuntimeException("Action3 expecting 3 arguments."); + } + f.call((T0) args[0], (T1) args[1], (T2) args[2]); + return null; + } + + }; + } + + /** + * 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. + */ + public static Func1 not(Func1 predicate) { + return new Not(predicate); + } + + public static Func1 alwaysTrue() { + return AlwaysTrue.INSTANCE; + } + + public static Func1 alwaysFalse() { + return AlwaysFalse.INSTANCE; + } + + public static Func1 identity() { + return new Func1() { + @Override + public T call(T o) { + return o; + } + }; + } + + private enum AlwaysTrue implements Func1 { + INSTANCE; + + @Override + 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/functions/Not.java b/rxjava-core/src/main/java/rx/functions/Not.java new file mode 100644 index 0000000000..da37ab473d --- /dev/null +++ b/rxjava-core/src/main/java/rx/functions/Not.java @@ -0,0 +1,42 @@ +/** + * Copyright 2014 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.functions; + +/** + * Implements the negation of a predicate. + * + * @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. + */ + public Not(Func1 predicate) { + this.predicate = predicate; + } + + @Override + public Boolean call(T param) { + return !predicate.call(param); + } +} diff --git a/rxjava-core/src/main/java/rx/joins/ActivePlan0.java b/rxjava-core/src/main/java/rx/joins/ActivePlan0.java index d8690136b7..aaaf75c447 100644 --- a/rxjava-core/src/main/java/rx/joins/ActivePlan0.java +++ b/rxjava-core/src/main/java/rx/joins/ActivePlan0.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,12 +23,13 @@ */ 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 index d5181b0982..1595e3f6d5 100644 --- a/rxjava-core/src/main/java/rx/joins/ActivePlan1.java +++ b/rxjava-core/src/main/java/rx/joins/ActivePlan1.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 @@ -16,8 +16,8 @@ package rx.joins; import rx.Notification; -import rx.util.functions.Action0; -import rx.util.functions.Action1; +import rx.functions.Action0; +import rx.functions.Action1; /** * Represents an active plan. @@ -26,6 +26,7 @@ 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; @@ -45,5 +46,5 @@ public void match() { } } } - + } diff --git a/rxjava-core/src/main/java/rx/joins/ActivePlan2.java b/rxjava-core/src/main/java/rx/joins/ActivePlan2.java index a91d4fd8f3..a477e99066 100644 --- a/rxjava-core/src/main/java/rx/joins/ActivePlan2.java +++ b/rxjava-core/src/main/java/rx/joins/ActivePlan2.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 @@ -16,8 +16,8 @@ package rx.joins; import rx.Notification; -import rx.util.functions.Action0; -import rx.util.functions.Action2; +import rx.functions.Action0; +import rx.functions.Action2; /** * Represents an active plan. @@ -27,6 +27,7 @@ public class ActivePlan2 extends ActivePlan0 { 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; @@ -41,7 +42,7 @@ 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 { @@ -50,5 +51,5 @@ public void match() { } } } - + } diff --git a/rxjava-core/src/main/java/rx/joins/ActivePlan3.java b/rxjava-core/src/main/java/rx/joins/ActivePlan3.java index 36bfa0e09e..d0a90002e4 100644 --- a/rxjava-core/src/main/java/rx/joins/ActivePlan3.java +++ b/rxjava-core/src/main/java/rx/joins/ActivePlan3.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 @@ -16,8 +16,8 @@ package rx.joins; import rx.Notification; -import rx.util.functions.Action0; -import rx.util.functions.Action3; +import rx.functions.Action0; +import rx.functions.Action3; /** * Represents an active plan. @@ -28,10 +28,11 @@ public class ActivePlan3 extends ActivePlan0 { private final JoinObserver1 first; private final JoinObserver1 second; private final JoinObserver1 third; - public ActivePlan3(JoinObserver1 first, - JoinObserver1 second, + + public ActivePlan3(JoinObserver1 first, + JoinObserver1 second, JoinObserver1 third, - Action3 onNext, + Action3 onNext, Action0 onCompleted) { this.onNext = onNext; this.onCompleted = onCompleted; @@ -45,13 +46,13 @@ public ActivePlan3(JoinObserver1 first, @Override public void match() { - if (!first.queue().isEmpty() + 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 { diff --git a/rxjava-core/src/main/java/rx/joins/JoinObserver.java b/rxjava-core/src/main/java/rx/joins/JoinObserver.java index 2557427895..80fe6d9aa0 100644 --- a/rxjava-core/src/main/java/rx/joins/JoinObserver.java +++ b/rxjava-core/src/main/java/rx/joins/JoinObserver.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 @@ -22,5 +22,6 @@ */ 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 index 873d3d1a7f..2937b3e0e2 100644 --- a/rxjava-core/src/main/java/rx/joins/JoinObserver1.java +++ b/rxjava-core/src/main/java/rx/joins/JoinObserver1.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,40 +19,52 @@ import java.util.LinkedList; import java.util.List; import java.util.Queue; +import java.util.concurrent.atomic.AtomicBoolean; + import rx.Notification; import rx.Observable; -import rx.subscriptions.SingleAssignmentSubscription; -import rx.util.functions.Action1; +import rx.Subscriber; +import rx.functions.Action1; +import rx.observers.SafeSubscriber; /** * Default implementation of a join observer. */ -public final class JoinObserver1 extends ObserverBase> implements JoinObserver { +public final class JoinObserver1 extends Subscriber> 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; - + private final AtomicBoolean subscribed = new AtomicBoolean(false); + private final SafeSubscriber> safeObserver; + public JoinObserver1(Observable source, Action1 onError) { this.source = source; this.onError = onError; queue = new LinkedList>(); - subscription = new SingleAssignmentSubscription(); activePlans = new ArrayList(); + safeObserver = new SafeSubscriber>(new InnerObserver()); + // add this subscription so it gets unsubscribed when the parent does + add(safeObserver); } + 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)); + if (subscribed.compareAndSet(false, true)) { + this.gate = gate; + source.materialize().subscribe(this); + } else { + throw new IllegalStateException("Can only be subscribed to once."); + } } @Override @@ -60,48 +72,59 @@ 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(); - } - } - } + public void onNext(Notification args) { + safeObserver.onNext(args); } @Override - protected void onErrorCore(Throwable e) { - // not expected + public void onError(Throwable e) { + safeObserver.onError(e); } @Override - protected void onCompletedCore() { - // not expected or ignored + public void onCompleted() { + safeObserver.onCompleted(); } - - + void removeActivePlan(ActivePlan0 activePlan) { activePlans.remove(activePlan); if (activePlans.isEmpty()) { unsubscribe(); } } + + + private final class InnerObserver extends Subscriber> { - @Override - public void unsubscribe() { - if (!done) { - done = true; - subscription.unsubscribe(); + @Override + public void onNext(Notification args) { + synchronized (gate) { + if (!isUnsubscribed()) { + 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 + public void onError(Throwable e) { + // not expected + } + + @Override + public void onCompleted() { + // not expected or ignored } } - -} + +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/joins/ObserverBase.java b/rxjava-core/src/main/java/rx/joins/ObserverBase.java deleted file mode 100644 index f1144a8ad2..0000000000 --- a/rxjava-core/src/main/java/rx/joins/ObserverBase.java +++ /dev/null @@ -1,72 +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.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/rxjava-core/src/main/java/rx/joins/Pattern.java b/rxjava-core/src/main/java/rx/joins/Pattern.java index cce00a3af7..a1d9f58a10 100644 --- a/rxjava-core/src/main/java/rx/joins/Pattern.java +++ b/rxjava-core/src/main/java/rx/joins/Pattern.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,9 @@ /** * 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 index 77600647e8..d31e388638 100644 --- a/rxjava-core/src/main/java/rx/joins/Pattern1.java +++ b/rxjava-core/src/main/java/rx/joins/Pattern1.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 @@ -16,25 +16,31 @@ package rx.joins; import rx.Observable; -import rx.util.functions.Func1; +import rx.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 + * 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 + * + * @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) { diff --git a/rxjava-core/src/main/java/rx/joins/Pattern2.java b/rxjava-core/src/main/java/rx/joins/Pattern2.java index b38ba37253..b967ad2930 100644 --- a/rxjava-core/src/main/java/rx/joins/Pattern2.java +++ b/rxjava-core/src/main/java/rx/joins/Pattern2.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 @@ -16,7 +16,7 @@ package rx.joins; import rx.Observable; -import rx.util.functions.Func2; +import rx.functions.Func2; /** * Represents a join pattern over observable sequences. @@ -24,19 +24,25 @@ 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. + * + * @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) { @@ -45,6 +51,7 @@ public Pattern3 and(Observable other) { } return new Pattern3(first, second, other); } + public Plan0 then(Func2 selector) { if (selector == null) { throw new NullPointerException(); diff --git a/rxjava-core/src/main/java/rx/joins/Pattern3.java b/rxjava-core/src/main/java/rx/joins/Pattern3.java index 0871b3d3c2..60d4daf2f8 100644 --- a/rxjava-core/src/main/java/rx/joins/Pattern3.java +++ b/rxjava-core/src/main/java/rx/joins/Pattern3.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 @@ -16,7 +16,7 @@ package rx.joins; import rx.Observable; -import rx.util.functions.Func3; +import rx.functions.Func3; /** * Represents a join pattern over observable sequences. @@ -25,31 +25,36 @@ 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 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 index 2a647aff3d..c10d5c1be7 100644 --- a/rxjava-core/src/main/java/rx/joins/Plan0.java +++ b/rxjava-core/src/main/java/rx/joins/Plan0.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,30 +16,31 @@ package rx.joins; import java.util.Map; + import rx.Observable; import rx.Observer; -import rx.util.functions.Action1; +import rx.functions.Action1; /** * Represents an execution plan for join patterns. */ public abstract class Plan0 { - public abstract ActivePlan0 activate(Map externalSubscriptions, + 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; + 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 index b64742e27b..7d1912e979 100644 --- a/rxjava-core/src/main/java/rx/joins/Plan1.java +++ b/rxjava-core/src/main/java/rx/joins/Plan1.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,12 @@ 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; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Actions; +import rx.functions.Func1; /** * Represents an execution plan for join patterns. @@ -29,15 +30,16 @@ 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; } @@ -45,11 +47,11 @@ public Func1 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) { @@ -63,18 +65,18 @@ public void call(T1 t1) { observer.onNext(result); } }, - new Action0() { - @Override - public void call() { - firstJoinObserver.removeActivePlan(self.get()); - deactivate.call(self.get()); - } - }); - + 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 index 456e3c2bb3..06495a51ca 100644 --- a/rxjava-core/src/main/java/rx/joins/Plan2.java +++ b/rxjava-core/src/main/java/rx/joins/Plan2.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 @@ -17,13 +17,13 @@ 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; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Action2; +import rx.functions.Actions; +import rx.functions.Func2; /** * Represents an execution plan for join patterns. @@ -31,21 +31,22 @@ 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, + 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) { @@ -59,21 +60,21 @@ public void call(T1 t1, T2 t2) { observer.onNext(result); } }, - new Action0() { - @Override - public void call() { - firstJoinObserver.removeActivePlan(self.get()); - secondJoinObserver.removeActivePlan(self.get()); - deactivate.call(self.get()); - } - }); - + 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 index 6f12235c51..9ed8fa4313 100644 --- a/rxjava-core/src/main/java/rx/joins/Plan3.java +++ b/rxjava-core/src/main/java/rx/joins/Plan3.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 @@ -17,13 +17,13 @@ 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; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Action3; +import rx.functions.Actions; +import rx.functions.Func3; /** * Represents an execution plan for join patterns. @@ -31,52 +31,53 @@ 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, + 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, + + 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()); - } - }); - + @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 8fe13c9e88..fdd8db73ed 100644 --- a/rxjava-core/src/main/java/rx/observables/BlockingObservable.java +++ b/rxjava-core/src/main/java/rx/observables/BlockingObservable.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,23 +21,25 @@ import java.util.concurrent.atomic.AtomicReference; import rx.Observable; -import rx.Observer; +import rx.Subscriber; import rx.Subscription; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.observers.SafeSubscriber; +import rx.operators.OperationLatest; import rx.operators.OperationMostRecent; import rx.operators.OperationNext; import rx.operators.OperationToFuture; import rx.operators.OperationToIterator; -import rx.operators.SafeObservableSubscription; -import rx.operators.SafeObserver; -import rx.util.functions.Action1; -import rx.util.functions.Func1; /** * An extension of {@link Observable} that provides blocking operators. *

- * You construct a BlockingObservable from an Observable with {@link #from(Observable)} or {@link Observable#toBlockingObservable()}

- * The documentation for this interface makes use of a form of marble diagram that has been - * modified to illustrate blocking operators. The following legend explains these marble diagrams: + * You construct a BlockingObservable from an + * Observable with {@link #from(Observable)} or {@link Observable#toBlockingObservable()}

+ * The documentation for this interface makes use of a form of marble diagram + * that has been modified to illustrate blocking operators. The following legend + * explains these marble diagrams: *

* *

@@ -62,45 +64,25 @@ public static BlockingObservable from(final Observable o) { return new BlockingObservable(o); } - private static T _singleOrDefault(BlockingObservable source, boolean hasDefault, T defaultValue) { - Iterator it = source.toIterable().iterator(); - - if (!it.hasNext()) { - if (hasDefault) { - return defaultValue; - } - throw new IllegalStateException("Expected single entry. Actually empty stream."); - } - - T result = it.next(); - - if (it.hasNext()) { - throw new IllegalStateException("Expected single entry. Actually more than one entry."); - } - - return result; - } - /** - * Used for protecting against errors being thrown from {@link Observer} implementations and - * ensuring onNext/onError/onCompleted contract compliance. + * Used for protecting against errors being thrown from {@link Subscriber} implementations and ensuring 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 discussion on + * "Guideline 6.4: Protect calls to user code from within an operator" */ - private Subscription protectivelyWrapAndSubscribe(Observer observer) { - SafeObservableSubscription subscription = new SafeObservableSubscription(); - return subscription.wrap(o.subscribe(new SafeObserver(subscription, observer))); + private Subscription protectivelyWrapAndSubscribe(Subscriber observer) { + return o.subscribe(new SafeSubscriber(observer)); } /** - * Invoke a method on each item emitted by the {@link Observable}; block until the Observable - * completes. + * 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. + * This is similar to {@link Observable#subscribe(Subscriber)}, but it blocks. + * Because it blocks it does not need the {@link Subscriber#onCompleted()} or {@link Subscriber#onError(Throwable)} methods. *

* * @@ -108,6 +90,7 @@ private Subscription protectivelyWrapAndSubscribe(Observer observer) * the {@link Action1} to invoke for every item emitted by the {@link Observable} * @throws RuntimeException * if an error occurs + * @see RxJava Wiki: forEach() */ public void forEach(final Action1 onNext) { final CountDownLatch latch = new CountDownLatch(1); @@ -116,9 +99,10 @@ public void forEach(final Action1 onNext) { /** * Wrapping since raw functions provided by the user are being invoked. * - * 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 discussion on + * "Guideline 6.4: Protect calls to user code from within an operator" */ - protectivelyWrapAndSubscribe(new Observer() { + protectivelyWrapAndSubscribe(new Subscriber() { @Override public void onCompleted() { latch.countDown(); @@ -127,10 +111,12 @@ public void onCompleted() { @Override public void onError(Throwable e) { /* - * If we receive an onError event we set the reference on the outer thread - * so we can git it and throw after the latch.await(). + * If we receive an onError event we set the reference on the + * outer thread so we can git it and throw after the + * latch.await(). * - * We do this instead of throwing directly since this may be on a different thread and the latch is still waiting. + * We do this instead of throwing directly since this may be on + * a different thread and the latch is still waiting. */ exceptionFromOnError.set(e); latch.countDown(); @@ -162,71 +148,139 @@ public void onNext(T args) { } /** - * Returns an {@link Iterator} that iterates over all items emitted by a specified {@link Observable}. + * Returns an {@link Iterator} that iterates over all items emitted by a + * specified {@link Observable}. *

* * - * @return an {@link Iterator} that can iterate over the items emitted by the {@link Observable} + * @return an {@link Iterator} that can iterate over the items emitted by + * the {@link Observable} + * @see RxJava Wiki: getIterator() */ public Iterator getIterator() { return OperationToIterator.toIterator(o); } /** - * Returns the last item emitted by a specified {@link Observable}. + * Returns the first item emitted by a specified {@link Observable}, or + * IllegalArgumentException if source contains no elements. + * + * @return the first item emitted by the source {@link Observable} + * @throws IllegalArgumentException + * if source contains no elements + * @see RxJava Wiki: first() + * @see MSDN: Observable.First + */ + public T first() { + return from(o.first()).single(); + } + + /** + * Returns the first item emitted by a specified {@link Observable} that + * matches a predicate, or IllegalArgumentException if no such + * item is emitted. + * + * @param predicate + * a predicate function to evaluate items emitted by the {@link Observable} + * @return the first item emitted by the {@link Observable} that matches the + * predicate + * @throws IllegalArgumentException + * if no such items are emitted + * @see RxJava Wiki: first() + * @see MSDN: Observable.First + */ + public T first(Func1 predicate) { + return from(o.first(predicate)).single(); + } + + /** + * Returns the first item emitted by a specified {@link Observable}, or a + * default value if no items are emitted. + * + * @param defaultValue + * a default value to return if the {@link Observable} emits no items + * @return the first item emitted by the {@link Observable}, or the default + * value if no items are emitted + * @see RxJava Wiki: firstOrDefault() + * @see MSDN: Observable.FirstOrDefault + */ + public T firstOrDefault(T defaultValue) { + return from(o.take(1)).singleOrDefault(defaultValue); + } + + /** + * Returns the first item emitted by a specified {@link Observable} that + * matches a predicate, or a default value if no such items are emitted. + * + * @param defaultValue + * a default value to return if the {@link Observable} emits no matching items + * @param predicate + * a predicate function to evaluate items emitted by the {@link Observable} + * @return the first item emitted by the {@link Observable} that matches the + * predicate, or the default value if no matching items are emitted + * @see RxJava Wiki: firstOrDefault() + * @see MSDN: Observable.FirstOrDefault + */ + public T firstOrDefault(T defaultValue, Func1 predicate) { + return from(o.filter(predicate)).firstOrDefault(defaultValue); + } + + /** + * Returns the last item emitted by a specified {@link Observable}, or + * throws IllegalArgumentException if it emits no items. *

* * * @return the last item emitted by the source {@link Observable} - * @throws IllegalArgumentException if source contains no elements + * @throws IllegalArgumentException + * if source contains no elements + * @see RxJava Wiki: last() + * @see MSDN: Observable.Last */ public T last() { - return new BlockingObservable(o.last()).single(); + return from(o.last()).single(); } /** - * Returns the last item emitted by a specified {@link Observable} that matches a predicate. + * Returns the last item emitted by a specified {@link Observable} that + * matches a predicate, or throws IllegalArgumentException if + * it emits no such items. *

* * * @param predicate * a predicate function to evaluate items emitted by the {@link Observable} - * @return the last item emitted by the {@link Observable} that matches the predicate + * @return the last item emitted by the {@link Observable} that matches the + * predicate + * @throws IllegalArgumentException + * if no such items are emitted + * @see RxJava Wiki: last() + * @see MSDN: Observable.Last */ public T last(final Func1 predicate) { - return from(o.filter(predicate)).last(); + return from(o.last(predicate)).single(); } /** - * Returns the last item emitted by a specified {@link Observable}, or a default value if no - * items are emitted. + * Returns the last item emitted by a specified {@link Observable}, or a + * default value if no items are emitted. *

* * * @param defaultValue * a default value to return if the {@link Observable} emits no items - * @return the last item emitted by the {@link Observable}, or the default value if no items - * are emitted + * @return the last item emitted by the {@link Observable}, or the default + * value if no items are emitted + * @see RxJava Wiki: lastOrDefault() + * @see MSDN: Observable.LastOrDefault */ public T lastOrDefault(T defaultValue) { - boolean found = false; - T result = null; - - for (T value : toIterable()) { - found = true; - result = value; - } - - if (!found) { - return defaultValue; - } - - return result; + return from(o.takeLast(1)).singleOrDefault(defaultValue); } /** - * Returns the last item emitted by a specified {@link Observable} that matches a predicate, or - * a default value if no such items are emitted. + * Returns the last item emitted by a specified {@link Observable} that + * matches a predicate, or a default value if no such items are emitted. *

* * @@ -234,82 +288,130 @@ public T lastOrDefault(T defaultValue) { * a default value to return if the {@link Observable} emits no matching items * @param predicate * a predicate function to evaluate items emitted by the {@link Observable} - * @return the last item emitted by the {@link Observable} that matches the predicate, or the - * default value if no matching items are emitted + * @return the last item emitted by the {@link Observable} that matches the + * predicate, or the default value if no matching items are emitted + * @see RxJava Wiki: lastOrDefault() + * @see MSDN: Observable.LastOrDefault */ public T lastOrDefault(T defaultValue, Func1 predicate) { return from(o.filter(predicate)).lastOrDefault(defaultValue); } /** - * Returns an {@link Iterable} that always returns the item most recently emitted by an {@link Observable}. + * 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 + * @return an {@link Iterable} that on each iteration returns the item that + * the {@link Observable} has most recently emitted + * @see RxJava wiki: mostRecent() + * @see MSDN: Observable.MostRecent */ public Iterable mostRecent(T initialValue) { return OperationMostRecent.mostRecent(o, initialValue); } /** - * Returns an {@link Iterable} that blocks until the {@link Observable} emits another item, - * then returns that item. + * 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 + * @see RxJava Wiki: next() + * @see MSDN: Observable.Next */ public Iterable next() { return OperationNext.next(o); } /** - * If the {@link Observable} completes after emitting a single item, return that item, - * otherwise throw an exception. + * Returns the latest item emitted by the underlying Observable, waiting if + * necessary for one to become available. + *

+ * If the underlying Observable produces items faster than the + * Iterator.next() takes them, onNext events might + * be skipped, but onError or onCompleted events + * are not. + *

+ * Note also that an onNext() directly followed by + * onCompleted() might hide the onNext() event. + * + * @return the Iterable sequence + * @see RxJava wiki: latest() + * @see MSDN: Observable.Latest + */ + public Iterable latest() { + return OperationLatest.latest(o); + } + + /** + * If the {@link Observable} completes after emitting a single item, return + * that item, otherwise throw an IllegalArgumentException. *

* * * @return the single item emitted by the {@link Observable} + * @see RxJava Wiki: single() + * @see MSDN: Observable.Single */ public T single() { - return _singleOrDefault(this, false, null); + return from(o.single()).toIterable().iterator().next(); } /** - * If the {@link Observable} completes after emitting a single item that matches a given - * predicate, return that item, otherwise throw an exception. + * If the {@link Observable} completes after emitting a single item that + * matches a given predicate, return that item, otherwise throw an + * IllegalArgumentException. *

* * * @param predicate * a predicate function to evaluate items emitted by the {@link Observable} - * @return the single item emitted by the source {@link Observable} that matches the predicate + * @return the single item emitted by the source {@link Observable} that + * matches the predicate + * @see RxJava Wiki: single() + * @see MSDN: Observable.Single */ public T single(Func1 predicate) { - return _singleOrDefault(from(o.filter(predicate)), false, null); + return from(o.single(predicate)).toIterable().iterator().next(); } /** - * If the {@link Observable} completes after emitting a single item, return that item; if it - * emits more than one item, throw an exception; if it emits no items, return a default value. + * If the {@link Observable} completes after emitting a single item, return + * that item; if it emits more than one item, throw an + * IllegalArgumentException; if it emits no items, return a + * default value. *

* * * @param defaultValue * a default value to return if the {@link Observable} emits no items - * @return the single item emitted by the {@link Observable}, or the default value if no items - * are emitted + * @return the single item emitted by the {@link Observable}, or the default + * value if no items are emitted + * @see RxJava Wiki: singleOrDefault() + * @see MSDN: Observable.SingleOrDefault */ public T singleOrDefault(T defaultValue) { - return _singleOrDefault(this, true, defaultValue); + Iterator it = this.toIterable().iterator(); + + if (!it.hasNext()) { + return defaultValue; + } + + T result = it.next(); + if (it.hasNext()) { + throw new IllegalArgumentException("Sequence contains too many elements"); + } + return result; } /** - * If the {@link Observable} completes after emitting a single item that matches a predicate, - * return that item; if it emits more than one such item, throw an exception; if it emits no + * If the {@link Observable} completes after emitting a single item that + * matches a predicate, return that item; if it emits more than one such + * item, throw an IllegalArgumentException; if it emits no * items, return a default value. *

* @@ -318,22 +420,25 @@ public T singleOrDefault(T defaultValue) { * a default value to return if the {@link Observable} emits no matching items * @param predicate * a predicate function to evaluate items emitted by the {@link Observable} - * @return the single item emitted by the {@link Observable} that matches the predicate, or the - * default value if no such items are emitted + * @return the single item emitted by the {@link Observable} that matches + * the predicate, or the default value if no such items are emitted + * @see RxJava Wiki: singleOrDefault() + * @see MSDN: Observable.SingleOrDefault */ public T singleOrDefault(T defaultValue, Func1 predicate) { - return _singleOrDefault(from(o.filter(predicate)), true, defaultValue); + return from(o.filter(predicate)).singleOrDefault(defaultValue); } /** * Returns a {@link Future} representing the single value emitted by an {@link Observable}. *

- * toFuture() throws an exception if the Observable emits more than one item. If - * the Observable may emit more than item, use {@link Observable#toList toList()}.toFuture(). + * toFuture() throws an exception if the Observable emits more + * than one item. If the Observable may emit more than item, use {@link Observable#toList toList()}.toFuture(). *

* * * @return a {@link Future} that expects a single item to be emitted by the source {@link Observable} + * @see RxJava Wiki: toFuture() */ public Future toFuture() { return OperationToFuture.toFuture(o); @@ -345,6 +450,7 @@ public Future toFuture() { * * * @return an {@link Iterable} version of the underlying {@link Observable} + * @see RxJava Wiki: toIterable() */ public Iterable toIterable() { return new Iterable() { diff --git a/rxjava-core/src/main/java/rx/observables/ConnectableObservable.java b/rxjava-core/src/main/java/rx/observables/ConnectableObservable.java index 1826905eaf..b71ad4d81e 100644 --- a/rxjava-core/src/main/java/rx/observables/ConnectableObservable.java +++ b/rxjava-core/src/main/java/rx/observables/ConnectableObservable.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,14 @@ package rx.observables; import rx.Observable; -import rx.Observer; +import rx.Subscriber; import rx.Subscription; import rx.operators.OperationRefCount; /** * A ConnectableObservable resembles an ordinary {@link Observable}, except that it does not begin * emitting items when it is subscribed to, but only when its {@link #connect} method is called. In - * this way you can wait for all intended {@link Observer}s to {@link Observable#subscribe} to the + * this way you can wait for all intended {@link Subscriber}s to {@link Observable#subscribe} to the * Observable before the Observable begins emitting items. *

* @@ -37,13 +37,13 @@ public abstract class ConnectableObservable extends Observable { - protected ConnectableObservable(OnSubscribeFunc onSubscribe) { + protected ConnectableObservable(OnSubscribe onSubscribe) { super(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 Subscriber}s. */ public abstract Subscription connect(); diff --git a/rxjava-core/src/main/java/rx/observables/GroupedObservable.java b/rxjava-core/src/main/java/rx/observables/GroupedObservable.java index f40e8c40af..120f742e77 100644 --- a/rxjava-core/src/main/java/rx/observables/GroupedObservable.java +++ b/rxjava-core/src/main/java/rx/observables/GroupedObservable.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package rx.observables; import rx.Observable; -import rx.util.functions.Func1; +import rx.functions.Func1; /** * An {@link Observable} that has been grouped by a key whose value can be obtained using {@link #getKey()}

@@ -31,7 +31,7 @@ public class GroupedObservable extends Observable { private final K key; - public GroupedObservable(K key, OnSubscribeFunc onSubscribe) { + public GroupedObservable(K key, OnSubscribe onSubscribe) { super(onSubscribe); this.key = key; } diff --git a/rxjava-core/src/main/java/rx/observers/EmptyObserver.java b/rxjava-core/src/main/java/rx/observers/EmptyObserver.java new file mode 100644 index 0000000000..99ba2f5d04 --- /dev/null +++ b/rxjava-core/src/main/java/rx/observers/EmptyObserver.java @@ -0,0 +1,26 @@ +package rx.observers; + +import rx.Observer; + + +/** + * Observer that does nothing... including swallowing errors. + */ +public class EmptyObserver implements Observer { + + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onNext(T args) { + + } + +} diff --git a/rxjava-core/src/main/java/rx/observers/Observers.java b/rxjava-core/src/main/java/rx/observers/Observers.java new file mode 100644 index 0000000000..81c3c8330d --- /dev/null +++ b/rxjava-core/src/main/java/rx/observers/Observers.java @@ -0,0 +1,129 @@ +package rx.observers; + +import rx.Observer; +import rx.exceptions.OnErrorNotImplementedException; +import rx.functions.Action0; +import rx.functions.Action1; + +public class Observers { + + private static final Observer EMPTY = new Observer() { + + @Override + public final void onCompleted() { + // do nothing + } + + @Override + public final void onError(Throwable e) { + throw new OnErrorNotImplementedException(e); + } + + @Override + public final void onNext(Object args) { + // do nothing + } + + }; + + @SuppressWarnings("unchecked") + public static Observer empty() { + return (Observer) EMPTY; + } + + /** + * Create an Observer that receives `onNext` and ignores `onError` and `onCompleted`. + */ + public static final Observer create(final Action1 onNext) { + if (onNext == null) { + throw new IllegalArgumentException("onNext can not be null"); + } + + return new Observer() { + + @Override + public final void onCompleted() { + // do nothing + } + + @Override + public final void onError(Throwable e) { + throw new OnErrorNotImplementedException(e); + } + + @Override + public final void onNext(T args) { + onNext.call(args); + } + + }; + } + + /** + * Create an Observer that receives `onNext` and `onError` and ignores `onCompleted`. + * + */ + public static final Observer create(final Action1 onNext, final Action1 onError) { + if (onNext == null) { + throw new IllegalArgumentException("onNext can not be null"); + } + if (onError == null) { + throw new IllegalArgumentException("onError can not be null"); + } + + return new Observer() { + + @Override + public final void onCompleted() { + // do nothing + } + + @Override + public final void onError(Throwable e) { + onError.call(e); + } + + @Override + public final void onNext(T args) { + onNext.call(args); + } + + }; + } + + /** + * Create an Observer that receives `onNext`, `onError` and `onCompleted`. + * + */ + public static final Observer create(final Action1 onNext, final Action1 onError, final Action0 onComplete) { + if (onNext == null) { + throw new IllegalArgumentException("onNext can not be null"); + } + if (onError == null) { + throw new IllegalArgumentException("onError can not be null"); + } + if (onComplete == null) { + throw new IllegalArgumentException("onComplete can not be null"); + } + + return new Observer() { + + @Override + public final void onCompleted() { + onComplete.call(); + } + + @Override + public final void onError(Throwable e) { + onError.call(e); + } + + @Override + public final void onNext(T args) { + onNext.call(args); + } + + }; + } + +} diff --git a/rxjava-core/src/main/java/rx/observers/SafeSubscriber.java b/rxjava-core/src/main/java/rx/observers/SafeSubscriber.java new file mode 100644 index 0000000000..922b61de95 --- /dev/null +++ b/rxjava-core/src/main/java/rx/observers/SafeSubscriber.java @@ -0,0 +1,200 @@ +/** + * Copyright 2014 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.observers; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + +import rx.Subscriber; +import rx.exceptions.CompositeException; +import rx.exceptions.Exceptions; +import rx.exceptions.OnErrorNotImplementedException; +import rx.plugins.RxJavaPlugins; + +/** + * Wrapper around Observer to ensure compliance with Rx contract. + *

+ * The following is taken from the Rx Design Guidelines document: http://go.microsoft.com/fwlink/?LinkID=205219 + *

+ * Messages sent to instances of the IObserver interface follow the following grammar:
+ * 
+ * OnNext* (OnCompleted | OnError)?
+ * 
+ * This grammar allows observable sequences to send any amount (0 or more) of OnNext messages to the subscribed
+ * observer instance, optionally followed by a single success (OnCompleted) or failure (OnError) message.
+ * 
+ * The single message indicating that an observable sequence has finished ensures that consumers of the observable
+ * sequence can deterministically establish that it is safe to perform cleanup operations.
+ * 
+ * A single failure further ensures that abort semantics can be maintained for operators that work on
+ * multiple observable sequences (see paragraph 6.6).
+ * 
+ * + *

+ * This wrapper will do the following: + *

    + *
  • Allow only single execution of either onError or onCompleted.
  • + *
  • Once an onComplete or onError are performed, no further calls can be executed
  • + *
  • If unsubscribe is called, this means we call completed() and don't allow any further onNext calls.
  • + *
  • When onError or onComplete occur it will unsubscribe from the Observable (if executing asynchronously).
  • + *
+ *

+ * It will not synchronize onNext execution. Use the {@link SynchronizedObserver} to do that. + * + * @param + */ +public class SafeSubscriber extends Subscriber { + + private final Subscriber actual; + private final AtomicBoolean isFinished = new AtomicBoolean(false); + + public SafeSubscriber(Subscriber actual) { + super(actual); + this.actual = actual; + } + + @Override + public void onCompleted() { + if (isFinished.compareAndSet(false, true)) { + try { + actual.onCompleted(); + } catch (Throwable e) { + // we handle here instead of another method so we don't add stacks to the frame + // which can prevent it from being able to handle StackOverflow + Exceptions.throwIfFatal(e); + // handle errors if the onCompleted implementation fails, not just if the Observable fails + _onError(e); + } finally { + // auto-unsubscribe + unsubscribe(); + } + } + } + + @Override + public void onError(Throwable e) { + // we handle here instead of another method so we don't add stacks to the frame + // which can prevent it from being able to handle StackOverflow + Exceptions.throwIfFatal(e); + if (isFinished.compareAndSet(false, true)) { + _onError(e); + } + } + + @Override + public void onNext(T args) { + try { + if (!isFinished.get()) { + actual.onNext(args); + } + } catch (Throwable e) { + // we handle here instead of another method so we don't add stacks to the frame + // which can prevent it from being able to handle StackOverflow + Exceptions.throwIfFatal(e); + // handle errors if the onNext implementation fails, not just if the Observable fails + onError(e); + } + } + + /* + * The logic for `onError` without the `isFinished` check so it can be called from within `onCompleted`. + * + * See https://github.com/Netflix/RxJava/issues/630 for the report of this bug. + */ + protected void _onError(Throwable e) { + try { + RxJavaPlugins.getInstance().getErrorHandler().handleError(e); + } catch (Throwable pluginException) { + handlePluginException(pluginException); + } + try { + actual.onError(e); + } catch (Throwable e2) { + if (e2 instanceof OnErrorNotImplementedException) { + /* + * onError isn't implemented so throw + * + * https://github.com/Netflix/RxJava/issues/198 + * + * Rx Design Guidelines 5.2 + * + * "when calling the Subscribe method that only has an onNext argument, the OnError behavior will be + * to rethrow the exception on the thread that the message comes out from the observable sequence. + * The OnCompleted behavior in this case is to do nothing." + */ + try { + unsubscribe(); + } catch (Throwable unsubscribeException) { + try { + RxJavaPlugins.getInstance().getErrorHandler().handleError(unsubscribeException); + } catch (Throwable pluginException) { + handlePluginException(pluginException); + } + throw new RuntimeException("Observer.onError not implemented and error while unsubscribing.", new CompositeException(Arrays.asList(e, unsubscribeException))); + } + throw (OnErrorNotImplementedException) e2; + } else { + /* + * throw since the Rx contract is broken if onError failed + * + * https://github.com/Netflix/RxJava/issues/198 + */ + try { + RxJavaPlugins.getInstance().getErrorHandler().handleError(e2); + } catch (Throwable pluginException) { + handlePluginException(pluginException); + } + try { + unsubscribe(); + } catch (Throwable unsubscribeException) { + try { + RxJavaPlugins.getInstance().getErrorHandler().handleError(unsubscribeException); + } catch (Throwable pluginException) { + handlePluginException(pluginException); + } + throw new RuntimeException("Error occurred when trying to propagate error to Observer.onError and during unsubscription.", new CompositeException(Arrays.asList(e, e2, unsubscribeException))); + } + + throw new RuntimeException("Error occurred when trying to propagate error to Observer.onError", new CompositeException(Arrays.asList(e, e2))); + } + } + // if we did not throw above we will unsubscribe here, if onError failed then unsubscribe happens in the catch + try { + unsubscribe(); + } catch (RuntimeException unsubscribeException) { + try { + RxJavaPlugins.getInstance().getErrorHandler().handleError(unsubscribeException); + } catch (Throwable pluginException) { + handlePluginException(pluginException); + } + throw unsubscribeException; + } + } + + private void handlePluginException(Throwable pluginException) { + /* + * We don't want errors from the plugin to affect normal flow. + * Since the plugin should never throw this is a safety net + * and will complain loudly to System.err so it gets fixed. + */ + System.err.println("RxJavaErrorHandler threw an Exception. It shouldn't. => " + pluginException.getMessage()); + pluginException.printStackTrace(); + } + + public Subscriber getActual() { + return actual; + } +} diff --git a/rxjava-core/src/main/java/rx/observers/Subscribers.java b/rxjava-core/src/main/java/rx/observers/Subscribers.java new file mode 100644 index 0000000000..bc9496e3e3 --- /dev/null +++ b/rxjava-core/src/main/java/rx/observers/Subscribers.java @@ -0,0 +1,151 @@ +package rx.observers; + +import rx.Observer; +import rx.Subscriber; +import rx.exceptions.OnErrorNotImplementedException; +import rx.functions.Action0; +import rx.functions.Action1; + +public class Subscribers { + + private static final Subscriber EMPTY = new Subscriber() { + + @Override + public final void onCompleted() { + // do nothing + } + + @Override + public final void onError(Throwable e) { + throw new OnErrorNotImplementedException(e); + } + + @Override + public final void onNext(Object args) { + // do nothing + } + + }; + + @SuppressWarnings("unchecked") + public static Subscriber empty() { + return (Subscriber) EMPTY; + } + + public static Subscriber from(final Observer o) { + return new Subscriber() { + + @Override + public void onCompleted() { + o.onCompleted(); + } + + @Override + public void onError(Throwable e) { + o.onError(e); + } + + @Override + public void onNext(T t) { + o.onNext(t); + } + + }; + } + + /** + * Create an Subscriber that receives `onNext` and ignores `onError` and `onCompleted`. + */ + public static final Subscriber create(final Action1 onNext) { + if (onNext == null) { + throw new IllegalArgumentException("onNext can not be null"); + } + + return new Subscriber() { + + @Override + public final void onCompleted() { + // do nothing + } + + @Override + public final void onError(Throwable e) { + throw new OnErrorNotImplementedException(e); + } + + @Override + public final void onNext(T args) { + onNext.call(args); + } + + }; + } + + /** + * Create an Subscriber that receives `onNext` and `onError` and ignores `onCompleted`. + * + */ + public static final Subscriber create(final Action1 onNext, final Action1 onError) { + if (onNext == null) { + throw new IllegalArgumentException("onNext can not be null"); + } + if (onError == null) { + throw new IllegalArgumentException("onError can not be null"); + } + + return new Subscriber() { + + @Override + public final void onCompleted() { + // do nothing + } + + @Override + public final void onError(Throwable e) { + onError.call(e); + } + + @Override + public final void onNext(T args) { + onNext.call(args); + } + + }; + } + + /** + * Create an Subscriber that receives `onNext`, `onError` and `onCompleted`. + * + */ + public static final Subscriber create(final Action1 onNext, final Action1 onError, final Action0 onComplete) { + if (onNext == null) { + throw new IllegalArgumentException("onNext can not be null"); + } + if (onError == null) { + throw new IllegalArgumentException("onError can not be null"); + } + if (onComplete == null) { + throw new IllegalArgumentException("onComplete can not be null"); + } + + return new Subscriber() { + + @Override + public final void onCompleted() { + onComplete.call(); + } + + @Override + public final void onError(Throwable e) { + onError.call(e); + } + + @Override + public final void onNext(T args) { + onNext.call(args); + } + + }; + } + +} diff --git a/rxjava-core/src/main/java/rx/observers/SynchronizedObserver.java b/rxjava-core/src/main/java/rx/observers/SynchronizedObserver.java new file mode 100644 index 0000000000..25ea8c3403 --- /dev/null +++ b/rxjava-core/src/main/java/rx/observers/SynchronizedObserver.java @@ -0,0 +1,82 @@ +/** + * Copyright 2014 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.observers; + +import rx.Observer; + +/** + * Synchronize execution to be single-threaded. + *

+ * This ONLY does synchronization. It does not involve itself in safety or subscriptions. See SafeSubscriber for that. + * + * @param + */ +public final class SynchronizedObserver implements Observer { + + /** + * Intrinsic synchronized locking with double-check short-circuiting was chosen after testing several other implementations. + * + * The code and results can be found here: + * - https://github.com/benjchristensen/JavaLockPerformanceTests/tree/master/results/Observer + * - https://github.com/benjchristensen/JavaLockPerformanceTests/tree/master/src/com/benjchristensen/performance/locks/Observer + * + * The major characteristic that made me choose synchronized instead of Reentrant or a customer AbstractQueueSynchronizer implementation + * is that intrinsic locking performed better when nested, and AtomicObserver will end up nested most of the time since Rx is + * compositional by its very nature. + * + * // TODO composing of this class should rarely happen now with updated design so this decision should be revisited + */ + + private final Observer observer; + private final Object lock; + private boolean isTerminated = false; + + public SynchronizedObserver(Observer subscriber) { + this.observer = subscriber; + this.lock = this; + } + + public SynchronizedObserver(Observer subscriber, Object lock) { + this.observer = subscriber; + this.lock = lock; + } + + public void onNext(T arg) { + synchronized (lock) { + if (!isTerminated) { + observer.onNext(arg); + } + } + } + + public void onError(Throwable e) { + synchronized (lock) { + if (!isTerminated) { + isTerminated = true; + observer.onError(e); + } + } + } + + public void onCompleted() { + synchronized (lock) { + if (!isTerminated) { + isTerminated = true; + observer.onCompleted(); + } + } + } +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/observers/SynchronizedSubscriber.java b/rxjava-core/src/main/java/rx/observers/SynchronizedSubscriber.java new file mode 100644 index 0000000000..8c01f506e1 --- /dev/null +++ b/rxjava-core/src/main/java/rx/observers/SynchronizedSubscriber.java @@ -0,0 +1,66 @@ +/** + * Copyright 2014 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.observers; + +import rx.Observer; +import rx.Subscriber; + +/** + * A thread-safe Observer for transitioning states in operators. + *

+ * Execution rules are: + *

    + *
  • Allow only single-threaded, synchronous, ordered execution of onNext, onCompleted, onError
  • + *
  • Once an onComplete or onError are performed, no further calls can be executed
  • + *
  • If unsubscribe is called, this means we call completed() and don't allow any further onNext calls.
  • + *
+ * + * @param + */ +public final class SynchronizedSubscriber extends Subscriber { + + private final Observer observer; + + public SynchronizedSubscriber(Subscriber subscriber, Object lock) { + super(subscriber); + this.observer = new SynchronizedObserver(subscriber, lock); + } + + /** + * Used when synchronizing an Subscriber without access to the subscription. + * + * @param Observer + */ + public SynchronizedSubscriber(Subscriber subscriber) { + this(subscriber, new Object()); + } + + @Override + public void onCompleted() { + observer.onCompleted(); + } + + @Override + public void onError(Throwable e) { + observer.onError(e); + } + + @Override + public void onNext(T t) { + observer.onNext(t); + } + +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/observers/TestObserver.java b/rxjava-core/src/main/java/rx/observers/TestObserver.java new file mode 100644 index 0000000000..f4e278118d --- /dev/null +++ b/rxjava-core/src/main/java/rx/observers/TestObserver.java @@ -0,0 +1,121 @@ +/** + * Copyright 2014 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.observers; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import rx.Notification; +import rx.Observer; + +/** + * Observer usable for unit testing to perform assertions, inspect received events or wrap a mocked Observer. + */ +public class TestObserver implements Observer { + + + private final Observer delegate; + private final ArrayList onNextEvents = new ArrayList(); + private final ArrayList onErrorEvents = new ArrayList(); + private final ArrayList> onCompletedEvents = new ArrayList>(); + + public TestObserver(Observer delegate) { + this.delegate = delegate; + } + + public TestObserver() { + this.delegate = Observers.empty(); + } + + @Override + public void onCompleted() { + onCompletedEvents.add(Notification. createOnCompleted()); + delegate.onCompleted(); + } + + public List> getOnCompletedEvents() { + return Collections.unmodifiableList(onCompletedEvents); + } + + @Override + public void onError(Throwable e) { + onErrorEvents.add(e); + delegate.onError(e); + } + + public List getOnErrorEvents() { + return Collections.unmodifiableList(onErrorEvents); + } + + @Override + public void onNext(T t) { + onNextEvents.add(t); + delegate.onNext(t); + } + + public List getOnNextEvents() { + return Collections.unmodifiableList(onNextEvents); + } + + public List getEvents() { + ArrayList events = new ArrayList(); + events.add(onNextEvents); + events.add(onErrorEvents); + events.add(onCompletedEvents); + return Collections.unmodifiableList(events); + } + + public void assertReceivedOnNext(List items) { + if (onNextEvents.size() != items.size()) { + throw new AssertionError("Number of items does not match. Provided: " + items.size() + " Actual: " + onNextEvents.size()); + } + + for (int i = 0; i < items.size(); i++) { + if (items.get(i) == null) { + // check for null equality + if (onNextEvents.get(i) != null) { + throw new AssertionError("Value at index: " + i + " expected to be [null] but was: [" + onNextEvents.get(i) + "]"); + } + } else if (!items.get(i).equals(onNextEvents.get(i))) { + throw new AssertionError("Value at index: " + i + " expected to be [" + items.get(i) + "] but was: [" + onNextEvents.get(i) + "]"); + } + } + + } + + /** + * Assert that a single terminal event occurred, either onCompleted or onError. + */ + public void assertTerminalEvent() { + if (onErrorEvents.size() > 1) { + throw new AssertionError("Too many onError events: " + onErrorEvents.size()); + } + + if (onCompletedEvents.size() > 1) { + throw new AssertionError("Too many onCompleted events: " + onCompletedEvents.size()); + } + + if (onCompletedEvents.size() == 1 && onErrorEvents.size() == 1) { + throw new AssertionError("Received both an onError and onCompleted. Should be one or the other."); + } + + if (onCompletedEvents.size() == 0 && onErrorEvents.size() == 0) { + throw new AssertionError("No terminal events received."); + } + } + +} diff --git a/rxjava-core/src/main/java/rx/observers/TestSubscriber.java b/rxjava-core/src/main/java/rx/observers/TestSubscriber.java new file mode 100644 index 0000000000..50bfabf059 --- /dev/null +++ b/rxjava-core/src/main/java/rx/observers/TestSubscriber.java @@ -0,0 +1,121 @@ +/** + * Copyright 2014 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.observers; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import rx.Notification; +import rx.Observer; +import rx.Subscriber; + +/** + * Observer usable for unit testing to perform assertions, inspect received events or wrap a mocked Observer. + */ +public class TestSubscriber extends Subscriber { + + private final TestObserver testObserver; + private final CountDownLatch latch = new CountDownLatch(1); + private volatile Thread lastSeenThread; + + public TestSubscriber(Subscriber delegate) { + this.testObserver = new TestObserver(delegate); + } + + public TestSubscriber(Observer delegate) { + this.testObserver = new TestObserver(delegate); + } + + public TestSubscriber() { + this.testObserver = new TestObserver(Subscribers. empty()); + } + + @Override + public void onCompleted() { + testObserver.onCompleted(); + latch.countDown(); + } + + public List> getOnCompletedEvents() { + return testObserver.getOnCompletedEvents(); + } + + @Override + public void onError(Throwable e) { + testObserver.onError(e); + latch.countDown(); + } + + public List getOnErrorEvents() { + return testObserver.getOnErrorEvents(); + } + + @Override + public void onNext(T t) { + lastSeenThread = Thread.currentThread(); + testObserver.onNext(t); + } + + public List getOnNextEvents() { + return testObserver.getOnNextEvents(); + } + + public void assertReceivedOnNext(List items) { + testObserver.assertReceivedOnNext(items); + } + + /** + * Assert that a single terminal event occurred, either onCompleted or onError. + */ + public void assertTerminalEvent() { + testObserver.assertTerminalEvent(); + } + + public void assertUnsubscribed() { + if (!isUnsubscribed()) { + throw new AssertionError("Not unsubscribed."); + } + } + + public void awaitTerminalEvent() { + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted", e); + } + } + + public void awaitTerminalEvent(long timeout, TimeUnit unit) { + try { + latch.await(timeout, unit); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted", e); + } + } + + public void awaitTerminalEventAndUnsubscribeOnTimeout(long timeout, TimeUnit unit) { + try { + awaitTerminalEvent(timeout, unit); + } catch (RuntimeException e) { + unsubscribe(); + } + } + + public Thread getLastSeenThread() { + return lastSeenThread; + } +} diff --git a/rxjava-core/src/main/java/rx/operators/BufferUntilSubscriber.java b/rxjava-core/src/main/java/rx/operators/BufferUntilSubscriber.java new file mode 100644 index 0000000000..7526674636 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/BufferUntilSubscriber.java @@ -0,0 +1,193 @@ +/** + * Copyright 2014 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.LinkedList; +import java.util.Queue; +import rx.Subscriber; +import rx.subscriptions.CompositeSubscription; + +/** + * Buffers the incoming events until notified, then replays the + * buffered events and continues as a simple pass-through subscriber. + * @param the streamed value type + */ +public class BufferUntilSubscriber extends Subscriber { + /** The actual subscriber. */ + private final Subscriber actual; + /** Indicate the pass-through mode. */ + private volatile boolean passthroughMode; + /** Protect mode transition. */ + private final Object gate = new Object(); + /** The buffered items. */ + private final Queue queue = new LinkedList(); + /** The queue capacity. */ + private final int capacity; + /** Null sentinel (in case queue type is changed). */ + private static final Object NULL_SENTINEL = new Object(); + /** Complete sentinel. */ + private static final Object COMPLETE_SENTINEL = new Object(); + /** + * Container for an onError event. + */ + private static final class ErrorSentinel { + final Throwable t; + + public ErrorSentinel(Throwable t) { + this.t = t; + } + + } + /** + * Constructor that wraps the actual subscriber and shares its subscription. + * @param capacity the queue capacity to accept before blocking, negative value indicates an unbounded queue + * @param actual + */ + public BufferUntilSubscriber(int capacity, Subscriber actual) { + super(actual); + this.actual = actual; + this.capacity = capacity; + } + /** + * Constructor that wraps the actual subscriber and uses the given composite + * subscription. + * @param capacity the queue capacity to accept before blocking, negative value indicates an unbounded queue + * @param actual + * @param cs + */ + public BufferUntilSubscriber(int capacity, Subscriber actual, CompositeSubscription cs) { + super(cs); + this.actual = actual; + this.capacity = capacity; + } + + /** + * Call this method to replay the buffered events and continue as a pass-through subscriber. + * If already in pass-through mode, this method is a no-op. + */ + public void enterPassthroughMode() { + if (!passthroughMode) { + synchronized (gate) { + if (!passthroughMode) { + while (!queue.isEmpty()) { + Object o = queue.poll(); + if (!actual.isUnsubscribed()) { + if (o == NULL_SENTINEL) { + actual.onNext(null); + } else + if (o == COMPLETE_SENTINEL) { + actual.onCompleted(); + } else + if (o instanceof ErrorSentinel) { + actual.onError(((ErrorSentinel)o).t); + } else + if (o != null) { + @SuppressWarnings("unchecked") + T v = (T)o; + actual.onNext(v); + } else { + throw new NullPointerException(); + } + } + } + passthroughMode = true; + gate.notifyAll(); + } + } + } + } + @Override + public void onNext(T t) { + if (!passthroughMode) { + synchronized (gate) { + if (!passthroughMode) { + if (capacity < 0 || queue.size() < capacity) { + queue.offer(t != null ? t : NULL_SENTINEL); + return; + } + try { + while (!passthroughMode) { + gate.wait(); + } + if (actual.isUnsubscribed()) { + return; + } + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + actual.onError(ex); + return; + } + } + } + } + actual.onNext(t); + } + + @Override + public void onError(Throwable e) { + if (!passthroughMode) { + synchronized (gate) { + if (!passthroughMode) { + if (capacity < 0 || queue.size() < capacity) { + queue.offer(new ErrorSentinel(e)); + return; + } + try { + while (!passthroughMode) { + gate.wait(); + } + if (actual.isUnsubscribed()) { + return; + } + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + actual.onError(ex); + return; + } + } + } + } + actual.onError(e); + } + + @Override + public void onCompleted() { + if (!passthroughMode) { + synchronized (gate) { + if (!passthroughMode) { + if (capacity < 0 || queue.size() < capacity) { + queue.offer(COMPLETE_SENTINEL); + return; + } + try { + while (!passthroughMode) { + gate.wait(); + } + if (actual.isUnsubscribed()) { + return; + } + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + actual.onError(ex); + return; + } + } + } + } + actual.onCompleted(); + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/ChunkedOperation.java b/rxjava-core/src/main/java/rx/operators/ChunkedOperation.java index 1d3b762891..74fea62687 100644 --- a/rxjava-core/src/main/java/rx/operators/ChunkedOperation.java +++ b/rxjava-core/src/main/java/rx/operators/ChunkedOperation.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,11 +27,11 @@ import rx.Observable; import rx.Observer; import rx.Scheduler; +import rx.Scheduler.Inner; import rx.Subscription; -import rx.util.functions.Action0; -import rx.util.functions.Action1; -import rx.util.functions.Func0; -import rx.util.functions.Func1; +import rx.functions.Action1; +import rx.functions.Func0; +import rx.functions.Func1; /** * The base class for operations that break observables into "chunks". Currently buffers and windows. @@ -148,7 +148,7 @@ public OverlappingChunks(Observer observer, Func0 The type of object being tracked by the {@link Chunk} */ - protected static class TimeAndSizeBasedChunks extends Chunks { + protected static class TimeAndSizeBasedChunks extends Chunks implements Subscription { private final ConcurrentMap, Subscription> subscriptions = new ConcurrentHashMap, Subscription>(); @@ -156,6 +156,7 @@ protected static class TimeAndSizeBasedChunks extends Chunks { private final long maxTime; private final TimeUnit unit; private final int maxSize; + private volatile boolean unsubscribed = false; public TimeAndSizeBasedChunks(Observer observer, Func0> chunkMaker, int maxSize, long maxTime, TimeUnit unit, Scheduler scheduler) { super(observer, chunkMaker); @@ -168,9 +169,9 @@ public TimeAndSizeBasedChunks(Observer observer, Func0 createChunk() { final Chunk chunk = super.createChunk(); - subscriptions.put(chunk, scheduler.schedule(new Action0() { + subscriptions.put(chunk, scheduler.schedule(new Action1() { @Override - public void call() { + public void call(Inner inner) { emitChunk(chunk); } }, maxTime, unit)); @@ -207,6 +208,19 @@ public void pushValue(T value) { } } } + + @Override + public void unsubscribe() { + unsubscribed = true; + for (Subscription s : subscriptions.values()) { + s.unsubscribe(); + } + } + + @Override + public boolean isUnsubscribed() { + return unsubscribed; + } } /** @@ -218,13 +232,14 @@ public void pushValue(T value) { * The type of object all internal {@link rx.operators.ChunkedOperation.Chunk} objects record. * The type of object being tracked by the {@link Chunk} */ - protected static class TimeBasedChunks extends OverlappingChunks { + protected static class TimeBasedChunks extends OverlappingChunks implements Subscription { private final ConcurrentMap, Subscription> subscriptions = new ConcurrentHashMap, Subscription>(); private final Scheduler scheduler; private final long time; private final TimeUnit unit; + private volatile boolean unsubscribed = false; public TimeBasedChunks(Observer observer, Func0> chunkMaker, long time, TimeUnit unit, Scheduler scheduler) { super(observer, chunkMaker); @@ -236,9 +251,9 @@ public TimeBasedChunks(Observer observer, Func0 @Override public Chunk createChunk() { final Chunk chunk = super.createChunk(); - subscriptions.put(chunk, scheduler.schedule(new Action0() { + subscriptions.put(chunk, scheduler.schedule(new Action1() { @Override - public void call() { + public void call(Inner inner) { emitChunk(chunk); } }, time, unit)); @@ -250,6 +265,20 @@ public void emitChunk(Chunk chunk) { subscriptions.remove(chunk); super.emitChunk(chunk); } + + @Override + public void unsubscribe() { + unsubscribed = true; + for (Subscription s : subscriptions.values()) { + s.unsubscribe(); + } + } + + @Override + public boolean isUnsubscribed() { + return unsubscribed; + } + } /** @@ -441,7 +470,7 @@ public void stop() { /** * This {@link rx.operators.ChunkedOperation.ChunkCreator} creates a new {@link rx.operators.ChunkedOperation.Chunk} whenever it receives an * object from the provided {@link rx.Observable} created with the - * chunkClosingSelector {@link rx.util.functions.Func0}. + * chunkClosingSelector {@link rx.functions.Func0}. * * @param * The type of object all internal {@link rx.operators.ChunkedOperation.Chunk} objects record. @@ -487,7 +516,7 @@ public void stop() { * This {@link rx.operators.ChunkedOperation.ChunkCreator} creates a new {@link rx.operators.ChunkedOperation.Chunk} whenever it receives * an object from the provided chunkOpenings {@link rx.Observable}, and closes the corresponding {@link rx.operators.ChunkedOperation.Chunk} object when it receives an object from the provided * {@link rx.Observable} created - * with the chunkClosingSelector {@link rx.util.functions.Func1}. + * with the chunkClosingSelector {@link rx.functions.Func1}. * * @param * The type of object all internal {@link rx.operators.ChunkedOperation.Chunk} objects record. @@ -538,18 +567,18 @@ protected static class TimeBasedChunkCreator implements ChunkCreator { private final SafeObservableSubscription subscription = new SafeObservableSubscription(); public TimeBasedChunkCreator(final NonOverlappingChunks chunks, long time, TimeUnit unit, Scheduler scheduler) { - this.subscription.wrap(scheduler.schedulePeriodically(new Action0() { + this.subscription.wrap(scheduler.schedulePeriodically(new Action1() { @Override - public void call() { + public void call(Inner inner) { chunks.emitAndReplaceChunk(); } }, 0, time, unit)); } public TimeBasedChunkCreator(final OverlappingChunks chunks, long time, TimeUnit unit, Scheduler scheduler) { - this.subscription.wrap(scheduler.schedulePeriodically(new Action0() { + this.subscription.wrap(scheduler.schedulePeriodically(new Action1() { @Override - public void call() { + public void call(Inner inner) { chunks.createChunk(); } }, 0, time, unit)); diff --git a/rxjava-core/src/main/java/rx/operators/OperationToObservableIterable.java b/rxjava-core/src/main/java/rx/operators/OnSubscribeFromIterable.java similarity index 52% rename from rxjava-core/src/main/java/rx/operators/OperationToObservableIterable.java rename to rxjava-core/src/main/java/rx/operators/OnSubscribeFromIterable.java index a8a970bdd8..45dbde9004 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationToObservableIterable.java +++ b/rxjava-core/src/main/java/rx/operators/OnSubscribeFromIterable.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,39 +15,34 @@ */ package rx.operators; -import rx.Observable.OnSubscribeFunc; -import rx.Observer; -import rx.Subscription; -import rx.subscriptions.Subscriptions; +import rx.Observable.OnSubscribe; +import rx.Subscriber; /** * Converts an Iterable sequence into an Observable. *

- * + * *

* You can convert any object that supports the Iterable interface into an Observable that emits * each item in the object, with the toObservable operation. */ -public final class OperationToObservableIterable { +public final class OnSubscribeFromIterable implements OnSubscribe { - public static OnSubscribeFunc toObservableIterable(Iterable list) { - return new ToObservableIterable(list); - } - - private static class ToObservableIterable implements OnSubscribeFunc { - public ToObservableIterable(Iterable list) { - this.iterable = list; - } + final Iterable is; - public Iterable iterable; + public OnSubscribeFromIterable(Iterable iterable) { + this.is = iterable; + } - public Subscription onSubscribe(Observer observer) { - for (T item : iterable) { - observer.onNext(item); + @Override + public void call(Subscriber o) { + for (T i : is) { + if (o.isUnsubscribed()) { + return; } - observer.onCompleted(); - - return Subscriptions.empty(); + o.onNext(i); } + o.onCompleted(); } + } diff --git a/rxjava-core/src/main/java/rx/operators/OnSubscribeRange.java b/rxjava-core/src/main/java/rx/operators/OnSubscribeRange.java new file mode 100644 index 0000000000..797bb49718 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OnSubscribeRange.java @@ -0,0 +1,45 @@ +/** + * Copyright 2014 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.OnSubscribe; +import rx.Subscriber; + +/** + * Emit ints from start to end inclusive. + */ +public final class OnSubscribeRange implements OnSubscribe { + + private final int start; + private final int end; + + public OnSubscribeRange(int start, int end) { + this.start = start; + this.end = end; + } + + @Override + public void call(Subscriber o) { + for (int i = start; i <= end; i++) { + if (o.isUnsubscribed()) { + return; + } + o.onNext(i); + } + o.onCompleted(); + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationAll.java b/rxjava-core/src/main/java/rx/operators/OperationAll.java index f3e0f0ebcd..681486f13c 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationAll.java +++ b/rxjava-core/src/main/java/rx/operators/OperationAll.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.util.functions.Func1; +import rx.functions.Func1; /** * Returns an Observable that emits a Boolean that indicates whether all items emitted by an diff --git a/rxjava-core/src/main/java/rx/operators/OperationAmb.java b/rxjava-core/src/main/java/rx/operators/OperationAmb.java index 639de2fa85..16a6c307dd 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationAmb.java +++ b/rxjava-core/src/main/java/rx/operators/OperationAmb.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rxjava-core/src/main/java/rx/operators/OperationAny.java b/rxjava-core/src/main/java/rx/operators/OperationAny.java index a06058bece..117e1a5437 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationAny.java +++ b/rxjava-core/src/main/java/rx/operators/OperationAny.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package rx.operators; -import static rx.util.functions.Functions.*; +import static rx.functions.Functions.*; import java.util.concurrent.atomic.AtomicBoolean; @@ -23,7 +23,7 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.util.functions.Func1; +import rx.functions.Func1; /** * Returns an {@link Observable} that emits true if any element of diff --git a/rxjava-core/src/main/java/rx/operators/OperationCast.java b/rxjava-core/src/main/java/rx/operators/OperationAsObservable.java similarity index 50% rename from rxjava-core/src/main/java/rx/operators/OperationCast.java rename to rxjava-core/src/main/java/rx/operators/OperationAsObservable.java index dc54c204f6..b266345984 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationCast.java +++ b/rxjava-core/src/main/java/rx/operators/OperationAsObservable.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 - * + * + * 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. @@ -17,19 +17,24 @@ import rx.Observable; import rx.Observable.OnSubscribeFunc; -import rx.util.functions.Func1; +import rx.Observer; +import rx.Subscription; /** - * Converts the elements of an observable sequence to the specified type. + * Hides the identity of another observable. + * + * @param + * the return value type of the wrapped observable. */ -public class OperationCast { +public final class OperationAsObservable implements OnSubscribeFunc { + private final Observable source; - public static OnSubscribeFunc cast( - Observable source, final Class klass) { - return OperationMap.map(source, new Func1() { - public R call(T t) { - return klass.cast(t); - } - }); + public OperationAsObservable(Observable source) { + this.source = source; + } + + @Override + public Subscription onSubscribe(Observer t1) { + return source.subscribe(t1); } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationAverage.java b/rxjava-core/src/main/java/rx/operators/OperationAverage.java index 35abc99eb5..1017df4524 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationAverage.java +++ b/rxjava-core/src/main/java/rx/operators/OperationAverage.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,11 @@ package rx.operators; import rx.Observable; -import rx.util.functions.Func1; -import rx.util.functions.Func2; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.functions.Func1; +import rx.functions.Func2; /** * A few operators for implementing the averaging operation. @@ -102,4 +105,244 @@ public Double call(Tuple2 result) { } }); } + + /** + * Compute the average by extracting integer values from the source via an + * extractor function. + * + * @param + * the source value type + */ + public static final class AverageIntegerExtractor implements OnSubscribeFunc { + final Observable source; + final Func1 valueExtractor; + + public AverageIntegerExtractor(Observable source, Func1 valueExtractor) { + this.source = source; + this.valueExtractor = valueExtractor; + } + + @Override + public Subscription onSubscribe(Observer t1) { + return source.subscribe(new AverageObserver(t1)); + } + + /** Computes the average. */ + private final class AverageObserver implements Observer { + final Observer observer; + int sum; + int count; + + public AverageObserver(Observer observer) { + this.observer = observer; + } + + @Override + public void onNext(T args) { + sum += valueExtractor.call(args); + count++; + } + + @Override + public void onError(Throwable e) { + observer.onError(e); + } + + @Override + public void onCompleted() { + if (count > 0) { + try { + observer.onNext(sum / count); + } catch (Throwable t) { + observer.onError(t); + return; + } + observer.onCompleted(); + } else { + observer.onError(new IllegalArgumentException("Sequence contains no elements")); + } + } + + } + } + + /** + * Compute the average by extracting long values from the source via an + * extractor function. + * + * @param + * the source value type + */ + public static final class AverageLongExtractor implements OnSubscribeFunc { + final Observable source; + final Func1 valueExtractor; + + public AverageLongExtractor(Observable source, Func1 valueExtractor) { + this.source = source; + this.valueExtractor = valueExtractor; + } + + @Override + public Subscription onSubscribe(Observer t1) { + return source.subscribe(new AverageObserver(t1)); + } + + /** Computes the average. */ + private final class AverageObserver implements Observer { + final Observer observer; + long sum; + int count; + + public AverageObserver(Observer observer) { + this.observer = observer; + } + + @Override + public void onNext(T args) { + sum += valueExtractor.call(args); + count++; + } + + @Override + public void onError(Throwable e) { + observer.onError(e); + } + + @Override + public void onCompleted() { + if (count > 0) { + try { + observer.onNext(sum / count); + } catch (Throwable t) { + observer.onError(t); + return; + } + observer.onCompleted(); + } else { + observer.onError(new IllegalArgumentException("Sequence contains no elements")); + } + } + + } + } + + /** + * Compute the average by extracting float values from the source via an + * extractor function. + * + * @param + * the source value type + */ + public static final class AverageFloatExtractor implements OnSubscribeFunc { + final Observable source; + final Func1 valueExtractor; + + public AverageFloatExtractor(Observable source, Func1 valueExtractor) { + this.source = source; + this.valueExtractor = valueExtractor; + } + + @Override + public Subscription onSubscribe(Observer t1) { + return source.subscribe(new AverageObserver(t1)); + } + + /** Computes the average. */ + private final class AverageObserver implements Observer { + final Observer observer; + float sum; + int count; + + public AverageObserver(Observer observer) { + this.observer = observer; + } + + @Override + public void onNext(T args) { + sum += valueExtractor.call(args); + count++; + } + + @Override + public void onError(Throwable e) { + observer.onError(e); + } + + @Override + public void onCompleted() { + if (count > 0) { + try { + observer.onNext(sum / count); + } catch (Throwable t) { + observer.onError(t); + return; + } + observer.onCompleted(); + } else { + observer.onError(new IllegalArgumentException("Sequence contains no elements")); + } + } + + } + } + + /** + * Compute the average by extracting double values from the source via an + * extractor function. + * + * @param + * the source value type + */ + public static final class AverageDoubleExtractor implements OnSubscribeFunc { + final Observable source; + final Func1 valueExtractor; + + public AverageDoubleExtractor(Observable source, Func1 valueExtractor) { + this.source = source; + this.valueExtractor = valueExtractor; + } + + @Override + public Subscription onSubscribe(Observer t1) { + return source.subscribe(new AverageObserver(t1)); + } + + /** Computes the average. */ + private final class AverageObserver implements Observer { + final Observer observer; + double sum; + int count; + + public AverageObserver(Observer observer) { + this.observer = observer; + } + + @Override + public void onNext(T args) { + sum += valueExtractor.call(args); + count++; + } + + @Override + public void onError(Throwable e) { + observer.onError(e); + } + + @Override + public void onCompleted() { + if (count > 0) { + try { + observer.onNext(sum / count); + } catch (Throwable t) { + observer.onError(t); + return; + } + observer.onCompleted(); + } else { + observer.onError(new IllegalArgumentException("Sequence contains no elements")); + } + } + + } + } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationBuffer.java b/rxjava-core/src/main/java/rx/operators/OperationBuffer.java index 02d0ce4573..b3dd33400b 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationBuffer.java +++ b/rxjava-core/src/main/java/rx/operators/OperationBuffer.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,17 +15,20 @@ */ package rx.operators; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Scheduler; import rx.Subscription; -import rx.concurrency.Schedulers; -import rx.util.functions.Func0; -import rx.util.functions.Func1; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.schedulers.Schedulers; +import rx.subscriptions.CompositeSubscription; public final class OperationBuffer extends ChunkedOperation { @@ -40,7 +43,7 @@ public Buffer call() { /** *

This method creates a {@link Func1} object which represents the buffer operation. This operation takes - * values from the specified {@link Observable} source and stores them in a buffer until the {@link Observable} constructed using the {@link Func0} argument, produces a + * values from the specified {@link Observable} source and stores them in a buffer until the {@link Observable} constructed using the {@link Func0} argument, produces a * value. The buffer is then * emitted, and a new buffer is created to replace it. A new {@link Observable} will be constructed using the * provided {@link Func0} object, which will determine when this new buffer is emitted. When the source {@link Observable} completes or produces an error, the current buffer is emitted, and the @@ -65,7 +68,9 @@ public static OnSubscribeFunc> buffer(final Observable public Subscription onSubscribe(Observer> observer) { NonOverlappingChunks> buffers = new NonOverlappingChunks>(observer, OperationBuffer. bufferMaker()); ChunkCreator creator = new ObservableBasedSingleChunkCreator, TClosing>(buffers, bufferClosingSelector); - return source.subscribe(new ChunkObserver>(buffers, observer, creator)); + return new CompositeSubscription( + new ChunkToSubscription(creator), + source.subscribe(new ChunkObserver>(buffers, observer, creator))); } }; } @@ -101,7 +106,9 @@ public static OnSubscribeFunc> buffer(final Obse public Subscription onSubscribe(final Observer> observer) { OverlappingChunks> buffers = new OverlappingChunks>(observer, OperationBuffer. bufferMaker()); ChunkCreator creator = new ObservableBasedMultiChunkCreator, TOpening, TClosing>(buffers, bufferOpenings, bufferClosingSelector); - return source.subscribe(new ChunkObserver>(buffers, observer, creator)); + return new CompositeSubscription( + new ChunkToSubscription(creator), + source.subscribe(new ChunkObserver>(buffers, observer, creator))); } }; } @@ -156,7 +163,9 @@ public static OnSubscribeFunc> buffer(final Observable source, fi public Subscription onSubscribe(final Observer> observer) { Chunks> chunks = new SizeBasedChunks>(observer, OperationBuffer. bufferMaker(), count); ChunkCreator creator = new SkippingChunkCreator>(chunks, skip); - return source.subscribe(new ChunkObserver>(chunks, observer, creator)); + return new CompositeSubscription( + new ChunkToSubscription(creator), + source.subscribe(new ChunkObserver>(chunks, observer, creator))); } }; } @@ -211,7 +220,9 @@ public static OnSubscribeFunc> buffer(final Observable source, fi public Subscription onSubscribe(final Observer> observer) { NonOverlappingChunks> buffers = new NonOverlappingChunks>(observer, OperationBuffer. bufferMaker()); ChunkCreator creator = new TimeBasedChunkCreator>(buffers, timespan, unit, scheduler); - return source.subscribe(new ChunkObserver>(buffers, observer, creator)); + return new CompositeSubscription( + new ChunkToSubscription(creator), + source.subscribe(new ChunkObserver>(buffers, observer, creator))); } }; } @@ -270,9 +281,12 @@ 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); + TimeAndSizeBasedChunks> chunks = new TimeAndSizeBasedChunks>(observer, OperationBuffer. bufferMaker(), count, timespan, unit, scheduler); ChunkCreator creator = new SingleChunkCreator>(chunks); - return source.subscribe(new ChunkObserver>(chunks, observer, creator)); + return new CompositeSubscription( + chunks, + new ChunkToSubscription(creator), + source.subscribe(new ChunkObserver>(chunks, observer, creator))); } }; } @@ -331,9 +345,12 @@ 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); + TimeBasedChunks> 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)); + return new CompositeSubscription( + buffers, + new ChunkToSubscription(creator), + source.subscribe(new ChunkObserver>(buffers, observer, creator))); } }; } @@ -355,4 +372,174 @@ public List getContents() { return contents; } } + + /** + * Converts a chunk creator into a subscription which stops the chunk. + */ + private static class ChunkToSubscription implements Subscription { + private ChunkCreator cc; + private final AtomicBoolean done; + + public ChunkToSubscription(ChunkCreator cc) { + this.cc = cc; + this.done = new AtomicBoolean(); + } + + @Override + public void unsubscribe() { + if (done.compareAndSet(false, true)) { + ChunkCreator cc0 = cc; + cc = null; + cc0.stop(); + } + } + + @Override + public boolean isUnsubscribed() { + return done.get(); + } + } + + /** + * Create a buffer operator with the given observable sequence as the buffer boundary. + */ + public static OnSubscribeFunc> bufferWithBoundaryObservable(Observable source, Observable boundary) { + return new BufferWithObservableBoundary(source, boundary, 16); + } + + /** + * Create a buffer operator with the given observable sequence as the buffer boundary and + * with the given initial capacity for buffers. + */ + public static OnSubscribeFunc> bufferWithBoundaryObservable(Observable source, Observable boundary, int initialCapacity) { + if (initialCapacity <= 0) { + throw new IllegalArgumentException("initialCapacity > 0 required"); + } + return new BufferWithObservableBoundary(source, boundary, initialCapacity); + } + + /** + * Buffer until an element is emitted from a helper observable. + * + * @param + * the buffered value type + */ + private static final class BufferWithObservableBoundary implements OnSubscribeFunc> { + final Observable source; + final Observable boundary; + final int initialCapacity; + + public BufferWithObservableBoundary(Observable source, Observable boundary, int initialCapacity) { + this.source = source; + this.boundary = boundary; + this.initialCapacity = initialCapacity; + } + + @Override + public Subscription onSubscribe(Observer> t1) { + CompositeSubscription csub = new CompositeSubscription(); + + SourceObserver so = new SourceObserver(t1, initialCapacity, csub); + csub.add(source.subscribe(so)); + csub.add(boundary.subscribe(new BoundaryObserver(so))); + + return csub; + } + + /** + * Observes the source. + */ + private static final class SourceObserver implements Observer { + final Observer> observer; + /** The buffer, if null, that indicates a terminal state. */ + List buffer; + final int initialCapacity; + final Object guard; + final Subscription cancel; + + public SourceObserver(Observer> observer, int initialCapacity, Subscription cancel) { + this.observer = observer; + this.initialCapacity = initialCapacity; + this.guard = new Object(); + this.cancel = cancel; + buffer = new ArrayList(initialCapacity); + } + + @Override + public void onNext(T args) { + synchronized (guard) { + buffer.add(args); + } + } + + @Override + public void onError(Throwable e) { + synchronized (guard) { + if (buffer == null) { + return; + } + buffer = null; + } + observer.onError(e); + cancel.unsubscribe(); + } + + @Override + public void onCompleted() { + emitAndComplete(); + cancel.unsubscribe(); + } + + void emitAndReplace() { + List buf; + synchronized (guard) { + if (buffer == null) { + return; + } + buf = buffer; + buffer = new ArrayList(initialCapacity); + } + observer.onNext(buf); + } + + void emitAndComplete() { + List buf; + synchronized (guard) { + if (buffer == null) { + return; + } + buf = buffer; + buffer = null; + } + observer.onNext(buf); + observer.onCompleted(); + } + } + + /** + * Observes the boundary. + */ + private static final class BoundaryObserver implements Observer { + final SourceObserver so; + + public BoundaryObserver(SourceObserver so) { + this.so = so; + } + + @Override + public void onNext(T args) { + so.emitAndReplace(); + } + + @Override + public void onError(Throwable e) { + so.onError(e); + } + + @Override + public void onCompleted() { + so.onCompleted(); + } + } + } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationCache.java b/rxjava-core/src/main/java/rx/operators/OperationCache.java index 71c681a179..eb10bc9ab6 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationCache.java +++ b/rxjava-core/src/main/java/rx/operators/OperationCache.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java b/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java index 8a581667ae..b423045c60 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java +++ b/rxjava-core/src/main/java/rx/operators/OperationCombineLatest.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,27 +15,28 @@ */ package rx.operators; -import java.util.LinkedList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -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.Functions; +import rx.functions.Func2; +import rx.functions.Func3; +import rx.functions.Func4; +import rx.functions.Func5; +import rx.functions.Func6; +import rx.functions.Func7; +import rx.functions.Func8; +import rx.functions.Func9; +import rx.functions.FuncN; +import rx.functions.Functions; +import rx.subscriptions.CompositeSubscription; /** * Returns an Observable that combines the emissions of multiple source observables. Once each @@ -59,290 +60,256 @@ public class OperationCombineLatest { * 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. */ + @SuppressWarnings("unchecked") public static OnSubscribeFunc combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) { - Aggregator a = new Aggregator(Functions.fromFunc(combineLatestFunction)); - a.addObserver(new CombineObserver(a, w0)); - a.addObserver(new CombineObserver(a, w1)); - return a; + return new CombineLatest(Arrays.asList(w0, w1), Functions.fromFunc(combineLatestFunction)); } /** * @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) */ + @SuppressWarnings("unchecked") 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)); - a.addObserver(new CombineObserver(a, w1)); - a.addObserver(new CombineObserver(a, w2)); - return a; + return new CombineLatest(Arrays.asList(w0, w1, w2), Functions.fromFunc(combineLatestFunction)); } /** * @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) */ + @SuppressWarnings("unchecked") 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)); - a.addObserver(new CombineObserver(a, w1)); - a.addObserver(new CombineObserver(a, w2)); - a.addObserver(new CombineObserver(a, w3)); - return a; + return new CombineLatest(Arrays.asList(w0, w1, w2, w3), Functions.fromFunc(combineLatestFunction)); } /** * @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) */ + @SuppressWarnings("unchecked") 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)); - a.addObserver(new CombineObserver(a, w1)); - a.addObserver(new CombineObserver(a, w2)); - a.addObserver(new CombineObserver(a, w3)); - a.addObserver(new CombineObserver(a, w4)); - return a; + return new CombineLatest(Arrays.asList(w0, w1, w2, w3, w4), Functions.fromFunc(combineLatestFunction)); } /** * @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) */ + @SuppressWarnings("unchecked") 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)); - a.addObserver(new CombineObserver(a, w1)); - a.addObserver(new CombineObserver(a, w2)); - a.addObserver(new CombineObserver(a, w3)); - a.addObserver(new CombineObserver(a, w4)); - a.addObserver(new CombineObserver(a, w5)); - return a; + return new CombineLatest(Arrays.asList(w0, w1, w2, w3, w4, w5), Functions.fromFunc(combineLatestFunction)); } /** * @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) */ + @SuppressWarnings("unchecked") 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)); - a.addObserver(new CombineObserver(a, w1)); - a.addObserver(new CombineObserver(a, w2)); - a.addObserver(new CombineObserver(a, w3)); - a.addObserver(new CombineObserver(a, w4)); - a.addObserver(new CombineObserver(a, w5)); - a.addObserver(new CombineObserver(a, w6)); - return a; + return new CombineLatest(Arrays.asList(w0, w1, w2, w3, w4, w5, w6), Functions.fromFunc(combineLatestFunction)); } /** * @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) */ + @SuppressWarnings("unchecked") 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)); - a.addObserver(new CombineObserver(a, w1)); - a.addObserver(new CombineObserver(a, w2)); - a.addObserver(new CombineObserver(a, w3)); - a.addObserver(new CombineObserver(a, w4)); - a.addObserver(new CombineObserver(a, w5)); - a.addObserver(new CombineObserver(a, w6)); - a.addObserver(new CombineObserver(a, w7)); - return a; + return new CombineLatest(Arrays.asList(w0, w1, w2, w3, w4, w5, w6, w7), Functions.fromFunc(combineLatestFunction)); } /** * @see #combineLatest(Observable w0, Observable w1, Func2 combineLatestFunction) */ + @SuppressWarnings("unchecked") 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)); - a.addObserver(new CombineObserver(a, w1)); - a.addObserver(new CombineObserver(a, w2)); - a.addObserver(new CombineObserver(a, w3)); - a.addObserver(new CombineObserver(a, w4)); - a.addObserver(new CombineObserver(a, w5)); - a.addObserver(new CombineObserver(a, w6)); - a.addObserver(new CombineObserver(a, w7)); - a.addObserver(new CombineObserver(a, w8)); - return a; + return new CombineLatest(Arrays.asList(w0, w1, w2, w3, w4, w5, w6, w7, w8), Functions.fromFunc(combineLatestFunction)); } - /* package accessible for unit tests */static class CombineObserver implements Observer { - final Observable w; - final Aggregator a; - private Subscription subscription; + static final class CombineLatest implements OnSubscribeFunc { + final List> sources; + final FuncN combiner; - public CombineObserver(Aggregator a, Observable w) { - this.a = a; - this.w = w; - } - - private void startWatching() { - if (subscription != null) { - throw new RuntimeException("This should only be called once."); + public CombineLatest(Iterable> sources, FuncN combiner) { + this.sources = new ArrayList>(); + this.combiner = combiner; + for (Observable source : sources) { + this.sources.add(source); } - subscription = w.subscribe(this); } @Override - public void onCompleted() { - a.complete(this); - } - - @Override - public void onError(Throwable e) { - a.error(e); - } - - @Override - public void onNext(T args) { - a.next(this, 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 - * at least one event. - */ - /* 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. - */ - private final List> observers = new LinkedList>(); - - public Aggregator(FuncN combineLatestFunction) { - this.combineLatestFunction = combineLatestFunction; - } - - /** - * Receive notification of a Observer starting (meaning we should require it for aggregation) - * - * @param w - * The observer to add. - */ - void addObserver(CombineObserver w) { - observers.add(w); - } + public Subscription onSubscribe(Observer t1) { + CompositeSubscription csub = new CompositeSubscription(); + + Collector collector = new Collector(t1, csub, sources.size()); + + int index = 0; + List observers = new ArrayList(sources.size() + 1); + for (Observable source : sources) { + SafeObservableSubscription sas = new SafeObservableSubscription(); + csub.add(sas); + observers.add(new SourceObserver(collector, sas, index, source)); + index++; + } - /** - * Receive notification of a Observer completing its iterations. - * - * @param w - * The observer that has completed. - */ - void complete(CombineObserver w) { - int completed = numCompleted.incrementAndGet(); - // if all CombineObservers are completed, we mark the whole thing as completed - if (completed == observers.size()) { - if (running.get()) { - // mark ourselves as done - observer.onCompleted(); - // just to ensure we stop processing in case we receive more onNext/complete/error calls after this - running.set(false); + for (SourceObserver so : observers) { + // if we run to completion, don't bother any further + if (!csub.isUnsubscribed()) { + so.connect(); } } - } - /** - * Receive error for a Observer. Throw the error up the chain and stop processing. - */ - void error(Throwable e) { - observer.onError(e); - /* tell all observers to unsubscribe since we had an error */ - stop(); + return csub; } /** - * Receive the next value from an observer. - *

- * If we have received values from all observers, trigger the combineLatest function, otherwise store the value and keep waiting. - * - * @param w - * @param arg + * The collector that combines the latest values from many sources. */ - void next(CombineObserver w, T arg) { - if (observer == null) { - throw new RuntimeException("This shouldn't be running if an Observer isn't registered"); + final class Collector { + final Observer observer; + final Subscription cancel; + final Lock lock; + final Object[] values; + /** Bitmap to keep track who produced a value already. */ + final BitSet hasValue; + /** Bitmap to keep track who has completed. */ + final BitSet completed; + /** Number of source observers who have produced a value. */ + int hasCount; + /** Number of completed source observers. */ + int completedCount; + + public Collector(Observer observer, Subscription cancel, int count) { + this.observer = observer; + this.cancel = cancel; + this.values = new Object[count]; + this.hasValue = new BitSet(count); + this.completed = new BitSet(count); + this.lock = new ReentrantLock(); } - /* 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; + public void next(int index, T value) { + Throwable err = null; + lock.lock(); + try { + if (!isTerminated()) { + values[index] = value; + if (!hasValue.get(index)) { + hasValue.set(index); + hasCount++; + } + if (hasCount == values.length) { + // clone: defensive copy due to varargs + try { + observer.onNext(combiner.call(values.clone())); + } catch (Throwable t) { + terminate(); + err = t; + } + } + } + } finally { + lock.unlock(); + } + if (err != null) { + // no need to lock here + observer.onError(err); + cancel.unsubscribe(); + } } - // remember this as the latest value for this observer - latestValue.put(w, arg); + public void error(int index, Throwable e) { + boolean unsub = false; + lock.lock(); + try { + if (!isTerminated()) { + terminate(); + unsub = true; + } + } finally { + lock.unlock(); + } + if (unsub) { + observer.onError(e); + cancel.unsubscribe(); + } + } - 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; + boolean isTerminated() { + return completedCount == values.length + 1; } - Object[] argsToCombineLatest = new Object[observers.size()]; - int i = 0; - for (CombineObserver _w : observers) { - argsToCombineLatest[i++] = latestValue.get(_w); + void terminate() { + completedCount = values.length + 1; + Arrays.fill(values, null); } - try { - R combinedValue = combineLatestFunction.call(argsToCombineLatest); - observer.onNext(combinedValue); - } catch (Throwable ex) { - observer.onError(ex); + public void completed(int index) { + boolean unsub = false; + lock.lock(); + try { + if (!completed.get(index)) { + completed.set(index); + completedCount++; + } + if ((!hasValue.get(index) || completedCount == values.length) + && !isTerminated()) { + terminate(); + unsub = true; + } + } finally { + lock.unlock(); + } + if (unsub) { + // no need to hold a lock at this point + observer.onCompleted(); + cancel.unsubscribe(); + } } } - @Override - public Subscription onSubscribe(Observer observer) { - if (this.observer != null) { - throw new IllegalStateException("Only one Observer can subscribe to this Observable."); + /** + * Observes a specific source and communicates with the collector. + */ + final class SourceObserver implements Observer { + final SafeObservableSubscription self; + final Collector collector; + final int index; + Observable source; + + public SourceObserver(Collector collector, + SafeObservableSubscription self, int index, + Observable source) { + this.self = self; + this.collector = collector; + this.index = index; + this.source = source; } - SafeObservableSubscription subscription = new SafeObservableSubscription(new Subscription() { - @Override - public void unsubscribe() { - stop(); - } - }); - this.observer = new SynchronizedObserver(observer, subscription); + @Override + public void onNext(T args) { + collector.next(index, args); + } - /* start the observers */ - for (CombineObserver rw : observers) { - rw.startWatching(); + @Override + public void onError(Throwable e) { + collector.error(index, e); } - return subscription; - } + @Override + public void onCompleted() { + collector.completed(index); + self.unsubscribe(); + } - private void stop() { - /* tell ourselves to stop processing onNext events */ - running.set(false); - /* propogate to all observers to unsubscribe */ - for (CombineObserver rw : observers) { - if (rw.subscription != null) { - rw.subscription.unsubscribe(); - } + /** Connect to the source. */ + void connect() { + self.wrap(source.subscribe(this)); + source = null; } } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationConcat.java b/rxjava-core/src/main/java/rx/operators/OperationConcat.java index e2c9be1ebf..6ab68f27a2 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationConcat.java +++ b/rxjava-core/src/main/java/rx/operators/OperationConcat.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,8 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; +import rx.functions.Action0; +import rx.subscriptions.Subscriptions; /** * Returns an Observable that emits the items emitted by two or more Observables, one after the @@ -55,7 +57,7 @@ public static OnSubscribeFunc concat(final Observable t1) { return new Concat(sequences).onSubscribe(t1); - } + } }; } @@ -153,9 +155,9 @@ public void onCompleted() { } })); - return new Subscription() { + return Subscriptions.create(new Action0() { @Override - public void unsubscribe() { + public void call() { Subscription q; synchronized (nextSequences) { q = innerSubscription; @@ -165,7 +167,7 @@ public void unsubscribe() { } outerSubscription.unsubscribe(); } - }; + }); } } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationDebounce.java b/rxjava-core/src/main/java/rx/operators/OperationDebounce.java index 3dbdd81f20..5a7da4a1bb 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationDebounce.java +++ b/rxjava-core/src/main/java/rx/operators/OperationDebounce.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,10 +22,14 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Scheduler; +import rx.Scheduler.Inner; import rx.Subscription; -import rx.concurrency.Schedulers; -import rx.util.functions.Action0; -import rx.util.functions.Func1; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.observers.SynchronizedObserver; +import rx.schedulers.Schedulers; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.SerialSubscription; /** * This operation is used to filter out bursts of events. This is done by ignoring the events from an observable which are too @@ -48,7 +52,7 @@ public final class OperationDebounce { * @return A {@link Func1} which performs the throttle operation. */ public static OnSubscribeFunc debounce(Observable items, long timeout, TimeUnit unit) { - return debounce(items, timeout, unit, Schedulers.threadPoolForComputation()); + return debounce(items, timeout, unit, Schedulers.computation()); } /** @@ -137,18 +141,178 @@ public void onError(Throwable e) { @Override public void onNext(final T v) { - Subscription previousSubscription = lastScheduledNotification.getAndSet(scheduler.schedule(new Action0() { + Subscription previousSubscription = lastScheduledNotification.getAndSet(scheduler.schedule(new Action1() { @Override - public void call() { + public void call(Inner inner) { observer.onNext(v); } }, timeout, unit)); + // cancel previous if not already executed if (previousSubscription != null) { previousSubscription.unsubscribe(); } } } + + /** + * Delay the emission via another observable if no new source appears in the meantime. + */ + public static OnSubscribeFunc debounceSelector( + Observable source, + Func1> debounceSelector) { + return new DebounceSelector(source, debounceSelector); + } + + /** + * Delay the emission via another observable if no new source appears in the meantime. + */ + private static final class DebounceSelector implements OnSubscribeFunc { + final Observable source; + final Func1> debounceSelector; + + public DebounceSelector(Observable source, Func1> debounceSelector) { + this.source = source; + this.debounceSelector = debounceSelector; + } + + @Override + public Subscription onSubscribe(Observer t1) { + CompositeSubscription csub = new CompositeSubscription(); + + csub.add(source.subscribe(new SourceObserver(t1, debounceSelector, csub))); + + return csub; + } + + /** Observe the source. */ + private static final class SourceObserver implements Observer { + final Observer observer; + final Func1> debounceSelector; + final CompositeSubscription cancel; + final SerialSubscription ssub = new SerialSubscription(); + long index; + T value; + boolean hasValue; + final Object guard; + + public SourceObserver( + Observer observer, + Func1> debounceSelector, + CompositeSubscription cancel) { + this.observer = observer; + this.debounceSelector = debounceSelector; + this.cancel = cancel; + this.cancel.add(ssub); + this.guard = new Object(); + } + + @Override + public void onNext(T args) { + Observable o; + try { + o = debounceSelector.call(args); + } catch (Throwable t) { + synchronized (guard) { + observer.onError(t); + } + cancel.unsubscribe(); + return; + } + long currentIndex; + synchronized (guard) { + hasValue = true; + value = args; + currentIndex = ++index; + } + + SerialSubscription osub = new SerialSubscription(); + ssub.set(osub); + + osub.set(o.subscribe(new DebounceObserver(this, osub, args, currentIndex))); + } + + @Override + public void onError(Throwable e) { + ssub.unsubscribe(); + try { + synchronized (guard) { + observer.onError(e); + hasValue = false; + value = null; + index++; + } + } finally { + cancel.unsubscribe(); + } + } + + @Override + public void onCompleted() { + ssub.unsubscribe(); + try { + synchronized (guard) { + if (hasValue) { + try { + observer.onNext(value); + } catch (Throwable t) { + observer.onError(t); + return; + } + } + observer.onCompleted(); + hasValue = false; + value = null; + index++; + } + } finally { + cancel.unsubscribe(); + } + } + } + + /** + * The debounce observer. + */ + private static final class DebounceObserver implements Observer { + final SourceObserver parent; + final Subscription cancel; + final T value; + final long currentIndex; + + public DebounceObserver(SourceObserver parent, Subscription cancel, T value, long currentIndex) { + this.parent = parent; + this.cancel = cancel; + this.value = value; + this.currentIndex = currentIndex; + } + + @Override + public void onNext(U args) { + onCompleted(); + } + + @Override + public void onError(Throwable e) { + synchronized (parent.guard) { + parent.observer.onError(e); + } + parent.cancel.unsubscribe(); + } + + @Override + public void onCompleted() { + synchronized (parent.guard) { + if (parent.hasValue && parent.index == currentIndex) { + parent.observer.onNext(value); + } + parent.hasValue = false; + } + cancel.unsubscribe(); + } + + } + } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationDefaultIfEmpty.java b/rxjava-core/src/main/java/rx/operators/OperationDefaultIfEmpty.java index 7bc74ac156..924a508500 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationDefaultIfEmpty.java +++ b/rxjava-core/src/main/java/rx/operators/OperationDefaultIfEmpty.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 - * + * + * 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/operators/OperationDefer.java b/rxjava-core/src/main/java/rx/operators/OperationDefer.java index 0d326b364e..95835b6b79 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationDefer.java +++ b/rxjava-core/src/main/java/rx/operators/OperationDefer.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.util.functions.Func0; +import rx.functions.Func0; /** * Do not create the Observable until an Observer subscribes; create a fresh Observable on each 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..56605429d8 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationDelay.java @@ -0,0 +1,306 @@ +/** + * Copyright 2014 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.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Scheduler; +import rx.Scheduler.Inner; +import rx.Subscription; +import rx.functions.Action1; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.observables.ConnectableObservable; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.SerialSubscription; +import rx.subscriptions.Subscriptions; + +public final class OperationDelay { + + public static Observable delay(Observable observable, final long delay, final TimeUnit unit, final Scheduler scheduler) { + // observable.map(x => Observable.timer(t).map(_ => x).startItAlreadyNow()).concat() + Observable> seqs = observable.map(new Func1>() { + public Observable call(final T x) { + ConnectableObservable co = Observable.timer(delay, unit, scheduler).map(new Func1() { + @Override + public T call(Long ignored) { + return x; + } + }).replay(); + co.connect(); + return co; + } + }); + return Observable.concat(seqs); + } + + /** + * Delays the subscription to the source by the given amount, running on the given scheduler. + */ + public static OnSubscribeFunc delaySubscription(Observable source, long time, TimeUnit unit, Scheduler scheduler) { + return new DelaySubscribeFunc(source, time, unit, scheduler); + } + + /** Subscribe function which schedules the actual subscription to source on a scheduler at a later time. */ + private static final class DelaySubscribeFunc implements OnSubscribeFunc { + final Observable source; + final Scheduler scheduler; + final long time; + final TimeUnit unit; + + public DelaySubscribeFunc(Observable source, long time, TimeUnit unit, Scheduler scheduler) { + this.source = source; + this.scheduler = scheduler; + this.time = time; + this.unit = unit; + } + + @Override + public Subscription onSubscribe(final Observer t1) { + final SerialSubscription ssub = new SerialSubscription(); + + ssub.set(scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + if (!ssub.isUnsubscribed()) { + ssub.set(source.subscribe(t1)); + } + } + }, time, unit)); + + return ssub; + } + } + + /** + * Delay the emission of the source items by a per-item observable that fires its first element. + */ + public static OnSubscribeFunc delay(Observable source, + Func1> itemDelay) { + return new DelayViaObservable(source, null, itemDelay); + } + + /** + * Delay the subscription and emission of the source items by a per-item observable that fires its first element. + */ + public static OnSubscribeFunc delay(Observable source, + Func0> subscriptionDelay, + Func1> itemDelay) { + return new DelayViaObservable(source, subscriptionDelay, itemDelay); + } + + /** + * Delay the emission of the source items by a per-item observable that fires its first element. + */ + private static final class DelayViaObservable implements OnSubscribeFunc { + final Observable source; + final Func0> subscriptionDelay; + final Func1> itemDelay; + + public DelayViaObservable(Observable source, + Func0> subscriptionDelay, + Func1> itemDelay) { + this.source = source; + this.subscriptionDelay = subscriptionDelay; + this.itemDelay = itemDelay; + } + + @Override + public Subscription onSubscribe(Observer t1) { + CompositeSubscription csub = new CompositeSubscription(); + + SerialSubscription sosub = new SerialSubscription(); + csub.add(sosub); + SourceObserver so = new SourceObserver(t1, itemDelay, csub, sosub); + if (subscriptionDelay == null) { + sosub.set(source.subscribe(so)); + } else { + Observable subscriptionSource; + try { + subscriptionSource = subscriptionDelay.call(); + } catch (Throwable t) { + t1.onError(t); + return Subscriptions.empty(); + } + SerialSubscription ssub = new SerialSubscription(); + csub.add(ssub); + ssub.set(subscriptionSource.subscribe(new SubscribeDelay(source, so, csub, ssub))); + } + + return csub; + } + + /** Subscribe delay observer. */ + private static final class SubscribeDelay implements Observer { + final Observable source; + final SourceObserver so; + final CompositeSubscription csub; + final Subscription self; + /** Prevent any onError once the first item was delivered. */ + boolean subscribed; + + public SubscribeDelay( + Observable source, + SourceObserver so, + CompositeSubscription csub, Subscription self) { + this.source = source; + this.so = so; + this.csub = csub; + this.self = self; + } + + @Override + public void onNext(U args) { + onCompleted(); + } + + @Override + public void onError(Throwable e) { + if (!subscribed) { + so.observer.onError(e); + csub.unsubscribe(); + } + } + + @Override + public void onCompleted() { + subscribed = true; + csub.remove(self); + so.self.set(source.subscribe(so)); + } + } + + /** The source observer. */ + private static final class SourceObserver implements Observer { + final Observer observer; + final Func1> itemDelay; + final CompositeSubscription csub; + final SerialSubscription self; + /** Guard to avoid overlapping events from the various sources. */ + final Object guard; + boolean done; + int wip; + + public SourceObserver(Observer observer, + Func1> itemDelay, + CompositeSubscription csub, + SerialSubscription self) { + this.observer = observer; + this.itemDelay = itemDelay; + this.csub = csub; + this.guard = new Object(); + this.self = self; + } + + @Override + public void onNext(T args) { + Observable delayer; + try { + delayer = itemDelay.call(args); + } catch (Throwable t) { + onError(t); + return; + } + + synchronized (guard) { + wip++; + } + + SerialSubscription ssub = new SerialSubscription(); + csub.add(ssub); + ssub.set(delayer.subscribe(new DelayObserver(args, this, ssub))); + } + + @Override + public void onError(Throwable e) { + synchronized (guard) { + observer.onError(e); + } + csub.unsubscribe(); + } + + @Override + public void onCompleted() { + boolean b; + synchronized (guard) { + done = true; + b = checkDone(); + } + if (b) { + csub.unsubscribe(); + } else { + self.unsubscribe(); + } + } + + void emit(T value, Subscription token) { + boolean b; + synchronized (guard) { + observer.onNext(value); + wip--; + b = checkDone(); + } + if (b) { + csub.unsubscribe(); + } else { + csub.remove(token); + } + } + + boolean checkDone() { + if (done && wip == 0) { + observer.onCompleted(); + return true; + } + return false; + } + } + + /** + * Delay observer. + */ + private static final class DelayObserver implements Observer { + final T value; + final SourceObserver parent; + final Subscription token; + + public DelayObserver(T value, SourceObserver parent, Subscription token) { + this.value = value; + this.parent = parent; + this.token = token; + } + + @Override + public void onNext(U args) { + parent.emit(value, token); + } + + @Override + public void onError(Throwable e) { + parent.onError(e); + } + + @Override + public void onCompleted() { + parent.emit(value, token); + } + + } + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationDematerialize.java b/rxjava-core/src/main/java/rx/operators/OperationDematerialize.java index 8e958cbc14..9bfd901f7e 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationDematerialize.java +++ b/rxjava-core/src/main/java/rx/operators/OperationDematerialize.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rxjava-core/src/main/java/rx/operators/OperationDistinct.java b/rxjava-core/src/main/java/rx/operators/OperationDistinct.java index d2db484254..6c794c019a 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationDistinct.java +++ b/rxjava-core/src/main/java/rx/operators/OperationDistinct.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,10 +25,10 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Func1; +import rx.functions.Functions; import rx.subscriptions.Subscriptions; -import rx.util.functions.Action0; -import rx.util.functions.Func1; -import rx.util.functions.Functions; /** * Returns an Observable that emits all distinct items emitted by the source. diff --git a/rxjava-core/src/main/java/rx/operators/OperationDistinctUntilChanged.java b/rxjava-core/src/main/java/rx/operators/OperationDistinctUntilChanged.java index eb1d6ed083..4760109c10 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationDistinctUntilChanged.java +++ b/rxjava-core/src/main/java/rx/operators/OperationDistinctUntilChanged.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,10 +21,10 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Func1; +import rx.functions.Functions; import rx.subscriptions.Subscriptions; -import rx.util.functions.Action0; -import rx.util.functions.Func1; -import rx.util.functions.Functions; /** * Returns an Observable that emits all sequentially distinct items emitted by the source. diff --git a/rxjava-core/src/main/java/rx/operators/OperationDoOnEach.java b/rxjava-core/src/main/java/rx/operators/OperationDoOnEach.java deleted file mode 100644 index 1b0aafb578..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationDoOnEach.java +++ /dev/null @@ -1,66 +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 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 index 3001043c80..729258e734 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationElementAt.java +++ b/rxjava-core/src/main/java/rx/operators/OperationElementAt.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rxjava-core/src/main/java/rx/operators/OperationFilter.java b/rxjava-core/src/main/java/rx/operators/OperationFilter.java deleted file mode 100644 index 560600dc9c..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationFilter.java +++ /dev/null @@ -1,71 +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 rx.Observable; -import rx.Observable.OnSubscribeFunc; -import rx.Observer; -import rx.Subscription; -import rx.util.functions.Func1; - -/** - * Filters an Observable by discarding any items it emits that do not meet some test. - *

- * - */ -public final class OperationFilter { - - public static OnSubscribeFunc filter(Observable that, Func1 predicate) { - return new Filter(that, predicate); - } - - private static class Filter implements OnSubscribeFunc { - - private final Observable that; - private final Func1 predicate; - - public Filter(Observable that, Func1 predicate) { - this.that = that; - this.predicate = predicate; - } - - public Subscription onSubscribe(final Observer observer) { - final SafeObservableSubscription subscription = new SafeObservableSubscription(); - return subscription.wrap(that.subscribe(new Observer() { - public void onNext(T value) { - try { - if (predicate.call(value)) { - 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(); - } - } - - public void onError(Throwable ex) { - observer.onError(ex); - } - - public void onCompleted() { - observer.onCompleted(); - } - })); - } - - } -} diff --git a/rxjava-core/src/main/java/rx/operators/OperationFinally.java b/rxjava-core/src/main/java/rx/operators/OperationFinally.java index f8237cbb20..a54a2586f8 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationFinally.java +++ b/rxjava-core/src/main/java/rx/operators/OperationFinally.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.util.functions.Action0; +import rx.functions.Action0; /** * Registers an action to be called when an Observable invokes onComplete or onError. diff --git a/rxjava-core/src/main/java/rx/operators/OperationFirstOrDefault.java b/rxjava-core/src/main/java/rx/operators/OperationFirstOrDefault.java deleted file mode 100644 index 1a37d68d73..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationFirstOrDefault.java +++ /dev/null @@ -1,119 +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 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.subscriptions.Subscriptions; -import rx.util.functions.Action0; -import rx.util.functions.Func1; - -/** - * Returns an Observable that emits the first item emitted by the source - * Observable, or a default value if the source emits nothing. - */ -public final class OperationFirstOrDefault { - - /** - * Returns an Observable that emits the first item emitted by the source - * Observable that satisfies the given condition, - * or a default value if the source emits no items that satisfy the given condition. - * - * @param source - * The source Observable to emit the first item for. - * @param predicate - * The condition the emitted source items have to satisfy. - * @param defaultValue - * The default value to use whenever the source Observable doesn't emit anything. - * @return A subscription function for creating the target Observable. - */ - 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. - * - * @param source - * The source Observable to emit the first item for. - * @param defaultValue - * The default value to use whenever the source Observable doesn't emit anything. - * @return A subscription function for creating the target Observable. - */ - public static OnSubscribeFunc firstOrDefault(Observable source, T defaultValue) { - return new FirstOrElse(source, alwaysTrue(), defaultValue); - } - - private static class FirstOrElse implements OnSubscribeFunc { - private final Observable source; - private final Func1 predicate; - private final T defaultValue; - - private FirstOrElse(Observable source, Func1 predicate, T defaultValue) { - this.source = source; - this.defaultValue = defaultValue; - this.predicate = predicate; - } - - @Override - 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()) { - observer.onNext(defaultValue); - observer.onCompleted(); - } - } - - @Override - public void onError(Throwable e) { - observer.onError(e); - } - - @Override - public void onNext(T next) { - try { - if (!hasEmitted.get() && predicate.call(next)) { - hasEmitted.set(true); - observer.onNext(next); - observer.onCompleted(); - } - } catch (Throwable t) { - // may happen within the predicate call (user code) - observer.onError(t); - } - } - }); - - return Subscriptions.create(new Action0() { - @Override - public void call() { - sourceSub.unsubscribe(); - } - }); - } - } -} diff --git a/rxjava-core/src/main/java/rx/operators/OperationFlatMap.java b/rxjava-core/src/main/java/rx/operators/OperationFlatMap.java new file mode 100644 index 0000000000..6dd4a0a4ec --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationFlatMap.java @@ -0,0 +1,393 @@ +/** + * Copyright 2014 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; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.SerialSubscription; + +/** + * Additional flatMap operators. + */ +public final class OperationFlatMap { + /** Utility class. */ + private OperationFlatMap() { + throw new IllegalStateException("No instances!"); + } + + /** + * Observable that pairs up the source values and all the derived collection + * values and projects them via the selector. + */ + public static OnSubscribeFunc flatMap(Observable source, + Func1> collectionSelector, + Func2 resultSelector + ) { + return new FlatMapPairSelector(source, collectionSelector, resultSelector); + } + + /** + * Converts the result Iterable of a function into an Observable. + */ + public static Func1> flatMapIterableFunc( + Func1> collectionSelector) { + return new IterableToObservableFunc(collectionSelector); + } + + /** + * Converts the result Iterable of a function into an Observable. + * + * @param + * the parameter type + * @param + * the result type + */ + private static final class IterableToObservableFunc implements Func1> { + final Func1> func; + + public IterableToObservableFunc(Func1> func) { + this.func = func; + } + + @Override + public Observable call(T t1) { + return Observable.from(func.call(t1)); + } + } + + /** + * Pairs up the source value with each of the associated observable values + * and uses a selector function to calculate the result sequence. + * + * @param + * the source value type + * @param + * the collection value type + * @param + * the result type + */ + private static final class FlatMapPairSelector implements OnSubscribeFunc { + final Observable source; + final Func1> collectionSelector; + final Func2 resultSelector; + + public FlatMapPairSelector(Observable source, Func1> collectionSelector, Func2 resultSelector) { + this.source = source; + this.collectionSelector = collectionSelector; + this.resultSelector = resultSelector; + } + + @Override + public Subscription onSubscribe(Observer t1) { + CompositeSubscription csub = new CompositeSubscription(); + + csub.add(source.subscribe(new SourceObserver(t1, collectionSelector, resultSelector, csub))); + + return csub; + } + + /** Observes the source, starts the collections and projects the result. */ + private static final class SourceObserver implements Observer { + final Observer observer; + final Func1> collectionSelector; + final Func2 resultSelector; + final CompositeSubscription csub; + final AtomicInteger wip; + /** Don't let various events run at the same time. */ + final Object guard; + boolean done; + + public SourceObserver(Observer observer, Func1> collectionSelector, Func2 resultSelector, CompositeSubscription csub) { + this.observer = observer; + this.collectionSelector = collectionSelector; + this.resultSelector = resultSelector; + this.csub = csub; + this.wip = new AtomicInteger(1); + this.guard = new Object(); + } + + @Override + public void onNext(T args) { + Observable coll; + try { + coll = collectionSelector.call(args); + } catch (Throwable e) { + onError(e); + return; + } + + SerialSubscription ssub = new SerialSubscription(); + csub.add(ssub); + wip.incrementAndGet(); + + ssub.set(coll.subscribe(new CollectionObserver(this, args, ssub))); + } + + @Override + public void onError(Throwable e) { + synchronized (guard) { + if (done) { + return; + } + done = true; + observer.onError(e); + } + csub.unsubscribe(); + } + + @Override + public void onCompleted() { + if (wip.decrementAndGet() == 0) { + synchronized (guard) { + if (done) { + return; + } + done = true; + observer.onCompleted(); + } + csub.unsubscribe(); + } + } + + void complete(Subscription s) { + csub.remove(s); + onCompleted(); + } + + void emit(T t, U u) { + R r; + try { + r = resultSelector.call(t, u); + } catch (Throwable e) { + onError(e); + return; + } + synchronized (guard) { + if (done) { + return; + } + observer.onNext(r); + } + } + } + + /** Observe a collection and call emit with the pair of the key and the value. */ + private static final class CollectionObserver implements Observer { + final SourceObserver so; + final Subscription cancel; + final T value; + + public CollectionObserver(SourceObserver so, T value, Subscription cancel) { + this.so = so; + this.value = value; + this.cancel = cancel; + } + + @Override + public void onNext(U args) { + so.emit(value, args); + } + + @Override + public void onError(Throwable e) { + so.onError(e); + } + + @Override + public void onCompleted() { + so.complete(cancel); + } + }; + } + + /** + * Projects the notification of an observable sequence to an observable + * sequence and merges the results into one. + */ + public static OnSubscribeFunc flatMap(Observable source, + Func1> onNext, + Func1> onError, + Func0> onCompleted) { + return new FlatMapTransform(source, onNext, onError, onCompleted); + } + + /** + * Projects the notification of an observable sequence to an observable + * sequence and merges the results into one. + * + * @param + * the source value type + * @param + * the result value type + */ + private static final class FlatMapTransform implements OnSubscribeFunc { + final Observable source; + final Func1> onNext; + final Func1> onError; + final Func0> onCompleted; + + public FlatMapTransform(Observable source, Func1> onNext, Func1> onError, Func0> onCompleted) { + this.source = source; + this.onNext = onNext; + this.onError = onError; + this.onCompleted = onCompleted; + } + + @Override + public Subscription onSubscribe(Observer t1) { + CompositeSubscription csub = new CompositeSubscription(); + + csub.add(source.subscribe(new SourceObserver(t1, onNext, onError, onCompleted, csub))); + + return csub; + } + + /** + * Observe the source and merge the values. + * + * @param + * the source value type + * @param + * the result value type + */ + private static final class SourceObserver implements Observer { + final Observer observer; + final Func1> onNext; + final Func1> onError; + final Func0> onCompleted; + final CompositeSubscription csub; + final AtomicInteger wip; + volatile boolean done; + final Object guard; + + public SourceObserver(Observer observer, Func1> onNext, Func1> onError, Func0> onCompleted, CompositeSubscription csub) { + this.observer = observer; + this.onNext = onNext; + this.onError = onError; + this.onCompleted = onCompleted; + this.csub = csub; + this.guard = new Object(); + this.wip = new AtomicInteger(1); + } + + @Override + public void onNext(T args) { + Observable o; + try { + o = onNext.call(args); + } catch (Throwable t) { + synchronized (guard) { + observer.onError(t); + } + csub.unsubscribe(); + return; + } + subscribeInner(o); + } + + @Override + public void onError(Throwable e) { + Observable o; + try { + o = onError.call(e); + } catch (Throwable t) { + synchronized (guard) { + observer.onError(t); + } + csub.unsubscribe(); + return; + } + subscribeInner(o); + done = true; + finish(); + } + + @Override + public void onCompleted() { + Observable o; + try { + o = onCompleted.call(); + } catch (Throwable t) { + synchronized (guard) { + observer.onError(t); + } + csub.unsubscribe(); + return; + } + subscribeInner(o); + done = true; + finish(); + } + + void subscribeInner(Observable o) { + SerialSubscription ssub = new SerialSubscription(); + wip.incrementAndGet(); + csub.add(ssub); + + ssub.set(o.subscribe(new CollectionObserver(this, ssub))); + } + + void finish() { + if (wip.decrementAndGet() == 0) { + synchronized (guard) { + observer.onCompleted(); + } + csub.unsubscribe(); + } + } + } + + /** Observes the collections. */ + private static final class CollectionObserver implements Observer { + final SourceObserver parent; + final Subscription cancel; + + public CollectionObserver(SourceObserver parent, Subscription cancel) { + this.parent = parent; + this.cancel = cancel; + } + + @Override + public void onNext(R args) { + synchronized (parent.guard) { + parent.observer.onNext(args); + } + } + + @Override + public void onError(Throwable e) { + synchronized (parent.guard) { + parent.observer.onError(e); + } + parent.csub.unsubscribe(); + } + + @Override + public void onCompleted() { + parent.csub.remove(cancel); + parent.finish(); + } + } + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationGroupBy.java b/rxjava-core/src/main/java/rx/operators/OperationGroupBy.java deleted file mode 100644 index a30b97523f..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationGroupBy.java +++ /dev/null @@ -1,241 +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.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; - -import rx.Observable; -import rx.Observable.OnSubscribeFunc; -import rx.Observer; -import rx.Subscription; -import rx.observables.GroupedObservable; -import rx.util.functions.Func1; -import rx.util.functions.Functions; - -/** - * Groups the items emitted by an Observable according to a specified criterion, and emits these - * grouped items as Observables, one Observable per group. - *

- * - */ -public final class OperationGroupBy { - - public static OnSubscribeFunc> groupBy(Observable source, final Func1 keySelector, final Func1 elementSelector) { - - final Observable> keyval = source.map(new Func1>() { - @Override - public KeyValue call(T t) { - K key = keySelector.call(t); - R value = elementSelector.call(t); - - return new KeyValue(key, value); - } - }); - - return new GroupBy(keyval); - } - - public static OnSubscribeFunc> groupBy(Observable source, final Func1 keySelector) { - return groupBy(source, keySelector, Functions. identity()); - } - - private static class GroupBy implements OnSubscribeFunc> { - - private final Observable> source; - private final ConcurrentHashMap> groupedObservables = new ConcurrentHashMap>(); - private final SafeObservableSubscription actualParentSubscription = new SafeObservableSubscription(); - private final AtomicInteger numGroupSubscriptions = new AtomicInteger(); - private final AtomicBoolean unsubscribeRequested = new AtomicBoolean(false); - - private GroupBy(Observable> source) { - this.source = source; - } - - @Override - public Subscription onSubscribe(final Observer> observer) { - final GroupBy _this = this; - actualParentSubscription.wrap(source.subscribe(new Observer>() { - - @Override - public void onCompleted() { - // we need to propagate to all children I imagine ... we can't just leave all of those Observable/Observers hanging - for (GroupedSubject o : groupedObservables.values()) { - o.onCompleted(); - } - // now the parent - observer.onCompleted(); - } - - @Override - public void onError(Throwable e) { - // we need to propagate to all children I imagine ... we can't just leave all of those Observable/Observers hanging - for (GroupedSubject o : groupedObservables.values()) { - o.onError(e); - } - // now the parent - observer.onError(e); - } - - @Override - public void onNext(KeyValue value) { - GroupedSubject gs = groupedObservables.get(value.key); - if (gs == null) { - if (unsubscribeRequested.get()) { - // unsubscribe has been requested so don't create new groups - // only send data to groups already created - return; - } - /* - * Technically the source should be single-threaded so we shouldn't need to do this but I am - * programming defensively as most operators are so this can work with a concurrent sequence - * if it ends up receiving one. - */ - GroupedSubject newGs = GroupedSubject. create(value.key, _this); - GroupedSubject existing = groupedObservables.putIfAbsent(value.key, newGs); - if (existing == null) { - // we won so use the one we created - gs = newGs; - // since we won the creation we emit this new GroupedObservable - observer.onNext(gs); - } else { - // another thread beat us so use the existing one - gs = existing; - } - } - gs.onNext(value.value); - } - })); - - return new Subscription() { - - @Override - public void unsubscribe() { - if (numGroupSubscriptions.get() == 0) { - // if we have no group subscriptions we will unsubscribe - actualParentSubscription.unsubscribe(); - // otherwise we mark to not send any more groups (waiting on existing groups to finish) - unsubscribeRequested.set(true); - } - } - }; - } - - /** - * Children notify of being subscribed to. - * - * @param key - */ - private void subscribeKey(K key) { - numGroupSubscriptions.incrementAndGet(); - } - - /** - * Children notify of being unsubscribed from. - * - * @param key - */ - private void unsubscribeKey(K key) { - int c = numGroupSubscriptions.decrementAndGet(); - if (c == 0) { - actualParentSubscription.unsubscribe(); - } - } - } - - private static class GroupedSubject extends GroupedObservable implements Observer { - - static GroupedSubject create(final K key, final GroupBy parent) { - final AtomicReference> subscribedObserver = new AtomicReference>(OperationGroupBy. emptyObserver()); - return new GroupedSubject(key, new OnSubscribeFunc() { - - private final SafeObservableSubscription subscription = new SafeObservableSubscription(); - - @Override - public Subscription onSubscribe(Observer observer) { - // register Observer - subscribedObserver.set(observer); - - parent.subscribeKey(key); - - return subscription.wrap(new Subscription() { - @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()); - // now we need to notify the parent that we're unsubscribed - parent.unsubscribeKey(key); - } - }); - } - }, subscribedObserver); - } - - private final AtomicReference> subscribedObserver; - - public GroupedSubject(K key, OnSubscribeFunc onSubscribe, AtomicReference> subscribedObserver) { - super(key, onSubscribe); - this.subscribedObserver = subscribedObserver; - } - - @Override - public void onCompleted() { - subscribedObserver.get().onCompleted(); - } - - @Override - public void onError(Throwable e) { - subscribedObserver.get().onError(e); - } - - @Override - public void onNext(T v) { - subscribedObserver.get().onNext(v); - } - - } - - 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 - } - }; - } - - private static class KeyValue { - private final K key; - private final V value; - - private KeyValue(K key, V value) { - this.key = key; - this.value = value; - } - } -} diff --git a/rxjava-core/src/main/java/rx/operators/OperationGroupByUntil.java b/rxjava-core/src/main/java/rx/operators/OperationGroupByUntil.java index 9869862b92..ab37d3a014 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationGroupByUntil.java +++ b/rxjava-core/src/main/java/rx/operators/OperationGroupByUntil.java @@ -1,39 +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. - */ +/** + * Copyright 2014 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.HashMap; import java.util.List; import java.util.Map; + import rx.Observable; +import rx.Observable.OnSubscribe; import rx.Observable.OnSubscribeFunc; import rx.Observer; +import rx.Subscriber; import rx.Subscription; +import rx.functions.Func1; import rx.observables.GroupedObservable; import rx.subjects.PublishSubject; import rx.subjects.Subject; import rx.subscriptions.CompositeSubscription; import rx.subscriptions.SerialSubscription; -import rx.subscriptions.Subscriptions; -import rx.util.functions.Func1; /** * Groups the elements of an observable sequence according to a specified key selector, value selector and duration selector function. - * + * * @see MSDN: Observable.GroupByUntil * @see MSDN: Observable.GroupByUntil */ @@ -41,24 +43,26 @@ public class OperationGroupByUntil implements final Observable source; final Func1 keySelector; final Func1 valueSelector; - final Func1, ? extends Observable> durationSelector; + final Func1, ? extends Observable> durationSelector; + public OperationGroupByUntil(Observable source, Func1 keySelector, Func1 valueSelector, - Func1, ? extends Observable> durationSelector) { + Func1, ? extends Observable> durationSelector) { this.source = source; this.keySelector = keySelector; this.valueSelector = valueSelector; this.durationSelector = durationSelector; } - + @Override public Subscription onSubscribe(Observer> t1) { SerialSubscription cancel = new SerialSubscription(); ResultSink sink = new ResultSink(t1, cancel); - cancel.setSubscription(sink.run()); + cancel.set(sink.run()); return cancel; } + /** The source value sink and group manager. */ class ResultSink implements Observer { /** Guarded by gate. */ @@ -68,20 +72,22 @@ class ResultSink implements Observer { protected final Object gate = new Object(); /** Guarded by gate. */ protected final Map> map = new HashMap>(); + public ResultSink(Observer> observer, Subscription cancel) { this.observer = observer; this.cancel = cancel; } + /** Prepare the subscription tree. */ public Subscription run() { SerialSubscription toSource = new SerialSubscription(); group.add(toSource); - - toSource.setSubscription(source.subscribe(this)); - + + toSource.set(source.subscribe(this)); + return group; } - + @Override public void onNext(TSource args) { TKey key; @@ -93,10 +99,10 @@ public void onNext(TSource args) { onError(t); return; } - + GroupSubject g; boolean newGroup = false; - synchronized (key) { + synchronized (gate) { g = map.get(key); if (g == null) { g = create(key); @@ -104,33 +110,33 @@ public void onNext(TSource args) { newGroup = true; } } - + if (newGroup) { - Observable duration; + Observable duration; try { - duration = durationSelector.call(g); + duration = durationSelector.call(g.toObservable()); } catch (Throwable t) { onError(t); return; } - + synchronized (gate) { - observer.onNext(g); + observer.onNext(g.toObservable()); } - + SerialSubscription durationHandle = new SerialSubscription(); group.add(durationHandle); - + DurationObserver durationObserver = new DurationObserver(key, durationHandle); - durationHandle.setSubscription(duration.subscribe(durationObserver)); - + durationHandle.set(duration.subscribe(durationObserver)); + } - + synchronized (gate) { g.onNext(value); } } - + @Override public void onError(Throwable e) { synchronized (gate) { @@ -143,7 +149,7 @@ public void onError(Throwable e) { } cancel.unsubscribe(); } - + @Override public void onCompleted() { synchronized (gate) { @@ -156,11 +162,13 @@ public void onCompleted() { } cancel.unsubscribe(); } + /** Create a new group. */ public GroupSubject create(TKey key) { PublishSubject publish = PublishSubject.create(); return new GroupSubject(key, publish); } + /** Terminate a group. */ public void expire(TKey key, Subscription handle) { synchronized (gate) { @@ -171,66 +179,68 @@ public void expire(TKey key, Subscription handle) { } handle.unsubscribe(); } + /** Observe the completion of a group. */ class DurationObserver implements Observer { final TKey key; final Subscription handle; + public DurationObserver(TKey key, Subscription handle) { this.key = key; this.handle = handle; } + @Override public void onNext(TDuration args) { expire(key, handle); } - + @Override public void onError(Throwable e) { ResultSink.this.onError(e); } - + @Override public void onCompleted() { expire(key, handle); } - + } } - protected static OnSubscribeFunc neverSubscribe() { - return new OnSubscribeFunc() { - @Override - public Subscription onSubscribe(Observer t1) { - return Subscriptions.empty(); - } - }; - } + /** A grouped observable with subject-like behavior. */ - public static class GroupSubject extends GroupedObservable implements Observer { + public static class GroupSubject implements Observer { protected final Subject publish; - public GroupSubject(K key, Subject publish) { - super(key, OperationGroupByUntil.neverSubscribe()); + private final K key; + + public GroupSubject(K key, final Subject publish) { + this.key = key; this.publish = publish; } - - @Override - public Subscription subscribe(Observer observer) { - return publish.subscribe(observer); + + public GroupedObservable toObservable() { + return new GroupedObservable(key, new OnSubscribe() { + @Override + public void call(Subscriber o) { + publish.subscribe(o); + } + }); } - + @Override public void onNext(V args) { publish.onNext(args); } - + @Override public void onError(Throwable e) { publish.onError(e); } - + @Override public void onCompleted() { publish.onCompleted(); } - + } -} \ No newline at end of file +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationGroupJoin.java b/rxjava-core/src/main/java/rx/operators/OperationGroupJoin.java index 9c14efd632..33f5db3848 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationGroupJoin.java +++ b/rxjava-core/src/main/java/rx/operators/OperationGroupJoin.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,17 +19,18 @@ 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.functions.Func1; +import rx.functions.Func2; import rx.subjects.PublishSubject; import rx.subjects.Subject; import rx.subscriptions.CompositeSubscription; import rx.subscriptions.RefCountSubscription; import rx.subscriptions.SerialSubscription; -import rx.util.functions.Func1; -import rx.util.functions.Func2; /** * Corrrelates two sequences when they overlap and groups the results. @@ -42,25 +43,27 @@ public class OperationGroupJoin implements OnSubscribeFunc protected final Func1> leftDuration; protected final Func1> rightDuration; protected final Func2, ? extends R> resultSelector; + public OperationGroupJoin( - Observable left, - Observable right, - Func1> leftDuration, - Func1> rightDuration, - Func2, ? extends R> resultSelector - ) { + Observable left, + Observable right, + Func1> leftDuration, + Func1> rightDuration, + Func2, ? extends R> resultSelector) { this.left = left; this.right = right; this.leftDuration = leftDuration; this.rightDuration = rightDuration; this.resultSelector = resultSelector; } + @Override public Subscription onSubscribe(Observer t1) { ResultManager ro = new ResultManager(t1); ro.init(); return ro; } + /** Manages sub-observers and subscriptions. */ class ResultManager implements Subscription { final RefCountSubscription cancel; @@ -73,27 +76,35 @@ class ResultManager implements Subscription { final Map rightMap = new HashMap(); boolean leftDone; boolean rightDone; + public ResultManager(Observer observer) { this.observer = observer; this.group = new CompositeSubscription(); this.cancel = new RefCountSubscription(group); } + public void init() { SerialSubscription s1 = new SerialSubscription(); SerialSubscription s2 = new SerialSubscription(); - + group.add(s1); group.add(s2); - + s1.setSubscription(left.subscribe(new LeftObserver(s1))); s2.setSubscription(right.subscribe(new RightObserver(s2))); - + } @Override public void unsubscribe() { cancel.unsubscribe(); } + + @Override + public boolean isUnsubscribed() { + return cancel.isUnsubscribed(); + } + void groupsOnCompleted() { List> list = new ArrayList>(leftMap.values()); leftMap.clear(); @@ -102,12 +113,15 @@ void groupsOnCompleted() { o.onCompleted(); } } + /** Observe the left source. */ class LeftObserver implements Observer { final Subscription tosource; + public LeftObserver(Subscription tosource) { this.tosource = tosource; } + @Override public void onNext(T1 args) { try { @@ -117,9 +131,9 @@ public void onNext(T1 args) { id = leftIds++; leftMap.put(id, subj); } - + Observable window = Observable.create(new WindowObservableFunc(subj, cancel)); - + Observable duration = leftDuration.call(args); SerialSubscription sduration = new SerialSubscription(); @@ -127,7 +141,7 @@ public void onNext(T1 args) { sduration.setSubscription(duration.subscribe(new LeftDurationObserver(id, sduration, subj))); R result = resultSelector.call(args, window); - + synchronized (guard) { observer.onNext(result); for (T2 t2 : rightMap.values()) { @@ -163,14 +177,16 @@ public void onError(Throwable e) { } } - } + /** Observe the right source. */ class RightObserver implements Observer { final Subscription tosource; + public RightObserver(Subscription tosource) { this.tosource = tosource; } + @Override public void onNext(T2 args) { try { @@ -194,10 +210,10 @@ public void onNext(T2 args) { onError(t); } } - + @Override public void onCompleted() { -// tosource.unsubscribe(); + // tosource.unsubscribe(); synchronized (guard) { rightDone = true; if (leftDone) { @@ -214,17 +230,19 @@ public void onError(Throwable e) { for (Observer o : leftMap.values()) { o.onError(e); } - + observer.onError(e); cancel.unsubscribe(); } - } + } } + /** Observe left duration and apply termination. */ class LeftDurationObserver implements Observer { final int id; final Subscription sduration; final Observer gr; + public LeftDurationObserver(int id, Subscription sduration, Observer gr) { this.id = id; this.sduration = sduration; @@ -254,10 +272,12 @@ public void onNext(D1 args) { onCompleted(); } } + /** Observe right duration and apply termination. */ class RightDurationObserver implements Observer { final int id; final Subscription sduration; + public RightDurationObserver(int id, Subscription sduration) { this.id = id; this.sduration = sduration; @@ -284,15 +304,18 @@ public void onNext(D2 args) { onCompleted(); } } + } - /** - * The reference-counted window observable. + + /** + * The reference-counted window observable. * Subscribes to the underlying Observable by using a reference-counted * subscription. */ static class WindowObservableFunc implements OnSubscribeFunc { final RefCountSubscription refCount; final Observable underlying; + public WindowObservableFunc(Observable underlying, RefCountSubscription refCount) { this.refCount = refCount; this.underlying = underlying; @@ -306,23 +329,28 @@ public Subscription onSubscribe(Observer t1) { cs.add(underlying.subscribe(wo)); return cs; } + /** Observe activities on the window. */ class WindowObserver implements Observer { final Observer observer; final Subscription self; + public WindowObserver(Observer observer, Subscription self) { this.observer = observer; this.self = self; } + @Override public void onNext(T args) { observer.onNext(args); } + @Override public void onError(Throwable e) { observer.onError(e); self.unsubscribe(); } + @Override public void onCompleted() { observer.onCompleted(); diff --git a/rxjava-core/src/main/java/rx/operators/OperationInterval.java b/rxjava-core/src/main/java/rx/operators/OperationInterval.java index 72d35d37de..a0cd28537c 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationInterval.java +++ b/rxjava-core/src/main/java/rx/operators/OperationInterval.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,10 +20,12 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Scheduler; +import rx.Scheduler.Inner; import rx.Subscription; -import rx.concurrency.Schedulers; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.schedulers.Schedulers; import rx.subscriptions.Subscriptions; -import rx.util.functions.Action0; /** * Returns an observable sequence that produces a value after each period. @@ -35,7 +37,7 @@ public final class OperationInterval { * Creates an event each time interval. */ public static OnSubscribeFunc interval(long interval, TimeUnit unit) { - return interval(interval, unit, Schedulers.threadPoolForComputation()); + return interval(interval, unit, Schedulers.computation()); } /** @@ -66,9 +68,9 @@ private Interval(long period, TimeUnit unit, Scheduler scheduler) { @Override public Subscription onSubscribe(final Observer observer) { - final Subscription wrapped = scheduler.schedulePeriodically(new Action0() { + final Subscription wrapped = scheduler.schedulePeriodically(new Action1() { @Override - public void call() { + public void call(Inner inner) { observer.onNext(currentValue); currentValue++; } diff --git a/rxjava-core/src/main/java/rx/operators/OperationJoin.java b/rxjava-core/src/main/java/rx/operators/OperationJoin.java index b75b8498b0..629ba9c708 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationJoin.java +++ b/rxjava-core/src/main/java/rx/operators/OperationJoin.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,14 +17,15 @@ import java.util.HashMap; import java.util.Map; + import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; +import rx.functions.Func1; +import rx.functions.Func2; 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. @@ -35,8 +36,9 @@ public class OperationJoin impl final Func1> leftDurationSelector; final Func1> rightDurationSelector; final Func2 resultSelector; + public OperationJoin( - Observable left, + Observable left, Observable right, Func1> leftDurationSelector, Func1> rightDurationSelector, @@ -55,6 +57,7 @@ public Subscription onSubscribe(Observer t1) { cancel.setSubscription(result.run()); return cancel; } + /** Manage the left and right sources. */ class ResultSink { final Object gate = new Object(); @@ -67,28 +70,33 @@ class ResultSink { 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) { @@ -98,16 +106,19 @@ protected void expire(int id, Subscription resource) { } group.remove(resource); } + @Override public void onNext(TLeft args) { - int id; + int id, highRightId; + synchronized (gate) { id = leftId++; leftMap.put(id, args); + highRightId = rightId; } SerialSubscription md = new SerialSubscription(); group.add(md); - + Observable duration; try { duration = leftDurationSelector.call(args); @@ -116,23 +127,27 @@ public void onNext(TLeft args) { 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; + for (Map.Entry entry : rightMap.entrySet()) { + if (entry.getKey() < highRightId) { + TRight r = entry.getValue(); + R result; + try { + result = resultSelector.call(args, r); + } catch (Throwable t) { + observer.onError(t); + cancel.unsubscribe(); + return; + } + observer.onNext(result); } - observer.onNext(result); } } } + @Override public void onError(Throwable e) { synchronized (gate) { @@ -140,6 +155,7 @@ public void onError(Throwable e) { cancel.unsubscribe(); } } + @Override public void onCompleted() { synchronized (gate) { @@ -152,10 +168,12 @@ public void onCompleted() { } } } + /** 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; @@ -175,15 +193,18 @@ public void onError(Throwable e) { 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) { @@ -193,16 +214,18 @@ void expire(int id, Subscription resource) { } group.remove(resource); } + @Override public void onNext(TRight args) { - int id = 0; + int id = 0, highLeftId; synchronized (gate) { id = rightId++; rightMap.put(id, args); + highLeftId = leftId; } SerialSubscription md = new SerialSubscription(); group.add(md); - + Observable duration; try { duration = rightDurationSelector.call(args); @@ -211,23 +234,27 @@ public void onNext(TRight args) { 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; + for (Map.Entry entry : leftMap.entrySet()) { + if (entry.getKey() < highLeftId) { + TLeft lv = entry.getValue(); + R result; + try { + result = resultSelector.call(lv, args); + } catch (Throwable t) { + observer.onError(t); + cancel.unsubscribe(); + return; + } + observer.onNext(result); } - observer.onNext(result); } } } + @Override public void onError(Throwable e) { synchronized (gate) { @@ -235,6 +262,7 @@ public void onError(Throwable e) { cancel.unsubscribe(); } } + @Override public void onCompleted() { synchronized (gate) { @@ -247,10 +275,12 @@ public void onCompleted() { } } } + /** 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; @@ -270,7 +300,7 @@ public void onError(Throwable e) { 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 index ffc304e12a..1b55cf819c 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationJoinPatterns.java +++ b/rxjava-core/src/main/java/rx/operators/OperationJoinPatterns.java @@ -1,18 +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. - */ +/** + * Copyright 2014 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; @@ -20,20 +20,19 @@ 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.functions.Action1; +import rx.functions.Func1; 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. @@ -51,6 +50,7 @@ public static Pattern2 and(/* this */Observable left, Obser } return new Pattern2(left, right); } + /** * Matches when the observable sequence has an available element and projects the element by invoking the selector function. */ @@ -63,6 +63,7 @@ public static Plan0 then(/* this */Observable source, Func1(source).then(selector); } + /** * Joins together the results from several patterns. */ @@ -72,6 +73,7 @@ public static OnSubscribeFunc when(Plan0... plans) { } return when(Arrays.asList(plans)); } + /** * Joins together the results from several patterns. */ @@ -85,12 +87,13 @@ 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()) { @@ -98,12 +101,13 @@ public void onError(Throwable e) { } t1.onError(e); } + @Override public void onCompleted() { t1.onCompleted(); } }; - + try { for (Plan0 plan : plans) { activePlans.add(plan.activate(externalSubscriptions, out, new Action1() { @@ -117,7 +121,7 @@ public void call(ActivePlan0 activePlan) { })); } } catch (Throwable t) { - return Observable.error(t).subscribe(t1); + return Observable. error(t).subscribe(t1); } CompositeSubscription group = new CompositeSubscription(); for (JoinObserver jo : externalSubscriptions.values()) { diff --git a/rxjava-core/src/main/java/rx/operators/OperationLast.java b/rxjava-core/src/main/java/rx/operators/OperationLast.java deleted file mode 100644 index 964afd5176..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationLast.java +++ /dev/null @@ -1,82 +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.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/OperationLatest.java b/rxjava-core/src/main/java/rx/operators/OperationLatest.java new file mode 100644 index 0000000000..4fda664f93 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationLatest.java @@ -0,0 +1,119 @@ +/** + * Copyright 2014 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.Iterator; +import java.util.NoSuchElementException; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicReference; + +import rx.Notification; +import rx.Observable; +import rx.Observer; +import rx.exceptions.Exceptions; + +/** + * Wait for and iterate over the latest values of the source observable. + * If the source works faster than the iterator, values may be skipped, but + * not the onError or onCompleted events. + */ +public final class OperationLatest { + /** Utility class. */ + private OperationLatest() { + throw new IllegalStateException("No instances!"); + } + + public static Iterable latest(final Observable source) { + return new Iterable() { + @Override + public Iterator iterator() { + LatestObserverIterator lio = new LatestObserverIterator(); + source.materialize().subscribe(lio); + return lio; + } + }; + } + + /** Observer of source, iterator for output. */ + static final class LatestObserverIterator implements Observer>, Iterator { + final Semaphore notify = new Semaphore(0); + // observer's notification + final AtomicReference> reference = new AtomicReference>(); + + @Override + public void onNext(Notification args) { + boolean wasntAvailable = reference.getAndSet(args) == null; + if (wasntAvailable) { + notify.release(); + } + } + + @Override + public void onError(Throwable e) { + // not expected + } + + @Override + public void onCompleted() { + // not expected + } + + // iterator's notification + Notification iNotif; + + @Override + public boolean hasNext() { + if (iNotif != null && iNotif.isOnError()) { + throw Exceptions.propagate(iNotif.getThrowable()); + } + if (iNotif == null || !iNotif.isOnCompleted()) { + if (iNotif == null) { + try { + notify.acquire(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + iNotif = new Notification(ex); + throw Exceptions.propagate(ex); + } + + iNotif = reference.getAndSet(null); + if (iNotif.isOnError()) { + throw Exceptions.propagate(iNotif.getThrowable()); + } + } + } + return !iNotif.isOnCompleted(); + } + + @Override + public T next() { + if (hasNext()) { + if (iNotif.isOnNext()) { + T v = iNotif.getValue(); + iNotif = null; + return v; + } + } + throw new NoSuchElementException(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Read-only iterator."); + } + + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationMap.java b/rxjava-core/src/main/java/rx/operators/OperationMap.java deleted file mode 100644 index d78b5dc6ef..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationMap.java +++ /dev/null @@ -1,140 +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 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 - * transformation as a new Observable. - *

- * - */ -public final class OperationMap { - - /** - * 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. - * @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 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); - } - }; - } - - /** - * Accepts a sequence of observable sequences and a transformation function. Returns a flattened sequence that is the result of - * applying the transformation function to each item in the sequence of each observable sequence. - *

- * The closure should return an Observable which will then be merged. - * - * @param sequence - * the input sequence. - * @param func - * a function to apply to each item in the sequence. - * @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 mapMany(Observable sequence, Func1> func) { - return OperationMerge.merge(Observable.create(map(sequence, func))); - } - - /** - * An observable sequence that is the result of applying a transformation to each item in an input sequence. - * - * @param - * the type of the input sequence. - * @param - * the type of the output sequence. - */ - private static class MapObservable implements OnSubscribeFunc { - public MapObservable(Observable sequence, Func2 func) { - this.sequence = sequence; - this.func = func; - } - - 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 void onNext(T value) { - observer.onNext(func.call(value, index)); - index++; - } - - @Override - public void onError(Throwable ex) { - observer.onError(ex); - } - - @Override - public void onCompleted() { - observer.onCompleted(); - } - }))); - } - } -} diff --git a/rxjava-core/src/main/java/rx/operators/OperationMaterialize.java b/rxjava-core/src/main/java/rx/operators/OperationMaterialize.java index f1998ffe64..66356e1db0 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationMaterialize.java +++ b/rxjava-core/src/main/java/rx/operators/OperationMaterialize.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rxjava-core/src/main/java/rx/operators/OperationMerge.java b/rxjava-core/src/main/java/rx/operators/OperationMerge.java deleted file mode 100644 index 12619e9176..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationMerge.java +++ /dev/null @@ -1,273 +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.Arrays; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; - -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. - *

- * - *

- * You can combine the items emitted by multiple Observables so that they act like a single - * Observable, by using the merge operation. - */ -public final class OperationMerge { - - /** - * Flattens the observable sequences from the list of Observables into one observable sequence without any transformation. - * - * @param o - * An observable sequence of elements to project. - * @return An observable sequence whose elements are the result of flattening the output from the list of Observables. - * @see Observable.Merge(TSource) Method (IObservable(TSource)[]) - */ - public static OnSubscribeFunc merge(final Observable> o) { - // wrap in a Func so that if a chain is built up, then asynchronously subscribed to twice we will have 2 instances of Take rather than 1 handing both, which is not thread-safe. - return new OnSubscribeFunc() { - - @Override - public Subscription onSubscribe(Observer observer) { - return new MergeObservable(o).onSubscribe(observer); - } - }; - } - - public static OnSubscribeFunc merge(final Observable... sequences) { - return merge(Arrays.asList(sequences)); - } - - public static OnSubscribeFunc merge(final Iterable> sequences) { - return merge(Observable.create(new OnSubscribeFunc>() { - - private volatile boolean unsubscribed = false; - - @Override - public Subscription onSubscribe(Observer> observer) { - for (Observable o : sequences) { - if (!unsubscribed) { - observer.onNext(o); - } else { - // break out of the loop if we are unsubscribed - break; - } - } - if (!unsubscribed) { - observer.onCompleted(); - } - - return new Subscription() { - - @Override - public void unsubscribe() { - unsubscribed = true; - } - - }; - } - })); - } - - /** - * This class is NOT thread-safe if invoked and referenced multiple times. In other words, don't subscribe to it multiple times from different threads. - *

- * It IS thread-safe from within it while receiving onNext events from multiple threads. - *

- * This should all be fine as long as it's kept as a private class and a new instance created from static factory method above. - *

- * Note how the take() factory method above protects us from a single instance being exposed with the Observable wrapper handling the subscribe flow. - * - * @param - */ - private static final class MergeObservable implements OnSubscribeFunc { - private final Observable> sequences; - private final MergeSubscription ourSubscription = new MergeSubscription(); - private AtomicBoolean stopped = new AtomicBoolean(false); - private volatile boolean parentCompleted = false; - private final ConcurrentHashMap childObservers = new ConcurrentHashMap(); - private final ConcurrentHashMap childSubscriptions = new ConcurrentHashMap(); - - private MergeObservable(Observable> sequences) { - this.sequences = 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. - *

- * The calls from each sequence must be serialized. - *

- * 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 - */ - completeSubscription.add(sequences.subscribe(new ParentObserver(synchronizedObserver))); - - /* return our subscription to allow unsubscribing */ - return completeSubscription; - } - - /** - * Manage the internal subscription with a thread-safe means of stopping/unsubscribing so we don't unsubscribe twice. - *

- * Also has the stop() method returning a boolean so callers know if their thread "won" and should perform further actions. - */ - private class MergeSubscription implements Subscription { - - @Override - public void unsubscribe() { - stop(); - } - - public boolean stop() { - // try setting to false unless another thread beat us - boolean didSet = stopped.compareAndSet(false, true); - if (didSet) { - // this thread won the race to stop, so unsubscribe from the actualSubscription - for (Subscription _s : childSubscriptions.values()) { - _s.unsubscribe(); - } - return true; - } else { - // another thread beat us - return false; - } - } - } - - /** - * Subscribe to the top level Observable to receive the sequence of Observable children. - * - * @param - */ - private class ParentObserver implements Observer> { - private final Observer actualObserver; - - public ParentObserver(Observer actualObserver) { - this.actualObserver = actualObserver; - } - - @Override - public void onCompleted() { - parentCompleted = true; - // this *can* occur before the children are done, so if it does we won't send onCompleted - // but will let the child worry about it - // if however this completes and there are no children processing, then we will send onCompleted - - if (childObservers.size() == 0) { - if (!stopped.get()) { - if (ourSubscription.stop()) { - actualObserver.onCompleted(); - } - } - } - } - - @Override - public void onError(Throwable e) { - actualObserver.onError(e); - } - - @Override - public void onNext(Observable childObservable) { - if (stopped.get()) { - // we won't act on any further items - return; - } - - if (childObservable == null) { - throw new IllegalArgumentException("Observable can not be null."); - } - - /** - * For each child Observable we receive we'll subscribe with a separate Observer - * that will each then forward their sequences to the actualObserver. - *

- * We use separate child Observers for each sequence to simplify the onComplete/onError handling so each sequence has its own lifecycle. - */ - ChildObserver _w = new ChildObserver(actualObserver); - childObservers.put(_w, _w); - Subscription _subscription = childObservable.subscribe(_w); - // remember this Observer and the subscription from it - childSubscriptions.put(_w, _subscription); - } - } - - /** - * Subscribe to each child Observable and forward their sequence of data to the actualObserver - * - */ - private class ChildObserver implements Observer { - - private final Observer actualObserver; - - public ChildObserver(Observer actualObserver) { - this.actualObserver = actualObserver; - } - - @Override - public void onCompleted() { - // remove self from map of Observers - childObservers.remove(this); - // if there are now 0 Observers left, so if the parent is also completed we send the onComplete to the actualObserver - // if the parent is not complete that means there is another sequence (and child Observer) to come - if (!stopped.get()) { - if (childObservers.size() == 0 && parentCompleted) { - if (ourSubscription.stop()) { - // this thread 'won' the race to unsubscribe/stop so let's send onCompleted - actualObserver.onCompleted(); - } - } - } - } - - @Override - public void onError(Throwable e) { - if (!stopped.get()) { - if (ourSubscription.stop()) { - // this thread 'won' the race to unsubscribe/stop so let's send the error - actualObserver.onError(e); - } - } - } - - @Override - public void onNext(T args) { - // in case the Observable is poorly behaved and doesn't listen to the unsubscribe request - // we'll ignore anything that comes in after we've unsubscribed - if (!stopped.get()) { - actualObserver.onNext(args); - } - } - - } - } -} diff --git a/rxjava-core/src/main/java/rx/operators/OperationMergeDelayError.java b/rxjava-core/src/main/java/rx/operators/OperationMergeDelayError.java index dbca17522d..b2498df6c2 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationMergeDelayError.java +++ b/rxjava-core/src/main/java/rx/operators/OperationMergeDelayError.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,10 +24,13 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.util.CompositeException; +import rx.exceptions.CompositeException; +import rx.observers.SynchronizedObserver; +import rx.subscriptions.BooleanSubscription; +import rx.subscriptions.CompositeSubscription; /** - * This behaves like {@link OperationMerge} except that if any of the merged Observables notify of + * This behaves like {@link OperatorMerge} except that if any of the merged Observables notify of * an error via onError, mergeDelayError will refrain from propagating that error * notification until all of the merged Observables have finished emitting items. *

@@ -67,29 +70,22 @@ public Subscription onSubscribe(Observer observer) { public static OnSubscribeFunc mergeDelayError(final Observable... sequences) { return mergeDelayError(Observable.create(new OnSubscribeFunc>() { - private volatile boolean unsubscribed = false; + private final BooleanSubscription s = new BooleanSubscription(); @Override public Subscription onSubscribe(Observer> observer) { for (Observable o : sequences) { - if (!unsubscribed) { + if (!s.isUnsubscribed()) { observer.onNext(o); } else { // break out of the loop if we are unsubscribed break; } } - if (!unsubscribed) { + if (!s.isUnsubscribed()) { observer.onCompleted(); } - return new Subscription() { - - @Override - public void unsubscribe() { - unsubscribed = true; - } - - }; + return s; } })); } @@ -97,30 +93,23 @@ public void unsubscribe() { public static OnSubscribeFunc mergeDelayError(final List> sequences) { return mergeDelayError(Observable.create(new OnSubscribeFunc>() { - private volatile boolean unsubscribed = false; + private final BooleanSubscription s = new BooleanSubscription(); @Override public Subscription onSubscribe(Observer> observer) { for (Observable o : sequences) { - if (!unsubscribed) { + if (!s.isUnsubscribed()) { observer.onNext(o); } else { // break out of the loop if we are unsubscribed break; } } - if (!unsubscribed) { + if (!s.isUnsubscribed()) { observer.onCompleted(); } - return new Subscription() { - - @Override - public void unsubscribe() { - unsubscribed = true; - } - - }; + return s; } })); } @@ -151,13 +140,24 @@ private MergeDelayErrorObservable(Observable> } 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. + *

+ * The calls from each sequence must be serialized. + *

+ * Bug report: https://github.com/Netflix/RxJava/issues/614 + */ + SynchronizedObserver synchronizedObserver = new SynchronizedObserver(actualObserver); + /** * Subscribe to the parent Observable to get to the children Observables */ - sequences.subscribe(new ParentObserver(actualObserver)); + completeSubscription.add(sequences.subscribe(new ParentObserver(synchronizedObserver))); /* return our subscription to allow unsubscribing */ - return ourSubscription; + return completeSubscription; } /** @@ -186,6 +186,11 @@ public boolean stop() { return false; } } + + @Override + public boolean isUnsubscribed() { + return stopped.get(); + } } /** diff --git a/rxjava-core/src/main/java/rx/operators/OperationMergeMaxConcurrent.java b/rxjava-core/src/main/java/rx/operators/OperationMergeMaxConcurrent.java new file mode 100644 index 0000000000..ec27002d4a --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationMergeMaxConcurrent.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 java.util.LinkedList; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.observers.SynchronizedObserver; +import rx.subscriptions.CompositeSubscription; + +/** + * Flattens a list of Observables into one Observable sequence, without any transformation. + *

+ * + *

+ * You can combine the items emitted by multiple Observables so that they act like a single + * Observable, by using the merge operation. + */ +public final class OperationMergeMaxConcurrent { + + public static OnSubscribeFunc merge(final Observable> o, final int maxConcurrent) { + if (maxConcurrent <= 0) { + throw new IllegalArgumentException("maxConcurrent must be positive"); + } + return new OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(Observer observer) { + return new MergeObservable(o, maxConcurrent).onSubscribe(observer); + } + }; + } + + /** + * This class is NOT thread-safe if invoked and referenced multiple times. In other words, don't subscribe to it multiple times from different threads. + *

+ * It IS thread-safe from within it while receiving onNext events from multiple threads. + *

+ * This should all be fine as long as it's kept as a private class and a new instance created from static factory method above. + *

+ * Note how the take() factory method above protects us from a single instance being exposed with the Observable wrapper handling the subscribe flow. + * + * @param + */ + private static final class MergeObservable implements OnSubscribeFunc { + private final Observable> sequences; + private final CompositeSubscription ourSubscription = new CompositeSubscription(); + private volatile boolean parentCompleted = false; + private final LinkedList> pendingObservables = new LinkedList>(); + private volatile int activeObservableCount = 0; + private final int maxConcurrent; + /** + * Protect both pendingObservables and activeObservableCount from concurrent accesses. + */ + private final Object gate = new Object(); + + private MergeObservable(Observable> sequences, int maxConcurrent) { + this.sequences = sequences; + this.maxConcurrent = maxConcurrent; + } + + public Subscription onSubscribe(Observer actualObserver) { + + /** + * We must synchronize a merge because we subscribe to multiple sequences in parallel that will each be emitting. + *

+ * The calls from each sequence must be serialized. + *

+ * Bug report: https://github.com/Netflix/RxJava/issues/200 + */ + SafeObservableSubscription subscription = new SafeObservableSubscription(ourSubscription); + SynchronizedObserver synchronizedObserver = new SynchronizedObserver( + new SafeObserver(subscription, actualObserver), // Create a SafeObserver as SynchronizedObserver does not automatically unsubscribe + subscription); + + /** + * Subscribe to the parent Observable to get to the children Observables + */ + ourSubscription.add(sequences.subscribe(new ParentObserver(synchronizedObserver))); + + return subscription; + } + + /** + * Subscribe to the top level Observable to receive the sequence of Observable children. + * + * @param + */ + private class ParentObserver implements Observer> { + private final SynchronizedObserver synchronizedObserver; + + public ParentObserver(SynchronizedObserver synchronizedObserver) { + this.synchronizedObserver = synchronizedObserver; + } + + @Override + public void onCompleted() { + parentCompleted = true; + if (ourSubscription.isUnsubscribed()) { + return; + } + // this *can* occur before the children are done, so if it does we won't send onCompleted + // but will let the child worry about it + // if however this completes and there are no children processing, then we will send onCompleted + if (isStopped()) { + synchronizedObserver.onCompleted(); + } + } + + @Override + public void onError(Throwable e) { + synchronizedObserver.onError(e); + } + + @Override + public void onNext(Observable childObservable) { + if (ourSubscription.isUnsubscribed()) { + // we won't act on any further items + return; + } + + if (childObservable == null) { + throw new IllegalArgumentException("Observable can not be null."); + } + + Observable observable = null; + synchronized (gate) { + if (activeObservableCount >= maxConcurrent) { + pendingObservables.add(childObservable); + } + else { + observable = childObservable; + activeObservableCount++; + } + } + if (observable != null) { + ourSubscription.add(observable.subscribe(new ChildObserver( + synchronizedObserver))); + } + } + } + + /** + * Subscribe to each child Observable and forward their sequence of data to the actualObserver + * + */ + private class ChildObserver implements Observer { + + private final SynchronizedObserver synchronizedObserver; + + public ChildObserver(SynchronizedObserver synchronizedObserver) { + this.synchronizedObserver = synchronizedObserver; + } + + @Override + public void onCompleted() { + if (ourSubscription.isUnsubscribed()) { + return; + } + + Observable childObservable = null; + // Try to fetch a pending observable + synchronized (gate) { + childObservable = pendingObservables.poll(); + if (childObservable == null) { + // There is no pending observable, decrease activeObservableCount. + activeObservableCount--; + } + else { + // Fetch an observable successfully. + // We will subscribe(this) at once. So don't change activeObservableCount. + } + } + if (childObservable != null) { + ourSubscription.add(childObservable.subscribe(this)); + } else { + // No pending observable. Need to check if it's necessary to emit an onCompleted + if (isStopped()) { + synchronizedObserver.onCompleted(); + } + } + } + + @Override + public void onError(Throwable e) { + synchronizedObserver.onError(e); + } + + @Override + public void onNext(T args) { + synchronizedObserver.onNext(args); + } + + } + + private boolean isStopped() { + synchronized (gate) { + return parentCompleted && activeObservableCount == 0 + && pendingObservables.size() == 0; + } + } + } +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/operators/OperationMinMax.java b/rxjava-core/src/main/java/rx/operators/OperationMinMax.java index 0d5a1c9378..b6490e9ef9 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationMinMax.java +++ b/rxjava-core/src/main/java/rx/operators/OperationMinMax.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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,8 +20,8 @@ import java.util.List; import rx.Observable; -import rx.util.functions.Func1; -import rx.util.functions.Func2; +import rx.functions.Func1; +import rx.functions.Func2; /** * Returns the minimum element in an observable sequence. diff --git a/rxjava-core/src/main/java/rx/operators/OperationMostRecent.java b/rxjava-core/src/main/java/rx/operators/OperationMostRecent.java index 15406aeece..4ae9ae5971 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationMostRecent.java +++ b/rxjava-core/src/main/java/rx/operators/OperationMostRecent.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import rx.Observable; import rx.Observer; -import rx.util.Exceptions; +import rx.exceptions.Exceptions; /** * Returns an Iterable that always returns the item most recently emitted by an Observable, or a @@ -31,16 +31,16 @@ */ public final class OperationMostRecent { - public static Iterable mostRecent(final Observable source, T initialValue) { - - MostRecentObserver mostRecentObserver = new MostRecentObserver(initialValue); - final MostRecentIterator nextIterator = new MostRecentIterator(mostRecentObserver); - - source.subscribe(mostRecentObserver); + public static Iterable mostRecent(final Observable source, final T initialValue) { return new Iterable() { @Override public Iterator iterator() { + MostRecentObserver mostRecentObserver = new MostRecentObserver(initialValue); + final MostRecentIterator nextIterator = new MostRecentIterator(mostRecentObserver); + + source.subscribe(mostRecentObserver); + return nextIterator; } }; diff --git a/rxjava-core/src/main/java/rx/operators/OperationMulticast.java b/rxjava-core/src/main/java/rx/operators/OperationMulticast.java index b634b2dbac..5f0803eb1e 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationMulticast.java +++ b/rxjava-core/src/main/java/rx/operators/OperationMulticast.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,10 +16,17 @@ package rx.operators; import rx.Observable; +import rx.Observable.OnSubscribeFunc; import rx.Observer; +import rx.Subscriber; import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Func0; +import rx.functions.Func1; import rx.observables.ConnectableObservable; import rx.subjects.Subject; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.Subscriptions; public class OperationMulticast { public static ConnectableObservable multicast(Observable source, final Subject subject) { @@ -35,10 +42,10 @@ private static class MulticastConnectableObservable extends ConnectableObs private Subscription subscription; public MulticastConnectableObservable(Observable source, final Subject subject) { - super(new OnSubscribeFunc() { + super(new OnSubscribe() { @Override - public Subscription onSubscribe(Observer observer) { - return subject.subscribe(observer); + public void call(Subscriber observer) { + subject.subscribe(observer); } }); this.source = source; @@ -67,9 +74,9 @@ public void onNext(T args) { } } - return new Subscription() { + return Subscriptions.create(new Action0() { @Override - public void unsubscribe() { + public void call() { synchronized (lock) { if (subscription != null) { subscription.unsubscribe(); @@ -77,8 +84,65 @@ public void unsubscribe() { } } } - }; + }); + } + + } + + /** + * Returns an observable sequence that contains the elements of a sequence + * produced by multicasting the source sequence within a selector function. + * + * @param source + * @param subjectFactory + * @param selector + * @return + * + * @see MSDN: Observable.Multicast + */ + public static Observable multicast( + final Observable source, + final Func0> subjectFactory, + final Func1, ? extends Observable> selector) { + return Observable.create(new MulticastSubscribeFunc(source, subjectFactory, selector)); + } + + /** The multicast subscription function. */ + private static final class MulticastSubscribeFunc implements OnSubscribeFunc { + final Observable source; + final Func0> subjectFactory; + final Func1, ? extends Observable> resultSelector; + + public MulticastSubscribeFunc(Observable source, + Func0> subjectFactory, + Func1, ? extends Observable> resultSelector) { + this.source = source; + this.subjectFactory = subjectFactory; + this.resultSelector = resultSelector; } + @Override + public Subscription onSubscribe(Observer t1) { + Observable observable; + ConnectableObservable connectable; + try { + Subject subject = subjectFactory.call(); + + connectable = new MulticastConnectableObservable(source, subject); + + observable = resultSelector.call(connectable); + } catch (Throwable t) { + t1.onError(t); + return Subscriptions.empty(); + } + + CompositeSubscription csub = new CompositeSubscription(); + + csub.add(observable.subscribe(new SafeObserver( + new SafeObservableSubscription(csub), t1))); + csub.add(connectable.connect()); + + return csub; + } } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationNext.java b/rxjava-core/src/main/java/rx/operators/OperationNext.java index 4f5e9dfd31..a37fba3cd1 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationNext.java +++ b/rxjava-core/src/main/java/rx/operators/OperationNext.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import rx.Notification; import rx.Observable; import rx.Observer; -import rx.util.Exceptions; +import rx.exceptions.Exceptions; /** * Returns an Iterable that blocks until the Observable emits another item, then returns that item. @@ -34,22 +34,22 @@ public final class OperationNext { public static Iterable next(final Observable items) { - - NextObserver nextObserver = new NextObserver(); - final NextIterator nextIterator = new NextIterator(nextObserver); - - items.materialize().subscribe(nextObserver); - return new Iterable() { @Override public Iterator iterator() { + NextObserver nextObserver = new NextObserver(); + final NextIterator nextIterator = new NextIterator(nextObserver); + + items.materialize().subscribe(nextObserver); + return nextIterator; } }; } - private static class NextIterator implements Iterator { + // test needs to access the observer.waiting flag non-blockingly. + /* private */static final class NextIterator implements Iterator { private final NextObserver observer; private T next; @@ -61,6 +61,12 @@ private NextIterator(NextObserver observer) { this.observer = observer; } + // in tests, set the waiting flag without blocking for the next value to + // allow lockstepping instead of multi-threading + void setWaiting(boolean value) { + observer.waiting.set(value); + } + @Override public boolean hasNext() { if (error != null) { diff --git a/rxjava-core/src/main/java/rx/operators/OperationObserveOn.java b/rxjava-core/src/main/java/rx/operators/OperationObserveOn.java deleted file mode 100644 index 22876a4ced..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationObserveOn.java +++ /dev/null @@ -1,128 +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.Observable; -import rx.Observable.OnSubscribeFunc; -import rx.Observer; -import rx.Scheduler; -import rx.Subscription; -import rx.concurrency.CurrentThreadScheduler; -import rx.concurrency.ImmediateScheduler; -import rx.subscriptions.CompositeSubscription; -import rx.subscriptions.SerialSubscription; -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. - *

- * - */ -public class OperationObserveOn { - - public static OnSubscribeFunc observeOn(Observable source, Scheduler scheduler) { - return new ObserveOn(source, scheduler); - } - - private static class ObserveOn implements OnSubscribeFunc { - private final Observable source; - private final Scheduler scheduler; - - public ObserveOn(Observable source, Scheduler scheduler) { - this.source = source; - this.scheduler = scheduler; - } - - @Override - 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 new Observation(observer).init(); - } - } - /** Observe through individual queue per observer. */ - private class Observation implements Action1> { - final Observer observer; - final CompositeSubscription s; - final ConcurrentLinkedQueue> queue; - final AtomicInteger counter; - private volatile Scheduler recursiveScheduler; - public Observation(Observer observer) { - this.observer = observer; - this.queue = new ConcurrentLinkedQueue>(); - this.counter = new AtomicInteger(0); - this.s = new CompositeSubscription(); - } - public Subscription init() { - s.add(source.materialize().subscribe(this)); - return s; - } - - @Override - public void call(Notification e) { - queue.offer(e); - 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(); - - return Subscriptions.empty(); - } - })); - } else { - processQueue(); - } - } - } - void processQueue() { - s.add(recursiveScheduler.schedule(new Action1() { - @Override - 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(); - } - - } - })); - } - } - } - -} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/operators/OperationOnErrorResumeNextViaFunction.java b/rxjava-core/src/main/java/rx/operators/OperationOnErrorResumeNextViaFunction.java deleted file mode 100644 index 0122a8c5e1..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationOnErrorResumeNextViaFunction.java +++ /dev/null @@ -1,117 +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.Arrays; -import java.util.concurrent.atomic.AtomicReference; - -import rx.Observable; -import rx.Observable.OnSubscribeFunc; -import rx.Observer; -import rx.Subscription; -import rx.util.CompositeException; -import rx.util.functions.Func1; - -/** - * Instruct an Observable to pass control to another Observable (the return value of a function) - * rather than invoking onError if it encounters an error. - *

- * - *

- * By default, when an Observable encounters an error that prevents it from emitting the expected - * item to its Observer, the Observable invokes its Observer's onError method, and - * then quits without invoking any more of its Observer's methods. The onErrorResumeNext operation - * changes this behavior. If you pass a function that returns an Observable (resumeFunction) to - * onErrorResumeNext, if the source Observable encounters an error, instead of invoking its - * Observer's onError method, it will instead relinquish control to this new - * Observable, which will invoke the Observer's 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. - */ -public final class OperationOnErrorResumeNextViaFunction { - - public static OnSubscribeFunc onErrorResumeNextViaFunction(Observable originalSequence, Func1> resumeFunction) { - return new OnErrorResumeNextViaFunction(originalSequence, resumeFunction); - } - - private static class OnErrorResumeNextViaFunction implements OnSubscribeFunc { - - private final Func1> resumeFunction; - private final Observable originalSequence; - - public OnErrorResumeNextViaFunction(Observable originalSequence, Func1> resumeFunction) { - this.resumeFunction = resumeFunction; - this.originalSequence = originalSequence; - } - - public Subscription onSubscribe(final Observer observer) { - // AtomicReference since we'll be accessing/modifying this across threads so we can switch it if needed - final AtomicReference subscriptionRef = new AtomicReference(new SafeObservableSubscription()); - - // subscribe to the original Observable and remember the subscription - subscriptionRef.get().wrap(new SafeObservableSubscription(originalSequence.subscribe(new Observer() { - public void onNext(T value) { - // forward the successful calls - observer.onNext(value); - } - - /** - * Instead of passing the onError forward, we intercept and "resume" with the resumeSequence. - */ - public void onError(Throwable ex) { - /* remember what the current subscription is so we can determine if someone unsubscribes concurrently */ - SafeObservableSubscription currentSubscription = subscriptionRef.get(); - // check that we have not been unsubscribed before we can process the error - if (currentSubscription != null) { - try { - Observable resumeSequence = resumeFunction.call(ex); - /* error occurred, so switch subscription to the 'resumeSequence' */ - SafeObservableSubscription innerSubscription = new SafeObservableSubscription(resumeSequence.subscribe(observer)); - /* we changed the sequence, so also change the subscription to the one of the 'resumeSequence' instead */ - if (!subscriptionRef.compareAndSet(currentSubscription, innerSubscription)) { - // we failed to set which means 'subscriptionRef' was set to NULL via the unsubscribe below - // so we want to immediately unsubscribe from the resumeSequence we just subscribed to - innerSubscription.unsubscribe(); - } - } catch (Throwable e) { - // the resume function failed so we need to call onError - // I am using CompositeException so that both exceptions can be seen - observer.onError(new CompositeException("OnErrorResume function failed", Arrays.asList(ex, e))); - } - } - } - - public void onCompleted() { - // forward the successful calls - observer.onCompleted(); - } - }))); - - return new Subscription() { - public void unsubscribe() { - // this will get either the original, or the resumeSequence one and unsubscribe on it - Subscription s = subscriptionRef.getAndSet(null); - if (s != null) { - s.unsubscribe(); - } - } - }; - } - } -} diff --git a/rxjava-core/src/main/java/rx/operators/OperationOnErrorResumeNextViaObservable.java b/rxjava-core/src/main/java/rx/operators/OperationOnErrorResumeNextViaObservable.java index 6c14d3618a..a69241262a 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationOnErrorResumeNextViaObservable.java +++ b/rxjava-core/src/main/java/rx/operators/OperationOnErrorResumeNextViaObservable.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; +import rx.functions.Action0; +import rx.subscriptions.Subscriptions; /** * Instruct an Observable to pass control to another Observable rather than invoking @@ -97,15 +99,15 @@ public void onCompleted() { } })); - return new Subscription() { - public void unsubscribe() { + return Subscriptions.create(new Action0() { + public void call() { // this will get either the original, or the resumeSequence one and unsubscribe on it Subscription s = subscriptionRef.getAndSet(null); if (s != null) { s.unsubscribe(); } } - }; + }); } } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationOnErrorReturn.java b/rxjava-core/src/main/java/rx/operators/OperationOnErrorReturn.java index d5d6a9fbc7..de06acf78e 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationOnErrorReturn.java +++ b/rxjava-core/src/main/java/rx/operators/OperationOnErrorReturn.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,10 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.util.CompositeException; -import rx.util.functions.Func1; +import rx.exceptions.CompositeException; +import rx.functions.Action0; +import rx.functions.Func1; +import rx.subscriptions.Subscriptions; /** * Instruct an Observable to emit a particular item to its Observer's onNext method @@ -107,15 +109,15 @@ public void onCompleted() { } })); - return new Subscription() { - public void unsubscribe() { + return Subscriptions.create(new Action0() { + public void call() { // this will get either the original, or the resumeSequence one and unsubscribe on it Subscription s = subscriptionRef.getAndSet(null); if (s != null) { s.unsubscribe(); } } - }; + }); } } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationOnExceptionResumeNextViaObservable.java b/rxjava-core/src/main/java/rx/operators/OperationOnExceptionResumeNextViaObservable.java index 864ba0730e..26153577b6 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationOnExceptionResumeNextViaObservable.java +++ b/rxjava-core/src/main/java/rx/operators/OperationOnExceptionResumeNextViaObservable.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; +import rx.functions.Action0; +import rx.subscriptions.Subscriptions; /** * Instruct an Observable to pass control to another Observable rather than invoking @@ -104,15 +106,15 @@ public void onCompleted() { } })); - return new Subscription() { - public void unsubscribe() { + return Subscriptions.create(new Action0() { + public void call() { // this will get either the original, or the resumeSequence one and unsubscribe on it Subscription s = subscriptionRef.getAndSet(null); if (s != null) { s.unsubscribe(); } } - }; + }); } } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationParallel.java b/rxjava-core/src/main/java/rx/operators/OperationParallel.java deleted file mode 100644 index 5a584ce370..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationParallel.java +++ /dev/null @@ -1,59 +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.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 index cb2dd0bc34..b71aaf5efa 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationParallelMerge.java +++ b/rxjava-core/src/main/java/rx/operators/OperationParallelMerge.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,9 @@ import rx.Observable; import rx.Scheduler; -import rx.concurrency.Schedulers; +import rx.functions.Func1; import rx.observables.GroupedObservable; -import rx.util.functions.Func1; +import rx.schedulers.Schedulers; public class OperationParallelMerge { diff --git a/rxjava-core/src/main/java/rx/operators/OperationRefCount.java b/rxjava-core/src/main/java/rx/operators/OperationRefCount.java index d19b700afd..ab99ca4628 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationRefCount.java +++ b/rxjava-core/src/main/java/rx/operators/OperationRefCount.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,9 +18,9 @@ import rx.Observable; import rx.Observer; import rx.Subscription; +import rx.functions.Action0; 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 diff --git a/rxjava-core/src/main/java/rx/operators/OperationReplay.java b/rxjava-core/src/main/java/rx/operators/OperationReplay.java new file mode 100644 index 0000000000..3bfcf6beb7 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationReplay.java @@ -0,0 +1,853 @@ +/** + * Copyright 2014 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.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import rx.Observable; +import rx.Observable.OnSubscribe; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Scheduler; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Func1; +import rx.functions.Functions; +import rx.schedulers.Timestamped; +import rx.subjects.Subject; +import rx.subscriptions.Subscriptions; + +/** + * Replay with limited buffer and/or time constraints. + * + * + * @see MSDN: Observable.Replay overloads + */ +public final class OperationReplay { + /** Utility class. */ + private OperationReplay() { + throw new IllegalStateException("No instances!"); + } + + /** + * Create a BoundedReplaySubject with the given buffer size. + */ + public static Subject replayBuffered(int bufferSize) { + return CustomReplaySubject.create(bufferSize); + } + + /** + * Creates a subject whose client observers will observe events + * propagated through the given wrapped subject. + */ + public static Subject createScheduledSubject(Subject subject, Scheduler scheduler) { + final Observable observedOn = subject.observeOn(scheduler); + SubjectWrapper s = new SubjectWrapper(new OnSubscribe() { + + @Override + public void call(Subscriber o) { + // TODO HACK between OnSubscribeFunc and Action1 + subscriberOf(observedOn).onSubscribe(o); + } + + }, subject); + return s; + } + + /** + * Create a CustomReplaySubject with the given time window length + * and optional buffer size. + * + * @param + * the source and return type + * @param time + * the length of the time window + * @param unit + * the unit of the time window length + * @param bufferSize + * the buffer size if >= 0, otherwise, the buffer will be unlimited + * @param scheduler + * the scheduler from where the current time is retrieved. The + * observers will not observe on this scheduler. + * @return a Subject with the required replay behavior + */ + public static Subject replayWindowed(long time, TimeUnit unit, int bufferSize, final Scheduler scheduler) { + final long ms = unit.toMillis(time); + if (ms <= 0) { + throw new IllegalArgumentException("The time window is less than 1 millisecond!"); + } + Func1> timestamp = new Func1>() { + @Override + public Timestamped call(T t1) { + return new Timestamped(scheduler.now(), t1); + } + }; + Func1, T> untimestamp = new Func1, T>() { + @Override + public T call(Timestamped t1) { + return t1.getValue(); + } + }; + + ReplayState, T> state; + + if (bufferSize >= 0) { + state = new ReplayState, T>(new VirtualBoundedList>(bufferSize), untimestamp); + } else { + state = new ReplayState, T>(new VirtualArrayList>(), untimestamp); + } + final ReplayState, T> fstate = state; + // time based eviction when a value is added + state.onValueAdded = new Action0() { + @Override + public void call() { + long now = scheduler.now(); + long before = now - ms; + for (int i = fstate.values.start(); i < fstate.values.end(); i++) { + Timestamped v = fstate.values.get(i); + if (v.getTimestampMillis() >= before) { + fstate.values.removeBefore(i); + break; + } + } + } + }; + // time based eviction when a client subscribes + state.onSubscription = state.onValueAdded; + + final CustomReplaySubject, T> brs = new CustomReplaySubject, T>( + new CustomReplaySubjectSubscribeFunc, T>(state), state, timestamp + ); + + return brs; + } + + /** + * Return an OnSubscribeFunc which delegates the subscription to the given observable. + */ + public static OnSubscribeFunc subscriberOf(final Observable target) { + return new OnSubscribeFunc() { + @Override + public Subscription onSubscribe(Observer t1) { + return target.subscribe(t1); + } + }; + } + +// /** +// * Subject that wraps another subject and uses a mapping function +// * to transform the received values. +// */ +// public static final class MappingSubject extends Subject { +// private final Subject subject; +// private final Func1 selector; +// private final OnSubscribe func; +// +// public MappingSubject(OnSubscribe func, Subject subject, Func1 selector) { +// this.func = func; +// this.subject = subject; +// this.selector = selector; +// } +// +// @Override +// public Observable toObservable() { +// return Observable.create(func); +// } +// +// @Override +// public void onNext(T args) { +// subject.onNext(selector.call(args)); +// } +// +// @Override +// public void onError(Throwable e) { +// subject.onError(e); +// } +// +// @Override +// public void onCompleted() { +// subject.onCompleted(); +// } +// +// } + + /** + * A subject that wraps another subject. + */ + public static final class SubjectWrapper extends Subject { + /** The wrapped subject. */ + final Subject subject; + + public SubjectWrapper(OnSubscribe func, Subject subject) { + super(func); + this.subject = subject; + } + + @Override + public void onNext(T args) { + subject.onNext(args); + } + + @Override + public void onError(Throwable e) { + subject.onError(e); + } + + @Override + public void onCompleted() { + subject.onCompleted(); + } + + } + + /** Base state with lock. */ + static class BaseState { + /** The lock to protect the other fields. */ + private final Lock lock = new ReentrantLock(); + + /** Lock. */ + public void lock() { + lock.lock(); + } + + /** Unlock. */ + public void unlock() { + lock.unlock(); + } + + } + + /** + * Base interface for logically indexing a list. + * + * @param + * the value type + */ + public interface VirtualList { + /** @return the number of elements in this list */ + int size(); + + /** + * Add an element to the list. + * + * @param value + * the value to add + */ + void add(T value); + + /** + * Retrieve an element at the specified logical index. + * + * @param index + * @return + */ + T get(int index); + + /** + * Remove elements up before the given logical index and move + * the start() to this index. + *

+ * For example, a list contains 3 items. Calling removeUntil 2 will + * remove the first two items. + * + * @param index + */ + void removeBefore(int index); + + /** + * Clear the elements of this list and increase the + * start by the number of elements. + */ + void clear(); + + /** + * Returns the current head index of this list. + * + * @return + */ + int start(); + + /** + * Returns the current tail index of this list (where the next value would appear). + * + * @return + */ + int end(); + + /** + * Clears and resets the indexes of the list. + */ + void reset(); + + /** + * Returns the current content as a list. + * + * @return + */ + List toList(); + } + + /** + * Behaves like a normal, unbounded ArrayList but with virtual index. + */ + public static final class VirtualArrayList implements VirtualList { + /** The backing list . */ + final List list = new ArrayList(); + /** The virtual start index of the list. */ + int startIndex; + + @Override + public int size() { + return list.size(); + } + + @Override + public void add(T value) { + list.add(value); + } + + @Override + public T get(int index) { + return list.get(index - startIndex); + } + + @Override + public void removeBefore(int index) { + int j = index - startIndex; + if (j > 0 && j <= list.size()) { + list.subList(0, j).clear(); + } + startIndex = index; + } + + @Override + public void clear() { + startIndex += list.size(); + list.clear(); + } + + @Override + public int start() { + return startIndex; + } + + @Override + public int end() { + return startIndex + list.size(); + } + + @Override + public void reset() { + list.clear(); + startIndex = 0; + } + + @Override + public List toList() { + return new ArrayList(list); + } + + } + + /** + * A bounded list which increases its size up to a maximum capacity, then + * behaves like a circular buffer with virtual indexes. + */ + public static final class VirtualBoundedList implements VirtualList { + /** A list that grows up to maxSize. */ + private final List list = new ArrayList(); + /** The maximum allowed size. */ + private final int maxSize; + /** The logical start index of the list. */ + int startIndex; + /** The head index inside the list, where the first readable value sits. */ + int head; + /** The tail index inside the list, where the next value will be added. */ + int tail; + /** The number of items in the list. */ + int count; + + /** + * Construct a VirtualBoundedList with the given maximum number of elements. + * + * @param maxSize + */ + public VirtualBoundedList(int maxSize) { + if (maxSize < 0) { + throw new IllegalArgumentException("maxSize < 0"); + } + this.maxSize = maxSize; + } + + @Override + public int start() { + return startIndex; + } + + @Override + public int end() { + return startIndex + count; + } + + @Override + public void clear() { + startIndex += count; + list.clear(); + head = 0; + tail = 0; + count = 0; + } + + @Override + public int size() { + return count; + } + + @Override + public void add(T value) { + if (list.size() == maxSize) { + list.set(tail, value); + head = (head + 1) % maxSize; + tail = (tail + 1) % maxSize; + startIndex++; + } else { + list.add(value); + tail = (tail + 1) % maxSize; + count++; + } + } + + @Override + public T get(int index) { + if (index < start() || index >= end()) { + throw new ArrayIndexOutOfBoundsException(index); + } + int idx = (head + (index - startIndex)) % maxSize; + return list.get(idx); + } + + @Override + public void removeBefore(int index) { + if (index <= start()) { + return; + } + if (index >= end()) { + clear(); + startIndex = index; + return; + } + int rc = index - startIndex; + int head2 = head + rc; + for (int i = head; i < head2; i++) { + list.set(i % maxSize, null); + count--; + } + startIndex = index; + head = head2 % maxSize; + } + + @Override + public List toList() { + List r = new ArrayList(list.size() + 1); + for (int i = head; i < head + count; i++) { + int idx = i % maxSize; + r.add(list.get(idx)); + } + return r; + } + + @Override + public void reset() { + list.clear(); + count = 0; + head = 0; + tail = 0; + } + + } + + /** + * The state class. + * + * @param + * the intermediate type stored in the values buffer + * @param + * the result type transformed via the resultSelector + */ + static final class ReplayState extends BaseState { + /** The values observed so far. */ + final VirtualList values; + /** The result selector. */ + final Func1 resultSelector; + /** The received error. */ + Throwable error; + /** General completion indicator. */ + boolean done; + /** The map of replayers. */ + final Map replayers = new LinkedHashMap(); + /** + * Callback once a value has been added but before it is replayed + * (I.e, run a time based eviction policy). + *

+ * Called while holding the state lock. + */ + protected Action0 onValueAdded = new Action0() { + @Override + public void call() { + } + }; + /** + * Callback once an error has been called but before it is replayed + * (I.e, run a time based eviction policy). + *

+ * Called while holding the state lock. + */ + protected Action0 onErrorAdded = new Action0() { + @Override + public void call() { + } + }; + /** + * Callback once completed has been called but before it is replayed + * (I.e, run a time based eviction policy). + *

+ * Called while holding the state lock. + */ + protected Action0 onCompletedAdded = new Action0() { + @Override + public void call() { + } + }; + /** + * Callback to pre-manage the values if an observer unsubscribes + * (I.e, run a time based eviction policy). + *

+ * Called while holding the state lock. + */ + protected Action0 onSubscription = new Action0() { + @Override + public void call() { + } + }; + + /** + * Construct a ReplayState with the supplied buffer and result selectors. + * + * @param values + * @param resultSelector + */ + public ReplayState(final VirtualList values, + final Func1 resultSelector) { + this.values = values; + this.resultSelector = resultSelector; + } + + /** + * Returns a live collection of the observers. + *

+ * Caller should hold the lock. + * + * @return + */ + Collection replayers() { + return new ArrayList(replayers.values()); + } + + /** + * Add a replayer to the replayers and create a Subscription for it. + *

+ * Caller should hold the lock. + * + * @param obs + * @return + */ + Subscription addReplayer(Observer obs) { + Subscription s = new Subscription() { + final AtomicBoolean once = new AtomicBoolean(); + + @Override + public void unsubscribe() { + if (once.compareAndSet(false, true)) { + remove(this); + } + } + + @Override + public boolean isUnsubscribed() { + return once.get(); + } + + }; + Replayer rp = new Replayer(obs, s); + replayers.put(s, rp); + rp.replayTill(values.start() + values.size()); + return s; + } + + /** The replayer that holds a value where the given observer is currently at. */ + final class Replayer { + protected final Observer wrapped; + /** Where this replayer was in reading the list. */ + protected int index; + /** To cancel and unsubscribe this replayer and observer. */ + protected final Subscription cancel; + + protected Replayer(Observer wrapped, Subscription cancel) { + this.wrapped = wrapped; + this.cancel = cancel; + } + + /** + * Replay up to the given index + * + * @param limit + */ + void replayTill(int limit) { + int si = values.start(); + if (index < si) { + index = si; + } + while (index < limit) { + TIntermediate value = values.get(index); + index++; + try { + wrapped.onNext(resultSelector.call(value)); + } catch (Throwable t) { + replayers.remove(cancel); + wrapped.onError(t); + return; + } + } + if (done) { + if (error != null) { + wrapped.onError(error); + } else { + wrapped.onCompleted(); + } + } + } + } + + /** + * Remove the subscription. + * + * @param s + */ + void remove(Subscription s) { + lock(); + try { + replayers.remove(s); + } finally { + unlock(); + } + } + + /** + * Add a notification value and limit the size of values. + *

+ * Caller should hold the lock. + * + * @param value + */ + void add(TIntermediate value) { + values.add(value); + } + + /** Clears the value list. */ + void clearValues() { + lock(); + try { + values.clear(); + } finally { + unlock(); + } + } + } + + /** + * A customizable replay subject with support for transformations. + * + * @param + * the Observer side's value type + * @param + * the type of the elements in the replay buffer + * @param + * the value type of the observers subscribing to this subject + */ + public static final class CustomReplaySubject extends Subject { + /** + * Return a subject that retains all events and will replay them to an {@link Observer} that subscribes. + * + * @return a subject that retains all events and will replay them to an {@link Observer} that subscribes. + */ + public static CustomReplaySubject create() { + ReplayState state = new ReplayState(new VirtualArrayList(), Functions. identity()); + return new CustomReplaySubject( + new CustomReplaySubjectSubscribeFunc(state), state, + Functions. identity()); + } + + /** + * Create a bounded replay subject with the given maximum buffer size. + * + * @param maxSize + * the maximum size in number of onNext notifications + * @return + */ + public static CustomReplaySubject create(int maxSize) { + ReplayState state = new ReplayState(new VirtualBoundedList(maxSize), Functions. identity()); + return new CustomReplaySubject( + new CustomReplaySubjectSubscribeFunc(state), state, + Functions. identity()); + } + + /** The replay state. */ + protected final ReplayState state; + /** The result selector. */ + protected final Func1 intermediateSelector; + + private CustomReplaySubject( + final OnSubscribeFunc onSubscribe, + ReplayState state, + Func1 intermediateSelector) { + super(new OnSubscribe() { + + @Override + public void call(Subscriber sub) { + onSubscribe.onSubscribe(sub); + } + + }); + this.state = state; + this.intermediateSelector = intermediateSelector; + } + + @Override + public void onCompleted() { + state.lock(); + try { + if (state.done) { + return; + } + state.done = true; + state.onCompletedAdded.call(); + replayValues(); + } finally { + state.unlock(); + } + } + + @Override + public void onError(Throwable e) { + state.lock(); + try { + if (state.done) { + return; + } + state.done = true; + state.error = e; + state.onErrorAdded.call(); + replayValues(); + } finally { + state.unlock(); + } + } + + @Override + public void onNext(TInput args) { + state.lock(); + try { + if (state.done) { + return; + } + state.add(intermediateSelector.call(args)); + state.onValueAdded.call(); + replayValues(); + } finally { + state.unlock(); + } + } + + /** + * Replay values up to the current index. + */ + protected void replayValues() { + int s = state.values.start() + state.values.size(); + for (ReplayState.Replayer rp : state.replayers()) { + rp.replayTill(s); + } + } + } + + /** + * The subscription function. + * + * @param + * the type of the elements in the replay buffer + * @param + * the value type of the observers subscribing to this subject + */ + protected static final class CustomReplaySubjectSubscribeFunc + implements Observable.OnSubscribeFunc { + + private final ReplayState state; + + protected CustomReplaySubjectSubscribeFunc(ReplayState state) { + this.state = state; + } + + @Override + public Subscription onSubscribe(Observer t1) { + VirtualList values; + Throwable error; + state.lock(); + try { + if (!state.done) { + state.onSubscription.call(); + return state.addReplayer(t1); + } + values = state.values; + error = state.error; + } finally { + state.unlock(); + } + // fully replay the subject + for (int i = values.start(); i < values.end(); i++) { + try { + t1.onNext(state.resultSelector.call(values.get(i))); + } catch (Throwable t) { + t1.onError(t); + return Subscriptions.empty(); + } + } + if (error != null) { + t1.onError(error); + } else { + t1.onCompleted(); + } + return Subscriptions.empty(); + } + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationRetry.java b/rxjava-core/src/main/java/rx/operators/OperationRetry.java index 430ef53ce4..ece2358a6c 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationRetry.java +++ b/rxjava-core/src/main/java/rx/operators/OperationRetry.java @@ -1,7 +1,22 @@ +/** + * Copyright 2014 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; /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,12 +36,10 @@ import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; -import rx.Scheduler; +import rx.Scheduler.Inner; import rx.Subscription; -import rx.concurrency.Schedulers; -import rx.subscriptions.CompositeSubscription; -import rx.subscriptions.MultipleAssignmentSubscription; -import rx.util.functions.Func2; +import rx.functions.Action1; +import rx.schedulers.Schedulers; public class OperationRetry { @@ -45,7 +58,6 @@ private static class Retry implements OnSubscribeFunc { private final Observable source; private final int retryCount; private final AtomicInteger attempts = new AtomicInteger(0); - private final CompositeSubscription subscription = new CompositeSubscription(); public Retry(Observable source, int retryCount) { this.source = source; @@ -53,20 +65,14 @@ public Retry(Observable source, int retryCount) { } @Override - public Subscription onSubscribe(Observer observer) { - MultipleAssignmentSubscription rescursiveSubscription = new MultipleAssignmentSubscription(); - subscription.add(Schedulers.currentThread().schedule(rescursiveSubscription, attemptSubscription(observer))); - subscription.add(rescursiveSubscription); - return subscription; - } - - private Func2 attemptSubscription(final Observer observer) { - return new Func2() { + public Subscription onSubscribe(final Observer observer) { + return Schedulers.trampoline().schedule(new Action1() { @Override - public Subscription call(final Scheduler scheduler, final MultipleAssignmentSubscription rescursiveSubscription) { + public void call(final Inner inner) { + final Action1 _self = this; attempts.incrementAndGet(); - return source.subscribe(new Observer() { + source.subscribe(new Observer() { @Override public void onCompleted() { @@ -75,10 +81,9 @@ public void onCompleted() { @Override public void onError(Throwable e) { - if ((retryCount == INFINITE_RETRY || attempts.get() <= retryCount) && !subscription.isUnsubscribed()) { + if ((retryCount == INFINITE_RETRY || attempts.get() <= retryCount) && !inner.isUnsubscribed()) { // retry again - // add the new subscription and schedule a retry recursively - rescursiveSubscription.setSubscription(scheduler.schedule(rescursiveSubscription, attemptSubscription(observer))); + inner.schedule(_self); } else { // give up and pass the failure observer.onError(e); @@ -89,11 +94,10 @@ public void onError(Throwable e) { public void onNext(T v) { observer.onNext(v); } - }); + }); } - - }; + }); } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationSample.java b/rxjava-core/src/main/java/rx/operators/OperationSample.java index f5f8f96e3f..56b5d09c5a 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSample.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSample.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,11 +24,10 @@ import rx.Observer; import rx.Scheduler; import rx.Subscription; -import rx.concurrency.Schedulers; +import rx.functions.Action0; +import rx.schedulers.Schedulers; import rx.subscriptions.CompositeSubscription; -import rx.subscriptions.SerialSubscription; import rx.subscriptions.Subscriptions; -import rx.util.functions.Action0; /** * Returns an Observable that emits the results of sampling the items emitted by the source @@ -42,7 +41,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.threadPoolForComputation()); + return new Sample(source, period, unit, Schedulers.computation()); } /** @@ -117,21 +116,26 @@ public void call() { }); } } - /** - * Sample with the help of another observable. + + /** + * Sample with the help of another observable. + * * @see MSDN: Observable.Sample */ public static class SampleWithObservable implements OnSubscribeFunc { final Observable source; final Observable sampler; + public SampleWithObservable(Observable source, Observable sampler) { this.source = source; this.sampler = sampler; } + @Override public Subscription onSubscribe(Observer t1) { return new ResultManager(t1).init(); } + /** Observe source values. */ class ResultManager implements Observer { final Observer observer; @@ -140,17 +144,20 @@ class ResultManager implements Observer { boolean valueTaken = true; boolean done; final Object guard; + public ResultManager(Observer observer) { this.observer = observer; cancel = new CompositeSubscription(); guard = new Object(); } + public Subscription init() { cancel.add(source.subscribe(this)); cancel.add(sampler.subscribe(new Sampler())); - + return cancel; } + @Override public void onNext(T args) { synchronized (guard) { @@ -169,7 +176,7 @@ public void onError(Throwable e) { } } } - + @Override public void onCompleted() { synchronized (guard) { @@ -180,6 +187,7 @@ public void onCompleted() { } } } + /** Take the latest value, but only once. */ class Sampler implements Observer { @Override @@ -189,7 +197,7 @@ public void onNext(U args) { valueTaken = true; observer.onNext(value); } - } + } } @Override diff --git a/rxjava-core/src/main/java/rx/operators/OperationScan.java b/rxjava-core/src/main/java/rx/operators/OperationScan.java deleted file mode 100644 index a5eedfee46..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationScan.java +++ /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.operators; - -import rx.Observable; -import rx.Observable.OnSubscribeFunc; -import rx.Observer; -import rx.Subscription; -import rx.util.functions.Func2; - -/** - * Returns an Observable that applies a function 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. - */ -public final class OperationScan { - /** - * Applies an accumulator function over an observable sequence and returns each intermediate result with the specified source and accumulator. - * - * @param sequence - * An observable sequence of elements to project. - * @param initialValue - * The initial (seed) accumulator value. - * @param accumulator - * 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)) - */ - public static OnSubscribeFunc scan(Observable sequence, R initialValue, Func2 accumulator) { - return new Accumulator(sequence, initialValue, accumulator); - } - - /** - * Applies an accumulator function over an observable sequence and returns each intermediate result with the specified source and accumulator. - * - * @param sequence - * An observable sequence of elements to project. - * @param accumulator - * 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) Method (IObservable(TSource), Func(TSource, TSource, TSource)) - */ - public static OnSubscribeFunc scan(Observable sequence, Func2 accumulator) { - return new AccuWithoutInitialValue(sequence, accumulator); - } - - 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) { - if (accumulatingObserver == null) { - observer.onNext(value); - accumulatingObserver = new AccumulatingObserver(observer, value, accumulatorFunction); - } else { - accumulatingObserver.onNext(value); - } - } - - @Override - public void onError(Throwable e) { - observer.onError(e); - } - - @Override - public void onCompleted() { - observer.onCompleted(); - } - }); - } - } - - private static class Accumulator implements OnSubscribeFunc { - private final Observable sequence; - private final R initialValue; - private final Func2 accumulatorFunction; - - private Accumulator(Observable sequence, R initialValue, Func2 accumulator) { - this.sequence = sequence; - this.initialValue = initialValue; - this.accumulatorFunction = accumulator; - } - - @Override - public Subscription onSubscribe(final Observer observer) { - observer.onNext(initialValue); - return sequence.subscribe(new AccumulatingObserver(observer, initialValue, accumulatorFunction)); - } - } - - private static class AccumulatingObserver implements Observer { - private final Observer observer; - private final Func2 accumulatorFunction; - - private R acc; - - private AccumulatingObserver(Observer observer, R initialValue, Func2 accumulator) { - this.observer = observer; - this.accumulatorFunction = accumulator; - - this.acc = initialValue; - } - - /** - * We must synchronize this because we can't allow - * multiple threads to execute the 'accumulatorFunction' at the same time because - * the accumulator code very often will be doing mutation of the 'acc' object such as a non-threadsafe HashMap - * - * Because it's synchronized it's using non-atomic variables since everything in this method is single-threaded - */ - @Override - public synchronized void onNext(T value) { - try { - acc = accumulatorFunction.call(acc, value); - observer.onNext(acc); - } catch (Throwable ex) { - observer.onError(ex); - } - } - - @Override - public void onError(Throwable e) { - observer.onError(e); - } - - @Override - public void onCompleted() { - observer.onCompleted(); - } - } -} diff --git a/rxjava-core/src/main/java/rx/operators/OperationSequenceEqual.java b/rxjava-core/src/main/java/rx/operators/OperationSequenceEqual.java index 423ddbc358..04b4c41d17 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSequenceEqual.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSequenceEqual.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,12 @@ */ package rx.operators; -import static rx.Observable.concat; -import static rx.Observable.from; -import static rx.Observable.zip; +import static rx.Observable.*; import rx.Notification; import rx.Observable; -import rx.util.functions.Func1; -import rx.util.functions.Func2; -import rx.util.functions.Functions; +import rx.functions.Func1; +import rx.functions.Func2; +import rx.functions.Functions; /** * Returns an Observable that emits a Boolean value that indicate whether two diff --git a/rxjava-core/src/main/java/rx/operators/OperationSingle.java b/rxjava-core/src/main/java/rx/operators/OperationSingle.java new file mode 100644 index 0000000000..871d565092 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationSingle.java @@ -0,0 +1,96 @@ +/** + * Copyright 2014 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; + +/** + * If the Observable completes after emitting a single item that matches a + * predicate, return an Observable containing that item. If it emits more than + * one such item or no item, throw an IllegalArgumentException. + */ +public class OperationSingle { + + public static OnSubscribeFunc single( + final Observable source) { + return single(source, false, null); + } + + public static OnSubscribeFunc singleOrDefault( + final Observable source, final T defaultValue) { + return single(source, true, defaultValue); + } + + private static OnSubscribeFunc single( + final Observable source, + final boolean hasDefaultValue, final T defaultValue) { + return new OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(final Observer observer) { + final SafeObservableSubscription subscription = new SafeObservableSubscription(); + subscription.wrap(source.subscribe(new Observer() { + + private T value; + private boolean isEmpty = true; + private boolean hasTooManyElemenets; + + @Override + public void onCompleted() { + if (hasTooManyElemenets) { + // We has already sent an onError message + } else { + if (isEmpty) { + if (hasDefaultValue) { + observer.onNext(defaultValue); + observer.onCompleted(); + } else { + observer.onError(new IllegalArgumentException( + "Sequence contains no elements")); + } + } else { + observer.onNext(value); + observer.onCompleted(); + } + } + } + + @Override + public void onError(Throwable e) { + observer.onError(e); + } + + @Override + public void onNext(T value) { + if (isEmpty) { + this.value = value; + isEmpty = false; + } else { + hasTooManyElemenets = true; + observer.onError(new IllegalArgumentException( + "Sequence contains too many elements")); + subscription.unsubscribe(); + } + } + })); + return subscription; + } + }; + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationSkip.java b/rxjava-core/src/main/java/rx/operators/OperationSkip.java index 4dc3359815..5267322e96 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSkip.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSkip.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,12 +15,18 @@ */ package rx.operators; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; +import rx.Scheduler; +import rx.Scheduler.Inner; import rx.Subscription; +import rx.functions.Action1; +import rx.subscriptions.CompositeSubscription; /** * Returns an Observable that skips the first num items emitted by the source @@ -107,4 +113,91 @@ public void onNext(T args) { } } + + /** + * Skip the items after subscription for the given duration. + * + * @param + * the value type + */ + public static final class SkipTimed implements OnSubscribeFunc { + final Observable source; + final long time; + final TimeUnit unit; + final Scheduler scheduler; + + public SkipTimed(Observable source, long time, TimeUnit unit, Scheduler scheduler) { + this.source = source; + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public Subscription onSubscribe(Observer t1) { + + SafeObservableSubscription timer = new SafeObservableSubscription(); + SafeObservableSubscription data = new SafeObservableSubscription(); + + CompositeSubscription csub = new CompositeSubscription(timer, data); + + final SourceObserver so = new SourceObserver(t1, csub); + data.wrap(source.subscribe(so)); + if (!data.isUnsubscribed()) { + timer.wrap(scheduler.schedule(so, time, unit)); + } + + return csub; + } + + /** + * Observes the source and relays its values once gate turns into true. + * + * @param + * the observed value type + */ + private static final class SourceObserver implements Observer, Action1 { + final AtomicBoolean gate; + final Observer observer; + final Subscription cancel; + + public SourceObserver(Observer observer, + Subscription cancel) { + this.gate = new AtomicBoolean(); + this.observer = observer; + this.cancel = cancel; + } + + @Override + public void onNext(T args) { + if (gate.get()) { + observer.onNext(args); + } + } + + @Override + public void onError(Throwable e) { + try { + observer.onError(e); + } finally { + cancel.unsubscribe(); + } + } + + @Override + public void onCompleted() { + try { + observer.onCompleted(); + } finally { + cancel.unsubscribe(); + } + } + + @Override + public void call(Inner inner) { + gate.set(true); + } + + } + } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationSkipLast.java b/rxjava-core/src/main/java/rx/operators/OperationSkipLast.java index f3cb462e55..894a3dc473 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSkipLast.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSkipLast.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,20 @@ */ package rx.operators; +import java.util.ArrayList; +import java.util.Collections; import java.util.Deque; import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; +import rx.Scheduler; import rx.Subscription; +import rx.schedulers.Timestamped; /** * Bypasses a specified number of elements at the end of an observable sequence. @@ -123,4 +129,76 @@ public void onNext(T value) { })); } } + + /** + * Skip delivering values in the time window before the values. + * + * @param + * the result value type + */ + public static final class SkipLastTimed implements OnSubscribeFunc { + final Observable source; + final long timeInMillis; + final Scheduler scheduler; + + public SkipLastTimed(Observable source, long time, TimeUnit unit, Scheduler scheduler) { + this.source = source; + this.timeInMillis = unit.toMillis(time); + this.scheduler = scheduler; + } + + @Override + public Subscription onSubscribe(Observer t1) { + return source.subscribe(new SourceObserver(t1, timeInMillis, scheduler)); + } + + /** Observes the source. */ + private static final class SourceObserver implements Observer { + final Observer observer; + final long timeInMillis; + final Scheduler scheduler; + List> buffer = new ArrayList>(); + + public SourceObserver(Observer observer, + long timeInMillis, Scheduler scheduler) { + this.observer = observer; + this.timeInMillis = timeInMillis; + this.scheduler = scheduler; + } + + @Override + public void onNext(T args) { + buffer.add(new Timestamped(scheduler.now(), args)); + } + + @Override + public void onError(Throwable e) { + buffer = Collections.emptyList(); + observer.onError(e); + } + + @Override + public void onCompleted() { + long limit = scheduler.now() - timeInMillis; + try { + for (Timestamped v : buffer) { + if (v.getTimestampMillis() < limit) { + try { + observer.onNext(v.getValue()); + } catch (Throwable t) { + observer.onError(t); + return; + } + } else { + observer.onCompleted(); + break; + } + } + } finally { + buffer = Collections.emptyList(); + } + } + + } + } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationSkipUntil.java b/rxjava-core/src/main/java/rx/operators/OperationSkipUntil.java index e8f04fd383..3bd44032b5 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSkipUntil.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSkipUntil.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package rx.operators; import java.util.concurrent.atomic.AtomicBoolean; + import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; @@ -32,6 +33,7 @@ public class OperationSkipUntil implements OnSubscribeFunc { protected final Observable source; protected final Observable other; + public OperationSkipUntil(Observable source, Observable other) { this.source = source; this.other = other; @@ -41,27 +43,30 @@ public OperationSkipUntil(Observable source, Observable other) { public Subscription onSubscribe(Observer t1) { return new ResultManager(t1).init(); } + /** Manage the source and other observers. */ private class ResultManager implements Subscription, Observer { final Observer observer; final CompositeSubscription cancel; final Object guard = new Object(); final AtomicBoolean running = new AtomicBoolean(); + public ResultManager(Observer observer) { this.observer = observer; this.cancel = new CompositeSubscription(); } + public ResultManager init() { - + SerialSubscription toSource = new SerialSubscription(); SerialSubscription toOther = new SerialSubscription(); - + cancel.add(toSource); cancel.add(toOther); - + toSource.setSubscription(source.subscribe(this)); toOther.setSubscription(other.subscribe(new OtherObserver(toOther))); - + return this; } @@ -69,6 +74,11 @@ public ResultManager init() { public void unsubscribe() { cancel.unsubscribe(); } + + @Override + public boolean isUnsubscribed() { + return cancel.isUnsubscribed(); + } @Override public void onNext(T args) { @@ -92,10 +102,11 @@ public void onCompleted() { unsubscribe(); } } - + /** Observe the other stream. */ private class OtherObserver implements Observer { final Subscription self; + public OtherObserver(Subscription self) { this.self = self; } @@ -115,7 +126,8 @@ public void onError(Throwable e) { public void onCompleted() { self.unsubscribe(); } - + } + } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationSkipWhile.java b/rxjava-core/src/main/java/rx/operators/OperationSkipWhile.java index 6bc747e9bf..aaa2914986 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSkipWhile.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSkipWhile.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,8 +22,8 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.util.functions.Func1; -import rx.util.functions.Func2; +import rx.functions.Func1; +import rx.functions.Func2; /** * Skips any emitted source items as long as the specified condition holds true. Emits all further source items diff --git a/rxjava-core/src/main/java/rx/operators/OperationSubscribeOn.java b/rxjava-core/src/main/java/rx/operators/OperationSubscribeOn.java deleted file mode 100644 index fe7aa00216..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationSubscribeOn.java +++ /dev/null @@ -1,76 +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 rx.Observable; -import rx.Observable.OnSubscribeFunc; -import rx.Observer; -import rx.Scheduler; -import rx.Subscription; -import rx.util.functions.Action0; -import rx.util.functions.Func2; - -/** - * Asynchronously subscribes and unsubscribes Observers on the specified Scheduler. - *

- * - */ -public class OperationSubscribeOn { - - public static OnSubscribeFunc subscribeOn(Observable source, Scheduler scheduler) { - return new SubscribeOn(source, scheduler); - } - - private static class SubscribeOn implements OnSubscribeFunc { - private final Observable source; - private final Scheduler scheduler; - - public SubscribeOn(Observable source, Scheduler scheduler) { - this.source = source; - this.scheduler = scheduler; - } - - @Override - public Subscription onSubscribe(final Observer observer) { - return scheduler.schedule(null, new Func2() { - @Override - public Subscription call(Scheduler s, T t) { - return new ScheduledSubscription(source.subscribe(observer), scheduler); - } - }); - } - } - - private static class ScheduledSubscription implements Subscription { - private final Subscription underlying; - private final Scheduler scheduler; - - private ScheduledSubscription(Subscription underlying, Scheduler scheduler) { - this.underlying = underlying; - this.scheduler = scheduler; - } - - @Override - public void unsubscribe() { - scheduler.schedule(new Action0() { - @Override - public void call() { - underlying.unsubscribe(); - } - }); - } - } -} diff --git a/rxjava-core/src/main/java/rx/operators/OperationSum.java b/rxjava-core/src/main/java/rx/operators/OperationSum.java index fef81a2625..7c2177836f 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSum.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSum.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,47 +16,74 @@ package rx.operators; import rx.Observable; -import rx.util.functions.Func2; +import rx.functions.Func2; /** * A few operators for implementing the sum operation. * - * @see MSDN: Observable.Sum + * @see MSDN: + * Observable.Sum */ public final class OperationSum { - public static Observable sum(Observable source) { - return source.reduce(0, new Func2() { - @Override - public Integer call(Integer accu, Integer next) { - return accu + next; - } - }); + + public static Observable sumIntegers(Observable source) { + return source.reduce(0, ACCUM_INT); } public static Observable sumLongs(Observable source) { - return source.reduce(0L, new Func2() { - @Override - public Long call(Long accu, Long next) { - return accu + next; - } - }); + return source.reduce(0l, ACCUM_LONG); } public static Observable sumFloats(Observable source) { - return source.reduce(0.0f, new Func2() { - @Override - public Float call(Float accu, Float next) { - return accu + next; - } - }); + return source.reduce(0.0f, ACCUM_FLOAT); } public static Observable sumDoubles(Observable source) { - return source.reduce(0.0d, new Func2() { - @Override - public Double call(Double accu, Double next) { - return accu + next; - } - }); + return source.reduce(0.0d, ACCUM_DOUBLE); + } + + public static Observable sumAtLeastOneIntegers(Observable source) { + return source.reduce(ACCUM_INT); + } + + public static Observable sumAtLeastOneLongs(Observable source) { + return source.reduce(ACCUM_LONG); + } + + public static Observable sumAtLeastOneFloats(Observable source) { + return source.reduce(ACCUM_FLOAT); } + + public static Observable sumAtLeastOneDoubles(Observable source) { + return source.reduce(ACCUM_DOUBLE); + } + + private static final Func2 ACCUM_INT = new Func2() { + @Override + public Integer call(Integer accu, Integer next) { + return accu + next; + } + }; + + private static final Func2 ACCUM_LONG = new Func2() { + @Override + public Long call(Long accu, Long next) { + return accu + next; + } + }; + + private static final Func2 ACCUM_FLOAT = new Func2() { + @Override + public Float call(Float accu, Float next) { + return accu + next; + } + }; + + private static final Func2 ACCUM_DOUBLE = new Func2() { + @Override + public Double call(Double accu, Double next) { + return accu + next; + } + }; } diff --git a/rxjava-core/src/main/java/rx/operators/OperationSwitch.java b/rxjava-core/src/main/java/rx/operators/OperationSwitch.java index 9094a2affd..332e1090a9 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSwitch.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSwitch.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,9 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; +import rx.functions.Func1; import rx.subscriptions.CompositeSubscription; -import rx.subscriptions.MultipleAssignmentSubscription; -import rx.util.functions.Func1; +import rx.subscriptions.SerialSubscription; /** * Transforms an Observable that emits Observables into a single Observable that @@ -62,8 +62,7 @@ public Subscription onSubscribe(Observer observer) { SafeObservableSubscription parent; parent = new SafeObservableSubscription(); - MultipleAssignmentSubscription child; - child = new MultipleAssignmentSubscription(); + SerialSubscription child = new SerialSubscription(); parent.wrap(sequences.subscribe(new SwitchObserver(observer, parent, child))); @@ -76,13 +75,13 @@ private static class SwitchObserver implements Observer observer; private final SafeObservableSubscription parent; - private final MultipleAssignmentSubscription child; + private final SerialSubscription child; private long latest; private boolean stopped; private boolean hasLatest; public SwitchObserver(Observer observer, SafeObservableSubscription parent, - MultipleAssignmentSubscription child) { + SerialSubscription child) { this.observer = observer; this.parent = parent; this.child = child; @@ -97,8 +96,7 @@ public void onNext(Observable args) { this.hasLatest = true; } - final SafeObservableSubscription sub; - sub = new SafeObservableSubscription(); + final SafeObservableSubscription sub = new SafeObservableSubscription(); sub.wrap(args.subscribe(new Observer() { @Override public void onNext(T args) { @@ -111,28 +109,35 @@ public void onNext(T args) { @Override public void onError(Throwable e) { + sub.unsubscribe(); + SafeObservableSubscription s = null; synchronized (gate) { - sub.unsubscribe(); if (latest == id) { SwitchObserver.this.observer.onError(e); - SwitchObserver.this.parent.unsubscribe(); + s = SwitchObserver.this.parent; } } + if (s != null) { + s.unsubscribe(); + } } @Override public void onCompleted() { + sub.unsubscribe(); + SafeObservableSubscription s = null; synchronized (gate) { - sub.unsubscribe(); if (latest == id) { SwitchObserver.this.hasLatest = false; - } - if (stopped) { - SwitchObserver.this.observer.onCompleted(); - SwitchObserver.this.parent.unsubscribe(); + if (stopped) { + SwitchObserver.this.observer.onCompleted(); + s = SwitchObserver.this.parent; + } } - + } + if (s != null) { + s.unsubscribe(); } } @@ -152,13 +157,17 @@ public void onError(Throwable e) { @Override public void onCompleted() { + SafeObservableSubscription s = null; synchronized (gate) { this.stopped = true; if (!this.hasLatest) { this.observer.onCompleted(); - this.parent.unsubscribe(); + s = this.parent; } } + if (s != null) { + s.unsubscribe(); + } } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationSynchronize.java b/rxjava-core/src/main/java/rx/operators/OperationSynchronize.java index 5ba8c0a98e..e94411b313 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationSynchronize.java +++ b/rxjava-core/src/main/java/rx/operators/OperationSynchronize.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; +import rx.observers.SynchronizedObserver; /** * Wraps an Observable in another Observable that ensures that the resulting Observable is @@ -85,14 +86,13 @@ public Synchronize(Observable innerObservable, Object lock) { private Object lock; public Subscription onSubscribe(Observer observer) { - SafeObservableSubscription subscription = new SafeObservableSubscription(); if (lock == null) { - atomicObserver = new SynchronizedObserver(observer, subscription); + atomicObserver = new SynchronizedObserver(observer); } else { - atomicObserver = new SynchronizedObserver(observer, subscription, lock); + atomicObserver = new SynchronizedObserver(observer, lock); } - return subscription.wrap(innerObservable.subscribe(atomicObserver)); + return innerObservable.subscribe(atomicObserver); } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationTakeLast.java b/rxjava-core/src/main/java/rx/operators/OperationTakeLast.java index d324f7ce42..53beddd7d4 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationTakeLast.java +++ b/rxjava-core/src/main/java/rx/operators/OperationTakeLast.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,15 @@ import java.util.Deque; import java.util.LinkedList; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; +import rx.Scheduler; import rx.Subscription; +import rx.schedulers.Timestamped; /** * Returns an Observable that emits the last count items emitted by the source @@ -119,4 +122,123 @@ public void onNext(T value) { } } + + /** + * Returns the items emitted by source whose arrived in the time window + * before the source completed. + */ + public static OnSubscribeFunc takeLast(Observable source, long time, TimeUnit unit, Scheduler scheduler) { + return new TakeLastTimed(source, -1, time, unit, scheduler); + } + + /** + * Returns the items emitted by source whose arrived in the time window + * before the source completed and at most count values. + */ + public static OnSubscribeFunc takeLast(Observable source, int count, long time, TimeUnit unit, Scheduler scheduler) { + return new TakeLastTimed(source, count, time, unit, scheduler); + } + + /** Take only the values which appeared some time before the completion. */ + static final class TakeLastTimed implements OnSubscribeFunc { + final Observable source; + final long ageMillis; + final Scheduler scheduler; + final int count; + + public TakeLastTimed(Observable source, int count, long time, TimeUnit unit, Scheduler scheduler) { + this.source = source; + this.ageMillis = unit.toMillis(time); + this.scheduler = scheduler; + this.count = count; + } + + @Override + public Subscription onSubscribe(Observer t1) { + SafeObservableSubscription sas = new SafeObservableSubscription(); + sas.wrap(source.subscribe(new TakeLastTimedObserver(t1, sas, count, ageMillis, scheduler))); + return sas; + } + } + + /** Observes source values and keeps the most recent items. */ + static final class TakeLastTimedObserver implements Observer { + final Observer observer; + final Subscription cancel; + final long ageMillis; + final Scheduler scheduler; + /** -1 indicates unlimited buffer. */ + final int count; + + final Deque> buffer = new LinkedList>(); + + public TakeLastTimedObserver(Observer observer, Subscription cancel, + int count, long ageMillis, Scheduler scheduler) { + this.observer = observer; + this.cancel = cancel; + this.ageMillis = ageMillis; + this.scheduler = scheduler; + this.count = count; + } + + protected void runEvictionPolicy(long now) { + // trim size + while (count >= 0 && buffer.size() > count) { + buffer.pollFirst(); + } + // remove old entries + while (!buffer.isEmpty()) { + Timestamped v = buffer.peekFirst(); + if (v.getTimestampMillis() < now - ageMillis) { + buffer.pollFirst(); + } else { + break; + } + } + } + + @Override + public void onNext(T args) { + long t = scheduler.now(); + buffer.add(new Timestamped(t, args)); + runEvictionPolicy(t); + } + + @Override + public void onError(Throwable e) { + buffer.clear(); + observer.onError(e); + cancel.unsubscribe(); + } + + /** + * Emit the contents of the buffer. + * + * @return true if no exception was raised in the process + */ + protected boolean emitBuffer() { + for (Timestamped v : buffer) { + try { + observer.onNext(v.getValue()); + } catch (Throwable t) { + buffer.clear(); + observer.onError(t); + return false; + } + } + buffer.clear(); + return true; + } + + @Override + public void onCompleted() { + runEvictionPolicy(scheduler.now()); + + if (emitBuffer()) { + observer.onCompleted(); + } + cancel.unsubscribe(); + } + + } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationTake.java b/rxjava-core/src/main/java/rx/operators/OperationTakeTimed.java similarity index 57% rename from rxjava-core/src/main/java/rx/operators/OperationTake.java rename to rxjava-core/src/main/java/rx/operators/OperationTakeTimed.java index 877ba4d5f3..88fd652026 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationTake.java +++ b/rxjava-core/src/main/java/rx/operators/OperationTakeTimed.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,26 +15,33 @@ */ package rx.operators; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; +import rx.Scheduler; +import rx.Scheduler.Inner; import rx.Subscription; +import rx.functions.Action1; +import rx.subscriptions.CompositeSubscription; import rx.subscriptions.Subscriptions; /** * Returns an Observable that emits the first num items emitted by the source * Observable. *

- * + * *

* You can choose to pay attention only to the first num items emitted by an * Observable by using the take operation. This operation returns an Observable that will invoke a * subscribing Observer's onNext function a maximum of num times before * invoking onCompleted. */ -public final class OperationTake { +public final class OperationTakeTimed { + + //TODO this has not been migrated to use bind yet /** * Returns a specified number of contiguous values from the start of an observable sequence. @@ -161,4 +168,124 @@ public void onNext(T args) { } } + + /** + * Takes values from the source until a timer fires. + * + * @param + * the result value type + */ + public static final class TakeTimed implements OnSubscribeFunc { + final Observable source; + final long time; + final TimeUnit unit; + final Scheduler scheduler; + + public TakeTimed(Observable source, long time, TimeUnit unit, Scheduler scheduler) { + this.source = source; + this.time = time; + this.unit = unit; + this.scheduler = scheduler; + } + + @Override + public Subscription onSubscribe(Observer t1) { + + SafeObservableSubscription timer = new SafeObservableSubscription(); + SafeObservableSubscription data = new SafeObservableSubscription(); + + CompositeSubscription csub = new CompositeSubscription(timer, data); + + final SourceObserver so = new SourceObserver(t1, csub); + data.wrap(source.subscribe(so)); + if (!data.isUnsubscribed()) { + timer.wrap(scheduler.schedule(so, time, unit)); + } + + return csub; + } + + /** + * Observes the source and relays its values until gate turns into false. + * + * @param + * the observed value type + */ + private static final class SourceObserver implements Observer, Action1 { + final Observer observer; + final Subscription cancel; + final AtomicInteger state = new AtomicInteger(); + static final int ACTIVE = 0; + static final int NEXT = 1; + static final int DONE = 2; + + public SourceObserver(Observer observer, + Subscription cancel) { + this.observer = observer; + this.cancel = cancel; + } + + @Override + public void onNext(T args) { + do { + int s = state.get(); + if (s == DONE) { + return; + } + if (state.compareAndSet(s, NEXT)) { + try { + observer.onNext(args); + } finally { + state.set(ACTIVE); + return; + } + } + } while (true); + } + + @Override + public void onError(Throwable e) { + do { + int s = state.get(); + if (s == DONE) { + return; + } else if (s == NEXT) { + continue; + } else if (state.compareAndSet(s, DONE)) { + try { + observer.onError(e); + } finally { + cancel.unsubscribe(); + } + return; + } + } while (true); + } + + @Override + public void onCompleted() { + do { + int s = state.get(); + if (s == DONE) { + return; + } else if (s == NEXT) { + continue; + } else if (state.compareAndSet(s, DONE)) { + try { + observer.onCompleted(); + } finally { + cancel.unsubscribe(); + } + return; + } + } while (true); + } + + @Override + public void call(Inner inner) { + onCompleted(); + } + + } + } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationTakeUntil.java b/rxjava-core/src/main/java/rx/operators/OperationTakeUntil.java index 4f344d72a1..fd3615b8b5 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationTakeUntil.java +++ b/rxjava-core/src/main/java/rx/operators/OperationTakeUntil.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.util.functions.Func1; +import rx.functions.Func1; /** * Returns an Observable that emits the items from the source Observable until another Observable @@ -120,7 +120,7 @@ public Subscription onSubscribe(final Observer> notifica return sequence.subscribe(new Observer() { @Override public void onCompleted() { - // Ignore + notificationObserver.onNext(Notification. halt()); } @Override diff --git a/rxjava-core/src/main/java/rx/operators/OperationTakeWhile.java b/rxjava-core/src/main/java/rx/operators/OperationTakeWhile.java index 479badf6a6..b2b7748991 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationTakeWhile.java +++ b/rxjava-core/src/main/java/rx/operators/OperationTakeWhile.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,8 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; -import rx.util.functions.Func1; -import rx.util.functions.Func2; +import rx.functions.Func1; +import rx.functions.Func2; /** * Returns an Observable that emits items emitted by the source Observable as long as a specified diff --git a/rxjava-core/src/main/java/rx/operators/OperationThrottleFirst.java b/rxjava-core/src/main/java/rx/operators/OperationThrottleFirst.java index e21617a82c..44d2d02083 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationThrottleFirst.java +++ b/rxjava-core/src/main/java/rx/operators/OperationThrottleFirst.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,8 +23,8 @@ import rx.Observer; import rx.Scheduler; import rx.Subscription; -import rx.concurrency.Schedulers; -import rx.util.functions.Func1; +import rx.functions.Func1; +import rx.schedulers.Schedulers; /** * Throttle by windowing a stream and returning the first value in each window. @@ -43,7 +43,7 @@ public final class OperationThrottleFirst { * @return A {@link Func1} which performs the throttle operation. */ public static OnSubscribeFunc throttleFirst(Observable items, long windowDuration, TimeUnit unit) { - return throttleFirst(items, windowDuration, unit, Schedulers.threadPoolForComputation()); + return throttleFirst(items, windowDuration, unit, Schedulers.computation()); } /** diff --git a/rxjava-core/src/main/java/rx/operators/OperationTimeInterval.java b/rxjava-core/src/main/java/rx/operators/OperationTimeInterval.java index 2cd1860711..04b84044ec 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationTimeInterval.java +++ b/rxjava-core/src/main/java/rx/operators/OperationTimeInterval.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,8 @@ import rx.Observer; import rx.Scheduler; import rx.Subscription; -import rx.concurrency.Schedulers; -import rx.util.TimeInterval; +import rx.schedulers.Schedulers; +import rx.schedulers.TimeInterval; /** * Records the time interval between consecutive elements in an observable sequence. diff --git a/rxjava-core/src/main/java/rx/operators/OperationTimeout.java b/rxjava-core/src/main/java/rx/operators/OperationTimeout.java deleted file mode 100644 index b52c7a5a43..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationTimeout.java +++ /dev/null @@ -1,157 +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 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/OperationTimer.java b/rxjava-core/src/main/java/rx/operators/OperationTimer.java new file mode 100644 index 0000000000..16f745129f --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperationTimer.java @@ -0,0 +1,92 @@ +/** + * Copyright 2014 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.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Scheduler; +import rx.Scheduler.Inner; +import rx.Subscription; +import rx.functions.Action1; + +/** + * Operation Timer with several overloads. + * + * @see MSDN Observable.Timer + */ +public final class OperationTimer { + private OperationTimer() { + throw new IllegalStateException("No instances!"); + } + + /** + * Emit a single 0L after the specified time elapses. + */ + public static class TimerOnce implements OnSubscribeFunc { + private final Scheduler scheduler; + private final long dueTime; + private final TimeUnit dueUnit; + + public TimerOnce(long dueTime, TimeUnit unit, Scheduler scheduler) { + this.scheduler = scheduler; + this.dueTime = dueTime; + this.dueUnit = unit; + } + + @Override + public Subscription onSubscribe(final Observer t1) { + return scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + t1.onNext(0L); + t1.onCompleted(); + } + + }, dueTime, dueUnit); + } + } + + /** + * Emit 0L after the initial period and ever increasing number after each period. + */ + public static class TimerPeriodically implements OnSubscribeFunc { + private final Scheduler scheduler; + private final long initialDelay; + private final long period; + private final TimeUnit unit; + + public TimerPeriodically(long initialDelay, long period, TimeUnit unit, Scheduler scheduler) { + this.scheduler = scheduler; + this.initialDelay = initialDelay; + this.period = period; + this.unit = unit; + } + + @Override + public Subscription onSubscribe(final Observer t1) { + return scheduler.schedulePeriodically(new Action1() { + long count; + + @Override + public void call(Inner inner) { + t1.onNext(count++); + } + }, initialDelay, period, unit); + } + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperationTimestamp.java b/rxjava-core/src/main/java/rx/operators/OperationTimestamp.java deleted file mode 100644 index a1044ba748..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationTimestamp.java +++ /dev/null @@ -1,59 +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 rx.Observable; -import rx.Observable.OnSubscribeFunc; -import rx.Scheduler; -import rx.util.Timestamped; -import rx.util.functions.Func1; - -/** - * Wraps each item emitted by a source Observable in a {@link Timestamped} object. - *

- * - */ -public final class OperationTimestamp { - - /** - * Accepts a sequence and adds timestamps to each item in it. - * - * @param sequence - * the input sequence. - * @param - * the type of the input sequence. - * @return a sequence of timestamped values created by adding timestamps to each item in the input sequence. - */ - public static OnSubscribeFunc> timestamp(Observable sequence) { - return OperationMap.map(sequence, new Func1>() { - @Override - public Timestamped call(T value) { - return new Timestamped(System.currentTimeMillis(), value); - } - }); - } - /** - * Timestamp the source elements based on the timing provided by the scheduler. - */ - public static OnSubscribeFunc> timestamp(Observable source, final Scheduler scheduler) { - return OperationMap.map(source, new Func1>() { - @Override - public Timestamped call(T value) { - return new Timestamped(scheduler.now(), value); - } - }); - } -} diff --git a/rxjava-core/src/main/java/rx/operators/OperationToFuture.java b/rxjava-core/src/main/java/rx/operators/OperationToFuture.java index d4433da9d6..005cd3a1e4 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationToFuture.java +++ b/rxjava-core/src/main/java/rx/operators/OperationToFuture.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rxjava-core/src/main/java/rx/operators/OperationToIterator.java b/rxjava-core/src/main/java/rx/operators/OperationToIterator.java index 2fcd51872e..399083582d 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationToIterator.java +++ b/rxjava-core/src/main/java/rx/operators/OperationToIterator.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,14 @@ package rx.operators; import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import rx.Notification; import rx.Observable; import rx.Observer; -import rx.util.Exceptions; +import rx.exceptions.Exceptions; /** * Returns an Iterator that iterates over all items emitted by a specified Observable. @@ -69,21 +70,20 @@ public boolean hasNext() { if (buf == null) { buf = take(); } + if (buf.isOnError()) { + throw Exceptions.propagate(buf.getThrowable()); + } return !buf.isOnCompleted(); } @Override public T next() { - if (buf == null) { - buf = take(); + if (hasNext()) { + T result = buf.getValue(); + buf = null; + return result; } - if (buf.isOnError()) { - throw Exceptions.propagate(buf.getThrowable()); - } - - T result = buf.getValue(); - buf = null; - return result; + throw new NoSuchElementException(); } private Notification take() { diff --git a/rxjava-core/src/main/java/rx/operators/OperationToMap.java b/rxjava-core/src/main/java/rx/operators/OperationToMap.java index 754ff82d64..5e346f1ddd 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationToMap.java +++ b/rxjava-core/src/main/java/rx/operators/OperationToMap.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,14 +18,15 @@ import java.util.HashMap; import java.util.Map; + import rx.Observable; import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.functions.Functions; 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 @@ -40,7 +41,7 @@ public class OperationToMap { public static OnSubscribeFunc> toMap(Observable source, Func1 keySelector) { return new ToMap(source, keySelector, - Functions.identity(), new DefaultToMapFactory()); + Functions. identity(), new DefaultToMapFactory()); } /** @@ -50,7 +51,7 @@ public static OnSubscribeFunc> toMap(Observable source, Func1 keySelector, Func1 valueSelector) { return new ToMap(source, keySelector, - valueSelector, new DefaultToMapFactory()); + valueSelector, new DefaultToMapFactory()); } /** @@ -61,7 +62,7 @@ public static OnSubscribeFunc> toMap(Observable source, Func1 valueSelector, Func0> mapFactory) { return new ToMap(source, keySelector, - valueSelector, mapFactory); + valueSelector, mapFactory); } /** The default map factory. */ @@ -71,13 +72,18 @@ 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 + * + * @param + * the source's value type + * @param + * the key type + * @param + * the value type */ public static class ToMap implements OnSubscribeFunc> { /** The source. */ @@ -88,18 +94,19 @@ public static class ToMap implements OnSubscribeFunc> { private final Func1 valueSelector; /** Map factory. */ private final Func0> mapFactory; + public ToMap( Observable source, Func1 keySelector, Func1 valueSelector, - Func0> mapFactory - ) { + Func0> mapFactory) { this.source = source; this.keySelector = keySelector; this.valueSelector = valueSelector; this.mapFactory = mapFactory; - + } + @Override public Subscription onSubscribe(Observer> t1) { Map map; @@ -112,6 +119,7 @@ public Subscription onSubscribe(Observer> t1) { return source.subscribe(new ToMapObserver( t1, keySelector, valueSelector, map)); } + /** * Observer that collects the source values of T into * a map. @@ -125,9 +133,9 @@ public static class ToMapObserver implements Observer { private final Func1 valueSelector; /** The observer who is receiving the completed map. */ private final Observer> t1; - + public ToMapObserver( - Observer> t1, + Observer> t1, Func1 keySelector, Func1 valueSelector, Map map) { @@ -136,17 +144,20 @@ public ToMapObserver( 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; diff --git a/rxjava-core/src/main/java/rx/operators/OperationToMultimap.java b/rxjava-core/src/main/java/rx/operators/OperationToMultimap.java index 7210ee45e7..94900abeda 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationToMultimap.java +++ b/rxjava-core/src/main/java/rx/operators/OperationToMultimap.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,17 +20,18 @@ 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.functions.Func0; +import rx.functions.Func1; +import rx.functions.Functions; 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 + * 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. * @@ -44,12 +45,11 @@ public class OperationToMultimap { public static OnSubscribeFunc>> toMultimap( Observable source, Func1 keySelector - ) { + ) { return new ToMultimap( - source, keySelector, Functions.identity(), + source, keySelector, Functions. identity(), new DefaultToMultimapFactory(), - new DefaultMultimapCollectionFactory() - ); + new DefaultMultimapCollectionFactory()); } /** @@ -60,13 +60,13 @@ public static OnSubscribeFunc>> toMultimap( Observable source, Func1 keySelector, Func1 valueSelector - ) { + ) { return new ToMultimap( source, keySelector, valueSelector, new DefaultToMultimapFactory(), - new DefaultMultimapCollectionFactory() - ); + new DefaultMultimapCollectionFactory()); } + /** * ToMultimap with key selector, custom value selector, * custom Map factory and default ArrayList collection factory. @@ -76,13 +76,13 @@ public static OnSubscribeFunc>> toMultimap( Func1 keySelector, Func1 valueSelector, Func0>> mapFactory - ) { + ) { return new ToMultimap( source, keySelector, valueSelector, mapFactory, - new DefaultMultimapCollectionFactory() - ); + new DefaultMultimapCollectionFactory()); } + /** * ToMultimap with key selector, custom value selector, * custom Map factory and custom collection factory. @@ -93,13 +93,13 @@ public static OnSubscribeFunc>> toMultimap( Func1 valueSelector, Func0>> mapFactory, Func1> collectionFactory - ) { + ) { return new ToMultimap( source, keySelector, valueSelector, mapFactory, - collectionFactory - ); + collectionFactory); } + /** * The default multimap factory returning a HashMap. */ @@ -109,17 +109,19 @@ 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> { + 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. @@ -130,19 +132,20 @@ public static class ToMultimap implements OnSubscribeFunc valueSelector; private final Func0>> mapFactory; private final Func1> collectionFactory; + public ToMultimap( Observable source, Func1 keySelector, Func1 valueSelector, Func0>> mapFactory, - Func1> collectionFactory - ) { + 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; @@ -154,8 +157,9 @@ public Subscription onSubscribe(Observer>> t1) { } return source.subscribe(new ToMultimapObserver( t1, keySelector, valueSelector, map, collectionFactory - )); + )); } + /** * Observer that collects the source values of Ts into a multimap. */ @@ -165,19 +169,20 @@ public static class ToMultimapObserver implements Observer { private final Func1> collectionFactory; private Map> map; private Observer>> t1; + public ToMultimapObserver( - Observer>> t1, - Func1 keySelector, - Func1 valueSelector, - Map> map, - Func1> collectionFactory - ) { + 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); @@ -189,11 +194,13 @@ public void onNext(T args) { } collection.add(value); } + @Override public void onError(Throwable e) { map = null; t1.onError(e); } + @Override public void onCompleted() { Map> map0 = map; diff --git a/rxjava-core/src/main/java/rx/operators/OperationToObservableFuture.java b/rxjava-core/src/main/java/rx/operators/OperationToObservableFuture.java index 875a0dc0d8..5d25bcb593 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationToObservableFuture.java +++ b/rxjava-core/src/main/java/rx/operators/OperationToObservableFuture.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/rxjava-core/src/main/java/rx/operators/OperationToObservableList.java b/rxjava-core/src/main/java/rx/operators/OperationToObservableList.java deleted file mode 100644 index b87a49fdd1..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationToObservableList.java +++ /dev/null @@ -1,90 +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.ArrayList; -import java.util.List; -import java.util.concurrent.ConcurrentLinkedQueue; - -import rx.Observable; -import rx.Observable.OnSubscribeFunc; -import rx.Observer; -import rx.Subscription; - -/** - * 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 Observer's - * onNext method for each such item. You can change this behavior, instructing the - * Observable to compose a list of all of these multiple items and then to invoke the Observer's - * onNext method once, passing it the entire list, by using the toList operator. - *

- * 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. - */ -public final class OperationToObservableList { - - public static OnSubscribeFunc> toObservableList(Observable that) { - return new ToObservableList(that); - } - - private static class ToObservableList implements OnSubscribeFunc> { - - private final Observable that; - - public ToObservableList(Observable that) { - this.that = that; - } - - 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); - } - - public void onError(Throwable ex) { - observer.onError(ex); - } - - public void onCompleted() { - try { - // copy from LinkedQueue to List since ConcurrentLinkedQueue does not implement the List interface - ArrayList l = new ArrayList(list.size()); - for (T t : list) { - l.add(t); - } - - // benjchristensen => I want to make this list immutable but some clients are sorting this - // instead of using toSortedList() and this change breaks them until we migrate their code. - // observer.onNext(Collections.unmodifiableList(l)); - observer.onNext(l); - observer.onCompleted(); - } catch (Throwable e) { - onError(e); - } - - } - }); - } - } -} diff --git a/rxjava-core/src/main/java/rx/operators/OperationToObservableSortedList.java b/rxjava-core/src/main/java/rx/operators/OperationToObservableSortedList.java deleted file mode 100644 index cfc7ba35fa..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationToObservableSortedList.java +++ /dev/null @@ -1,138 +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.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.concurrent.ConcurrentLinkedQueue; - -import rx.Observable; -import rx.Observable.OnSubscribeFunc; -import rx.Observer; -import rx.Subscription; -import rx.util.functions.Func2; - -/** - * Return an Observable that emits the items emitted by the source Observable, in a sorted order - * (each item emitted by the Observable must implement Comparable with respect to all other items - * in the sequence, or you must pass in a sort function). - *

- * - * - * @param - */ -public final class OperationToObservableSortedList { - - /** - * Sort T objects by their natural order (object must implement Comparable). - * - * @param sequence - * @throws ClassCastException - * if T objects do not implement Comparable - * @return an observable containing the sorted list - */ - public static OnSubscribeFunc> toSortedList(Observable sequence) { - return new ToObservableSortedList(sequence); - } - - /** - * Sort T objects using the defined sort function. - * - * @param sequence - * @param sortFunction - * @return an observable containing the sorted list - */ - public static OnSubscribeFunc> toSortedList(Observable sequence, Func2 sortFunction) { - return new ToObservableSortedList(sequence, sortFunction); - } - - private static class ToObservableSortedList implements OnSubscribeFunc> { - - private final Observable that; - private final ConcurrentLinkedQueue list = new ConcurrentLinkedQueue(); - private final Func2 sortFunction; - - // unchecked as we're support Object for the default - @SuppressWarnings("unchecked") - private ToObservableSortedList(Observable that) { - this(that, defaultSortFunction); - } - - private ToObservableSortedList(Observable that, Func2 sortFunction) { - this.that = that; - this.sortFunction = sortFunction; - } - - public Subscription onSubscribe(final Observer> observer) { - return that.subscribe(new Observer() { - public void onNext(T value) { - // onNext can be concurrently executed so list must be thread-safe - list.add(value); - } - - public void onError(Throwable ex) { - observer.onError(ex); - } - - public void onCompleted() { - try { - // copy from LinkedQueue to List since ConcurrentLinkedQueue does not implement the List interface - ArrayList l = new ArrayList(list.size()); - for (T t : list) { - l.add(t); - } - - // sort the list before delivery - Collections.sort(l, new Comparator() { - - @Override - public int compare(T o1, T o2) { - return sortFunction.call(o1, o2); - } - - }); - - observer.onNext(Collections.unmodifiableList(l)); - observer.onCompleted(); - } catch (Throwable e) { - onError(e); - } - - } - }); - } - - // raw because we want to support Object for this default - @SuppressWarnings("rawtypes") - private static Func2 defaultSortFunction = new DefaultComparableFunction(); - - private static class DefaultComparableFunction implements Func2 { - - // unchecked because we want to support Object for this default - @SuppressWarnings("unchecked") - @Override - public Integer call(Object t1, Object t2) { - Comparable c1 = (Comparable) t1; - Comparable c2 = (Comparable) t2; - return c1.compareTo(c2); - } - - } - - } -} diff --git a/rxjava-core/src/main/java/rx/operators/OperationUsing.java b/rxjava-core/src/main/java/rx/operators/OperationUsing.java index dd0cc38d65..15108481f4 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationUsing.java +++ b/rxjava-core/src/main/java/rx/operators/OperationUsing.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 - * + * + * 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,10 +19,10 @@ import rx.Observable.OnSubscribeFunc; import rx.Observer; import rx.Subscription; +import rx.functions.Func0; +import rx.functions.Func1; 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. diff --git a/rxjava-core/src/main/java/rx/operators/OperationWindow.java b/rxjava-core/src/main/java/rx/operators/OperationWindow.java index f18a700bd2..c0d96f3a79 100644 --- a/rxjava-core/src/main/java/rx/operators/OperationWindow.java +++ b/rxjava-core/src/main/java/rx/operators/OperationWindow.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,9 +22,13 @@ import rx.Observer; import rx.Scheduler; import rx.Subscription; -import rx.concurrency.Schedulers; -import rx.util.functions.Func0; -import rx.util.functions.Func1; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.schedulers.Schedulers; +import rx.subjects.PublishSubject; +import rx.subjects.Subject; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.Subscriptions; public final class OperationWindow extends ChunkedOperation { @@ -38,11 +42,11 @@ public Window call() { } /** - *

This method creates a {@link rx.util.functions.Func1} object which represents the window operation. This operation takes - * values from the specified {@link rx.Observable} source and stores them in a window until the {@link rx.Observable} constructed using the {@link rx.util.functions.Func0} argument, produces a + *

This method creates a {@link rx.functions.Func1} object which represents the window operation. This operation takes + * values from the specified {@link rx.Observable} source and stores them in a window until the {@link rx.Observable} constructed using the {@link rx.functions.Func0} argument, produces a * value. The window is then * emitted, and a new window is created to replace it. A new {@link rx.Observable} will be constructed using the - * provided {@link rx.util.functions.Func0} object, which will determine when this new window is emitted. When the source {@link rx.Observable} completes or produces an error, the current window + * provided {@link rx.functions.Func0} object, which will determine when this new window is emitted. When the source {@link rx.Observable} completes or produces an error, the current window * is emitted, and the event is propagated * to all subscribed {@link rx.Observer}s.

* @@ -52,10 +56,10 @@ public Window call() { * @param source * The {@link rx.Observable} which produces values. * @param windowClosingSelector - * A {@link rx.util.functions.Func0} object which produces {@link rx.Observable}s. These {@link rx.Observable}s determine when a window is emitted and replaced by simply + * A {@link rx.functions.Func0} object which produces {@link rx.Observable}s. These {@link rx.Observable}s determine when a window is emitted and replaced by simply * producing an object. * @return - * the {@link rx.util.functions.Func1} object representing the specified window operation. + * the {@link rx.functions.Func1} object representing the specified window operation. */ public static OnSubscribeFunc> window(final Observable source, final Func0> windowClosingSelector) { return new OnSubscribeFunc>() { @@ -70,7 +74,7 @@ public Subscription onSubscribe(final Observer> observer) } /** - *

This method creates a {@link rx.util.functions.Func1} object which represents the window operation. This operation takes + *

This method creates a {@link rx.functions.Func1} object which represents the window operation. This operation takes * values from the specified {@link rx.Observable} source and stores them in the currently active window. Initially * there are no windows active.

* @@ -89,10 +93,10 @@ public Subscription onSubscribe(final Observer> observer) * An {@link rx.Observable} which when it produces a {@link rx.util.Opening} value will * create a new window which instantly starts recording the "source" {@link rx.Observable}. * @param windowClosingSelector - * A {@link rx.util.functions.Func0} object which produces {@link rx.Observable}s. These {@link rx.Observable}s determine when a window is emitted and replaced by simply + * A {@link rx.functions.Func0} object which produces {@link rx.Observable}s. These {@link rx.Observable}s determine when a window is emitted and replaced by simply * producing an object. * @return - * the {@link rx.util.functions.Func1} object representing the specified window operation. + * the {@link rx.functions.Func1} object representing the specified window operation. */ public static OnSubscribeFunc> window(final Observable source, final Observable windowOpenings, final Func1> windowClosingSelector) { return new OnSubscribeFunc>() { @@ -106,7 +110,7 @@ public Subscription onSubscribe(final Observer> observer) } /** - *

This method creates a {@link rx.util.functions.Func1} object which represents the window operation. This operation takes + *

This method creates a {@link rx.functions.Func1} object which represents the window operation. This operation takes * values from the specified {@link rx.Observable} source and stores them in a window until the window contains * a specified number of elements. The window is then emitted, and a new window is created to replace it. * When the source {@link rx.Observable} completes or produces an error, the current window is emitted, and @@ -120,14 +124,14 @@ public Subscription onSubscribe(final Observer> observer) * @param count * The number of elements a window should have before being emitted and replaced. * @return - * the {@link rx.util.functions.Func1} object representing the specified window operation. + * the {@link rx.functions.Func1} object representing the specified window operation. */ public static OnSubscribeFunc> window(Observable source, int count) { return window(source, count, count); } /** - *

This method creates a {@link rx.util.functions.Func1} object which represents the window operation. This operation takes + *

This method creates a {@link rx.functions.Func1} object which represents the window operation. This operation takes * values from the specified {@link rx.Observable} source and stores them in all active windows until the window * contains a specified number of elements. The window is then emitted. windows are created after a certain * amount of values have been received. When the source {@link rx.Observable} completes or produces an error, the @@ -147,7 +151,7 @@ public static OnSubscribeFunc> window(Observable * > "count" non-overlapping windows will be created and some values will not be pushed * into a window at all! * @return - * the {@link rx.util.functions.Func1} object representing the specified window operation. + * the {@link rx.functions.Func1} object representing the specified window operation. */ public static OnSubscribeFunc> window(final Observable source, final int count, final int skip) { return new OnSubscribeFunc>() { @@ -161,7 +165,7 @@ public Subscription onSubscribe(final Observer> observer) } /** - *

This method creates a {@link rx.util.functions.Func1} object which represents the window operation. This operation takes + *

This method creates a {@link rx.functions.Func1} object which represents the window operation. This operation takes * values from the specified {@link rx.Observable} source and stores them in a window. Periodically the window * is emitted and replaced with a new window. How often this is done depends on the specified timespan. * When the source {@link rx.Observable} completes or produces an error, the current window is emitted, and @@ -177,14 +181,14 @@ public Subscription onSubscribe(final Observer> observer) * @param unit * The {@link java.util.concurrent.TimeUnit} defining the unit of time for the timespan. * @return - * the {@link rx.util.functions.Func1} object representing the specified window operation. + * the {@link rx.functions.Func1} object representing the specified window operation. */ public static OnSubscribeFunc> window(Observable source, long timespan, TimeUnit unit) { return window(source, timespan, unit, Schedulers.threadPoolForComputation()); } /** - *

This method creates a {@link rx.util.functions.Func1} object which represents the window operation. This operation takes + *

This method creates a {@link rx.functions.Func1} object which represents the window operation. This operation takes * values from the specified {@link rx.Observable} source and stores them in a window. Periodically the window * is emitted and replaced with a new window. How often this is done depends on the specified timespan. * When the source {@link rx.Observable} completes or produces an error, the current window is emitted, and @@ -202,7 +206,7 @@ public static OnSubscribeFunc> window(Observable * @param scheduler * The {@link rx.Scheduler} to use for timing windows. * @return - * the {@link rx.util.functions.Func1} object representing the specified window operation. + * the {@link rx.functions.Func1} object representing the specified window operation. */ public static OnSubscribeFunc> window(final Observable source, final long timespan, final TimeUnit unit, final Scheduler scheduler) { return new OnSubscribeFunc>() { @@ -216,7 +220,7 @@ public Subscription onSubscribe(final Observer> observer) } /** - *

This method creates a {@link rx.util.functions.Func1} object which represents the window operation. This operation takes + *

This method creates a {@link rx.functions.Func1} object which represents the window operation. This operation takes * values from the specified {@link rx.Observable} source and stores them in a window. Periodically the window * is emitted and replaced with a new window. How often this is done depends on the specified timespan. * Additionally the window is automatically emitted once it reaches a specified number of elements. @@ -235,14 +239,14 @@ public Subscription onSubscribe(final Observer> observer) * @param count * The maximum size of the window. Once a window reaches this size, it is emitted. * @return - * the {@link rx.util.functions.Func1} object representing the specified window operation. + * the {@link rx.functions.Func1} object representing the specified window operation. */ public static OnSubscribeFunc> window(Observable source, long timespan, TimeUnit unit, int count) { return window(source, timespan, unit, count, Schedulers.threadPoolForComputation()); } /** - *

This method creates a {@link rx.util.functions.Func1} object which represents the window operation. This operation takes + *

This method creates a {@link rx.functions.Func1} object which represents the window operation. This operation takes * values from the specified {@link rx.Observable} source and stores them in a window. Periodically the window * is emitted and replaced with a new window. How often this is done depends on the specified timespan. * Additionally the window is automatically emitted once it reaches a specified number of elements. @@ -263,7 +267,7 @@ public static OnSubscribeFunc> window(Observable * @param scheduler * The {@link rx.Scheduler} to use for timing windows. * @return - * the {@link rx.util.functions.Func1} object representing the specified window operation. + * the {@link rx.functions.Func1} object representing the specified window operation. */ public static OnSubscribeFunc> window(final Observable source, final long timespan, final TimeUnit unit, final int count, final Scheduler scheduler) { return new OnSubscribeFunc>() { @@ -277,7 +281,7 @@ public Subscription onSubscribe(final Observer> observer) } /** - *

This method creates a {@link rx.util.functions.Func1} object which represents the window operation. This operation takes + *

This method creates a {@link rx.functions.Func1} object which represents the window operation. This operation takes * values from the specified {@link rx.Observable} source and stores them in a window. Periodically the window * is emitted and replaced with a new window. How often this is done depends on the specified timespan. * The creation of windows is also periodical. How often this is done depends on the specified timeshift. @@ -296,14 +300,14 @@ public Subscription onSubscribe(final Observer> observer) * @param unit * The {@link java.util.concurrent.TimeUnit} defining the unit of time for the timespan. * @return - * the {@link rx.util.functions.Func1} object representing the specified window operation. + * the {@link rx.functions.Func1} object representing the specified window operation. */ public static OnSubscribeFunc> window(Observable source, long timespan, long timeshift, TimeUnit unit) { return window(source, timespan, timeshift, unit, Schedulers.threadPoolForComputation()); } /** - *

This method creates a {@link rx.util.functions.Func1} object which represents the window operation. This operation takes + *

This method creates a {@link rx.functions.Func1} object which represents the window operation. This operation takes * values from the specified {@link rx.Observable} source and stores them in a window. Periodically the window * is emitted and replaced with a new window. How often this is done depends on the specified timespan. * The creation of windows is also periodical. How often this is done depends on the specified timeshift. @@ -324,7 +328,7 @@ public static OnSubscribeFunc> window(Observable * @param scheduler * The {@link rx.Scheduler} to use for timing windows. * @return - * the {@link rx.util.functions.Func1} object representing the specified window operation. + * the {@link rx.functions.Func1} object representing the specified window operation. */ public static OnSubscribeFunc> window(final Observable source, final long timespan, final long timeshift, final TimeUnit unit, final Scheduler scheduler) { return new OnSubscribeFunc>() { @@ -354,4 +358,150 @@ public Observable getContents() { return Observable.from(contents); } } + + /** + * Emits windows of values of the source Observable where the window boundary is + * determined by the items of the boundary Observable. + */ + public static OnSubscribeFunc> window(Observable source, Observable boundary) { + return new WindowViaObservable(source, boundary); + } + + /** + * Create non-overlapping windows from the source values by using another observable's + * values as to when to replace a window. + */ + private static final class WindowViaObservable implements OnSubscribeFunc> { + final Observable source; + final Observable boundary; + + public WindowViaObservable(Observable source, Observable boundary) { + this.source = source; + this.boundary = boundary; + } + + @Override + public Subscription onSubscribe(Observer> t1) { + CompositeSubscription csub = new CompositeSubscription(); + + final SourceObserver so = new SourceObserver(t1, csub); + try { + t1.onNext(so.subject); + } catch (Throwable t) { + t1.onError(t); + return Subscriptions.empty(); + } + csub.add(source.subscribe(so)); + + if (!csub.isUnsubscribed()) { + csub.add(boundary.subscribe(new BoundaryObserver(so))); + } + + return csub; + } + + /** + * Observe the source and emit the values into the current window. + */ + private static final class SourceObserver implements Observer { + final Observer> observer; + final Subscription cancel; + final Object guard; + Subject subject; + + public SourceObserver(Observer> observer, Subscription cancel) { + this.observer = observer; + this.cancel = cancel; + this.guard = new Object(); + this.subject = create(); + } + + Subject create() { + return PublishSubject.create(); + } + + @Override + public void onNext(T args) { + synchronized (guard) { + if (subject == null) { + return; + } + subject.onNext(args); + } + } + + @Override + public void onError(Throwable e) { + synchronized (guard) { + if (subject == null) { + return; + } + Subject s = subject; + subject = null; + + s.onError(e); + observer.onError(e); + } + cancel.unsubscribe(); + } + + @Override + public void onCompleted() { + synchronized (guard) { + if (subject == null) { + return; + } + Subject s = subject; + subject = null; + + s.onCompleted(); + observer.onCompleted(); + } + cancel.unsubscribe(); + } + + public void replace() { + try { + synchronized (guard) { + if (subject == null) { + return; + } + Subject s = subject; + s.onCompleted(); + + subject = create(); + observer.onNext(subject); + } + } catch (Throwable t) { + onError(t); + } + } + } + + /** + * Observe the boundary and replace the window on each item. + */ + private static final class BoundaryObserver implements Observer { + final SourceObserver so; + + public BoundaryObserver(SourceObserver so) { + this.so = so; + } + + @Override + public void onNext(U args) { + so.replace(); + } + + @Override + public void onError(Throwable e) { + so.onError(e); + } + + @Override + public void onCompleted() { + so.onCompleted(); + } + } + } } diff --git a/rxjava-core/src/main/java/rx/operators/OperationZip.java b/rxjava-core/src/main/java/rx/operators/OperationZip.java deleted file mode 100644 index 6ecbb87e49..0000000000 --- a/rxjava-core/src/main/java/rx/operators/OperationZip.java +++ /dev/null @@ -1,531 +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.ArrayList; -import java.util.Arrays; -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 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.CompositeSubscription; -import rx.subscriptions.SerialSubscription; -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.Functions; - -/** - * Returns an Observable that emits the results of a function applied to sets of items emitted, in - * sequence, by two or more other Observables. - *

- * - *

- * The zip operation 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 each zipped - * Observable; the second item emitted by the new Observable will be the result of the function - * applied to the second item emitted by each zipped Observable; and so forth. - *

- * The resulting Observable returned from zip will invoke onNext as many times as the - * number of onNext invocations of the source Observable that emits the fewest items. - */ -public final class OperationZip { - - @SuppressWarnings("unchecked") - public static OnSubscribeFunc zip(Observable o1, Observable o2, final Func2 zipFunction) { - return zip(Arrays.asList(o1, o2), Functions.fromFunc(zipFunction)); - } - - @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)); - } - - @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)); - } - - @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, - 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, - 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, - 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, 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, final FuncN zipFunction) { - ManyObservables a = new ManyObservables(ws, zipFunction); - return a; - } - - /* - * 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 { - a.next(this, args); - } catch (Throwable e) { - onError(e); - } - } - } - - /** - * 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 - */ - /* 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 - */ - 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) { - // store that this ZipObserver is completed - completed.put(w, Boolean.TRUE); - // if all ZipObservers are completed, we mark the whole thing as completed - if (completed.size() == observers.size()) { - if (running.compareAndSet(true, false)) { - // this thread succeeded in setting running=false so let's propagate the completion - // mark ourselves as done - observer.onCompleted(); - } - } - } - - /** - * Receive error for a Observer. Throw the error up the chain and stop processing. - * - * @param w - */ - void error(ZipObserver w, Throwable e) { - if (running.compareAndSet(true, false)) { - // this thread succeeded in setting running=false so let's propagate the error - observer.onError(e); - /* since we receive an error we want to tell everyone to stop */ - 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 - */ - 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 - for (ZipObserver rw : receivedValuesPerObserver.keySet()) { - if (receivedValuesPerObserver.get(rw).peek() == null) { - // we have a null meaning the queues aren't all populated so won't do anything - return; - } - } - // if we get to here this means all the queues have data - int i = 0; - for (ZipObserver rw : observers) { - argsToZip[i++] = receivedValuesPerObserver.get(rw).remove(); - } - } - // if we did not return above from the synchronized block we can now invoke the zipFunction with all of the args - // we do this outside the synchronized block as it is now safe to call this concurrently and don't need to block other threads from calling - // 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)) { - SafeObservableSubscription subscription = new SafeObservableSubscription(); - this.observer = new SynchronizedObserver(observer, subscription); - /* start the Observers */ - 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 - */ - private void stop() { - /* tell ourselves to stop processing onNext events by setting running=false */ - if (running.compareAndSet(true, false)) { - /* propogate to all Observers to unsubscribe if this thread succeeded in setting running=false */ - for (ZipObserver o : observers) { - if (o.subscription != null) { - o.subscription.unsubscribe(); - } - } - } - } - - } - /** - * 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; - /** - * Constructor. - * @param sources the sources - * @param selector the result selector - */ - public ManyObservables( - Iterable> sources, - FuncN selector) { - this.sources = sources; - this.selector = selector; - } - - @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 void onCompleted() { - observer.onCompleted(); - } - @Override - public void onError(Throwable t) { - observer.onError(t); - } - @Override - public void onNext(List value) { - observer.onNext(selector.call(value.toArray(new Object[value.size()]))); - } - }; - - 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; - } - /** - * 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; - } - continue; - } - 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); - } else { - break; - } - } - } finally { - rwLock.writeLock().unlock(); - } - } - } - - @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(); - } - if (c) { - unsubscribe(); - } - } - - @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(); - } - } - /** Connect to the source observable. */ - public void connect() { - toSource.setSubscription(source.subscribe(this)); - } - @Override - public void unsubscribe() { - toSource.unsubscribe(); - } - - } - } -} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorCast.java b/rxjava-core/src/main/java/rx/operators/OperatorCast.java new file mode 100644 index 0000000000..cbad677443 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorCast.java @@ -0,0 +1,58 @@ +/** + * Copyright 2014 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.Operator; +import rx.Subscriber; +import rx.exceptions.Exceptions; +import rx.exceptions.OnErrorThrowable; + +/** + * Converts the elements of an observable sequence to the specified type. + */ +public class OperatorCast implements Operator { + + private final Class castClass; + + public OperatorCast(Class castClass) { + this.castClass = castClass; + } + + @Override + public Subscriber call(final Subscriber o) { + return new Subscriber(o) { + + @Override + public void onCompleted() { + o.onCompleted(); + } + + @Override + public void onError(Throwable e) { + o.onError(e); + } + + @Override + public void onNext(T t) { + try { + o.onNext(castClass.cast(t)); + } catch (Throwable e) { + onError(OnErrorThrowable.addValueAsLastCause(e, t)); + } + } + }; + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorDoOnEach.java b/rxjava-core/src/main/java/rx/operators/OperatorDoOnEach.java new file mode 100644 index 0000000000..150ad112b3 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorDoOnEach.java @@ -0,0 +1,70 @@ +/** + * Copyright 2014 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.Operator; +import rx.Observer; +import rx.Subscriber; +import rx.exceptions.OnErrorThrowable; + +/** + * Converts the elements of an observable sequence to the specified type. + */ +public class OperatorDoOnEach implements Operator { + private final Observer doOnEachObserver; + + public OperatorDoOnEach(Observer doOnEachObserver) { + this.doOnEachObserver = doOnEachObserver; + } + + @Override + public Subscriber call(final Subscriber observer) { + return new Subscriber(observer) { + @Override + public void onCompleted() { + try { + doOnEachObserver.onCompleted(); + } catch (Throwable e) { + onError(e); + return; + } + observer.onCompleted(); + } + + @Override + public void onError(Throwable e) { + try { + doOnEachObserver.onError(e); + } catch (Throwable e2) { + observer.onError(e2); + return; + } + observer.onError(e); + } + + @Override + public void onNext(T value) { + try { + doOnEachObserver.onNext(value); + } catch (Throwable e) { + onError(OnErrorThrowable.addValueAsLastCause(e, value)); + return; + } + observer.onNext(value); + } + }; + } +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/operators/OperatorFilter.java b/rxjava-core/src/main/java/rx/operators/OperatorFilter.java new file mode 100644 index 0000000000..76b256c720 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorFilter.java @@ -0,0 +1,64 @@ +/** + * Copyright 2014 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.Operator; +import rx.Subscriber; +import rx.exceptions.OnErrorThrowable; +import rx.functions.Func1; + +/** + * Filters an Observable by discarding any items it emits that do not meet some test. + *

+ * + */ +public final class OperatorFilter implements Operator { + + private final Func1 predicate; + + public OperatorFilter(Func1 predicate) { + this.predicate = predicate; + } + + @Override + public Subscriber call(final Subscriber child) { + return new Subscriber(child) { + + @Override + public void onCompleted() { + child.onCompleted(); + } + + @Override + public void onError(Throwable e) { + child.onError(e); + } + + @Override + public void onNext(T t) { + try { + if (predicate.call(t)) { + child.onNext(t); + } + } catch (Throwable e) { + child.onError(OnErrorThrowable.addValueAsLastCause(e, t)); + } + } + + }; + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorGroupBy.java b/rxjava-core/src/main/java/rx/operators/OperatorGroupBy.java new file mode 100644 index 0000000000..a8e5ffb53f --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorGroupBy.java @@ -0,0 +1,154 @@ +/** + * Copyright 2014 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 java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import rx.Observable.OnSubscribe; +import rx.Observable.Operator; +import rx.Subscriber; +import rx.exceptions.OnErrorThrowable; +import rx.functions.Action0; +import rx.functions.Func1; +import rx.observables.GroupedObservable; +import rx.subjects.PublishSubject; +import rx.subjects.Subject; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.Subscriptions; + +/** + * Groups the items emitted by an Observable according to a specified criterion, and emits these + * grouped items as Observables, one Observable per group. + *

+ * + */ +public final class OperatorGroupBy implements Operator, T> { + + final Func1 keySelector; + + public OperatorGroupBy(final Func1 keySelector) { + this.keySelector = keySelector; + } + + @Override + public Subscriber call(final Subscriber> childObserver) { + // a new CompositeSubscription to decouple the subscription as the inner subscriptions need a separate lifecycle + // and will unsubscribe on this parent if they are all unsubscribed + return new Subscriber(new CompositeSubscription()) { + private final Map> groups = new HashMap>(); + private final AtomicInteger completionCounter = new AtomicInteger(0); + private final AtomicBoolean completed = new AtomicBoolean(false); + + @Override + public void onCompleted() { + completed.set(true); + // if we receive onCompleted from our parent we onComplete children + for (Subject ps : groups.values()) { + ps.onCompleted(); + } + + // special case for empty (no groups emitted) + if (completionCounter.get() == 0) { + childObserver.onCompleted(); + } + } + + @Override + public void onError(Throwable e) { + // we immediately tear everything down if we receive an error + childObserver.onError(e); + } + + @Override + public void onNext(T t) { + try { + final K key = keySelector.call(t); + Subject gps = groups.get(key); + if (gps == null) { + // this group doesn't exist + if (childObserver.isUnsubscribed()) { + // we have been unsubscribed on the outer so won't send any more groups + return; + } + gps = PublishSubject.create(); + final Subject _gps = gps; + + GroupedObservable go = new GroupedObservable(key, new OnSubscribe() { + + @Override + public void call(final Subscriber o) { + // number of children we have running + completionCounter.incrementAndGet(); + o.add(Subscriptions.create(new Action0() { + + @Override + public void call() { + completeInner(); + } + + })); + _gps.subscribe(new Subscriber(o) { + + @Override + public void onCompleted() { + o.onCompleted(); + completeInner(); + } + + @Override + public void onError(Throwable e) { + o.onError(e); + } + + @Override + public void onNext(T t) { + o.onNext(t); + } + + }); + } + + }); + groups.put(key, gps); + childObserver.onNext(go); + } + // we have the correct group so send value to it + gps.onNext(t); + } catch (Throwable e) { + onError(OnErrorThrowable.addValueAsLastCause(e, t)); + } + } + + private void completeInner() { + if (completionCounter.decrementAndGet() == 0 && (completed.get() || childObserver.isUnsubscribed())) { + if (childObserver.isUnsubscribed()) { + // if the entire groupBy has been unsubscribed and children are completed we will propagate the unsubscribe up. + unsubscribe(); + } + for (Subject ps : groups.values()) { + ps.onCompleted(); + } + childObserver.onCompleted(); + } + } + + }; + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorMap.java b/rxjava-core/src/main/java/rx/operators/OperatorMap.java new file mode 100644 index 0000000000..6418005521 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorMap.java @@ -0,0 +1,63 @@ +/** + * Copyright 2014 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.Operator; +import rx.Subscriber; +import rx.exceptions.OnErrorThrowable; +import rx.functions.Func1; + +/** + * Applies a function of your choosing to every item emitted by an Observable, and returns this + * transformation as a new Observable. + *

+ * + */ +public final class OperatorMap implements Operator { + + private final Func1 transformer; + + public OperatorMap(Func1 transformer) { + this.transformer = transformer; + } + + @Override + public Subscriber call(final Subscriber o) { + return new Subscriber(o) { + + @Override + public void onCompleted() { + o.onCompleted(); + } + + @Override + public void onError(Throwable e) { + o.onError(e); + } + + @Override + public void onNext(T t) { + try { + o.onNext(transformer.call(t)); + } catch (Throwable e) { + onError(OnErrorThrowable.addValueAsLastCause(e, t)); + } + } + + }; + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorMerge.java b/rxjava-core/src/main/java/rx/operators/OperatorMerge.java new file mode 100644 index 0000000000..5b51485224 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorMerge.java @@ -0,0 +1,104 @@ +/** + * Copyright 2014 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.Operator; +import rx.Subscriber; +import rx.observers.SynchronizedSubscriber; +import rx.subscriptions.CompositeSubscription; + +/** + * Flattens a list of Observables into one Observable sequence, without any transformation. + *

+ * + *

+ * You can combine the items emitted by multiple Observables so that they act like a single + * Observable, by using the merge operation. + */ +public final class OperatorMerge implements Operator> { + + @Override + public Subscriber> call(final Subscriber outerOperation) { + + final Subscriber o = new SynchronizedSubscriber(outerOperation); + final CompositeSubscription childrenSubscriptions = new CompositeSubscription(); + outerOperation.add(childrenSubscriptions); + + return new Subscriber>(outerOperation) { + + private volatile boolean completed = false; + private final AtomicInteger runningCount = new AtomicInteger(); + + @Override + public void onCompleted() { + completed = true; + if (runningCount.get() == 0) { + o.onCompleted(); + } + } + + @Override + public void onError(Throwable e) { + o.onError(e); + } + + @Override + public void onNext(Observable innerObservable) { + runningCount.incrementAndGet(); + Subscriber i = new InnerObserver(); + childrenSubscriptions.add(i); + innerObservable.subscribe(i); + } + + final class InnerObserver extends Subscriber { + + public InnerObserver() { + } + + @Override + public void onCompleted() { + if (runningCount.decrementAndGet() == 0 && completed) { + o.onCompleted(); + } + cleanup(); + } + + @Override + public void onError(Throwable e) { + o.onError(e); + cleanup(); + } + + @Override + public void onNext(T a) { + o.onNext(a); + } + + private void cleanup() { + // remove subscription onCompletion so it cleans up immediately and doesn't memory leak + // see https://github.com/Netflix/RxJava/issues/897 + childrenSubscriptions.remove(this); + } + + }; + + }; + + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorObserveOn.java b/rxjava-core/src/main/java/rx/operators/OperatorObserveOn.java new file mode 100644 index 0000000000..c2457dd8d2 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorObserveOn.java @@ -0,0 +1,155 @@ +/** + * Copyright 2014 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.AtomicLong; + +import rx.Observable.Operator; +import rx.Scheduler; +import rx.Scheduler.Inner; +import rx.Subscriber; +import rx.functions.Action1; +import rx.schedulers.ImmediateScheduler; +import rx.schedulers.TrampolineScheduler; + +/** + * Delivers events on the specified Scheduler asynchronously via an unbounded buffer. + * + * + */ +public class OperatorObserveOn implements Operator { + + private final Scheduler scheduler; + + /** + * @param scheduler + */ + public OperatorObserveOn(Scheduler scheduler) { + this.scheduler = scheduler; + } + + @Override + public Subscriber call(Subscriber child) { + if (scheduler instanceof ImmediateScheduler) { + // avoid overhead, execute directly + return child; + } else if (scheduler instanceof TrampolineScheduler) { + // avoid overhead, execute directly + return child; + } else { + return new ObserveOnSubscriber(child); + } + } + + private static class Sentinel { + + } + + private static Sentinel NULL_SENTINEL = new Sentinel(); + private static Sentinel COMPLETE_SENTINEL = new Sentinel(); + + private static class ErrorSentinel extends Sentinel { + final Throwable e; + + ErrorSentinel(Throwable e) { + this.e = e; + } + } + + /** Observe through individual queue per observer. */ + private class ObserveOnSubscriber extends Subscriber { + final Subscriber observer; + private volatile Scheduler.Inner recursiveScheduler; + + private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue(); + final AtomicLong counter = new AtomicLong(0); + + public ObserveOnSubscriber(Subscriber observer) { + super(observer); + this.observer = observer; + } + + @Override + public void onNext(final T t) { + if (t == null) { + queue.offer(NULL_SENTINEL); + } else { + queue.offer(t); + } + schedule(); + } + + @Override + public void onCompleted() { + queue.offer(COMPLETE_SENTINEL); + schedule(); + } + + @Override + public void onError(final Throwable e) { + queue.offer(new ErrorSentinel(e)); + schedule(); + } + + protected void schedule() { + if (counter.getAndIncrement() == 0) { + if (recursiveScheduler == null) { + add(scheduler.schedule(new Action1() { + + @Override + public void call(Inner inner) { + recursiveScheduler = inner; + pollQueue(); + } + + })); + } else { + recursiveScheduler.schedule(new Action1() { + + @Override + public void call(Inner inner) { + pollQueue(); + } + + }); + } + } + } + + @SuppressWarnings("unchecked") + private void pollQueue() { + do { + Object v = queue.poll(); + if (v != null) { + if (v instanceof Sentinel) { + if (v == NULL_SENTINEL) { + observer.onNext(null); + } else if (v == COMPLETE_SENTINEL) { + observer.onCompleted(); + } else if (v instanceof ErrorSentinel) { + observer.onError(((ErrorSentinel) v).e); + } + } else { + observer.onNext((T) v); + } + } + } while (counter.decrementAndGet() > 0); + } + + } + +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/operators/OperatorObserveOnBounded.java b/rxjava-core/src/main/java/rx/operators/OperatorObserveOnBounded.java new file mode 100644 index 0000000000..97c53ae431 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorObserveOnBounded.java @@ -0,0 +1,330 @@ +/** + * Copyright 2014 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.Semaphore; +import java.util.concurrent.atomic.AtomicLong; + +import rx.Observable.Operator; +import rx.Scheduler; +import rx.Scheduler.Inner; +import rx.Subscriber; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.schedulers.ImmediateScheduler; +import rx.schedulers.TestScheduler; +import rx.schedulers.TrampolineScheduler; +import rx.subscriptions.Subscriptions; + +/** + * Delivers events on the specified Scheduler. + *

+ * This provides backpressure by blocking the incoming onNext when there is already one in the queue. + *

+ * This means that at any given time the max number of "onNext" in flight is 3: + * -> 1 being delivered on the Scheduler + * -> 1 in the queue waiting for the Scheduler + * -> 1 blocking on the queue waiting to deliver it + * + * I have chosen to allow 1 in the queue rather than using an Exchanger style process so that the Scheduler + * can loop and have something to do each time around to optimize for avoiding rescheduling when it + * can instead just loop. I'm avoiding having the Scheduler thread ever block as it could be an event-loop + * thus if the queue is empty it exits and next time something is added it will reschedule. + * + * + */ +public class OperatorObserveOnBounded implements Operator { + + private final Scheduler scheduler; + private final int bufferSize; + + /** + * + * @param scheduler + * @param bufferSize + * that will be rounded up to the next power of 2 + */ + public OperatorObserveOnBounded(Scheduler scheduler, int bufferSize) { + this.scheduler = scheduler; + this.bufferSize = roundToNextPowerOfTwoIfNecessary(bufferSize); + } + + public OperatorObserveOnBounded(Scheduler scheduler) { + this(scheduler, 1); + } + + private static int roundToNextPowerOfTwoIfNecessary(int num) { + if ((num & -num) == num) { + return num; + } else { + int result = 1; + while (num != 0) + { + num >>= 1; + result <<= 1; + } + return result; + } + } + + @Override + public Subscriber call(Subscriber child) { + if (scheduler instanceof ImmediateScheduler) { + // avoid overhead, execute directly + return child; + } else if (scheduler instanceof TrampolineScheduler) { + // avoid overhead, execute directly + return child; + } else if (scheduler instanceof TestScheduler) { + // this one will deadlock as it is single-threaded and won't run the scheduled + // work until it manually advances, which it won't be able to do as it will block + return child; + } else { + return new ObserveOnSubscriber(child); + } + } + + private static Object NULL_SENTINEL = new Object(); + private static Object COMPLETE_SENTINEL = new Object(); + + private static class ErrorSentinel { + final Throwable e; + + ErrorSentinel(Throwable e) { + this.e = e; + } + } + + /** Observe through individual queue per observer. */ + private class ObserveOnSubscriber extends Subscriber { + final Subscriber observer; + private volatile Scheduler.Inner recursiveScheduler; + + private final InterruptibleBlockingQueue queue = new InterruptibleBlockingQueue(bufferSize); + final AtomicLong counter = new AtomicLong(0); + + public ObserveOnSubscriber(Subscriber observer) { + super(observer); + this.observer = observer; + } + + @Override + public void onNext(final T t) { + try { + // we want to block for natural back-pressure + // so that the producer waits for each value to be consumed + if (t == null) { + queue.addBlocking(NULL_SENTINEL); + } else { + queue.addBlocking(t); + } + schedule(); + } catch (InterruptedException e) { + if (!isUnsubscribed()) { + onError(e); + } + } + } + + @Override + public void onCompleted() { + try { + // we want to block for natural back-pressure + // so that the producer waits for each value to be consumed + queue.addBlocking(COMPLETE_SENTINEL); + schedule(); + } catch (InterruptedException e) { + onError(e); + } + } + + @Override + public void onError(final Throwable e) { + try { + // we want to block for natural back-pressure + // so that the producer waits for each value to be consumed + queue.addBlocking(new ErrorSentinel(e)); + schedule(); + } catch (InterruptedException e2) { + // call directly if we can't schedule + observer.onError(e2); + } + } + + protected void schedule() { + if (counter.getAndIncrement() == 0) { + if (recursiveScheduler == null) { + // first time through, register a Subscription + // that can interrupt this thread + add(Subscriptions.create(new Action0() { + + @Override + public void call() { + // we have to interrupt the parent thread because + // it can be blocked on queue.put + queue.interrupt(); + } + + })); + add(scheduler.schedule(new Action1() { + + @Override + public void call(Inner inner) { + recursiveScheduler = inner; + pollQueue(); + } + + })); + } else { + recursiveScheduler.schedule(new Action1() { + + @Override + public void call(Inner inner) { + pollQueue(); + } + + }); + } + } + } + + @SuppressWarnings("unchecked") + private void pollQueue() { + do { + Object v = queue.poll(); + if (v != null) { + if (v == NULL_SENTINEL) { + observer.onNext(null); + } else if (v == COMPLETE_SENTINEL) { + observer.onCompleted(); + } else if (v instanceof ErrorSentinel) { + observer.onError(((ErrorSentinel) v).e); + } else { + observer.onNext((T) v); + } + } + } while (counter.decrementAndGet() > 0); + } + + } + + /** + * Single-producer-single-consumer queue (only thread-safe for 1 producer thread with 1 consumer thread). + * + * This supports an interrupt() being called externally rather than needing to interrupt the thread. This allows + * unsubscribe behavior when this queue is being used. + * + * @param + */ + private static class InterruptibleBlockingQueue { + + private final Semaphore semaphore; + private volatile boolean interrupted = false; + + private final E[] buffer; + + private AtomicLong tail = new AtomicLong(); + private AtomicLong head = new AtomicLong(); + private final int capacity; + private final int mask; + + @SuppressWarnings("unchecked") + public InterruptibleBlockingQueue(final int size) { + this.semaphore = new Semaphore(size); + this.capacity = size; + this.mask = size - 1; + buffer = (E[]) new Object[size]; + } + + /** + * Used to unsubscribe and interrupt the producer if blocked in put() + */ + public void interrupt() { + interrupted = true; + semaphore.release(); + } + + public void addBlocking(final E e) throws InterruptedException { + if (interrupted) { + throw new InterruptedException("Interrupted by Unsubscribe"); + } + semaphore.acquire(); + if (interrupted) { + throw new InterruptedException("Interrupted by Unsubscribe"); + } + if (e == null) { + throw new IllegalArgumentException("Can not put null"); + } + + if (offer(e)) { + return; + } else { + throw new IllegalStateException("Queue is full"); + } + } + + private boolean offer(final E e) { + final long _t = tail.get(); + if (_t - head.get() == capacity) { + // queue is full + return false; + } + int index = (int) (_t & mask); + buffer[index] = e; + // move the tail forward + tail.lazySet(_t + 1); + + return true; + } + + public E poll() { + if (interrupted) { + return null; + } + final long _h = head.get(); + if (tail.get() == _h) { + // nothing available + return null; + } + int index = (int) (_h & mask); + + // fetch the item + E v = buffer[index]; + // allow GC to happen + buffer[index] = null; + // increment and signal we're done + head.lazySet(_h + 1); + if (v != null) { + semaphore.release(); + } + return v; + } + + public int size() + { + int size; + do + { + final long currentHead = head.get(); + final long currentTail = tail.get(); + size = (int) (currentTail - currentHead); + } while (size > buffer.length); + + return size; + } + + } +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/operators/OperatorOnErrorFlatMap.java b/rxjava-core/src/main/java/rx/operators/OperatorOnErrorFlatMap.java new file mode 100644 index 0000000000..92806aad7e --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorOnErrorFlatMap.java @@ -0,0 +1,82 @@ +/** + * Copyright 2014 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.Operator; +import rx.Subscriber; +import rx.exceptions.OnErrorThrowable; +import rx.functions.Func1; + +/** + * Allows inserting onNext events into a stream when onError events are received + * and continuing the original sequence instead of terminating. Thus it allows a sequence + * with multiple onError events. + */ +public final class OperatorOnErrorFlatMap implements Operator { + + private final Func1> resumeFunction; + + public OperatorOnErrorFlatMap(Func1> f) { + this.resumeFunction = f; + } + + @Override + public Subscriber call(final Subscriber child) { + return new Subscriber(child) { + + @Override + public void onCompleted() { + child.onCompleted(); + } + + @Override + public void onError(Throwable e) { + try { + Observable resume = resumeFunction.call(OnErrorThrowable.from(e)); + resume.subscribe(new Subscriber() { + + @Override + public void onCompleted() { + // ignore as we will continue the parent Observable + } + + @Override + public void onError(Throwable e) { + // if the splice also fails we shut it all down + child.onError(e); + } + + @Override + public void onNext(T t) { + child.onNext(t); + } + + }); + } catch (Throwable e2) { + child.onError(e2); + } + } + + @Override + public void onNext(T t) { + child.onNext(t); + } + + }; + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorOnErrorResumeNextViaFunction.java b/rxjava-core/src/main/java/rx/operators/OperatorOnErrorResumeNextViaFunction.java new file mode 100644 index 0000000000..5b3adc2360 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorOnErrorResumeNextViaFunction.java @@ -0,0 +1,77 @@ +/** + * Copyright 2014 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.Operator; +import rx.Subscriber; +import rx.functions.Func1; + +/** + * Instruct an Observable to pass control to another Observable (the return value of a function) + * rather than invoking onError if it encounters an error. + *

+ * + *

+ * By default, when an Observable encounters an error that prevents it from emitting the expected + * item to its Observer, the Observable invokes its Observer's onError method, and + * then quits without invoking any more of its Observer's methods. The onErrorResumeNext operation + * changes this behavior. If you pass a function that returns an Observable (resumeFunction) to + * onErrorResumeNext, if the source Observable encounters an error, instead of invoking its + * Observer's onError method, it will instead relinquish control to this new + * Observable, which will invoke the Observer's 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. + */ +public final class OperatorOnErrorResumeNextViaFunction implements Operator { + + private final Func1> resumeFunction; + + public OperatorOnErrorResumeNextViaFunction(Func1> f) { + this.resumeFunction = f; + } + + @Override + public Subscriber call(final Subscriber child) { + return new Subscriber(child) { + + @Override + public void onCompleted() { + child.onCompleted(); + } + + @Override + public void onError(Throwable e) { + try { + Observable resume = resumeFunction.call(e); + resume.subscribe(child); + } catch (Throwable e2) { + child.onError(e2); + } + } + + @Override + public void onNext(T t) { + child.onNext(t); + } + + }; + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorParallel.java b/rxjava-core/src/main/java/rx/operators/OperatorParallel.java new file mode 100644 index 0000000000..0e9893a54b --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorParallel.java @@ -0,0 +1,71 @@ +/** + * Copyright 2014 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.Operator; +import rx.Scheduler; +import rx.Subscriber; +import rx.functions.Func1; +import rx.observables.GroupedObservable; + +/** + * Identifies unit of work that can be executed in parallel on a given Scheduler. + */ +public final class OperatorParallel implements Operator { + + private final Scheduler scheduler; + private final Func1, Observable> f; + private final int degreeOfParallelism; + + public OperatorParallel(Func1, Observable> f, Scheduler scheduler) { + this.scheduler = scheduler; + this.f = f; + this.degreeOfParallelism = scheduler.degreeOfParallelism(); + } + + @Override + public Subscriber call(Subscriber op) { + + Func1>, Subscriber> groupBy = + new OperatorGroupBy(new Func1() { + + long i = 0; + + @Override + public Long call(T t) { + return i++ % degreeOfParallelism; + } + + }); + + Func1>, Subscriber>> map = + new OperatorMap, Observable>( + new Func1, Observable>() { + + @Override + public Observable call(GroupedObservable g) { + // Must use observeOn not subscribeOn because we have a single source behind groupBy. + // The origin is already subscribed to, we are moving each group on to a new thread + // but the origin itself can only be on a single thread. + return f.call(g.observeOn(scheduler)); + } + }); + + // bind together Observers + return groupBy.call(map.call(new OperatorMerge().call(op))); + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorRepeat.java b/rxjava-core/src/main/java/rx/operators/OperatorRepeat.java new file mode 100644 index 0000000000..d587aa8a6b --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorRepeat.java @@ -0,0 +1,109 @@ +/** + * Copyright 2014 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.Operator; +import rx.Scheduler; +import rx.Scheduler.Inner; +import rx.Subscriber; +import rx.functions.Action1; +import rx.observers.Subscribers; +import rx.schedulers.Schedulers; + +public class OperatorRepeat implements Operator> { + + private final Scheduler scheduler; + private final long count; + + public OperatorRepeat(long count, Scheduler scheduler) { + this.scheduler = scheduler; + this.count = count; + } + + public OperatorRepeat(Scheduler scheduler) { + this(-1, scheduler); + } + + public OperatorRepeat(long count) { + this(count, Schedulers.trampoline()); + } + + public OperatorRepeat() { + this(-1, Schedulers.trampoline()); + } + + @Override + public Subscriber> call(final Subscriber child) { + if (count == 0) { + child.onCompleted(); + return Subscribers.empty(); + } + return new Subscriber>(child) { + + int executionCount = 0; + + @Override + public void onCompleted() { + // ignore as we will keep repeating + } + + @Override + public void onError(Throwable e) { + // we should never receive this but if we do we pass it on + child.onError(new IllegalStateException("Error received on nested Observable.", e)); + } + + @Override + public void onNext(final Observable t) { + scheduler.schedule(new Action1() { + + final Action1 self = this; + + @Override + public void call(final Inner inner) { + executionCount++; + t.subscribe(new Subscriber(child) { + + @Override + public void onCompleted() { + if (count == -1 || executionCount < count) { + inner.schedule(self); + } else { + child.onCompleted(); + } + } + + @Override + public void onError(Throwable e) { + child.onError(e); + } + + @Override + public void onNext(T t) { + child.onNext(t); + } + + }); + } + + }); + } + + }; + } +} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/operators/OperatorScan.java b/rxjava-core/src/main/java/rx/operators/OperatorScan.java new file mode 100644 index 0000000000..2cd8de2626 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorScan.java @@ -0,0 +1,117 @@ +/** + * Copyright 2014 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.Operator; +import rx.Subscriber; +import rx.exceptions.OnErrorThrowable; +import rx.functions.Func2; + +/** + * Returns an Observable that applies a function 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. + */ +public final class OperatorScan implements Operator { + + private final R initialValue; + private final Func2 accumulator; + // sentinel if we don't receive an initial value + private static final Object NO_INITIAL_VALUE = new Object(); + + /** + * Applies an accumulator function over an observable sequence and returns each intermediate + * result with the specified source and accumulator. + * + * @param sequence + * An observable sequence of elements to project. + * @param initialValue + * The initial (seed) accumulator value. + * @param accumulator + * 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)) + */ + public OperatorScan(R initialValue, Func2 accumulator) { + this.initialValue = initialValue; + this.accumulator = accumulator; + } + + /** + * Applies an accumulator function over an observable sequence and returns each intermediate + * result with the specified source and accumulator. + * + * @param sequence + * An observable sequence of elements to project. + * @param accumulator + * 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) Method (IObservable(TSource), Func(TSource, TSource, TSource)) + */ + @SuppressWarnings("unchecked") + public OperatorScan(final Func2 accumulator) { + this((R) NO_INITIAL_VALUE, accumulator); + } + + @Override + public Subscriber call(final Subscriber observer) { + if (initialValue != NO_INITIAL_VALUE) { + observer.onNext(initialValue); + } + return new Subscriber(observer) { + private R value = initialValue; + + @SuppressWarnings("unchecked") + @Override + public void onNext(T value) { + if (this.value == NO_INITIAL_VALUE) { + // if there is NO_INITIAL_VALUE then we know it is type T for both so cast T to R + this.value = (R) value; + } else { + try { + this.value = accumulator.call(this.value, value); + } catch (Throwable e) { + observer.onError(OnErrorThrowable.addValueAsLastCause(e, value)); + } + } + observer.onNext(this.value); + } + + @Override + public void onError(Throwable e) { + observer.onError(e); + } + + @Override + public void onCompleted() { + observer.onCompleted(); + } + }; + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorSubscribeOn.java b/rxjava-core/src/main/java/rx/operators/OperatorSubscribeOn.java new file mode 100644 index 0000000000..d549c146c2 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorSubscribeOn.java @@ -0,0 +1,65 @@ +/** + * Copyright 2014 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.Operator; +import rx.Scheduler; +import rx.Scheduler.Inner; +import rx.Subscriber; +import rx.functions.Action1; + +/** + * Subscribes Observers on the specified Scheduler. + *

+ * + */ +public class OperatorSubscribeOn implements Operator> { + + private final Scheduler scheduler; + + public OperatorSubscribeOn(Scheduler scheduler) { + this.scheduler = scheduler; + } + + @Override + public Subscriber> call(final Subscriber subscriber) { + return new Subscriber>(subscriber) { + + @Override + public void onCompleted() { + // ignore because this is a nested Observable and we expect only 1 Observable emitted to onNext + } + + @Override + public void onError(Throwable e) { + subscriber.onError(e); + } + + @Override + public void onNext(final Observable o) { + subscriber.add(scheduler.schedule(new Action1() { + + @Override + public void call(final Inner inner) { + o.subscribe(subscriber); + } + })); + } + + }; + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorSubscribeOnBounded.java b/rxjava-core/src/main/java/rx/operators/OperatorSubscribeOnBounded.java new file mode 100644 index 0000000000..969f0df8b9 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorSubscribeOnBounded.java @@ -0,0 +1,113 @@ +/** + * Copyright 2014 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.Operator; +import rx.Scheduler; +import rx.Scheduler.Inner; +import rx.Subscriber; +import rx.functions.Action1; + +/** + * Subscribes and unsubscribes Observers on the specified Scheduler. + *

+ * Will occur asynchronously except when subscribing to `GroupedObservable`, `PublishSubject` and possibly other "hot" Observables + * in which case it will subscribe synchronously and buffer/block onNext calls until the subscribe has occurred. + *

+ * See https://github.com/Netflix/RxJava/issues/844 for more information on the "time gap" issue that the synchronous + * subscribe is solving. + * + * + */ +public class OperatorSubscribeOnBounded implements Operator> { + + private final Scheduler scheduler; + /** + * Indicate that events fired between the original subscription time and + * the actual subscription time should not get lost. + */ + private final boolean dontLoseEvents; + /** The buffer size to avoid flooding. Negative value indicates an unbounded buffer. */ + private final int bufferSize; + + public OperatorSubscribeOnBounded(Scheduler scheduler) { + this.scheduler = scheduler; + this.dontLoseEvents = false; + this.bufferSize = -1; + } + + /** + * Construct a SubscribeOn operator. + * + * @param scheduler + * the target scheduler + * @param bufferSize + * if dontLoseEvents == true, this indicates the buffer size. Filling the buffer will + * block the source. -1 indicates an unbounded buffer + */ + public OperatorSubscribeOnBounded(Scheduler scheduler, int bufferSize) { + this.scheduler = scheduler; + this.dontLoseEvents = true; + this.bufferSize = bufferSize; + } + + @Override + public Subscriber> call(final Subscriber subscriber) { + return new Subscriber>(subscriber) { + + @Override + public void onCompleted() { + // ignore + } + + @Override + public void onError(Throwable e) { + subscriber.onError(e); + } + + boolean checkNeedBuffer(Observable o) { + return dontLoseEvents; + } + + @Override + public void onNext(final Observable o) { + if (checkNeedBuffer(o)) { + // use buffering (possibly blocking) for a possibly synchronous subscribe + final BufferUntilSubscriber bus = new BufferUntilSubscriber(bufferSize, subscriber); + o.subscribe(bus); + subscriber.add(scheduler.schedule(new Action1() { + @Override + public void call(final Inner inner) { + bus.enterPassthroughMode(); + } + })); + return; + } else { + // no buffering (async subscribe) + subscriber.add(scheduler.schedule(new Action1() { + + @Override + public void call(final Inner inner) { + o.subscribe(subscriber); + } + })); + } + } + + }; + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorTake.java b/rxjava-core/src/main/java/rx/operators/OperatorTake.java new file mode 100644 index 0000000000..62ab98b41c --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorTake.java @@ -0,0 +1,92 @@ +/** + * Copyright 2014 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.Operator; +import rx.Subscriber; +import rx.subscriptions.CompositeSubscription; + +/** + * Returns an Observable that emits the first num items emitted by the source + * Observable. + *

+ * + *

+ * You can choose to pay attention only to the first num items emitted by an + * Observable by using the take operation. This operation returns an Observable that will invoke a + * subscribing Observer's onNext function a maximum of num times before + * invoking onCompleted. + */ +public final class OperatorTake implements Operator { + + final int limit; + + public OperatorTake(int limit) { + this.limit = limit; + } + + @Override + public Subscriber call(final Subscriber child) { + final CompositeSubscription parent = new CompositeSubscription(); + if (limit == 0) { + child.onCompleted(); + parent.unsubscribe(); + } + + /* + * We decouple the parent and child subscription so there can be multiple take() in a chain + * such as for the groupBy Observer use case where you may take(1) on groups and take(20) on the children. + * + * Thus, we only unsubscribe UPWARDS to the parent and an onComplete DOWNSTREAM. + * + * However, if we receive an unsubscribe from the child we still want to propagate it upwards so we register 'parent' with 'child' + */ + child.add(parent); + return new Subscriber(parent) { + + int count = 0; + boolean completed = false; + + @Override + public void onCompleted() { + if (!completed) { + child.onCompleted(); + } + } + + @Override + public void onError(Throwable e) { + if (!completed) { + child.onError(e); + } + } + + @Override + public void onNext(T i) { + if (!isUnsubscribed()) { + child.onNext(i); + if (++count >= limit) { + completed = true; + child.onCompleted(); + unsubscribe(); + } + } + } + + }; + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorTimeout.java b/rxjava-core/src/main/java/rx/operators/OperatorTimeout.java new file mode 100644 index 0000000000..eb5debfcd5 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorTimeout.java @@ -0,0 +1,65 @@ +/** + * Copyright 2014 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.Observable; +import rx.Scheduler; +import rx.Scheduler.Inner; +import rx.Subscription; +import rx.functions.Action1; + +/** + * 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 OperatorTimeout extends OperatorTimeoutBase { + + public OperatorTimeout(final long timeout, final TimeUnit timeUnit, + Observable other, final Scheduler scheduler) { + super(new FirstTimeoutStub() { + + @Override + public Subscription call( + final TimeoutSubscriber timeoutSubscriber, + final Long seqId) { + return scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + timeoutSubscriber.onTimeout(seqId); + } + }, timeout, timeUnit); + } + }, new TimeoutStub() { + + @Override + public Subscription call( + final TimeoutSubscriber timeoutSubscriber, + final Long seqId, T value) { + return scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + timeoutSubscriber.onTimeout(seqId); + } + }, timeout, timeUnit); + } + }, other); + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorTimeoutBase.java b/rxjava-core/src/main/java/rx/operators/OperatorTimeoutBase.java new file mode 100644 index 0000000000..3d6dc97998 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorTimeoutBase.java @@ -0,0 +1,163 @@ +/** + * Copyright 2014 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.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import rx.Observable; +import rx.Observable.Operator; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Func2; +import rx.functions.Func3; +import rx.observers.SynchronizedSubscriber; +import rx.subscriptions.SerialSubscription; + +class OperatorTimeoutBase implements Operator { + + /** + * Set up the timeout action on the first value. + * + * @param + */ + /* package-private */static interface FirstTimeoutStub extends + Func2, Long, Subscription> { + } + + /** + * Set up the timeout action based on every value + * + * @param + */ + /* package-private */static interface TimeoutStub extends + Func3, Long, T, Subscription> { + } + + private final FirstTimeoutStub firstTimeoutStub; + private final TimeoutStub timeoutStub; + private final Observable other; + + /* package-private */OperatorTimeoutBase( + FirstTimeoutStub firstTimeoutStub, TimeoutStub timeoutStub, + Observable other) { + this.firstTimeoutStub = firstTimeoutStub; + this.timeoutStub = timeoutStub; + this.other = other; + } + + @Override + public Subscriber call(Subscriber subscriber) { + final SerialSubscription serial = new SerialSubscription(); + subscriber.add(serial); + // Use SynchronizedSubscriber for safe memory access + // as the subscriber will be accessed in the current thread or the + // scheduler or other Observables. + final SynchronizedSubscriber synchronizedSubscriber = new SynchronizedSubscriber( + subscriber); + + TimeoutSubscriber timeoutSubscriber = new TimeoutSubscriber( + synchronizedSubscriber, timeoutStub, serial, other); + serial.set(firstTimeoutStub.call(timeoutSubscriber, 0L)); + return timeoutSubscriber; + } + + /* package-private */static class TimeoutSubscriber extends + Subscriber { + + private final AtomicBoolean terminated = new AtomicBoolean(false); + private final AtomicLong actual = new AtomicLong(0L); + private final SerialSubscription serial; + private final Object gate = new Object(); + + private final SynchronizedSubscriber synchronizedSubscriber; + + private final TimeoutStub timeoutStub; + + private final Observable other; + + private TimeoutSubscriber( + SynchronizedSubscriber synchronizedSubscriber, + TimeoutStub timeoutStub, SerialSubscription serial, + Observable other) { + this.synchronizedSubscriber = synchronizedSubscriber; + this.timeoutStub = timeoutStub; + this.serial = serial; + this.other = other; + } + + @Override + public void onNext(T value) { + boolean onNextWins = false; + synchronized (gate) { + if (!terminated.get()) { + actual.incrementAndGet(); + onNextWins = true; + } + } + if (onNextWins) { + synchronizedSubscriber.onNext(value); + serial.set(timeoutStub.call(this, actual.get(), value)); + } + } + + @Override + public void onError(Throwable error) { + boolean onErrorWins = false; + synchronized (gate) { + if (!terminated.getAndSet(true)) { + onErrorWins = true; + } + } + if (onErrorWins) { + serial.unsubscribe(); + synchronizedSubscriber.onError(error); + } + } + + @Override + public void onCompleted() { + boolean onCompletedWins = false; + synchronized (gate) { + if (!terminated.getAndSet(true)) { + onCompletedWins = true; + } + } + if (onCompletedWins) { + serial.unsubscribe(); + synchronizedSubscriber.onCompleted(); + } + } + + public void onTimeout(long seqId) { + long expected = seqId; + boolean timeoutWins = false; + synchronized (gate) { + if (expected == actual.get() && !terminated.getAndSet(true)) { + timeoutWins = true; + } + } + if (timeoutWins) { + if (other == null) { + synchronizedSubscriber.onError(new TimeoutException()); + } else { + serial.set(other.subscribe(synchronizedSubscriber)); + } + } + } + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorTimeoutWithSelector.java b/rxjava-core/src/main/java/rx/operators/OperatorTimeoutWithSelector.java new file mode 100644 index 0000000000..ffc79f749b --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorTimeoutWithSelector.java @@ -0,0 +1,112 @@ +/** + * Copyright 2014 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.Subscriber; +import rx.Subscription; +import rx.exceptions.Exceptions; +import rx.functions.Func0; +import rx.functions.Func1; +import rx.subscriptions.Subscriptions; + +/** + * Returns an Observable that mirrors the source Observable. If either the first + * item emitted by the source Observable or any subsequent item don't arrive + * within time windows defined by provided Observables, switch to the + * other Observable if provided, or emit a TimeoutException . + */ +public class OperatorTimeoutWithSelector extends + OperatorTimeoutBase { + + public OperatorTimeoutWithSelector( + final Func0> firstTimeoutSelector, + final Func1> timeoutSelector, + Observable other) { + super(new FirstTimeoutStub() { + + @Override + public Subscription call( + final TimeoutSubscriber timeoutSubscriber, + final Long seqId) { + if (firstTimeoutSelector != null) { + Observable o = null; + try { + o = firstTimeoutSelector.call(); + } catch (Throwable t) { + Exceptions.throwIfFatal(t); + timeoutSubscriber.onError(t); + return Subscriptions.empty(); + } + return o.subscribe(new Subscriber() { + + @Override + public void onCompleted() { + timeoutSubscriber.onTimeout(seqId); + } + + @Override + public void onError(Throwable e) { + timeoutSubscriber.onError(e); + } + + @Override + public void onNext(U t) { + timeoutSubscriber.onTimeout(seqId); + } + + }); + } else { + return Subscriptions.empty(); + } + } + }, new TimeoutStub() { + + @Override + public Subscription call( + final TimeoutSubscriber timeoutSubscriber, + final Long seqId, T value) { + Observable o = null; + try { + o = timeoutSelector.call(value); + } catch (Throwable t) { + Exceptions.throwIfFatal(t); + timeoutSubscriber.onError(t); + return Subscriptions.empty(); + } + return o.subscribe(new Subscriber() { + + @Override + public void onCompleted() { + timeoutSubscriber.onTimeout(seqId); + } + + @Override + public void onError(Throwable e) { + timeoutSubscriber.onError(e); + } + + @Override + public void onNext(V t) { + timeoutSubscriber.onTimeout(seqId); + } + + }); + } + }, other); + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorTimestamp.java b/rxjava-core/src/main/java/rx/operators/OperatorTimestamp.java new file mode 100644 index 0000000000..a6d2a360da --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorTimestamp.java @@ -0,0 +1,61 @@ +/** + * Copyright 2014 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.Operator; +import rx.Scheduler; +import rx.Subscriber; +import rx.schedulers.Timestamped; + +/** + * Wraps each item emitted by a source Observable in a {@link Timestamped} object. + *

+ * + */ +public final class OperatorTimestamp implements Operator, T> { + + private final Scheduler scheduler; + + public OperatorTimestamp(Scheduler scheduler) { + this.scheduler = scheduler; + } + + /** + * @return a sequence of timestamped values created by adding timestamps to each item in the input sequence. + */ + @Override + public Subscriber call(final Subscriber> o) { + return new Subscriber(o) { + + @Override + public void onCompleted() { + o.onCompleted(); + } + + @Override + public void onError(Throwable e) { + o.onError(e); + } + + @Override + public void onNext(T t) { + o.onNext(new Timestamped(scheduler.now(), t)); + } + + }; + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorToObservableList.java b/rxjava-core/src/main/java/rx/operators/OperatorToObservableList.java new file mode 100644 index 0000000000..3466c87ca5 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorToObservableList.java @@ -0,0 +1,69 @@ +/** + * Copyright 2014 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 rx.Observable.Operator; +import rx.Subscriber; + +/** + * 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 Observer's + * onNext method for each such item. You can change this behavior, instructing the + * Observable to compose a list of all of these multiple items and then to invoke the Observer's + * onNext method once, passing it the entire list, by using the toList Observer. + *

+ * Be careful not to use this Observer on Observables that emit infinite or very large numbers of + * items, as you do not have the option to unsubscribe. + */ +public final class OperatorToObservableList implements Operator, T> { + + @Override + public Subscriber call(final Subscriber> o) { + return new Subscriber(o) { + + final List list = new ArrayList(); + + @Override + public void onCompleted() { + try { + o.onNext(new ArrayList(list)); + o.onCompleted(); + } catch (Throwable e) { + onError(e); + } + } + + @Override + public void onError(Throwable e) { + o.onError(e); + } + + @Override + public void onNext(T value) { + list.add(value); + } + + }; + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorToObservableSortedList.java b/rxjava-core/src/main/java/rx/operators/OperatorToObservableSortedList.java new file mode 100644 index 0000000000..fa1c931ca5 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorToObservableSortedList.java @@ -0,0 +1,104 @@ +/** + * Copyright 2014 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.Collections; +import java.util.Comparator; +import java.util.List; + +import rx.Observable.Operator; +import rx.Subscriber; +import rx.functions.Func2; + +/** + * Return an Observable that emits the items emitted by the source Observable, in a sorted order + * (each item emitted by the Observable must implement Comparable with respect to all other items + * in the sequence, or you must pass in a sort function). + *

+ * + * + * @param + */ +public final class OperatorToObservableSortedList implements Operator, T> { + private final Func2 sortFunction; + + @SuppressWarnings("unchecked") + public OperatorToObservableSortedList() { + this.sortFunction = defaultSortFunction; + } + + public OperatorToObservableSortedList(Func2 sortFunction) { + this.sortFunction = sortFunction; + } + + @Override + public Subscriber call(final Subscriber> o) { + return new Subscriber(o) { + + final List list = new ArrayList(); + + @Override + public void onCompleted() { + try { + + // sort the list before delivery + Collections.sort(list, new Comparator() { + + @Override + public int compare(T o1, T o2) { + return sortFunction.call(o1, o2); + } + + }); + + o.onNext(Collections.unmodifiableList(list)); + o.onCompleted(); + } catch (Throwable e) { + onError(e); + } + } + + @Override + public void onError(Throwable e) { + o.onError(e); + } + + @Override + public void onNext(T value) { + list.add(value); + } + + }; + } + + // raw because we want to support Object for this default + @SuppressWarnings("rawtypes") + private static Func2 defaultSortFunction = new DefaultComparableFunction(); + + private static class DefaultComparableFunction implements Func2 { + + // unchecked because we want to support Object for this default + @SuppressWarnings("unchecked") + @Override + public Integer call(Object t1, Object t2) { + Comparable c1 = (Comparable) t1; + Comparable c2 = (Comparable) t2; + return c1.compareTo(c2); + } + + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorUnsubscribeOn.java b/rxjava-core/src/main/java/rx/operators/OperatorUnsubscribeOn.java new file mode 100644 index 0000000000..c48e0bc69f --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorUnsubscribeOn.java @@ -0,0 +1,79 @@ +/** + * Copyright 2014 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.Operator; +import rx.Scheduler; +import rx.Scheduler.Inner; +import rx.Subscriber; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.MultipleAssignmentSubscription; +import rx.subscriptions.Subscriptions; + +/** + * Unsubscribes on the specified Scheduler. + *

+ */ +public class OperatorUnsubscribeOn implements Operator { + + private final Scheduler scheduler; + + public OperatorUnsubscribeOn(Scheduler scheduler) { + this.scheduler = scheduler; + } + + @Override + public Subscriber call(final Subscriber subscriber) { + final CompositeSubscription parentSubscription = new CompositeSubscription(); + subscriber.add(Subscriptions.create(new Action0() { + + @Override + public void call() { + final MultipleAssignmentSubscription mas = new MultipleAssignmentSubscription(); + mas.set(scheduler.schedule(new Action1() { + + @Override + public void call(final Inner inner) { + parentSubscription.unsubscribe(); + mas.unsubscribe(); + } + })); + } + + })); + + return new Subscriber(parentSubscription) { + + @Override + public void onCompleted() { + subscriber.onCompleted(); + } + + @Override + public void onError(Throwable e) { + subscriber.onError(e); + } + + @Override + public void onNext(T t) { + subscriber.onNext(t); + } + + }; + } +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorZip.java b/rxjava-core/src/main/java/rx/operators/OperatorZip.java new file mode 100644 index 0000000000..f211e42815 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorZip.java @@ -0,0 +1,251 @@ +/** + * Copyright 2014 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.AtomicLong; + +import rx.Observable; +import rx.Observable.Operator; +import rx.Observer; +import rx.Subscriber; +import rx.exceptions.OnErrorThrowable; +import rx.functions.Func2; +import rx.functions.Func3; +import rx.functions.Func4; +import rx.functions.Func5; +import rx.functions.Func6; +import rx.functions.Func7; +import rx.functions.Func8; +import rx.functions.Func9; +import rx.functions.FuncN; +import rx.functions.Functions; +import rx.subscriptions.CompositeSubscription; + +/** + * Returns an Observable that emits the results of a function applied to sets of items emitted, in + * sequence, by two or more other Observables. + *

+ * + *

+ * The zip operation 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 each zipped + * Observable; the second item emitted by the new Observable will be the result of the function + * applied to the second item emitted by each zipped Observable; and so forth. + *

+ * The resulting Observable returned from zip will invoke onNext as many times as the + * number of onNext invocations of the source Observable that emits the fewest items. + */ +public final class OperatorZip implements Operator[]> { + /* + * Raw types are used so we can use a single implementation for all arities such as zip(t1, t2) and zip(t1, t2, t3) etc. + * The types will be cast on the edges so usage will be the type-safe but the internals are not. + */ + + final FuncN zipFunction; + + public OperatorZip(FuncN f) { + this.zipFunction = f; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public OperatorZip(Func2 f) { + this.zipFunction = Functions.fromFunc(f); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public OperatorZip(Func3 f) { + this.zipFunction = Functions.fromFunc(f); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public OperatorZip(Func4 f) { + this.zipFunction = Functions.fromFunc(f); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public OperatorZip(Func5 f) { + this.zipFunction = Functions.fromFunc(f); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public OperatorZip(Func6 f) { + this.zipFunction = Functions.fromFunc(f); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public OperatorZip(Func7 f) { + this.zipFunction = Functions.fromFunc(f); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public OperatorZip(Func8 f) { + this.zipFunction = Functions.fromFunc(f); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public OperatorZip(Func9 f) { + this.zipFunction = Functions.fromFunc(f); + } + + @SuppressWarnings("rawtypes") + @Override + public Subscriber call(final Subscriber observer) { + return new Subscriber(observer) { + + @Override + public void onCompleted() { + // we only complete once a child Observable completes or errors + } + + @Override + public void onError(Throwable e) { + observer.onError(e); + } + + @Override + public void onNext(Observable[] observables) { + new Zip(observables, observer, zipFunction).zip(); + } + + }; + } + + private static class Zip { + @SuppressWarnings("rawtypes") + final Observable[] os; + final Object[] observers; + final Observer observer; + final FuncN zipFunction; + final CompositeSubscription childSubscription = new CompositeSubscription(); + + static Object NULL_SENTINEL = new Object(); + static Object COMPLETE_SENTINEL = new Object(); + + @SuppressWarnings("rawtypes") + public Zip(Observable[] os, final Subscriber observer, FuncN zipFunction) { + this.os = os; + this.observer = observer; + this.zipFunction = zipFunction; + observers = new Object[os.length]; + for (int i = 0; i < os.length; i++) { + InnerObserver io = new InnerObserver(); + observers[i] = io; + childSubscription.add(io); + } + + observer.add(childSubscription); + } + + @SuppressWarnings("unchecked") + public void zip() { + for (int i = 0; i < os.length; i++) { + os[i].subscribe((InnerObserver) observers[i]); + } + } + + final AtomicLong counter = new AtomicLong(0); + + /** + * check if we have values for each and emit if we do + * + * This will only allow one thread at a time to do the work, but ensures via `counter` increment/decrement + * that there is always once who acts on each `tick`. Same concept as used in OperationObserveOn. + * + */ + @SuppressWarnings("unchecked") + void tick() { + if (counter.getAndIncrement() == 0) { + do { + Object[] vs = new Object[observers.length]; + boolean allHaveValues = true; + for (int i = 0; i < observers.length; i++) { + vs[i] = ((InnerObserver) observers[i]).items.peek(); + if (vs[i] == NULL_SENTINEL) { + // special handling for null + vs[i] = null; + } else if (vs[i] == COMPLETE_SENTINEL) { + // special handling for onComplete + observer.onCompleted(); + // we need to unsubscribe from all children since children are independently subscribed + childSubscription.unsubscribe(); + return; + } else if (vs[i] == null) { + allHaveValues = false; + // we continue as there may be an onCompleted on one of the others + continue; + } + } + if (allHaveValues) { + try { + // all have something so emit + observer.onNext(zipFunction.call(vs)); + } catch (Throwable e) { + observer.onError(OnErrorThrowable.addValueAsLastCause(e, vs)); + return; + } + // now remove them + for (int i = 0; i < observers.length; i++) { + ((InnerObserver) observers[i]).items.poll(); + // eagerly check if the next item on this queue is an onComplete + if (((InnerObserver) observers[i]).items.peek() == COMPLETE_SENTINEL) { + // it is an onComplete so shut down + observer.onCompleted(); + // we need to unsubscribe from all children since children are independently subscribed + childSubscription.unsubscribe(); + return; + } + } + } + } while (counter.decrementAndGet() > 0); + } + + } + + // used to observe each Observable we are zipping together + // it collects all items in an internal queue + @SuppressWarnings("rawtypes") + final class InnerObserver extends Subscriber { + // Concurrent* since we need to read it from across threads + final ConcurrentLinkedQueue items = new ConcurrentLinkedQueue(); + + @SuppressWarnings("unchecked") + @Override + public void onCompleted() { + items.add(COMPLETE_SENTINEL); + tick(); + } + + @Override + public void onError(Throwable e) { + // emit error and shut down + observer.onError(e); + } + + @SuppressWarnings("unchecked") + @Override + public void onNext(Object t) { + if (t == null) { + items.add(NULL_SENTINEL); + } else { + items.add(t); + } + tick(); + } + }; + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/OperatorZipIterable.java b/rxjava-core/src/main/java/rx/operators/OperatorZipIterable.java new file mode 100644 index 0000000000..05f2a47c82 --- /dev/null +++ b/rxjava-core/src/main/java/rx/operators/OperatorZipIterable.java @@ -0,0 +1,73 @@ +/** + * Copyright 2014 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.Iterator; + +import rx.Observable.Operator; +import rx.Subscriber; +import rx.functions.Func2; +import rx.observers.Subscribers; + +public final class OperatorZipIterable implements Operator { + + final Iterable iterable; + final Func2 zipFunction; + + public OperatorZipIterable(Iterable iterable, Func2 zipFunction) { + this.iterable = iterable; + this.zipFunction = zipFunction; + } + + @Override + public Subscriber call(final Subscriber subscriber) { + final Iterator iterator = iterable.iterator(); + try { + if (!iterator.hasNext()) { + subscriber.onCompleted(); + return Subscribers.empty(); + } + } catch (Throwable e) { + subscriber.onError(e); + } + return new Subscriber() { + + @Override + public void onCompleted() { + subscriber.onCompleted(); + } + + @Override + public void onError(Throwable e) { + subscriber.onError(e); + } + + @Override + public void onNext(T1 t) { + try { + subscriber.onNext(zipFunction.call(t, iterator.next())); + if (!iterator.hasNext()) { + onCompleted(); + } + } catch (Throwable e) { + onError(e); + } + } + + }; + } + +} diff --git a/rxjava-core/src/main/java/rx/operators/README.txt b/rxjava-core/src/main/java/rx/operators/README.txt index 1be4baa962..a3a2a3e394 100644 --- a/rxjava-core/src/main/java/rx/operators/README.txt +++ b/rxjava-core/src/main/java/rx/operators/README.txt @@ -1,5 +1,5 @@ ==== - Copyright 2013 Netflix, Inc. + Copyright 2014 Netflix, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/rxjava-core/src/main/java/rx/operators/SafeObservableSubscription.java b/rxjava-core/src/main/java/rx/operators/SafeObservableSubscription.java index ef33ebd3d3..af496c05d7 100644 --- a/rxjava-core/src/main/java/rx/operators/SafeObservableSubscription.java +++ b/rxjava-core/src/main/java/rx/operators/SafeObservableSubscription.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,14 +21,10 @@ /** * Thread-safe wrapper around Observable Subscription that ensures unsubscribe can be called only once. - *

- * Also used to: - *

- *

    - *
  • allow the AtomicObserver to have access to the subscription in asynchronous execution for checking if unsubscribed occurred without onComplete/onError.
  • - *
  • handle both synchronous and asynchronous subscribe() execution flows
  • - *
+ * + * @deprecated since `Observer` now implements `Subscription` and `CompositeSubscription` etc handle these things */ +@Deprecated public final class SafeObservableSubscription implements Subscription { private static final Subscription UNSUBSCRIBED = new Subscription() @@ -37,6 +33,11 @@ public final class SafeObservableSubscription implements Subscription { public void unsubscribe() { } + + @Override + public boolean isUnsubscribed() { + return true; + } }; private final AtomicReference actualSubscription = new AtomicReference(); diff --git a/rxjava-core/src/main/java/rx/operators/SafeObserver.java b/rxjava-core/src/main/java/rx/operators/SafeObserver.java index 3e6508dac9..f776619905 100644 --- a/rxjava-core/src/main/java/rx/operators/SafeObserver.java +++ b/rxjava-core/src/main/java/rx/operators/SafeObserver.java @@ -19,9 +19,12 @@ import java.util.concurrent.atomic.AtomicBoolean; import rx.Observer; +import rx.Subscription; +import rx.exceptions.CompositeException; +import rx.exceptions.OnErrorNotImplementedException; +import rx.observers.SynchronizedObserver; import rx.plugins.RxJavaPlugins; -import rx.util.CompositeException; -import rx.util.OnErrorNotImplementedException; +import rx.subscriptions.Subscriptions; /** * Wrapper around Observer to ensure compliance with Rx contract. @@ -54,12 +57,19 @@ * It will not synchronize onNext execution. Use the {@link SynchronizedObserver} to do that. * * @param + * @Deprecated Replaced by SafeSubscriber */ +@Deprecated public class SafeObserver implements Observer { private final Observer actual; private final AtomicBoolean isFinished = new AtomicBoolean(false); - private final SafeObservableSubscription subscription; + private final Subscription subscription; + + public SafeObserver(Observer actual) { + this.subscription = Subscriptions.empty(); + this.actual = actual; + } public SafeObserver(SafeObservableSubscription subscription, Observer actual) { this.subscription = subscription; @@ -73,44 +83,18 @@ public void onCompleted() { actual.onCompleted(); } catch (Throwable e) { // handle errors if the onCompleted implementation fails, not just if the Observable fails - onError(e); + _onError(e); + } finally { + // auto-unsubscribe + subscription.unsubscribe(); } - // auto-unsubscribe - subscription.unsubscribe(); } } @Override public void onError(Throwable e) { if (isFinished.compareAndSet(false, true)) { - try { - actual.onError(e); - } catch (Throwable e2) { - if (e2 instanceof OnErrorNotImplementedException) { - /** - * onError isn't implemented so throw - * - * https://github.com/Netflix/RxJava/issues/198 - * - * Rx Design Guidelines 5.2 - * - * "when calling the Subscribe method that only has an onNext argument, the OnError behavior will be - * to rethrow the exception on the thread that the message comes out from the observable sequence. - * The OnCompleted behavior in this case is to do nothing." - */ - throw (OnErrorNotImplementedException) e2; - } else { - // if the onError itself fails then pass to the plugin - // see https://github.com/Netflix/RxJava/issues/216 for further discussion - RxJavaPlugins.getInstance().getErrorHandler().handleError(e); - RxJavaPlugins.getInstance().getErrorHandler().handleError(e2); - // and throw exception despite that not being proper for Rx - // https://github.com/Netflix/RxJava/issues/198 - throw new RuntimeException("Error occurred when trying to propagate error to Observer.onError", new CompositeException(Arrays.asList(e, e2))); - } - } - // auto-unsubscribe - subscription.unsubscribe(); + _onError(e); } } @@ -126,4 +110,59 @@ public void onNext(T args) { } } + /* + * The logic for `onError` without the `isFinished` check so it can be called from within `onCompleted`. + * + * See https://github.com/Netflix/RxJava/issues/630 for the report of this bug. + */ + protected void _onError(Throwable e) { + try { + RxJavaPlugins.getInstance().getErrorHandler().handleError(e); + actual.onError(e); + } catch (Throwable e2) { + if (e2 instanceof OnErrorNotImplementedException) { + /* + * onError isn't implemented so throw + * + * https://github.com/Netflix/RxJava/issues/198 + * + * Rx Design Guidelines 5.2 + * + * "when calling the Subscribe method that only has an onNext argument, the OnError behavior will be + * to rethrow the exception on the thread that the message comes out from the observable sequence. + * The OnCompleted behavior in this case is to do nothing." + */ + try { + subscription.unsubscribe(); + } catch (Throwable unsubscribeException) { + RxJavaPlugins.getInstance().getErrorHandler().handleError(unsubscribeException); + throw new RuntimeException("Observer.onError not implemented and error while unsubscribing.", new CompositeException(Arrays.asList(e, unsubscribeException))); + } + throw (OnErrorNotImplementedException) e2; + } else { + /* + * throw since the Rx contract is broken if onError failed + * + * https://github.com/Netflix/RxJava/issues/198 + */ + RxJavaPlugins.getInstance().getErrorHandler().handleError(e2); + try { + subscription.unsubscribe(); + } catch (Throwable unsubscribeException) { + RxJavaPlugins.getInstance().getErrorHandler().handleError(unsubscribeException); + throw new RuntimeException("Error occurred when trying to propagate error to Observer.onError and during unsubscription.", new CompositeException(Arrays.asList(e, e2, unsubscribeException))); + } + + throw new RuntimeException("Error occurred when trying to propagate error to Observer.onError", new CompositeException(Arrays.asList(e, e2))); + } + } + // if we did not throw about we will unsubscribe here, if onError failed then unsubscribe happens in the catch + try { + subscription.unsubscribe(); + } catch (RuntimeException unsubscribeException) { + RxJavaPlugins.getInstance().getErrorHandler().handleError(unsubscribeException); + throw unsubscribeException; + } + } + } diff --git a/rxjava-core/src/main/java/rx/operators/SynchronizedObserver.java b/rxjava-core/src/main/java/rx/operators/SynchronizedObserver.java deleted file mode 100644 index 2daef3e280..0000000000 --- a/rxjava-core/src/main/java/rx/operators/SynchronizedObserver.java +++ /dev/null @@ -1,121 +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 rx.Observer; - -/** - * A thread-safe Observer for transitioning states in operators. - *

- * Execution rules are: - *

    - *
  • Allow only single-threaded, synchronous, ordered execution of onNext, onCompleted, onError
  • - *
  • Once an onComplete or onError are performed, no further calls can be executed
  • - *
  • If unsubscribe is called, this means we call completed() and don't allow any further onNext calls.
  • - *
- * - * @param - */ -public final class SynchronizedObserver implements Observer { - - /** - * Intrinsic synchronized locking with double-check short-circuiting was chosen after testing several other implementations. - * - * The code and results can be found here: - * - https://github.com/benjchristensen/JavaLockPerformanceTests/tree/master/results/Observer - * - https://github.com/benjchristensen/JavaLockPerformanceTests/tree/master/src/com/benjchristensen/performance/locks/Observer - * - * The major characteristic that made me choose synchronized instead of Reentrant or a customer AbstractQueueSynchronizer implementation - * is that intrinsic locking performed better when nested, and AtomicObserver will end up nested most of the time since Rx is - * compositional by its very nature. - * - * // TODO composing of this class should rarely happen now with updated design so this decision should be revisited - */ - - private final Observer 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, new SafeObservableSubscription()); - } - - public void onNext(T arg) { - if (finished || finishRequested || subscription.isUnsubscribed()) { - // if we're already stopped, or a finish request has been received, we won't allow further onNext requests - return; - } - 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 - return; - } - observer.onNext(arg); - } - } - - public void onError(Throwable e) { - if (finished || subscription.isUnsubscribed()) { - // another thread has already finished us, so we won't proceed - return; - } - finishRequested = true; - synchronized (lock) { - // check again since this could have changed while waiting - if (finished || subscription.isUnsubscribed()) { - return; - } - observer.onError(e); - finished = true; - } - } - - public void onCompleted() { - if (finished || subscription.isUnsubscribed()) { - // another thread has already finished us, so we won't proceed - return; - } - finishRequested = true; - synchronized (lock) { - // check again since this could have changed while waiting - if (finished || subscription.isUnsubscribed()) { - return; - } - observer.onCompleted(); - finished = true; - } - } -} \ No newline at end of file diff --git a/rxjava-core/src/main/java/rx/operators/package.html b/rxjava-core/src/main/java/rx/operators/package.html index 80ba7542bf..657138e453 100644 --- a/rxjava-core/src/main/java/rx/operators/package.html +++ b/rxjava-core/src/main/java/rx/operators/package.html @@ -1,6 +1,6 @@ subscribe to infinite sequence"); + System.out.println("Starting thread: " + Thread.currentThread()); + int i = 1; + while (!o.isUnsubscribed()) { + o.onNext(i++); + Thread.yield(); + } + o.onCompleted(); + latch.countDown(); + System.out.println("Ending thread: " + Thread.currentThread()); + } + }); + t.start(); + + } + + }); + } +} diff --git a/rxjava-core/src/test/java/rx/operators/SafeObservableSubscriptionTest.java b/rxjava-core/src/test/java/rx/operators/SafeObservableSubscriptionTest.java index bb317884bc..c0a0f87669 100644 --- a/rxjava-core/src/test/java/rx/operators/SafeObservableSubscriptionTest.java +++ b/rxjava-core/src/test/java/rx/operators/SafeObservableSubscriptionTest.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 - * + * + * 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/test/java/rx/operators/OperationSynchronizeTest.java b/rxjava-core/src/test/java/rx/operators/SafeSubscriberTest.java similarity index 62% rename from rxjava-core/src/test/java/rx/operators/OperationSynchronizeTest.java rename to rxjava-core/src/test/java/rx/operators/SafeSubscriberTest.java index 73db076000..7f23a8379a 100644 --- a/rxjava-core/src/test/java/rx/operators/OperationSynchronizeTest.java +++ b/rxjava-core/src/test/java/rx/operators/SafeSubscriberTest.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 - * + * + * 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. @@ -17,7 +17,6 @@ import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; -import static rx.operators.OperationSynchronize.*; import org.junit.Test; import org.mockito.Mockito; @@ -25,68 +24,10 @@ import rx.Observable; import rx.Observer; import rx.Subscription; +import rx.observers.SafeSubscriber; +import rx.observers.TestSubscriber; -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)); - } +public class SafeSubscriberTest { /** * Ensure onNext can not be called after onError @@ -94,12 +35,12 @@ public void testOnErrorAfterUnSubscribe() { @Test public void testOnNextAfterOnError() { TestObservable t = new TestObservable(null); - Observable st = Observable.create(synchronize(Observable.create(t))); + Observable st = Observable.create(t); @SuppressWarnings("unchecked") Observer w = mock(Observer.class); @SuppressWarnings("unused") - Subscription ws = st.subscribe(w); + Subscription ws = st.subscribe(new SafeSubscriber(new TestSubscriber(w))); t.sendOnNext("one"); t.sendOnError(new RuntimeException("bad")); @@ -116,12 +57,12 @@ public void testOnNextAfterOnError() { @Test public void testOnCompletedAfterOnError() { TestObservable t = new TestObservable(null); - Observable st = Observable.create(synchronize(Observable.create(t))); + Observable st = Observable.create(t); @SuppressWarnings("unchecked") Observer w = mock(Observer.class); @SuppressWarnings("unused") - Subscription ws = st.subscribe(w); + Subscription ws = st.subscribe(new SafeSubscriber(new TestSubscriber(w))); t.sendOnNext("one"); t.sendOnError(new RuntimeException("bad")); @@ -138,12 +79,12 @@ public void testOnCompletedAfterOnError() { @Test public void testOnNextAfterOnCompleted() { TestObservable t = new TestObservable(null); - Observable st = Observable.create(synchronize(Observable.create(t))); + Observable st = Observable.create(t); @SuppressWarnings("unchecked") Observer w = mock(Observer.class); @SuppressWarnings("unused") - Subscription ws = st.subscribe(w); + Subscription ws = st.subscribe(new SafeSubscriber(new TestSubscriber(w))); t.sendOnNext("one"); t.sendOnCompleted(); @@ -161,12 +102,12 @@ public void testOnNextAfterOnCompleted() { @Test public void testOnErrorAfterOnCompleted() { TestObservable t = new TestObservable(null); - Observable st = Observable.create(synchronize(Observable.create(t))); + Observable st = Observable.create(t); @SuppressWarnings("unchecked") Observer w = mock(Observer.class); @SuppressWarnings("unused") - Subscription ws = st.subscribe(w); + Subscription ws = st.subscribe(new SafeSubscriber(new TestSubscriber(w))); t.sendOnNext("one"); t.sendOnCompleted(); @@ -210,6 +151,12 @@ public Subscription onSubscribe(final Observer observer) { @Override public void unsubscribe() { // going to do nothing to pretend I'm a bad Observable that keeps allowing events to be sent + System.out.println("==> SynchronizeTest unsubscribe that does nothing!"); + } + + @Override + public boolean isUnsubscribed() { + return false; } }; diff --git a/rxjava-core/src/test/java/rx/plugins/RxJavaPluginsTest.java b/rxjava-core/src/test/java/rx/plugins/RxJavaPluginsTest.java index ce96b43edc..c3bfce52c5 100644 --- a/rxjava-core/src/test/java/rx/plugins/RxJavaPluginsTest.java +++ b/rxjava-core/src/test/java/rx/plugins/RxJavaPluginsTest.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 - * + * + * 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. @@ -17,10 +17,25 @@ import static org.junit.Assert.*; +import org.junit.After; +import org.junit.Before; import org.junit.Test; +import rx.Observable; +import rx.Subscriber; + public class RxJavaPluginsTest { + @Before + public void resetBefore() { + RxJavaPlugins.getInstance().reset(); + } + + @After + public void resetAfter() { + RxJavaPlugins.getInstance().reset(); + } + @Test public void testErrorHandlerDefaultImpl() { RxJavaErrorHandler impl = new RxJavaPlugins().getErrorHandler(); @@ -50,7 +65,17 @@ public void testErrorHandlerViaProperty() { // inside test so it is stripped from Javadocs public static class RxJavaErrorHandlerTestImpl extends RxJavaErrorHandler { - // just use defaults + + @SuppressWarnings("unused") + private volatile Throwable e; + private volatile int count = 0; + + @Override + public void handleError(Throwable e) { + this.e = e; + count++; + } + } @Test @@ -81,6 +106,45 @@ public void testObservableExecutionHookViaProperty() { } } + @Test + public void testOnErrorWhenImplementedViaSubscribe() { + RxJavaErrorHandlerTestImpl errorHandler = new RxJavaErrorHandlerTestImpl(); + RxJavaPlugins.getInstance().registerErrorHandler(errorHandler); + + RuntimeException re = new RuntimeException("test onError"); + Observable.error(re).subscribe(new Subscriber() { + + @Override + public void onCompleted() { + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onNext(Object args) { + } + }); + assertEquals(re, errorHandler.e); + assertEquals(1, errorHandler.count); + } + + @Test + public void testOnErrorWhenNotImplemented() { + RxJavaErrorHandlerTestImpl errorHandler = new RxJavaErrorHandlerTestImpl(); + RxJavaPlugins.getInstance().registerErrorHandler(errorHandler); + + RuntimeException re = new RuntimeException("test onError"); + try { + Observable.error(re).subscribe(); + } catch (Throwable e) { + // ignore as we expect it to throw + } + assertEquals(re, errorHandler.e); + assertEquals(1, errorHandler.count); + } + // inside test so it is stripped from Javadocs public static class RxJavaObservableExecutionHookTestImpl extends RxJavaObservableExecutionHook { // just use defaults diff --git a/rxjava-core/src/test/java/rx/schedulers/AbstractSchedulerConcurrencyTests.java b/rxjava-core/src/test/java/rx/schedulers/AbstractSchedulerConcurrencyTests.java new file mode 100644 index 0000000000..39cac1740b --- /dev/null +++ b/rxjava-core/src/test/java/rx/schedulers/AbstractSchedulerConcurrencyTests.java @@ -0,0 +1,395 @@ +/** + * Copyright 2014 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.schedulers; + +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.Scheduler; +import rx.Scheduler.Inner; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.subscriptions.Subscriptions; + +/** + * Base tests for schedulers that involve threads (concurrency). + * + * These can only run on Schedulers that launch threads since they expect async/concurrent behavior. + * + * The Current/Immediate schedulers will not work with these tests. + */ +public abstract class AbstractSchedulerConcurrencyTests extends AbstractSchedulerTests { + + /** + * Bug report: https://github.com/Netflix/RxJava/issues/431 + */ + @Test + public final void testUnSubscribeForScheduler() throws InterruptedException { + final AtomicInteger countReceived = new AtomicInteger(); + final AtomicInteger countGenerated = new AtomicInteger(); + final CountDownLatch latch = new CountDownLatch(1); + + Observable.interval(50, TimeUnit.MILLISECONDS) + .map(new Func1() { + @Override + public Long call(Long aLong) { + countGenerated.incrementAndGet(); + return aLong; + } + }) + .subscribeOn(getScheduler()) + .observeOn(getScheduler()) + .subscribe(new Subscriber() { + @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) { + 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()); + } + + @Test + public void testUnsubscribeRecursiveScheduleFromOutside() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + final CountDownLatch unsubscribeLatch = new CountDownLatch(1); + final AtomicInteger counter = new AtomicInteger(); + Subscription s = getScheduler().schedule(new Action1() { + + @Override + public void call(final Inner inner) { + inner.schedule(new Action1() { + + int i = 0; + + @Override + public void call(Inner s) { + System.out.println("Run: " + i++); + if (i == 10) { + latch.countDown(); + try { + // wait for unsubscribe to finish so we are not racing it + unsubscribeLatch.await(); + } catch (InterruptedException e) { + // we expect the countDown if unsubscribe is not working + // or to be interrupted if unsubscribe is successful since + // the unsubscribe will interrupt it as it is calling Future.cancel(true) + // so we will ignore the stacktrace + } + } + + counter.incrementAndGet(); + inner.schedule(this); + } + }); + } + + }); + + latch.await(); + s.unsubscribe(); + unsubscribeLatch.countDown(); + Thread.sleep(200); // let time pass to see if the scheduler is still doing work + assertEquals(10, counter.get()); + } + + @Test + public void testUnsubscribeRecursiveScheduleFromInside() throws InterruptedException { + final CountDownLatch unsubscribeLatch = new CountDownLatch(1); + final AtomicInteger counter = new AtomicInteger(); + getScheduler().schedule(new Action1() { + + @Override + public void call(Inner inner) { + inner.schedule(new Action1() { + + int i = 0; + + @Override + public void call(Inner inner) { + System.out.println("Run: " + i++); + if (i == 10) { + inner.unsubscribe(); + } + + counter.incrementAndGet(); + inner.schedule(this); + } + }); + } + + }); + + unsubscribeLatch.countDown(); + Thread.sleep(200); // let time pass to see if the scheduler is still doing work + assertEquals(10, counter.get()); + } + + @Test + public void testUnsubscribeRecursiveScheduleWithDelay() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + final CountDownLatch unsubscribeLatch = new CountDownLatch(1); + final AtomicInteger counter = new AtomicInteger(); + Subscription s = getScheduler().schedule(new Action1() { + + @Override + public void call(final Inner innerScheduler) { + innerScheduler.schedule(new Action1() { + + long i = 1L; + + @Override + public void call(Inner s) { + if (i++ == 10) { + latch.countDown(); + try { + // wait for unsubscribe to finish so we are not racing it + unsubscribeLatch.await(); + } catch (InterruptedException e) { + // we expect the countDown if unsubscribe is not working + // or to be interrupted if unsubscribe is successful since + // the unsubscribe will interrupt it as it is calling Future.cancel(true) + // so we will ignore the stacktrace + } + } + + counter.incrementAndGet(); + innerScheduler.schedule(this, 10, TimeUnit.MILLISECONDS); + } + }, 10, TimeUnit.MILLISECONDS); + } + }); + + latch.await(); + s.unsubscribe(); + unsubscribeLatch.countDown(); + Thread.sleep(200); // let time pass to see if the scheduler is still doing work + assertEquals(10, counter.get()); + } + + @Test + public void recursionFromOuterActionAndUnsubscribeInside() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + getScheduler().schedule(new Action1() { + + int i = 0; + + @Override + public void call(Inner inner) { + i++; + if (i % 100000 == 0) { + System.out.println(i + " Total Memory: " + Runtime.getRuntime().totalMemory() + " Free: " + Runtime.getRuntime().freeMemory()); + } + if (i < 1000000L) { + inner.schedule(this); + } else { + latch.countDown(); + } + } + }); + + latch.await(); + } + + @Test + public void testRecursion() throws InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + getScheduler().schedule(new Action1() { + + private long i = 0; + + @Override + public void call(Inner inner) { + i++; + if (i % 100000 == 0) { + System.out.println(i + " Total Memory: " + Runtime.getRuntime().totalMemory() + " Free: " + Runtime.getRuntime().freeMemory()); + } + if (i < 1000000L) { + inner.schedule(this); + } else { + latch.countDown(); + } + } + }); + + latch.await(); + } + + @Test + public void testRecursionAndOuterUnsubscribe() throws InterruptedException { + // use latches instead of Thread.sleep + final CountDownLatch latch = new CountDownLatch(10); + final CountDownLatch completionLatch = new CountDownLatch(1); + + Observable obs = Observable.create(new OnSubscribeFunc() { + @Override + public Subscription onSubscribe(final Observer observer) { + + final Subscription s = getScheduler().schedule(new Action1() { + @Override + public void call(Inner inner) { + observer.onNext(42); + latch.countDown(); + + // this will recursively schedule this task for execution again + inner.schedule(this); + } + }); + + return Subscriptions.create(new Action0() { + + @Override + public void call() { + s.unsubscribe(); + observer.onCompleted(); + completionLatch.countDown(); + } + + }); + + } + }); + + final AtomicInteger count = new AtomicInteger(); + final AtomicBoolean completed = new AtomicBoolean(false); + Subscription subscribe = obs.subscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("Completed"); + completed.set(true); + } + + @Override + public void onError(Throwable e) { + System.out.println("Error"); + } + + @Override + public void onNext(Integer args) { + count.incrementAndGet(); + System.out.println(args); + } + }); + + if (!latch.await(5000, TimeUnit.MILLISECONDS)) { + fail("Timed out waiting on onNext latch"); + } + + // now unsubscribe and ensure it stops the recursive loop + subscribe.unsubscribe(); + System.out.println("unsubscribe"); + + if (!completionLatch.await(5000, TimeUnit.MILLISECONDS)) { + fail("Timed out waiting on completion latch"); + } + + // the count can be 10 or higher due to thread scheduling of the unsubscribe vs the scheduler looping to emit the count + assertTrue(count.get() >= 10); + assertTrue(completed.get()); + } + + @Test + public final void testSubscribeWithScheduler() throws InterruptedException { + final Scheduler scheduler = getScheduler(); + + final AtomicInteger count = new AtomicInteger(); + + Observable o1 = Observable. from(1, 2, 3, 4, 5); + + o1.subscribe(new Action1() { + + @Override + public void call(Integer t) { + System.out.println("Thread: " + Thread.currentThread().getName()); + System.out.println("t: " + t); + count.incrementAndGet(); + } + }); + + // the above should be blocking so we should see a count of 5 + assertEquals(5, count.get()); + + count.set(0); + + // now we'll subscribe with a scheduler and it should be async + + final String currentThreadName = Thread.currentThread().getName(); + + // latches for deterministically controlling the test below across threads + final CountDownLatch latch = new CountDownLatch(5); + final CountDownLatch first = new CountDownLatch(1); + + o1.subscribe(new Action1() { + + @Override + public void call(Integer t) { + try { + // we block the first one so we can assert this executes asynchronously with a count + first.await(1000, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException("The latch should have released if we are async.", e); + } + + assertFalse(Thread.currentThread().getName().equals(currentThreadName)); + System.out.println("Thread: " + Thread.currentThread().getName()); + System.out.println("t: " + t); + count.incrementAndGet(); + latch.countDown(); + } + }, scheduler); + + // assert we are async + assertEquals(0, count.get()); + // release the latch so it can go forward + first.countDown(); + + // wait for all 5 responses + latch.await(); + assertEquals(5, count.get()); + } + +} diff --git a/rxjava-core/src/test/java/rx/schedulers/AbstractSchedulerTests.java b/rxjava-core/src/test/java/rx/schedulers/AbstractSchedulerTests.java new file mode 100644 index 0000000000..0947bcd884 --- /dev/null +++ b/rxjava-core/src/test/java/rx/schedulers/AbstractSchedulerTests.java @@ -0,0 +1,474 @@ +/** + * Copyright 2014 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.schedulers; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.Arrays; +import java.util.List; +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 org.mockito.InOrder; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Scheduler; +import rx.Scheduler.Inner; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.subscriptions.Subscriptions; + +/** + * Base tests for all schedulers including Immediate/Current. + */ +public abstract class AbstractSchedulerTests { + + /** + * The scheduler to test + */ + protected abstract Scheduler getScheduler(); + + @Test + public void testNestedActions() throws InterruptedException { + Scheduler scheduler = getScheduler(); + final CountDownLatch latch = new CountDownLatch(1); + + 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 Action1 firstAction = new Action1() { + @Override + public void call(Inner inner) { + firstStepStart.call(); + firstStepEnd.call(); + latch.countDown(); + } + }; + final Action1 secondAction = new Action1() { + @Override + public void call(Inner inner) { + secondStepStart.call(); + inner.schedule(firstAction); + secondStepEnd.call(); + + } + }; + final Action1 thirdAction = new Action1() { + @Override + public void call(Inner inner) { + thirdStepStart.call(); + inner.schedule(secondAction); + thirdStepEnd.call(); + } + }; + + InOrder inOrder = inOrder(firstStepStart, firstStepEnd, secondStepStart, secondStepEnd, thirdStepStart, thirdStepEnd); + + scheduler.schedule(thirdAction); + + latch.await(); + + 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 final void testNestedScheduling() { + + Observable ids = Observable.from(Arrays.asList(1, 2), getScheduler()); + + Observable m = ids.flatMap(new Func1>() { + + @Override + public Observable call(Integer id) { + return Observable.from(Arrays.asList("a-" + id, "b-" + id), getScheduler()) + .map(new Func1() { + + @Override + public String call(String s) { + return "names=>" + s; + } + }); + } + + }); + + List strings = m.toList().toBlockingObservable().last(); + + assertEquals(4, strings.size()); + // because flatMap does a merge there is no guarantee of order + assertTrue(strings.contains("names=>a-1")); + assertTrue(strings.contains("names=>a-2")); + assertTrue(strings.contains("names=>b-1")); + assertTrue(strings.contains("names=>b-2")); + } + + /** + * The order of execution is nondeterministic. + * + * @throws InterruptedException + */ + @SuppressWarnings("rawtypes") + @Test + public final void testSequenceOfActions() throws InterruptedException { + final Scheduler scheduler = getScheduler(); + + final CountDownLatch latch = new CountDownLatch(2); + final Action1 first = mock(Action1.class); + final Action1 second = mock(Action1.class); + + // make it wait until both the first and second are called + doAnswer(new Answer() { + + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + try { + return invocation.getMock(); + } finally { + latch.countDown(); + } + } + }).when(first).call(any(Inner.class)); + doAnswer(new Answer() { + + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + try { + return invocation.getMock(); + } finally { + latch.countDown(); + } + } + }).when(second).call(any(Inner.class)); + + scheduler.schedule(first); + scheduler.schedule(second); + + latch.await(); + + verify(first, times(1)).call(any(Inner.class)); + verify(second, times(1)).call(any(Inner.class)); + + } + + @Test + public void testSequenceOfDelayedActions() throws InterruptedException { + Scheduler scheduler = getScheduler(); + + final CountDownLatch latch = new CountDownLatch(1); + final Action1 first = mock(Action1.class); + final Action1 second = mock(Action1.class); + + scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + inner.schedule(first, 30, TimeUnit.MILLISECONDS); + inner.schedule(second, 10, TimeUnit.MILLISECONDS); + inner.schedule(new Action1() { + + @Override + public void call(Inner inner) { + latch.countDown(); + } + }, 40, TimeUnit.MILLISECONDS); + } + }); + + latch.await(); + InOrder inOrder = inOrder(first, second); + + inOrder.verify(second, times(1)).call(any(Inner.class)); + inOrder.verify(first, times(1)).call(any(Inner.class)); + + } + + @Test + public void testMixOfDelayedAndNonDelayedActions() throws InterruptedException { + Scheduler scheduler = getScheduler(); + + final CountDownLatch latch = new CountDownLatch(1); + final Action1 first = mock(Action1.class); + final Action1 second = mock(Action1.class); + final Action1 third = mock(Action1.class); + final Action1 fourth = mock(Action1.class); + + scheduler.schedule(new Action1() { + @Override + public void call(Inner inner) { + inner.schedule(first); + inner.schedule(second, 300, TimeUnit.MILLISECONDS); + inner.schedule(third, 100, TimeUnit.MILLISECONDS); + inner.schedule(fourth); + inner.schedule(new Action1() { + + @Override + public void call(Inner inner) { + latch.countDown(); + } + }, 400, TimeUnit.MILLISECONDS); + } + }); + + latch.await(); + InOrder inOrder = inOrder(first, second, third, fourth); + + inOrder.verify(first, times(1)).call(any(Inner.class)); + inOrder.verify(fourth, times(1)).call(any(Inner.class)); + inOrder.verify(third, times(1)).call(any(Inner.class)); + inOrder.verify(second, times(1)).call(any(Inner.class)); + } + + @Test + public final void testRecursiveExecution() throws InterruptedException { + final Scheduler scheduler = getScheduler(); + final AtomicInteger i = new AtomicInteger(); + final CountDownLatch latch = new CountDownLatch(1); + scheduler.schedule(new Action1() { + + @Override + public void call(Inner inner) { + if (i.incrementAndGet() < 100) { + inner.schedule(this); + } else { + latch.countDown(); + } + } + }); + + latch.await(); + assertEquals(100, i.get()); + } + + @Test + public final void testRecursiveExecutionWithDelayTime() throws InterruptedException { + Scheduler scheduler = getScheduler(); + final AtomicInteger i = new AtomicInteger(); + final CountDownLatch latch = new CountDownLatch(1); + + scheduler.schedule(new Action1() { + + int state = 0; + + @Override + public void call(Inner inner) { + i.set(state); + if (state++ < 100) { + inner.schedule(this, 1, TimeUnit.MILLISECONDS); + } else { + latch.countDown(); + } + } + + }); + + latch.await(); + assertEquals(100, i.get()); + } + + @Test + public final void testRecursiveSchedulerInObservable() { + Observable obs = Observable.create(new OnSubscribeFunc() { + @Override + public Subscription onSubscribe(final Observer observer) { + return getScheduler().schedule(new Action1() { + int i = 0; + + @Override + public void call(Inner inner) { + if (i > 42) { + observer.onCompleted(); + return; + } + + observer.onNext(i++); + + inner.schedule(this); + } + }); + } + }); + + final AtomicInteger lastValue = new AtomicInteger(); + obs.toBlockingObservable().forEach(new Action1() { + + @Override + public void call(Integer v) { + System.out.println("Value: " + v); + lastValue.set(v); + } + }); + + assertEquals(42, lastValue.get()); + } + + @Test + public final void testConcurrentOnNextFailsValidation() throws InterruptedException { + final int count = 10; + final CountDownLatch latch = new CountDownLatch(count); + Observable o = Observable.create(new OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(final Observer observer) { + for (int i = 0; i < count; i++) { + final int v = i; + new Thread(new Runnable() { + + @Override + public void run() { + observer.onNext("v: " + v); + + latch.countDown(); + } + }).start(); + } + return Subscriptions.empty(); + } + }); + + ConcurrentObserverValidator observer = new ConcurrentObserverValidator(); + // this should call onNext concurrently + o.subscribe(observer); + + if (!observer.completed.await(3000, TimeUnit.MILLISECONDS)) { + fail("timed out"); + } + + if (observer.error.get() == null) { + fail("We expected error messages due to concurrency"); + } + } + + @Test + public final void testObserveOn() throws InterruptedException { + final Scheduler scheduler = getScheduler(); + + Observable o = Observable.from("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"); + + ConcurrentObserverValidator observer = new ConcurrentObserverValidator(); + + o.observeOn(scheduler).subscribe(observer); + + if (!observer.completed.await(3000, TimeUnit.MILLISECONDS)) { + fail("timed out"); + } + + if (observer.error.get() != null) { + observer.error.get().printStackTrace(); + fail("Error: " + observer.error.get().getMessage()); + } + } + + @Test + public final void testSubscribeOnNestedConcurrency() throws InterruptedException { + final Scheduler scheduler = getScheduler(); + + Observable o = Observable.from("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten") + .mergeMap(new Func1>() { + + @Override + public Observable call(final String v) { + return Observable.create(new OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(final Observer observer) { + observer.onNext("value_after_map-" + v); + observer.onCompleted(); + return Subscriptions.empty(); + } + }).subscribeOn(scheduler); + } + }); + + ConcurrentObserverValidator observer = new ConcurrentObserverValidator(); + + o.subscribe(observer); + + if (!observer.completed.await(3000, TimeUnit.MILLISECONDS)) { + fail("timed out"); + } + + if (observer.error.get() != null) { + observer.error.get().printStackTrace(); + fail("Error: " + observer.error.get().getMessage()); + } + } + + /** + * Used to determine if onNext is being invoked concurrently. + * + * @param + */ + private static class ConcurrentObserverValidator extends Subscriber { + + final AtomicInteger concurrentCounter = new AtomicInteger(); + final AtomicReference error = new AtomicReference(); + final CountDownLatch completed = new CountDownLatch(1); + + @Override + public void onCompleted() { + completed.countDown(); + } + + @Override + public void onError(Throwable e) { + completed.countDown(); + error.set(e); + } + + @Override + public void onNext(T args) { + int count = concurrentCounter.incrementAndGet(); + System.out.println("ConcurrentObserverValidator.onNext: " + args); + if (count > 1) { + onError(new RuntimeException("we should not have concurrent execution of onNext")); + } + try { + try { + // take some time so other onNext calls could pile up (I haven't yet thought of a way to do this without sleeping) + Thread.sleep(50); + } catch (InterruptedException e) { + // ignore + } + } finally { + concurrentCounter.decrementAndGet(); + } + } + + } + +} diff --git a/rxjava-core/src/test/java/rx/schedulers/ExecutorSchedulerTests.java b/rxjava-core/src/test/java/rx/schedulers/ExecutorSchedulerTests.java new file mode 100644 index 0000000000..bb217ad2ca --- /dev/null +++ b/rxjava-core/src/test/java/rx/schedulers/ExecutorSchedulerTests.java @@ -0,0 +1,160 @@ +/** + * Copyright 2014 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.schedulers; + +import static org.junit.Assert.*; + +import java.util.HashMap; +import java.util.concurrent.CountDownLatch; + +import org.junit.Test; + +import rx.Observable; +import rx.Scheduler; +import rx.Scheduler.Inner; +import rx.functions.Action1; +import rx.functions.Func1; + +public class ExecutorSchedulerTests extends AbstractSchedulerConcurrencyTests { + + @Override + protected Scheduler getScheduler() { + // this is an implementation of ExecutorScheduler + return Schedulers.computation(); + } + + @Test + public void testThreadSafetyWhenSchedulerIsHoppingBetweenThreads() { + + final int NUM = 1000000; + final CountDownLatch latch = new CountDownLatch(1); + final HashMap map = new HashMap(); + + Schedulers.computation().schedule(new Action1() { + + private HashMap statefulMap = map; + int nonThreadSafeCounter = 0; + + @Override + public void call(Inner inner) { + Integer i = statefulMap.get("a"); + if (i == null) { + i = 1; + statefulMap.put("a", i); + statefulMap.put("b", i); + } else { + i++; + statefulMap.put("a", i); + statefulMap.put("b", i); + } + nonThreadSafeCounter++; + statefulMap.put("nonThreadSafeCounter", nonThreadSafeCounter); + if (i < NUM) { + inner.schedule(this); + } else { + latch.countDown(); + } + } + }); + + try { + latch.await(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + System.out.println("Count A: " + map.get("a")); + System.out.println("Count B: " + map.get("b")); + System.out.println("nonThreadSafeCounter: " + map.get("nonThreadSafeCounter")); + + assertEquals(NUM, map.get("a").intValue()); + assertEquals(NUM, map.get("b").intValue()); + assertEquals(NUM, map.get("nonThreadSafeCounter").intValue()); + } + + @Test + public final void testComputationThreadPool1() { + final Scheduler scheduler = getScheduler(); + + Observable o1 = Observable. from(1, 2, 3, 4, 5); + Observable o2 = Observable. from(6, 7, 8, 9, 10); + Observable o = Observable. merge(o1, o2).map(new Func1() { + + @Override + public String call(Integer t) { + assertTrue(Thread.currentThread().getName().startsWith("RxComputationThreadPool")); + return "Value_" + t + "_Thread_" + Thread.currentThread().getName(); + } + }); + + o.subscribeOn(Schedulers.computation()).toBlockingObservable().forEach(new Action1() { + + @Override + public void call(String t) { + System.out.println("t: " + t); + } + }); + } + + @Test + public final void testIOThreadPool1() { + + Observable o1 = Observable. from(1, 2, 3, 4, 5); + Observable o2 = Observable. from(6, 7, 8, 9, 10); + Observable o = Observable. merge(o1, o2).map(new Func1() { + + @Override + public String call(Integer t) { + assertTrue(Thread.currentThread().getName().startsWith("RxIOThreadPool")); + return "Value_" + t + "_Thread_" + Thread.currentThread().getName(); + } + }); + + o.subscribeOn(Schedulers.threadPoolForIO()).toBlockingObservable().forEach(new Action1() { + + @Override + public void call(String t) { + System.out.println("t: " + t); + } + }); + } + + @Test + public final void testMergeWithExecutorScheduler() { + + final String currentThreadName = Thread.currentThread().getName(); + + Observable o1 = Observable. from(1, 2, 3, 4, 5); + Observable o2 = Observable. from(6, 7, 8, 9, 10); + Observable o = Observable. merge(o1, o2).subscribeOn(Schedulers.computation()).map(new Func1() { + + @Override + public String call(Integer t) { + assertFalse(Thread.currentThread().getName().equals(currentThreadName)); + assertTrue(Thread.currentThread().getName().startsWith("RxComputationThreadPool")); + return "Value_" + t + "_Thread_" + Thread.currentThread().getName(); + } + }); + + o.toBlockingObservable().forEach(new Action1() { + + @Override + public void call(String t) { + System.out.println("t: " + t); + } + }); + } +} diff --git a/rxjava-core/src/test/java/rx/schedulers/ImmediateSchedulerTest.java b/rxjava-core/src/test/java/rx/schedulers/ImmediateSchedulerTest.java new file mode 100644 index 0000000000..9f729ea0d3 --- /dev/null +++ b/rxjava-core/src/test/java/rx/schedulers/ImmediateSchedulerTest.java @@ -0,0 +1,104 @@ +/** + * Copyright 2014 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.schedulers; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import rx.Observable; +import rx.Scheduler; +import rx.functions.Action1; +import rx.functions.Func1; + +public class ImmediateSchedulerTest extends AbstractSchedulerTests { + + @Override + protected Scheduler getScheduler() { + return ImmediateScheduler.getInstance(); + } + + @Override + @Test + public final void testNestedActions() { + // ordering of nested actions will not match other schedulers + // because there is no reordering or concurrency with ImmediateScheduler + } + + @Override + @Test + public final void testSequenceOfDelayedActions() { + // ordering of nested actions will not match other schedulers + // because there is no reordering or concurrency with ImmediateScheduler + } + + @Override + @Test + public final void testMixOfDelayedAndNonDelayedActions() { + // ordering of nested actions will not match other schedulers + // because there is no reordering or concurrency with ImmediateScheduler + } + + @Test + public final void testMergeWithoutScheduler() { + + final String currentThreadName = Thread.currentThread().getName(); + + Observable o1 = Observable. from(1, 2, 3, 4, 5); + Observable o2 = Observable. from(6, 7, 8, 9, 10); + Observable o = Observable. merge(o1, o2).map(new Func1() { + + @Override + public String call(Integer t) { + assertTrue(Thread.currentThread().getName().equals(currentThreadName)); + return "Value_" + t + "_Thread_" + Thread.currentThread().getName(); + } + }); + + o.toBlockingObservable().forEach(new Action1() { + + @Override + public void call(String t) { + System.out.println("t: " + t); + } + }); + } + + @Test + public final void testMergeWithImmediateScheduler1() { + + final String currentThreadName = Thread.currentThread().getName(); + + Observable o1 = Observable. from(1, 2, 3, 4, 5); + Observable o2 = Observable. from(6, 7, 8, 9, 10); + Observable o = Observable. merge(o1, o2).subscribeOn(Schedulers.immediate()).map(new Func1() { + + @Override + public String call(Integer t) { + assertTrue(Thread.currentThread().getName().equals(currentThreadName)); + return "Value_" + t + "_Thread_" + Thread.currentThread().getName(); + } + }); + + o.toBlockingObservable().forEach(new Action1() { + + @Override + public void call(String t) { + System.out.println("t: " + t); + } + }); + } +} diff --git a/rxjava-core/src/test/java/rx/schedulers/NewThreadSchedulerTest.java b/rxjava-core/src/test/java/rx/schedulers/NewThreadSchedulerTest.java new file mode 100644 index 0000000000..a05e60925d --- /dev/null +++ b/rxjava-core/src/test/java/rx/schedulers/NewThreadSchedulerTest.java @@ -0,0 +1,28 @@ +/** + * Copyright 2014 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.schedulers; + +import rx.Scheduler; + +public class NewThreadSchedulerTest extends AbstractSchedulerConcurrencyTests { + + @Override + protected Scheduler getScheduler() { + return NewThreadScheduler.getInstance(); + } + +} diff --git a/rxjava-core/src/test/java/rx/schedulers/TestSchedulerTest.java b/rxjava-core/src/test/java/rx/schedulers/TestSchedulerTest.java new file mode 100644 index 0000000000..09adaa7e28 --- /dev/null +++ b/rxjava-core/src/test/java/rx/schedulers/TestSchedulerTest.java @@ -0,0 +1,96 @@ +/** + * Copyright 2014 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.schedulers; + +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.AtomicInteger; + +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; + +import rx.Scheduler.Inner; +import rx.Subscription; +import rx.functions.Action1; +import rx.functions.Func1; + +public class TestSchedulerTest { + + @SuppressWarnings("unchecked") + // mocking is unchecked, unfortunately + @Test + public final void testPeriodicScheduling() { + final Func1 calledOp = mock(Func1.class); + + final TestScheduler scheduler = new TestScheduler(); + Subscription subscription = scheduler.schedulePeriodically(new Action1() { + @Override + public void call(Inner inner) { + 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 final void testImmediateUnsubscribes() { + TestScheduler s = new TestScheduler(); + + final AtomicInteger counter = new AtomicInteger(0); + + Subscription subscription = s.schedule(new Action1() { + + @Override + public void call(Inner inner) { + counter.incrementAndGet(); + System.out.println("counter: " + counter.get()); + inner.schedule(this); + } + + }); + subscription.unsubscribe(); + assertEquals(0, counter.get()); + } + +} diff --git a/rxjava-core/src/test/java/rx/schedulers/TrampolineSchedulerTest.java b/rxjava-core/src/test/java/rx/schedulers/TrampolineSchedulerTest.java new file mode 100644 index 0000000000..efea8d874e --- /dev/null +++ b/rxjava-core/src/test/java/rx/schedulers/TrampolineSchedulerTest.java @@ -0,0 +1,58 @@ +/** + * Copyright 2014 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.schedulers; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import rx.Observable; +import rx.Scheduler; +import rx.functions.Action1; +import rx.functions.Func1; + +public class TrampolineSchedulerTest extends AbstractSchedulerTests { + + @Override + protected Scheduler getScheduler() { + return TrampolineScheduler.getInstance(); + } + + @Test + public final void testMergeWithCurrentThreadScheduler1() { + + final String currentThreadName = Thread.currentThread().getName(); + + Observable o1 = Observable. from(1, 2, 3, 4, 5); + Observable o2 = Observable. from(6, 7, 8, 9, 10); + Observable o = Observable. merge(o1, o2).subscribeOn(Schedulers.currentThread()).map(new Func1() { + + @Override + public String call(Integer t) { + assertTrue(Thread.currentThread().getName().equals(currentThreadName)); + return "Value_" + t + "_Thread_" + Thread.currentThread().getName(); + } + }); + + o.toBlockingObservable().forEach(new Action1() { + + @Override + public void call(String t) { + System.out.println("t: " + t); + } + }); + } +} diff --git a/rxjava-core/src/test/java/rx/subjects/AsyncSubjectTest.java b/rxjava-core/src/test/java/rx/subjects/AsyncSubjectTest.java index b483b9c99f..90eda0ad16 100644 --- a/rxjava-core/src/test/java/rx/subjects/AsyncSubjectTest.java +++ b/rxjava-core/src/test/java/rx/subjects/AsyncSubjectTest.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,8 +28,7 @@ import rx.Observer; import rx.Subscription; -import rx.util.functions.Action1; -import rx.util.functions.Func0; +import rx.functions.Action1; public class AsyncSubjectTest { @@ -40,16 +39,16 @@ public void testNeverCompleted() { AsyncSubject subject = AsyncSubject.create(); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + subject.subscribe(observer); 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(); + verify(observer, Mockito.never()).onNext(anyString()); + verify(observer, Mockito.never()).onError(testException); + verify(observer, Mockito.never()).onCompleted(); } @Test @@ -57,17 +56,17 @@ public void testCompleted() { AsyncSubject subject = AsyncSubject.create(); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + subject.subscribe(observer); 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(); + verify(observer, times(1)).onNext("three"); + verify(observer, Mockito.never()).onError(any(Throwable.class)); + verify(observer, times(1)).onCompleted(); } @Test @@ -75,15 +74,15 @@ public void testNull() { AsyncSubject subject = AsyncSubject.create(); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + subject.subscribe(observer); subject.onNext(null); subject.onCompleted(); - verify(aObserver, times(1)).onNext(null); - verify(aObserver, Mockito.never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); + verify(observer, times(1)).onNext(null); + verify(observer, Mockito.never()).onError(any(Throwable.class)); + verify(observer, times(1)).onCompleted(); } @Test @@ -91,18 +90,18 @@ public void testSubscribeAfterCompleted() { AsyncSubject subject = AsyncSubject.create(); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); + Observer observer = mock(Observer.class); subject.onNext("one"); subject.onNext("two"); subject.onNext("three"); subject.onCompleted(); - subject.subscribe(aObserver); + subject.subscribe(observer); - verify(aObserver, times(1)).onNext("three"); - verify(aObserver, Mockito.never()).onError(any(Throwable.class)); - verify(aObserver, times(1)).onCompleted(); + verify(observer, times(1)).onNext("three"); + verify(observer, Mockito.never()).onError(any(Throwable.class)); + verify(observer, times(1)).onCompleted(); } @Test @@ -110,7 +109,7 @@ public void testSubscribeAfterError() { AsyncSubject subject = AsyncSubject.create(); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); + Observer observer = mock(Observer.class); subject.onNext("one"); subject.onNext("two"); @@ -119,11 +118,11 @@ public void testSubscribeAfterError() { RuntimeException re = new RuntimeException("failed"); subject.onError(re); - subject.subscribe(aObserver); + subject.subscribe(observer); - verify(aObserver, times(1)).onError(re); - verify(aObserver, Mockito.never()).onNext(any(String.class)); - verify(aObserver, Mockito.never()).onCompleted(); + verify(observer, times(1)).onError(re); + verify(observer, Mockito.never()).onNext(any(String.class)); + verify(observer, Mockito.never()).onCompleted(); } @Test @@ -131,8 +130,8 @@ public void testError() { AsyncSubject subject = AsyncSubject.create(); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + subject.subscribe(observer); subject.onNext("one"); subject.onNext("two"); @@ -142,9 +141,9 @@ public void testError() { subject.onError(new Throwable()); subject.onCompleted(); - verify(aObserver, Mockito.never()).onNext(anyString()); - verify(aObserver, times(1)).onError(testException); - verify(aObserver, Mockito.never()).onCompleted(); + verify(observer, Mockito.never()).onNext(anyString()); + verify(observer, times(1)).onError(testException); + verify(observer, Mockito.never()).onCompleted(); } @Test @@ -152,47 +151,24 @@ public void testUnsubscribeBeforeCompleted() { AsyncSubject subject = AsyncSubject.create(); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - Subscription subscription = subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + Subscription subscription = subject.subscribe(observer); 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(); + verify(observer, Mockito.never()).onNext(anyString()); + verify(observer, Mockito.never()).onError(any(Throwable.class)); + verify(observer, 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 - ); + verify(observer, Mockito.never()).onNext(anyString()); + verify(observer, Mockito.never()).onError(any(Throwable.class)); + verify(observer, Mockito.never()).onCompleted(); } @Test @@ -200,28 +176,28 @@ public void testEmptySubjectCompleted() { AsyncSubject subject = AsyncSubject.create(); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + subject.subscribe(observer); 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 inOrder = inOrder(observer); + inOrder.verify(observer, never()).onNext(null); + inOrder.verify(observer, never()).onNext(any(String.class)); + inOrder.verify(observer, times(1)).onCompleted(); inOrder.verifyNoMoreInteractions(); } /** * Can receive timeout if subscribe never receives an onError/onCompleted ... which reveals a race condition. */ - @Test + @Test(timeout = 10000) 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. + * 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(); diff --git a/rxjava-core/src/test/java/rx/subjects/BehaviorSubjectTest.java b/rxjava-core/src/test/java/rx/subjects/BehaviorSubjectTest.java index 57958c108f..646e1acc26 100644 --- a/rxjava-core/src/test/java/rx/subjects/BehaviorSubjectTest.java +++ b/rxjava-core/src/test/java/rx/subjects/BehaviorSubjectTest.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,69 +24,100 @@ 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"); + public void testThatObserverReceivesDefaultValueAndSubsequentEvents() { + BehaviorSubject subject = BehaviorSubject.create("default"); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + subject.subscribe(observer); 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(); + verify(observer, times(1)).onNext("default"); + verify(observer, times(1)).onNext("one"); + verify(observer, times(1)).onNext("two"); + verify(observer, times(1)).onNext("three"); + verify(observer, Mockito.never()).onError(testException); + verify(observer, Mockito.never()).onCompleted(); } @Test - public void testThatObserverDoesNotReceiveDefaultValueIfSomethingWasPublished() { + public void testThatObserverReceivesLatestAndThenSubsequentEvents() { BehaviorSubject subject = BehaviorSubject.create("default"); subject.onNext("one"); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + subject.subscribe(observer); 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(); + verify(observer, Mockito.never()).onNext("default"); + verify(observer, times(1)).onNext("one"); + verify(observer, times(1)).onNext("two"); + verify(observer, times(1)).onNext("three"); + verify(observer, Mockito.never()).onError(testException); + verify(observer, Mockito.never()).onCompleted(); } @Test - public void testCompleted() { + public void testSubscribeThenOnComplete() { BehaviorSubject subject = BehaviorSubject.create("default"); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + subject.subscribe(observer); + + subject.onNext("one"); + subject.onCompleted(); + + verify(observer, times(1)).onNext("default"); + verify(observer, times(1)).onNext("one"); + verify(observer, Mockito.never()).onError(any(Throwable.class)); + verify(observer, times(1)).onCompleted(); + } + @Test + public void testSubscribeToCompletedOnlyEmitsOnComplete() { + BehaviorSubject subject = BehaviorSubject.create("default"); 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(); + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + subject.subscribe(observer); + + verify(observer, never()).onNext("default"); + verify(observer, never()).onNext("one"); + verify(observer, Mockito.never()).onError(any(Throwable.class)); + verify(observer, times(1)).onCompleted(); + } + + @Test + public void testSubscribeToErrorOnlyEmitsOnError() { + BehaviorSubject subject = BehaviorSubject.create("default"); + subject.onNext("one"); + RuntimeException re = new RuntimeException("test error"); + subject.onError(re); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + subject.subscribe(observer); + + verify(observer, never()).onNext("default"); + verify(observer, never()).onNext("one"); + verify(observer, times(1)).onError(re); + verify(observer, never()).onCompleted(); } @Test @@ -136,47 +167,74 @@ public void testCompletedStopsEmittingData() { } @Test - public void testCompletedAfterError() { + public void testCompletedAfterErrorIsNotSent() { BehaviorSubject subject = BehaviorSubject.create("default"); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + subject.subscribe(observer); 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); + verify(observer, times(1)).onNext("default"); + verify(observer, times(1)).onNext("one"); + verify(observer, times(1)).onError(testException); + verify(observer, never()).onNext("two"); + verify(observer, never()).onCompleted(); } @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"); - } - } - ); + public void testCompletedAfterErrorIsNotSent2() { + BehaviorSubject subject = BehaviorSubject.create("default"); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + subject.subscribe(observer); + + subject.onNext("one"); + subject.onError(testException); + subject.onNext("two"); + subject.onCompleted(); + + verify(observer, times(1)).onNext("default"); + verify(observer, times(1)).onNext("one"); + verify(observer, times(1)).onError(testException); + verify(observer, never()).onNext("two"); + verify(observer, never()).onCompleted(); + + Observer o2 = mock(Observer.class); + subject.subscribe(o2); + verify(o2, times(1)).onError(testException); + verify(o2, never()).onNext(any()); + verify(o2, never()).onCompleted(); + } + + @Test + public void testCompletedAfterErrorIsNotSent3() { + BehaviorSubject subject = BehaviorSubject.create("default"); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + subject.subscribe(observer); + + subject.onNext("one"); + subject.onCompleted(); + subject.onNext("two"); + subject.onCompleted(); + + verify(observer, times(1)).onNext("default"); + verify(observer, times(1)).onNext("one"); + verify(observer, times(1)).onCompleted(); + verify(observer, never()).onError(any(Throwable.class)); + verify(observer, never()).onNext("two"); + + Observer o2 = mock(Observer.class); + subject.subscribe(o2); + verify(o2, times(1)).onCompleted(); + verify(o2, never()).onNext(any()); + verify(observer, never()).onError(any(Throwable.class)); } } diff --git a/rxjava-core/src/test/java/rx/subjects/PublishSubjectTest.java b/rxjava-core/src/test/java/rx/subjects/PublishSubjectTest.java index 5e9cc2790f..9c71d4aa5c 100644 --- a/rxjava-core/src/test/java/rx/subjects/PublishSubjectTest.java +++ b/rxjava-core/src/test/java/rx/subjects/PublishSubjectTest.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,9 +29,8 @@ import rx.Observable; import rx.Observer; import rx.Subscription; -import rx.util.functions.Action1; -import rx.util.functions.Func0; -import rx.util.functions.Func1; +import rx.functions.Action1; +import rx.functions.Func1; public class PublishSubjectTest { @@ -40,8 +39,8 @@ public void testCompleted() { PublishSubject subject = PublishSubject.create(); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + subject.subscribe(observer); subject.onNext("one"); subject.onNext("two"); @@ -56,7 +55,7 @@ public void testCompleted() { subject.onCompleted(); subject.onError(new Throwable()); - assertCompletedObserver(aObserver); + assertCompletedObserver(observer); // todo bug? assertNeverObserver(anotherObserver); } @@ -103,12 +102,12 @@ public void testCompletedStopsEmittingData() { 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(); + private void assertCompletedObserver(Observer observer) { + verify(observer, times(1)).onNext("one"); + verify(observer, times(1)).onNext("two"); + verify(observer, times(1)).onNext("three"); + verify(observer, Mockito.never()).onError(any(Throwable.class)); + verify(observer, times(1)).onCompleted(); } @Test @@ -116,8 +115,8 @@ public void testError() { PublishSubject subject = PublishSubject.create(); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + subject.subscribe(observer); subject.onNext("one"); subject.onNext("two"); @@ -132,16 +131,16 @@ public void testError() { subject.onError(new Throwable()); subject.onCompleted(); - assertErrorObserver(aObserver); + assertErrorObserver(observer); // 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(); + private void assertErrorObserver(Observer observer) { + verify(observer, times(1)).onNext("one"); + verify(observer, times(1)).onNext("two"); + verify(observer, times(1)).onNext("three"); + verify(observer, times(1)).onError(testException); + verify(observer, Mockito.never()).onCompleted(); } @Test @@ -149,13 +148,13 @@ public void testSubscribeMidSequence() { PublishSubject subject = PublishSubject.create(); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + subject.subscribe(observer); subject.onNext("one"); subject.onNext("two"); - assertObservedUntilTwo(aObserver); + assertObservedUntilTwo(observer); @SuppressWarnings("unchecked") Observer anotherObserver = mock(Observer.class); @@ -164,16 +163,16 @@ public void testSubscribeMidSequence() { subject.onNext("three"); subject.onCompleted(); - assertCompletedObserver(aObserver); + assertCompletedObserver(observer); 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(); + private void assertCompletedStartingWithThreeObserver(Observer observer) { + verify(observer, Mockito.never()).onNext("one"); + verify(observer, Mockito.never()).onNext("two"); + verify(observer, times(1)).onNext("three"); + verify(observer, Mockito.never()).onError(any(Throwable.class)); + verify(observer, times(1)).onCompleted(); } @Test @@ -181,14 +180,14 @@ public void testUnsubscribeFirstObserver() { PublishSubject subject = PublishSubject.create(); @SuppressWarnings("unchecked") - Observer aObserver = mock(Observer.class); - Subscription subscription = subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + Subscription subscription = subject.subscribe(observer); subject.onNext("one"); subject.onNext("two"); subscription.unsubscribe(); - assertObservedUntilTwo(aObserver); + assertObservedUntilTwo(observer); @SuppressWarnings("unchecked") Observer anotherObserver = mock(Observer.class); @@ -197,43 +196,16 @@ public void testUnsubscribeFirstObserver() { subject.onNext("three"); subject.onCompleted(); - assertObservedUntilTwo(aObserver); + assertObservedUntilTwo(observer); 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"); - } - } - ); + private void assertObservedUntilTwo(Observer observer) { + verify(observer, times(1)).onNext("one"); + verify(observer, times(1)).onNext("two"); + verify(observer, Mockito.never()).onNext("three"); + verify(observer, Mockito.never()).onError(any(Throwable.class)); + verify(observer, Mockito.never()).onCompleted(); } @Test @@ -246,7 +218,7 @@ public void testNestedSubscribe() { final ArrayList list = new ArrayList(); - s.mapMany(new Func1>() { + s.flatMap(new Func1>() { @Override public Observable call(final Integer v) { @@ -325,7 +297,6 @@ public void testReSubscribe() { s2.unsubscribe(); } - private final Throwable testException = new Throwable(); } diff --git a/rxjava-core/src/test/java/rx/subjects/ReplaySubjectConcurrencyTest.java b/rxjava-core/src/test/java/rx/subjects/ReplaySubjectConcurrencyTest.java new file mode 100644 index 0000000000..3f032b19d0 --- /dev/null +++ b/rxjava-core/src/test/java/rx/subjects/ReplaySubjectConcurrencyTest.java @@ -0,0 +1,327 @@ +/** + * Copyright 2014 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 java.util.ArrayList; +import java.util.Collections; +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 rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Action1; +import rx.subscriptions.Subscriptions; + +public class ReplaySubjectConcurrencyTest { + + public static void main(String args[]) { + try { + for (int i = 0; i < 100; i++) { + new ReplaySubjectConcurrencyTest().testSubscribeCompletionRaceCondition(); + new ReplaySubjectConcurrencyTest().testReplaySubjectConcurrentSubscriptions(); + new ReplaySubjectConcurrencyTest().testReplaySubjectConcurrentSubscribersDoingReplayDontBlockEachOther(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Test(timeout = 4000) + public void testReplaySubjectConcurrentSubscribersDoingReplayDontBlockEachOther() throws InterruptedException { + final ReplaySubject replay = ReplaySubject.create(); + Thread source = new Thread(new Runnable() { + + @Override + public void run() { + Observable.create(new OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(Observer o) { + System.out.println("********* Start Source Data ***********"); + for (long l = 1; l <= 10000; l++) { + o.onNext(l); + } + System.out.println("********* Finished Source Data ***********"); + o.onCompleted(); + return Subscriptions.empty(); + } + }).subscribe(replay); + } + }); + source.start(); + + long v = replay.toBlockingObservable().last(); + assertEquals(10000, v); + + // it's been played through once so now it will all be replays + final CountDownLatch slowLatch = new CountDownLatch(1); + Thread slowThread = new Thread(new Runnable() { + + @Override + public void run() { + Subscriber slow = new Subscriber() { + + @Override + public void onCompleted() { + System.out.println("*** Slow Observer completed"); + slowLatch.countDown(); + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onNext(Long args) { + if (args == 1) { + System.out.println("*** Slow Observer STARTED"); + } + try { + if (args % 10 == 0) { + Thread.sleep(1); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }; + replay.subscribe(slow); + try { + slowLatch.await(); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } + }); + slowThread.start(); + + Thread fastThread = new Thread(new Runnable() { + + @Override + public void run() { + final CountDownLatch fastLatch = new CountDownLatch(1); + Subscriber fast = new Subscriber() { + + @Override + public void onCompleted() { + System.out.println("*** Fast Observer completed"); + fastLatch.countDown(); + } + + @Override + public void onError(Throwable e) { + } + + @Override + public void onNext(Long args) { + if (args == 1) { + System.out.println("*** Fast Observer STARTED"); + } + } + }; + replay.subscribe(fast); + try { + fastLatch.await(); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } + }); + fastThread.start(); + fastThread.join(); + + // slow should not yet be completed when fast completes + assertEquals(1, slowLatch.getCount()); + + slowThread.join(); + } + + @Test + public void testReplaySubjectConcurrentSubscriptions() throws InterruptedException { + final ReplaySubject replay = ReplaySubject.create(); + Thread source = new Thread(new Runnable() { + + @Override + public void run() { + Observable.create(new OnSubscribeFunc() { + + @Override + public Subscription onSubscribe(Observer o) { + System.out.println("********* Start Source Data ***********"); + for (long l = 1; l <= 10000; l++) { + o.onNext(l); + } + System.out.println("********* Finished Source Data ***********"); + o.onCompleted(); + return Subscriptions.empty(); + } + }).subscribe(replay); + } + }); + + // used to collect results of each thread + final List> listOfListsOfValues = Collections.synchronizedList(new ArrayList>()); + final List threads = Collections.synchronizedList(new ArrayList()); + + for (int i = 1; i <= 200; i++) { + final int count = i; + if (count == 20) { + // start source data after we have some already subscribed + // and while others are in process of subscribing + source.start(); + } + if (count == 100) { + // wait for source to finish then keep adding after it's done + source.join(); + } + Thread t = new Thread(new Runnable() { + + @Override + public void run() { + List values = replay.toList().toBlockingObservable().last(); + listOfListsOfValues.add(values); + System.out.println("Finished thread: " + count); + } + }); + t.start(); + System.out.println("Started thread: " + i); + threads.add(t); + } + + // wait for all threads to complete + for (Thread t : threads) { + t.join(); + } + + // assert all threads got the same results + List sums = new ArrayList(); + for (List values : listOfListsOfValues) { + long v = 0; + for (long l : values) { + v += l; + } + sums.add(v); + } + + long expected = sums.get(0); + boolean success = true; + for (long l : sums) { + if (l != expected) { + success = false; + System.out.println("FAILURE => Expected " + expected + " but got: " + l); + } + } + + if (success) { + System.out.println("Success! " + sums.size() + " each had the same sum of " + expected); + } else { + throw new RuntimeException("Concurrency Bug"); + } + + } + + /** + * Can receive timeout if subscribe never receives an onError/onCompleted ... which reveals a race condition. + */ + @Test(timeout = 10000) + public void testSubscribeCompletionRaceCondition() { + for (int i = 0; i < 50; i++) { + final ReplaySubject subject = ReplaySubject.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 ReplaySubject subject; + private final AtomicReference value = new AtomicReference(); + + public SubjectObserverThread(ReplaySubject 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/ReplaySubjectTest.java b/rxjava-core/src/test/java/rx/subjects/ReplaySubjectTest.java index fef45ca63d..3a360300f5 100644 --- a/rxjava-core/src/test/java/rx/subjects/ReplaySubjectTest.java +++ b/rxjava-core/src/test/java/rx/subjects/ReplaySubjectTest.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 - * + * + * 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,21 @@ */ package rx.subjects; +import static org.junit.Assert.*; import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicReference; + import org.junit.Test; import org.mockito.InOrder; import org.mockito.Mockito; import rx.Observer; +import rx.Subscriber; import rx.Subscription; -import rx.util.functions.Action1; -import rx.util.functions.Func0; +import rx.schedulers.Schedulers; public class ReplaySubjectTest { @@ -56,14 +60,101 @@ public void testCompleted() { assertCompletedObserver(o2); } - private void assertCompletedObserver(Observer aObserver) { - InOrder inOrder = inOrder(aObserver); + @Test + public void testCompletedStopsEmittingData() { + ReplaySubject channel = ReplaySubject.create(); + @SuppressWarnings("unchecked") + Observer observerA = mock(Observer.class); + @SuppressWarnings("unchecked") + Observer observerB = mock(Observer.class); + @SuppressWarnings("unchecked") + Observer observerC = mock(Observer.class); + @SuppressWarnings("unchecked") + Observer observerD = 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); + InOrder inOrderD = inOrder(observerD); + + channel.onNext(42); + + // both A and B should have received 42 from before subscription + inOrderA.verify(observerA).onNext(42); + inOrderB.verify(observerB).onNext(42); + + a.unsubscribe(); + + // a should receive no more + inOrderA.verifyNoMoreInteractions(); + + channel.onNext(4711); + + // only be should receive 4711 at this point + inOrderB.verify(observerB).onNext(4711); + + channel.onCompleted(); + + // B is subscribed so should receive onCompleted + inOrderB.verify(observerB).onCompleted(); + + Subscription c = channel.subscribe(observerC); + + // when C subscribes it should receive 42, 4711, onCompleted + inOrderC.verify(observerC).onNext(42); + inOrderC.verify(observerC).onNext(4711); + inOrderC.verify(observerC).onCompleted(); + + // if further events are propagated they should be ignored + channel.onNext(13); + channel.onNext(14); + channel.onNext(15); + channel.onError(new RuntimeException()); + + // a new subscription should only receive what was emitted prior to terminal state onCompleted + Subscription d = channel.subscribe(observerD); + + inOrderD.verify(observerD).onNext(42); + inOrderD.verify(observerD).onNext(4711); + inOrderD.verify(observerD).onCompleted(); + + Mockito.verifyNoMoreInteractions(observerA); + Mockito.verifyNoMoreInteractions(observerB); + Mockito.verifyNoMoreInteractions(observerC); + Mockito.verifyNoMoreInteractions(observerD); + + } + + @Test + public void testCompletedAfterError() { + ReplaySubject subject = ReplaySubject.create(); + + @SuppressWarnings("unchecked") + Observer observer = mock(Observer.class); + + subject.onNext("one"); + subject.onError(testException); + subject.onNext("two"); + subject.onCompleted(); + subject.onError(new RuntimeException()); + + subject.subscribe(observer); + verify(observer, times(1)).onNext("one"); + verify(observer, times(1)).onError(testException); + verifyNoMoreInteractions(observer); + } + + private void assertCompletedObserver(Observer observer) { + InOrder inOrder = inOrder(observer); - 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.verify(observer, times(1)).onNext("one"); + inOrder.verify(observer, times(1)).onNext("two"); + inOrder.verify(observer, times(1)).onNext("three"); + inOrder.verify(observer, Mockito.never()).onError(any(Throwable.class)); + inOrder.verify(observer, times(1)).onCompleted(); inOrder.verifyNoMoreInteractions(); } @@ -72,8 +163,8 @@ private void assertCompletedObserver(Observer aObserver) { public void testError() { ReplaySubject subject = ReplaySubject.create(); - Observer aObserver = mock(Observer.class); - subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + subject.subscribe(observer); subject.onNext("one"); subject.onNext("two"); @@ -84,19 +175,19 @@ public void testError() { subject.onError(new Throwable()); subject.onCompleted(); - assertErrorObserver(aObserver); + assertErrorObserver(observer); - aObserver = mock(Observer.class); - subject.subscribe(aObserver); - assertErrorObserver(aObserver); + observer = mock(Observer.class); + subject.subscribe(observer); + assertErrorObserver(observer); } - 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(); + private void assertErrorObserver(Observer observer) { + verify(observer, times(1)).onNext("one"); + verify(observer, times(1)).onNext("two"); + verify(observer, times(1)).onNext("three"); + verify(observer, times(1)).onError(testException); + verify(observer, Mockito.never()).onCompleted(); } @SuppressWarnings("unchecked") @@ -104,13 +195,13 @@ private void assertErrorObserver(Observer aObserver) { public void testSubscribeMidSequence() { ReplaySubject subject = ReplaySubject.create(); - Observer aObserver = mock(Observer.class); - subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + subject.subscribe(observer); subject.onNext("one"); subject.onNext("two"); - assertObservedUntilTwo(aObserver); + assertObservedUntilTwo(observer); Observer anotherObserver = mock(Observer.class); subject.subscribe(anotherObserver); @@ -119,7 +210,7 @@ public void testSubscribeMidSequence() { subject.onNext("three"); subject.onCompleted(); - assertCompletedObserver(aObserver); + assertCompletedObserver(observer); assertCompletedObserver(anotherObserver); } @@ -128,14 +219,14 @@ public void testSubscribeMidSequence() { public void testUnsubscribeFirstObserver() { ReplaySubject subject = ReplaySubject.create(); - Observer aObserver = mock(Observer.class); - Subscription subscription = subject.subscribe(aObserver); + Observer observer = mock(Observer.class); + Subscription subscription = subject.subscribe(observer); subject.onNext("one"); subject.onNext("two"); subscription.unsubscribe(); - assertObservedUntilTwo(aObserver); + assertObservedUntilTwo(observer); Observer anotherObserver = mock(Observer.class); subject.subscribe(anotherObserver); @@ -144,42 +235,114 @@ public void testUnsubscribeFirstObserver() { subject.onNext("three"); subject.onCompleted(); - assertObservedUntilTwo(aObserver); + assertObservedUntilTwo(observer); 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(); + private void assertObservedUntilTwo(Observer observer) { + verify(observer, times(1)).onNext("one"); + verify(observer, times(1)).onNext("two"); + verify(observer, Mockito.never()).onNext("three"); + verify(observer, Mockito.never()).onError(any(Throwable.class)); + verify(observer, 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"); + @Test(timeout = 2000) + public void testNewSubscriberDoesntBlockExisting() throws InterruptedException { + + final AtomicReference lastValueForObserver1 = new AtomicReference(); + Subscriber observer1 = new Subscriber() { + + @Override + public void onCompleted() { + + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onNext(String v) { + System.out.println("observer1: " + v); + lastValueForObserver1.set(v); + } + + }; + + final AtomicReference lastValueForObserver2 = new AtomicReference(); + final CountDownLatch oneReceived = new CountDownLatch(1); + final CountDownLatch makeSlow = new CountDownLatch(1); + final CountDownLatch completed = new CountDownLatch(1); + Subscriber observer2 = new Subscriber() { + + @Override + public void onCompleted() { + completed.countDown(); + } + + @Override + public void onError(Throwable e) { + + } + + @Override + public void onNext(String v) { + System.out.println("observer2: " + v); + if (v.equals("one")) { + oneReceived.countDown(); + } else { + try { + makeSlow.await(); + } catch (InterruptedException e) { + e.printStackTrace(); } + lastValueForObserver2.set(v); } - ); + } + + }; + + ReplaySubject subject = ReplaySubject.create(); + Subscription s1 = subject.subscribe(observer1); + subject.onNext("one"); + assertEquals("one", lastValueForObserver1.get()); + subject.onNext("two"); + assertEquals("two", lastValueForObserver1.get()); + + // use subscribeOn to make this async otherwise we deadlock as we are using CountDownLatches + Subscription s2 = subject.subscribeOn(Schedulers.newThread()).subscribe(observer2); + + System.out.println("before waiting for one"); + + // wait until observer2 starts having replay occur + oneReceived.await(); + + System.out.println("after waiting for one"); + + subject.onNext("three"); + + System.out.println("sent three"); + + // if subscription blocked existing subscribers then 'makeSlow' would cause this to not be there yet + assertEquals("three", lastValueForObserver1.get()); + + System.out.println("about to send onCompleted"); + + subject.onCompleted(); + + System.out.println("completed subject"); + + // release + makeSlow.countDown(); + + System.out.println("makeSlow released"); + + completed.await(); + // all of them should be emitted with the last being "three" + assertEquals("three", lastValueForObserver2.get()); + } + } diff --git a/rxjava-core/src/test/java/rx/subscriptions/CompositeSubscriptionTest.java b/rxjava-core/src/test/java/rx/subscriptions/CompositeSubscriptionTest.java index e916ac9795..d61929096c 100644 --- a/rxjava-core/src/test/java/rx/subscriptions/CompositeSubscriptionTest.java +++ b/rxjava-core/src/test/java/rx/subscriptions/CompositeSubscriptionTest.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,7 @@ */ package rx.subscriptions; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import java.util.ArrayList; import java.util.List; @@ -28,7 +25,7 @@ import org.junit.Test; import rx.Subscription; -import rx.util.CompositeException; +import rx.exceptions.CompositeException; public class CompositeSubscriptionTest { @@ -42,6 +39,11 @@ public void testSuccess() { public void unsubscribe() { counter.incrementAndGet(); } + + @Override + public boolean isUnsubscribed() { + return false; + } }); s.add(new Subscription() { @@ -50,6 +52,11 @@ public void unsubscribe() { public void unsubscribe() { counter.incrementAndGet(); } + + @Override + public boolean isUnsubscribed() { + return false; + } }); s.unsubscribe(); @@ -71,6 +78,11 @@ public void shouldUnsubscribeAll() throws InterruptedException { public void unsubscribe() { counter.incrementAndGet(); } + + @Override + public boolean isUnsubscribed() { + return false; + } }); } @@ -109,6 +121,11 @@ public void testException() { public void unsubscribe() { throw new RuntimeException("failed on first one"); } + + @Override + public boolean isUnsubscribed() { + return false; + } }); s.add(new Subscription() { @@ -117,6 +134,66 @@ public void unsubscribe() { public void unsubscribe() { counter.incrementAndGet(); } + + @Override + public boolean isUnsubscribed() { + return false; + } + }); + + try { + s.unsubscribe(); + fail("Expecting an exception"); + } catch (RuntimeException e) { + // we expect this + assertEquals(e.getMessage(), "failed on first one"); + } + + // we should still have unsubscribed to the second one + assertEquals(1, counter.get()); + } + + @Test + public void testCompositeException() { + final AtomicInteger counter = new AtomicInteger(); + CompositeSubscription s = new CompositeSubscription(); + s.add(new Subscription() { + + @Override + public void unsubscribe() { + throw new RuntimeException("failed on first one"); + } + + @Override + public boolean isUnsubscribed() { + return false; + } + }); + + s.add(new Subscription() { + + @Override + public void unsubscribe() { + throw new RuntimeException("failed on second one too"); + } + + @Override + public boolean isUnsubscribed() { + return false; + } + }); + + s.add(new Subscription() { + + @Override + public void unsubscribe() { + counter.incrementAndGet(); + } + + @Override + public boolean isUnsubscribed() { + return false; + } }); try { @@ -124,7 +201,7 @@ public void unsubscribe() { fail("Expecting an exception"); } catch (CompositeException e) { // we expect this - assertEquals(1, e.getExceptions().size()); + assertEquals(e.getExceptions().size(), 2); } // we should still have unsubscribed to the second one @@ -161,7 +238,7 @@ public void testClear() { s.clear(); assertTrue(s1.isUnsubscribed()); - assertTrue(s1.isUnsubscribed()); + assertTrue(s2.isUnsubscribed()); assertFalse(s.isUnsubscribed()); BooleanSubscription s3 = new BooleanSubscription(); @@ -183,6 +260,11 @@ public void testUnsubscribeIdempotence() { public void unsubscribe() { counter.incrementAndGet(); } + + @Override + public boolean isUnsubscribed() { + return false; + } }); s.unsubscribe(); @@ -207,6 +289,11 @@ public void testUnsubscribeIdempotenceConcurrently() public void unsubscribe() { counter.incrementAndGet(); } + + @Override + public boolean isUnsubscribed() { + return false; + } }); final List threads = new ArrayList(); diff --git a/rxjava-core/src/test/java/rx/subscriptions/MultipleAssignmentSubscriptionTest.java b/rxjava-core/src/test/java/rx/subscriptions/MultipleAssignmentSubscriptionTest.java new file mode 100644 index 0000000000..f7b8e0b7c3 --- /dev/null +++ b/rxjava-core/src/test/java/rx/subscriptions/MultipleAssignmentSubscriptionTest.java @@ -0,0 +1,80 @@ +/** + * Copyright 2014 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 junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; + +import rx.Subscription; +import rx.functions.Action0; + +public class MultipleAssignmentSubscriptionTest { + + Action0 unsubscribe; + Subscription s; + + @Before + public void before() { + unsubscribe = mock(Action0.class); + s = create(unsubscribe); + } + + @Test + public void testNoUnsubscribeWhenReplaced() { + MultipleAssignmentSubscription mas = new MultipleAssignmentSubscription(); + + mas.set(s); + mas.set(Subscriptions.empty()); + mas.unsubscribe(); + + verify(unsubscribe, never()).call(); + } + + @Test + public void testUnsubscribeWhenParentUnsubscribes() { + MultipleAssignmentSubscription mas = new MultipleAssignmentSubscription(); + mas.set(s); + mas.unsubscribe(); + mas.unsubscribe(); + + verify(unsubscribe, times(1)).call(); + + Assert.assertEquals(true, mas.isUnsubscribed()); + } + + @Test + public void subscribingWhenUnsubscribedCausesImmediateUnsubscription() { + MultipleAssignmentSubscription mas = new MultipleAssignmentSubscription(); + mas.unsubscribe(); + Subscription underlying = mock(Subscription.class); + mas.set(underlying); + verify(underlying).unsubscribe(); + } + + @Test + public void testSubscriptionRemainsAfterUnsubscribe() { + MultipleAssignmentSubscription mas = new MultipleAssignmentSubscription(); + + mas.set(s); + mas.unsubscribe(); + + Assert.assertEquals(true, mas.get() == s); + } +} \ No newline at end of file diff --git a/rxjava-core/src/test/java/rx/subscriptions/RefCountSubscriptionTest.java b/rxjava-core/src/test/java/rx/subscriptions/RefCountSubscriptionTest.java new file mode 100644 index 0000000000..1d0e36d424 --- /dev/null +++ b/rxjava-core/src/test/java/rx/subscriptions/RefCountSubscriptionTest.java @@ -0,0 +1,115 @@ +/** + * Copyright 2014 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.Before; +import org.junit.Test; +import org.mockito.InOrder; + +import rx.Subscription; +import rx.functions.Action0; + +public class RefCountSubscriptionTest { + Action0 main; + RefCountSubscription rcs; + + @Before + public void before() { + main = mock(Action0.class); + rcs = new RefCountSubscription(create(main)); + } + + @Test + public void testImmediateUnsubscribe() { + InOrder inOrder = inOrder(main); + + rcs.unsubscribe(); + + inOrder.verify(main, times(1)).call(); + + rcs.unsubscribe(); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testRCSUnsubscribeBeforeClient() { + InOrder inOrder = inOrder(main); + + Subscription s = rcs.get(); + + rcs.unsubscribe(); + + inOrder.verify(main, never()).call(); + + s.unsubscribe(); + + inOrder.verify(main, times(1)).call(); + + rcs.unsubscribe(); + s.unsubscribe(); + + inOrder.verifyNoMoreInteractions(); + + } + + @Test + public void testMultipleClientsUnsubscribeFirst() { + InOrder inOrder = inOrder(main); + + Subscription s1 = rcs.get(); + Subscription s2 = rcs.get(); + + s1.unsubscribe(); + inOrder.verify(main, never()).call(); + s2.unsubscribe(); + inOrder.verify(main, never()).call(); + + rcs.unsubscribe(); + inOrder.verify(main, times(1)).call(); + + s1.unsubscribe(); + s2.unsubscribe(); + rcs.unsubscribe(); + + inOrder.verifyNoMoreInteractions(); + } + + @Test + public void testMultipleClientsMainUnsubscribeFirst() { + InOrder inOrder = inOrder(main); + + Subscription s1 = rcs.get(); + Subscription s2 = rcs.get(); + + rcs.unsubscribe(); + inOrder.verify(main, never()).call(); + s1.unsubscribe(); + inOrder.verify(main, never()).call(); + s2.unsubscribe(); + + inOrder.verify(main, times(1)).call(); + + s1.unsubscribe(); + s2.unsubscribe(); + rcs.unsubscribe(); + + inOrder.verifyNoMoreInteractions(); + } +} diff --git a/rxjava-core/src/test/java/rx/subscriptions/SerialSubscriptionTests.java b/rxjava-core/src/test/java/rx/subscriptions/SerialSubscriptionTests.java index 1569d9c7df..084323a19c 100644 --- a/rxjava-core/src/test/java/rx/subscriptions/SerialSubscriptionTests.java +++ b/rxjava-core/src/test/java/rx/subscriptions/SerialSubscriptionTests.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,13 +15,8 @@ */ package rx.subscriptions; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.verifyZeroInteractions; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; import java.util.ArrayList; import java.util.List; @@ -49,28 +44,20 @@ public void unsubscribingWithoutUnderlyingDoesNothing() { } @Test - public void getSubscriptionShouldReturnSubscriptionAfterUnsubscribe() { + public void getSubscriptionShouldReturnset() { final Subscription underlying = mock(Subscription.class); - serialSubscription.setSubscription(underlying); - serialSubscription.unsubscribe(); - assertEquals(null, serialSubscription.getSubscription()); - } - - @Test - public void getSubscriptionShouldReturnSetSubscription() { - final Subscription underlying = mock(Subscription.class); - serialSubscription.setSubscription(underlying); - assertSame(underlying, serialSubscription.getSubscription()); + serialSubscription.set(underlying); + assertSame(underlying, serialSubscription.get()); final Subscription another = mock(Subscription.class); - serialSubscription.setSubscription(another); - assertSame(another, serialSubscription.getSubscription()); + serialSubscription.set(another); + assertSame(another, serialSubscription.get()); } @Test public void unsubscribingTwiceDoesUnsubscribeOnce() { Subscription underlying = mock(Subscription.class); - serialSubscription.setSubscription(underlying); + serialSubscription.set(underlying); serialSubscription.unsubscribe(); verify(underlying).unsubscribe(); @@ -82,16 +69,16 @@ public void unsubscribingTwiceDoesUnsubscribeOnce() { @Test public void settingSameSubscriptionTwiceDoesUnsubscribeIt() { Subscription underlying = mock(Subscription.class); - serialSubscription.setSubscription(underlying); + serialSubscription.set(underlying); verifyZeroInteractions(underlying); - serialSubscription.setSubscription(underlying); + serialSubscription.set(underlying); verify(underlying).unsubscribe(); } @Test public void unsubscribingWithSingleUnderlyingUnsubscribes() { Subscription underlying = mock(Subscription.class); - serialSubscription.setSubscription(underlying); + serialSubscription.set(underlying); underlying.unsubscribe(); verify(underlying).unsubscribe(); } @@ -99,18 +86,18 @@ public void unsubscribingWithSingleUnderlyingUnsubscribes() { @Test public void replacingFirstUnderlyingCausesUnsubscription() { Subscription first = mock(Subscription.class); - serialSubscription.setSubscription(first); + serialSubscription.set(first); Subscription second = mock(Subscription.class); - serialSubscription.setSubscription(second); + serialSubscription.set(second); verify(first).unsubscribe(); } @Test public void whenUnsubscribingSecondUnderlyingUnsubscribed() { Subscription first = mock(Subscription.class); - serialSubscription.setSubscription(first); + serialSubscription.set(first); Subscription second = mock(Subscription.class); - serialSubscription.setSubscription(second); + serialSubscription.set(second); serialSubscription.unsubscribe(); verify(second).unsubscribe(); } @@ -119,7 +106,7 @@ public void whenUnsubscribingSecondUnderlyingUnsubscribed() { public void settingUnderlyingWhenUnsubscribedCausesImmediateUnsubscription() { serialSubscription.unsubscribe(); Subscription underlying = mock(Subscription.class); - serialSubscription.setSubscription(underlying); + serialSubscription.set(underlying); verify(underlying).unsubscribe(); } @@ -127,7 +114,7 @@ public void settingUnderlyingWhenUnsubscribedCausesImmediateUnsubscription() { public void settingUnderlyingWhenUnsubscribedCausesImmediateUnsubscriptionConcurrently() throws InterruptedException { final Subscription firstSet = mock(Subscription.class); - serialSubscription.setSubscription(firstSet); + serialSubscription.set(firstSet); final CountDownLatch start = new CountDownLatch(1); @@ -135,7 +122,7 @@ public void settingUnderlyingWhenUnsubscribedCausesImmediateUnsubscriptionConcur final CountDownLatch end = new CountDownLatch(count); final List threads = new ArrayList(); - for (int i = 0 ; i < count ; i++) { + for (int i = 0; i < count; i++) { final Thread t = new Thread() { @Override public void run() { @@ -155,7 +142,7 @@ public void run() { final Subscription underlying = mock(Subscription.class); start.countDown(); - serialSubscription.setSubscription(underlying); + serialSubscription.set(underlying); end.await(); verify(firstSet).unsubscribe(); verify(underlying).unsubscribe(); @@ -175,7 +162,7 @@ public void concurrentSetSubscriptionShouldNotInterleave() final CountDownLatch end = new CountDownLatch(count); final List threads = new ArrayList(); - for (int i = 0 ; i < count ; i++) { + for (int i = 0; i < count; i++) { final Subscription subscription = mock(Subscription.class); subscriptions.add(subscription); @@ -184,7 +171,7 @@ public void concurrentSetSubscriptionShouldNotInterleave() public void run() { try { start.await(); - serialSubscription.setSubscription(subscription); + serialSubscription.set(subscription); } catch (InterruptedException e) { fail(e.getMessage()); } finally { @@ -200,7 +187,7 @@ public void run() { end.await(); serialSubscription.unsubscribe(); - for(final Subscription subscription : subscriptions) { + for (final Subscription subscription : subscriptions) { verify(subscription).unsubscribe(); } diff --git a/rxjava-core/src/test/java/rx/subscriptions/SubscriptionsTest.java b/rxjava-core/src/test/java/rx/subscriptions/SubscriptionsTest.java index abeab16833..adf9ee4477 100644 --- a/rxjava-core/src/test/java/rx/subscriptions/SubscriptionsTest.java +++ b/rxjava-core/src/test/java/rx/subscriptions/SubscriptionsTest.java @@ -1,12 +1,12 @@ /** - * Copyright 2013 Netflix, Inc. - * + * Copyright 2014 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 - * + * + * 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. @@ -21,7 +21,7 @@ import org.junit.Test; import rx.Subscription; -import rx.util.functions.Action0; +import rx.functions.Action0; public class SubscriptionsTest { diff --git a/rxjava-core/src/test/java/rx/test/OperatorTester.java b/rxjava-core/src/test/java/rx/test/OperatorTester.java index 6bd905c264..b6239130e9 100644 --- a/rxjava-core/src/test/java/rx/test/OperatorTester.java +++ b/rxjava-core/src/test/java/rx/test/OperatorTester.java @@ -1,5 +1,5 @@ /** - * Copyright 2013 Netflix, Inc. + * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,7 @@ import rx.Scheduler; import rx.Subscription; -import rx.util.functions.Action0; -import rx.util.functions.Func2; +import rx.functions.Action1; /** * Common utility functions for testing operator implementations. @@ -57,38 +56,14 @@ public ForwardingScheduler(Scheduler underlying) { } @Override - public Subscription schedule(Action0 action) { + public Subscription schedule(Action1 action) { return underlying.schedule(action); } @Override - public Subscription schedule(T state, Func2 action) { - return underlying.schedule(state, action); + public Subscription schedule(Action1 action, long delayTime, TimeUnit unit) { + return underlying.schedule(action, delayTime, unit); } - @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/AssertObservable.java b/rxjava-core/src/test/java/rx/util/AssertObservable.java index 1bd34fcd23..571bf7df63 100644 --- a/rxjava-core/src/test/java/rx/util/AssertObservable.java +++ b/rxjava-core/src/test/java/rx/util/AssertObservable.java @@ -1,9 +1,24 @@ +/** + * Copyright 2014 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 rx.Notification; import rx.Observable; -import rx.util.functions.Func1; -import rx.util.functions.Func2; +import rx.functions.Func1; +import rx.functions.Func2; public class AssertObservable { /** @@ -38,8 +53,7 @@ public static void assertObservableEqualsBlocking(String message, Observable /** * Asserts that two {@link Observable}s are equal and returns an empty {@link Observable}. If - * they are not, an {@link Observable} is returned that calls onError with an - * {@link AssertionError} when subscribed to. If expected and actual + * they are not, an {@link Observable} is returned that calls onError with an {@link AssertionError} when subscribed to. If expected and actual * are null, they are considered equal. * * @param message @@ -55,8 +69,7 @@ public static Observable assertObservableEquals(Observable expected /** * Asserts that two {@link Observable}s are equal and returns an empty {@link Observable}. If - * they are not, an {@link Observable} is returned that calls onError with an - * {@link AssertionError} when subscribed to with the given message. If expected + * they are not, an {@link Observable} is returned that calls onError with an {@link AssertionError} when subscribed to with the given message. If expected * and actual are null, they are considered equal. * * @param message @@ -87,7 +100,7 @@ public Notification call(Notification expectedNotfication, Notificati message.append(" ").append(expectedNotfication.getValue()); if (expectedNotfication.hasThrowable()) message.append(" ").append(expectedNotfication.getThrowable()); - return new Notification("equals " + message.toString()); + return Notification.createOnNext("equals " + message.toString()); } else { StringBuilder error = new StringBuilder(); @@ -103,7 +116,7 @@ public Notification call(Notification expectedNotfication, Notificati error.append(" ").append(actualNotification.getThrowable()); error.append(">"); - return new Notification(new AssertionError(error.toString())); + return Notification.createOnError(new AssertionError(error.toString())); } } }; @@ -118,9 +131,9 @@ public Notification call(Notification a, Notification b) fail |= b.isOnError(); if (fail) - return new Notification(new AssertionError(message)); + return Notification.createOnError(new AssertionError(message)); else - return new Notification(message); + return Notification.createOnNext(message); } }; @@ -129,9 +142,9 @@ public Notification call(Notification a, Notification b) public Notification call(Notification outcome) { if (outcome.isOnError()) { String fullMessage = (message != null ? message + ": " : "") + "Observables are different\n\t" + outcome.getThrowable().getMessage(); - return new Notification(new AssertionError(fullMessage)); + return Notification.createOnError(new AssertionError(fullMessage)); } - return new Notification(); + return Notification.createOnCompleted(); } }).dematerialize(); return outcomeObservable; diff --git a/rxjava-core/src/test/java/rx/util/AssertObservableTest.java b/rxjava-core/src/test/java/rx/util/AssertObservableTest.java index f2182bd8cf..8be0f5dadd 100644 --- a/rxjava-core/src/test/java/rx/util/AssertObservableTest.java +++ b/rxjava-core/src/test/java/rx/util/AssertObservableTest.java @@ -1,3 +1,18 @@ +/** + * Copyright 2014 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 org.junit.Test; diff --git a/rxjava-core/src/test/java/rx/util/RangeTest.java b/rxjava-core/src/test/java/rx/util/RangeTest.java deleted file mode 100644 index 297e7a769a..0000000000 --- a/rxjava-core/src/test/java/rx/util/RangeTest.java +++ /dev/null @@ -1,65 +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.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 176e9150c5..5be4134cef 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,4 +8,7 @@ include 'rxjava-core', \ 'rxjava-contrib:rxjava-swing', \ 'rxjava-contrib:rxjava-android', \ 'rxjava-contrib:rxjava-apache-http', \ -'rxjava-contrib:rxjava-string' +'rxjava-contrib:rxjava-string', \ +'rxjava-contrib:rxjava-debug', \ +'rxjava-contrib:rxjava-async-util', \ +'rxjava-contrib:rxjava-computation-expressions'