Repository: nurkiewicz/rxjava-book-examples Branch: master Commit: ef53aec9cd43 Files: 146 Total size: 217.1 KB Directory structure: gitextract_yjbw__u9/ ├── .gitignore ├── LICENSE.md ├── README.md ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src/ └── test/ └── java/ └── com/ └── oreilly/ └── rxjava/ ├── appendix1/ │ ├── ClientConnection.java │ ├── HttpServer.java │ ├── SingleThread.java │ ├── ThreadPerConnection.java │ └── ThreadPool.java ├── ch1/ │ ├── Callback.java │ └── Chapter1.java ├── ch2/ │ ├── Chapter2.java │ ├── Config.java │ ├── Data.java │ ├── LazyTwitterObservable.java │ ├── NaturalNumbersIterator.java │ ├── Tweet.java │ ├── TwitterSample.java │ └── TwitterSubject.java ├── ch3/ │ ├── Car.java │ ├── CarPhoto.java │ ├── CashTransfer.java │ ├── CassandraFactStore.java │ ├── Chapter3.java │ ├── City.java │ ├── CustomOperators.java │ ├── Customer.java │ ├── Data.java │ ├── FactStore.java │ ├── Flight.java │ ├── Hotel.java │ ├── LicensePlate.java │ ├── Licenses.java │ ├── OperatorMap.java │ ├── Order.java │ ├── Profile.java │ ├── Rating.java │ ├── Reservation.java │ ├── ReservationEvent.java │ ├── Reservations.java │ ├── Shakespeare.java │ ├── Sound.java │ ├── User.java │ ├── Vacation.java │ ├── Weather.java │ └── WeatherStation.java ├── ch4/ │ ├── Book.java │ ├── Chapter4.java │ ├── Flight.java │ ├── Flights.java │ ├── Item.java │ ├── JmsConsumer.java │ ├── Messaging.java │ ├── Passenger.java │ ├── Person.java │ ├── PersonDao.java │ ├── RxGroceries.java │ ├── Schedulers.java │ ├── SimplifiedHandlerScheduler.java │ ├── SmtpResponse.java │ └── Ticket.java ├── ch5/ │ ├── Chapter5.java │ ├── CompletableFutures.java │ ├── EurUsdCurrencyTcpServer.java │ ├── Flight.java │ ├── GeoLocation.java │ ├── HttpHandler.java │ ├── HttpInitializer.java │ ├── HttpTcpNettyServer.java │ ├── HttpTcpRxNettyServer.java │ ├── Postgres.java │ ├── RestCurrencyServer.java │ ├── RxNettyHttpServer.java │ ├── SingleThread.java │ ├── Singles.java │ ├── Ticket.java │ ├── TravelAgency.java │ ├── User.java │ └── Util.java ├── ch6/ │ ├── Backpressure.java │ ├── Chapter6.java │ ├── Debounce.java │ ├── Dish.java │ ├── KeyEvent.java │ ├── Record.java │ ├── Repository.java │ ├── TeleData.java │ └── TradingPlatform.java ├── ch7/ │ ├── Agreement.java │ ├── Chapter7.java │ ├── Confirmation.java │ ├── Monitoring.java │ ├── MyService.java │ ├── MyServiceWithTimeout.java │ ├── Person.java │ ├── PrintHouse.java │ ├── RetryTimeouts.java │ ├── Testing.java │ └── TrackingId.java ├── ch8/ │ ├── Android.java │ ├── ApiFactory.java │ ├── Chapter8.java │ ├── Cities.java │ ├── City.java │ ├── GeoNames.java │ ├── Geoname.java │ ├── Incident.java │ ├── Insurance.java │ ├── MeetupApi.java │ ├── Person.java │ ├── Picture.java │ ├── SearchResult.java │ ├── hystrix/ │ │ ├── BlockingCmd.java │ │ ├── Book.java │ │ ├── CitiesCmd.java │ │ ├── FetchManyRatings.java │ │ ├── FetchRatingsCollapser.java │ │ └── Hystrix.java │ ├── rxandroid/ │ │ ├── AndroidSchedulers.java │ │ ├── LooperScheduler.java │ │ ├── MainActivity.java │ │ ├── MainThreadSubscription.java │ │ ├── RxAndroidPlugins.java │ │ └── RxAndroidSchedulersHook.java │ └── rxbinding/ │ ├── RxTextView.java │ ├── RxView.java │ ├── TextViewAfterTextChangeEvent.java │ ├── TextViewAfterTextChangeEventOnSubscribe.java │ ├── TextViewBeforeTextChangeEvent.java │ ├── TextViewBeforeTextChangeEventOnSubscribe.java │ ├── TextViewEditorActionEvent.java │ ├── TextViewEditorActionEventOnSubscribe.java │ ├── TextViewEditorActionOnSubscribe.java │ ├── TextViewTextChangeEvent.java │ ├── TextViewTextChangeEventOnSubscribe.java │ ├── TextViewTextOnSubscribe.java │ ├── ViewClickOnSubscribe.java │ ├── ViewEvent.java │ └── internal/ │ ├── Functions.java │ └── Preconditions.java ├── ch9/ │ └── Chapter9.java └── util/ └── Sleeper.java ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ build .gradle *.iml .idea ================================================ FILE: LICENSE.md ================================================ MIT License Copyright (c) 2019 Tomasz Nurkiewicz Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Source code of examples from _Reactive Programming with RxJava_ Book is available on [O'Reilly](http://shop.oreilly.com/product/0636920042228.do) and [Amazon](http://amzn.to/2gJ6Vhx). If you find any example incomplete or broken, please [submit a PR](https://github.com/nurkiewicz/rxjava-book-examples/pulls) or [create an issue](https://github.com/nurkiewicz/rxjava-book-examples/issues/new). * [Chapter 1](https://github.com/nurkiewicz/rxjava-book-examples/tree/master/src/test/java/com/oreilly/rxjava/ch1) * [Chapter 2](https://github.com/nurkiewicz/rxjava-book-examples/tree/master/src/test/java/com/oreilly/rxjava/ch2) * [Chapter 3](https://github.com/nurkiewicz/rxjava-book-examples/tree/master/src/test/java/com/oreilly/rxjava/ch3) * [Chapter 4](https://github.com/nurkiewicz/rxjava-book-examples/tree/master/src/test/java/com/oreilly/rxjava/ch4) * [Chapter 5](https://github.com/nurkiewicz/rxjava-book-examples/tree/master/src/test/java/com/oreilly/rxjava/ch5) * [Chapter 6](https://github.com/nurkiewicz/rxjava-book-examples/tree/master/src/test/java/com/oreilly/rxjava/ch6) * [Chapter 7](https://github.com/nurkiewicz/rxjava-book-examples/tree/master/src/test/java/com/oreilly/rxjava/ch7) * [Chapter 8](https://github.com/nurkiewicz/rxjava-book-examples/tree/master/src/test/java/com/oreilly/rxjava/ch8) * [Chapter 9](https://github.com/nurkiewicz/rxjava-book-examples/tree/master/src/test/java/com/oreilly/rxjava/ch9) * [Appendix 1](https://github.com/nurkiewicz/rxjava-book-examples/tree/master/src/test/java/com/oreilly/rxjava/appendix1) # Remarks 1. Some examples were slightly modified to support newer versions of dependent libraries 2. Java projects can't simply import Android `.aar` libraries. Therefore parts of [RxAndroid](https://github.com/ReactiveX/RxAndroid) and [RxBinding](https://github.com/JakeWharton/RxBinding) source code were copied directly. ================================================ FILE: build.gradle ================================================ apply plugin: 'java' sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { mavenCentral() } dependencies { testCompile 'io.reactivex:rxjava:1.2.2' testCompile 'io.reactivex:rxnetty-http:0.5.2-rc.4' testCompile 'com.netflix.hystrix:hystrix-core:1.5.8' testCompile 'com.netflix.hystrix:hystrix-metrics-event-stream:1.5.8' testCompile 'com.google.guava:guava:18.0' testCompile 'org.apache.commons:commons-collections4:4.1' testCompile 'org.apache.commons:commons-lang3:3.5' testCompile 'commons-dbutils:commons-dbutils:1.6' testCompile 'commons-io:commons-io:2.5' testCompile 'commons-dbutils:commons-dbutils:1.6' testCompile 'ch.qos.logback:logback-classic:1.1.7' testCompile 'org.slf4j:slf4j-api:1.7.21' testCompile 'io.dropwizard:dropwizard-metrics:1.0.3' testCompile 'org.springframework:spring-context:4.3.4.RELEASE' testCompile 'org.springframework:spring-jms:4.3.4.RELEASE' testCompile 'org.springframework:spring-jdbc:4.3.4.RELEASE' testCompile 'com.google.android:android:4.1.1.4' // testCompile 'com.jakewharton.rxbinding:rxbinding:0.4.0' testCompile 'org.postgresql:postgresql:9.4-1206-jdbc42' testCompile 'org.twitter4j:twitter4j-stream:4.0.5' testCompile 'com.ning:async-http-client:1.9.40' testCompile 'javax.jms:jms-api:1.1-rev-1' testCompile 'org.apache.activemq:activemq-client:5.14.1' testCompile 'org.eclipse.jetty:jetty-servlet:9.4.0.RC2' testCompile 'com.couchbase.client:java-client:2.3.5' testCompile 'org.mongodb:mongodb-driver-rx:1.2.0' testCompile 'org.apache.camel:camel-rx:2.17.3' testCompile 'org.apache.camel:camel-kafka:2.17.3' testCompile 'org.apache.activemq:activemq-camel:5.14.0' testCompile 'org.apache.activemq:activemq-client:5.14.0' testCompile 'com.squareup.retrofit2:adapter-rxjava:2.0.1' testCompile 'com.squareup.retrofit2:converter-jackson:2.0.1' testCompile 'junit:junit:4.12' testCompile 'org.assertj:assertj-core:3.5.2' testCompile 'org.mockito:mockito-core:2.2.15' } task wrapper(type: Wrapper) { gradleVersion = '3.1' } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ #Sat Nov 19 14:44:58 CET 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-3.1-all.zip ================================================ FILE: gradlew ================================================ #!/usr/bin/env bash ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn ( ) { echo "$*" } die ( ) { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules function splitJvmOpts() { JVM_OPTS=("$@") } eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" ================================================ FILE: gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: src/test/java/com/oreilly/rxjava/appendix1/ClientConnection.java ================================================ package com.oreilly.rxjava.appendix1; import org.apache.commons.io.IOUtils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.Socket; class ClientConnection implements Runnable { public static final byte[] RESPONSE = ( "HTTP/1.1 200 OK\r\n" + "Content-length: 2\r\n" + "\r\n" + "OK").getBytes(); public static final byte[] SERVICE_UNAVAILABLE = ( "HTTP/1.1 503 Service unavailable\r\n").getBytes(); private final Socket client; ClientConnection(Socket client) { this.client = client; } public void run() { try { while (!Thread.currentThread().isInterrupted()) { readFullRequest(); client.getOutputStream().write(RESPONSE); } } catch (Exception e) { e.printStackTrace(); IOUtils.closeQuietly(client); } } private void readFullRequest() throws IOException { BufferedReader reader = new BufferedReader( new InputStreamReader(client.getInputStream())); String line = reader.readLine(); while (line != null && !line.isEmpty()) { line = reader.readLine(); } } public void serviceUnavailable() { try { client.getOutputStream().write(SERVICE_UNAVAILABLE); } catch (IOException e) { throw new RuntimeException(e); } } } ================================================ FILE: src/test/java/com/oreilly/rxjava/appendix1/HttpServer.java ================================================ package com.oreilly.rxjava.appendix1; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; abstract class HttpServer { void run(int port) throws IOException { final ServerSocket serverSocket = new ServerSocket(port, 100); while (!Thread.currentThread().isInterrupted()) { final Socket client = serverSocket.accept(); handle(new ClientConnection(client)); } } abstract void handle(ClientConnection clientConnection); } ================================================ FILE: src/test/java/com/oreilly/rxjava/appendix1/SingleThread.java ================================================ package com.oreilly.rxjava.appendix1; public class SingleThread extends HttpServer { public static void main(String[] args) throws Exception { new SingleThread().run(8080); } @Override void handle(ClientConnection clientConnection) { clientConnection.run(); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/appendix1/ThreadPerConnection.java ================================================ package com.oreilly.rxjava.appendix1; import java.io.IOException; public class ThreadPerConnection extends HttpServer { public static void main(String[] args) throws IOException { new ThreadPerConnection().run(8080); } @Override void handle(ClientConnection clientConnection) { new Thread(clientConnection).start(); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/appendix1/ThreadPool.java ================================================ package com.oreilly.rxjava.appendix1; import java.io.IOException; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import static java.util.concurrent.TimeUnit.MILLISECONDS; class ThreadPool extends HttpServer { private final ThreadPoolExecutor executor; public static void main(String[] args) throws IOException { new ThreadPool().run(8080); } public ThreadPool() { BlockingQueue workQueue = new ArrayBlockingQueue<>(1000); executor = new ThreadPoolExecutor(100, 100, 0L, MILLISECONDS, workQueue, (r, ex) -> { ((ClientConnection) r).serviceUnavailable(); }); } @Override void handle(ClientConnection clientConnection) { executor.execute(clientConnection); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch1/Callback.java ================================================ package com.oreilly.rxjava.ch1; import java.util.function.Consumer; class Callback { private Consumer onResponse = x -> {}; private Consumer onFailure = x -> {}; Callback onResponse(Consumer consumer) { this.onResponse = consumer; return this; } Callback onFailure(Consumer consumer) { this.onFailure = consumer; return this; } public Consumer getOnResponse() { return onResponse; } public Consumer getOnFailure() { return onFailure; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch1/Chapter1.java ================================================ package com.oreilly.rxjava.ch1; import com.oreilly.rxjava.util.Sleeper; import org.junit.Ignore; import org.junit.Test; import rx.Completable; import rx.Observable; import rx.Single; import rx.schedulers.Schedulers; import java.time.Duration; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.stream.IntStream; import java.util.stream.Stream; @Ignore public class Chapter1 { private static final String SOME_KEY = "FOO"; @Test public void sample_6() throws Exception { Observable.create(s -> { s.onNext("Hello World!"); s.onCompleted(); }).subscribe(hello -> System.out.println(hello)); } @Test public void sample_17() throws Exception { Map cache = new ConcurrentHashMap<>(); cache.put(SOME_KEY, "123"); Observable.create(s -> { s.onNext(cache.get(SOME_KEY)); s.onCompleted(); }).subscribe(value -> System.out.println(value)); } @Test public void sample_35() throws Exception { // pseudo-code Observable.create(s -> { String fromCache = getFromCache(SOME_KEY); if (fromCache != null) { // emit synchronously s.onNext(fromCache); s.onCompleted(); } else { // fetch asynchronously getDataAsynchronously(SOME_KEY) .onResponse(v -> { putInCache(SOME_KEY, v); s.onNext(v); s.onCompleted(); }) .onFailure(exception -> { s.onError(exception); }); } }).subscribe(s -> System.out.println(s)); Sleeper.sleep(Duration.ofSeconds(2)); } private void putInCache(String key, String value) { //do nothing } private Callback getDataAsynchronously(String key) { final Callback callback = new Callback(); new Thread(() -> { Sleeper.sleep(Duration.ofSeconds(1)); callback.getOnResponse().accept(key + ":123"); }).start(); return callback; } private String getFromCache(String key) { // return null; return key + ":123"; } @Test public void sample_81() throws Exception { Observable o = Observable.create(s -> { s.onNext(1); s.onNext(2); s.onNext(3); s.onCompleted(); }); o.map(i -> "Number " + i) .subscribe(s -> System.out.println(s)); } @Test public void sample_94() throws Exception { Observable.create(s -> { //... async subscription and data emission ... new Thread(() -> s.onNext(42), "MyThread").start(); }) .doOnNext(i -> System.out.println(Thread.currentThread())) .filter(i -> i % 2 == 0) .map(i -> "Value " + i + " processed on " + Thread.currentThread()) .subscribe(s -> System.out.println("SOME VALUE =>" + s)); System.out.println("Will print BEFORE values are emitted because Observable is async"); Sleeper.sleep(Duration.ofSeconds(1)); } @Test public void sample_108() throws Exception { Observable.create(s -> { new Thread(() -> { s.onNext("one"); s.onNext("two"); s.onNext("three"); s.onNext("four"); s.onCompleted(); }).start(); }); } @Test public void sample_121() throws Exception { // DO NOT DO THIS Observable.create(s -> { // Thread A new Thread(() -> { s.onNext("one"); s.onNext("two"); }).start(); // Thread B new Thread(() -> { s.onNext("three"); s.onNext("four"); }).start(); // ignoring need to emit s.onCompleted() due to race of threads }); // DO NOT DO THIS } @Test public void sample_142() throws Exception { Observable a = Observable.create(s -> { new Thread(() -> { s.onNext("one"); s.onNext("two"); s.onCompleted(); }).start(); }); Observable b = Observable.create(s -> { new Thread(() -> { s.onNext("three"); s.onNext("four"); s.onCompleted(); }).start(); }); // this subscribes to a and b concurrently, and merges into a third sequential stream Observable c = Observable.merge(a, b); } @Test public void sample_164() throws Exception { String args = SOME_KEY; Observable someData = Observable.create(s -> { getDataFromServerWithCallback(args, data -> { s.onNext(data); s.onCompleted(); }); }); someData.subscribe(s -> System.out.println("Subscriber 1: " + s)); someData.subscribe(s -> System.out.println("Subscriber 2: " + s)); Observable lazyFallback = Observable.just("Fallback"); someData .onErrorResumeNext(lazyFallback) .subscribe(s -> System.out.println(s)); } private void getDataFromServerWithCallback(String args, Consumer consumer) { consumer.accept("Random: " + Math.random()); } @Test public void sample_188() throws Exception { // Iterable as Stream // that contains 75 strings getDataFromLocalMemorySynchronously() .skip(10) .limit(5) .map(s -> s + "_transformed") .forEach(System.out::println); } private Stream getDataFromLocalMemorySynchronously() { return IntStream .range(0, 100) .mapToObj(Integer::toString); } @Test public void sample_205() throws Exception { // Observable // that emits 75 strings getDataFromNetworkAsynchronously() .skip(10) .take(5) .map(s -> s + "_transformed") .subscribe(System.out::println); } private Observable getDataFromNetworkAsynchronously() { return Observable .range(0, 100) .map(Object::toString); } @Test public void sample_225() throws Exception { CompletableFuture f1 = getDataAsFuture(1); CompletableFuture f2 = getDataAsFuture(2); CompletableFuture f3 = f1.thenCombine(f2, (x, y) -> { return x+y; }); } private CompletableFuture getDataAsFuture(int i) { return CompletableFuture.completedFuture("Done: " + i + "\n"); } @Test public void sample_240() throws Exception { Observable o1 = getDataAsObservable(1); Observable o2 = getDataAsObservable(2); Observable o3 = Observable.zip(o1, o2, (x, y) -> { return x+y; }); } private Observable getDataAsObservable(int i) { return Observable.just("Done: " + i + "\n"); } @Test public void sample_254() throws Exception { Observable o1 = getDataAsObservable(1); Observable o2 = getDataAsObservable(2); // o3 is now a stream of o1 and o2 that emits each item without waiting Observable o3 = Observable.merge(o1, o2); } @Test public void sample_265() throws Exception { // merge a & b into an Observable stream of 2 values Observable a_merge_b = getDataA().mergeWith(getDataB()); } public static Single getDataA() { return Single. create(o -> { o.onSuccess("DataA"); }).subscribeOn(Schedulers.io()); } @Test public void sample_277() throws Exception { // Observable o1 = getDataAsObservable(1); // Observable o2 = getDataAsObservable(2); Single s1 = getDataAsSingle(1); Single s2 = getDataAsSingle(2); // o3 is now a stream of s1 and s2 that emits each item without waiting Observable o3 = Single.merge(s1, s2); } private Single getDataAsSingle(int i) { return Single.just("Done: " + i); } public static Single getDataB() { return Single.just("DataB") .subscribeOn(Schedulers.io()); } static Completable writeToDatabase(Object data) { return Completable.create(s -> { doAsyncWrite(data, // callback for successful completion () -> s.onCompleted(), // callback for failure with Throwable error -> s.onError(error)); }); } static void doAsyncWrite(Object data, Runnable onSuccess, Consumer onError) { //store data an run asynchronously: onSuccess.run(); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch2/Chapter2.java ================================================ package com.oreilly.rxjava.ch2; import com.oreilly.rxjava.util.Sleeper; import org.junit.Ignore; import org.junit.Test; import rx.Observable; import rx.Observer; import rx.Subscriber; import rx.Subscription; import rx.subscriptions.Subscriptions; import java.math.BigInteger; import java.time.Duration; import java.util.Collection; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import static java.math.BigInteger.ONE; import static java.math.BigInteger.ZERO; import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.SECONDS; @Ignore public class Chapter2 { @Test public void sample_6() throws Exception { Observable tweets = Observable.empty(); //... tweets.subscribe((Tweet tweet) -> System.out.println(tweet)); } @Test public void sample_17() throws Exception { Observable tweets = Observable.empty(); //... tweets.subscribe( (Tweet tweet) -> { System.out.println(tweet); }, (Throwable t) -> { t.printStackTrace(); } ); } @Test public void sample_27() throws Exception { Observable tweets = Observable.empty(); //... tweets.subscribe( (Tweet tweet) -> { System.out.println(tweet); }, (Throwable t) -> { t.printStackTrace(); }, () -> {this.noMore();} ); } @Test public void sample_38() throws Exception { Observable tweets = Observable.empty(); //... tweets.subscribe( System.out::println, Throwable::printStackTrace, this::noMore); } private void noMore() { } @Test public void sample_51() throws Exception { Observable tweets = Observable.empty(); //... Observer observer = new Observer() { @Override public void onNext(Tweet tweet) { System.out.println(tweet); } @Override public void onError(Throwable e) { e.printStackTrace(); } @Override public void onCompleted() { noMore(); } }; //... tweets.subscribe(observer); } @Test public void sample_78() throws Exception { Observable tweets = Observable.empty(); //... Subscription subscription = tweets.subscribe(System.out::println); //... subscription.unsubscribe(); } @Test public void sample_91() throws Exception { Observable tweets = Observable.empty(); //... Subscriber subscriber = new Subscriber() { @Override public void onNext(Tweet tweet) { if (tweet.getText().contains("Java")) { unsubscribe(); } } @Override public void onCompleted() {} @Override public void onError(Throwable e) { e.printStackTrace(); } }; tweets.subscribe(subscriber); } private static void log(Object msg) { System.out.println( Thread.currentThread().getName() + ": " + msg); } @Test public void sample_117() throws Exception { log("Before"); Observable .range(5, 3) .subscribe(i -> { log(i); }); log("After"); } @Test public void sample_135() throws Exception { Observable ints = Observable .create(new Observable.OnSubscribe() { @Override public void call(Subscriber subscriber) { log("Create"); subscriber.onNext(5); subscriber.onNext(6); subscriber.onNext(7); subscriber.onCompleted(); log("Completed"); } }); log("Starting"); ints.subscribe(i -> log("Element: " + i)); log("Exit"); } static Observable just(T x) { return Observable.create(subscriber -> { subscriber.onNext(x); subscriber.onCompleted(); } ); } @Test public void sample_162() throws Exception { Observable ints = Observable.create(subscriber -> { log("Create"); subscriber.onNext(42); subscriber.onCompleted(); } ); log("Starting"); ints.subscribe(i -> log("Element A: " + i)); ints.subscribe(i -> log("Element B: " + i)); log("Exit"); } @Test public void sample_177() throws Exception { Observable ints = Observable.create(subscriber -> { //... } ) .cache(); } @Test public void sample_187() throws Exception { //BROKEN! Don't do this Observable naturalNumbers = Observable.create( subscriber -> { BigInteger i = ZERO; while (true) { //don't do this! subscriber.onNext(i); i = i.add(ONE); } }); naturalNumbers.subscribe(x -> log(x)); } private Observable naturalNumbers() { Observable naturalNumbers = Observable.create( subscriber -> { Runnable r = () -> { BigInteger i = ZERO; while (!subscriber.isUnsubscribed()) { subscriber.onNext(i); i = i.add(ONE); } }; new Thread(r).start(); }); return naturalNumbers; } @Test public void sample_221() throws Exception { final Observable naturalNumbers = naturalNumbers(); Subscription subscription = naturalNumbers.subscribe(x -> log(x)); //after some time... subscription.unsubscribe(); } static Observable delayed(T x) { return Observable.create( subscriber -> { Runnable r = () -> { sleep(10, SECONDS); if (!subscriber.isUnsubscribed()) { subscriber.onNext(x); subscriber.onCompleted(); } }; new Thread(r).start(); }); } static void sleep(int timeout, TimeUnit unit) { try { unit.sleep(timeout); } catch (InterruptedException ignored) { //intentionally ignored } } static Observable delayed2(T x) { return Observable.create( subscriber -> { Runnable r = () -> {/* ... */}; final Thread thread = new Thread(r); thread.start(); subscriber.add(Subscriptions.create(thread::interrupt)); }); } Observable loadAll(Collection ids) { return Observable.create(subscriber -> { ExecutorService pool = Executors.newFixedThreadPool(10); AtomicInteger countDown = new AtomicInteger(ids.size()); //DANGER, violates Rx contract. Don't do this! ids.forEach(id -> pool.submit(() -> { final Data data = load(id); subscriber.onNext(data); if (countDown.decrementAndGet() == 0) { pool.shutdownNow(); subscriber.onCompleted(); } })); }); } private Data load(Integer id) { return new Data(); } Observable rxLoad(int id) { return Observable.create(subscriber -> { try { subscriber.onNext(load(id)); subscriber.onCompleted(); } catch (Exception e) { subscriber.onError(e); } }); } Observable rxLoad2(int id) { return Observable.fromCallable(() -> load(id)); } @Test public void sample_304() throws Exception { Observable .timer(1, TimeUnit.SECONDS) .subscribe((Long zero) -> log(zero)); Sleeper.sleep(Duration.ofSeconds(2)); } @Test public void sample_311() throws Exception { Observable .interval(1_000_000 / 60, MICROSECONDS) .subscribe((Long i) -> log(i)); Sleeper.sleep(Duration.ofSeconds(2)); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch2/Config.java ================================================ package com.oreilly.rxjava.ch2; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; import rx.Observable; import rx.observables.ConnectableObservable; import twitter4j.Status; @Configuration class Config implements ApplicationListener { private static final Logger log = LoggerFactory.getLogger(Config.class); private final ConnectableObservable observable = Observable.create(subscriber -> { log.info("Starting"); //... }).publish(); @Bean public Observable observable() { return observable; } @Override public void onApplicationEvent(ContextRefreshedEvent event) { log.info("Connecting"); observable.connect(); } } @Component class Foo { private static final Logger log = LoggerFactory.getLogger(Foo.class); @Autowired public Foo(Observable tweets) { tweets.subscribe(status -> { log.info(status.getText()); }); log.info("Subscribed"); } } @Component class Bar { private static final Logger log = LoggerFactory.getLogger(Bar.class); @Autowired public Bar(Observable tweets) { tweets.subscribe(status -> { log.info(status.getText()); }); log.info("Subscribed"); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch2/Data.java ================================================ package com.oreilly.rxjava.ch2; class Data { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch2/LazyTwitterObservable.java ================================================ package com.oreilly.rxjava.ch2; import rx.Observable; import rx.Subscriber; import rx.subscriptions.Subscriptions; import twitter4j.*; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; //DON'T DO THIS, Very brittle and error prone class LazyTwitterObservable { private final Set> subscribers = new CopyOnWriteArraySet<>(); private final TwitterStream twitterStream; public LazyTwitterObservable() { this.twitterStream = new TwitterStreamFactory().getInstance(); this.twitterStream.addListener(new StatusListener() { @Override public void onStatus(Status status) { subscribers.forEach(s -> s.onNext(status)); } @Override public void onDeletionNotice(StatusDeletionNotice statusDeletionNotice) { //... } @Override public void onTrackLimitationNotice(int numberOfLimitedStatuses) { //... } @Override public void onScrubGeo(long userId, long upToStatusId) { //... } @Override public void onStallWarning(StallWarning warning) { //... } @Override public void onException(Exception ex) { subscribers.forEach(s -> s.onError(ex)); } }); } private final Observable observable = Observable.create( subscriber -> { register(subscriber); subscriber.add(Subscriptions.create(() -> this.deregister(subscriber))); }); Observable observe() { return observable; } private synchronized void register(Subscriber subscriber) { if (subscribers.isEmpty()) { subscribers.add(subscriber); twitterStream.sample(); } else { subscribers.add(subscriber); } } private synchronized void deregister(Subscriber subscriber) { subscribers.remove(subscriber); if (subscribers.isEmpty()) { twitterStream.shutdown(); } } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch2/NaturalNumbersIterator.java ================================================ package com.oreilly.rxjava.ch2; import java.math.BigInteger; import java.util.Iterator; class NaturalNumbersIterator implements Iterator { private BigInteger current = BigInteger.ZERO; public boolean hasNext() { return true; } @Override public BigInteger next() { current = current.add(BigInteger.ONE); return current; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch2/Tweet.java ================================================ package com.oreilly.rxjava.ch2; class Tweet { private final String text; Tweet(String text) { this.text = text; } String getText() { return text; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch2/TwitterSample.java ================================================ package com.oreilly.rxjava.ch2; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import rx.Subscription; import rx.observables.ConnectableObservable; import rx.subscriptions.Subscriptions; import twitter4j.*; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @Ignore public class TwitterSample { private static final Logger log = LoggerFactory.getLogger(TwitterSample.class); @Test public void sample_18() throws Exception { TwitterStream twitterStream = new TwitterStreamFactory().getInstance(); twitterStream.addListener(new twitter4j.StatusListener() { @Override public void onStatus(Status status) { log.info("Status: {}", status); } @Override public void onDeletionNotice(StatusDeletionNotice statusDeletionNotice) { //... } @Override public void onTrackLimitationNotice(int numberOfLimitedStatuses) { //... } @Override public void onScrubGeo(long userId, long upToStatusId) { //... } @Override public void onStallWarning(StallWarning warning) { //... } @Override public void onException(Exception ex) { log.error("Error callback", ex); } //other callbacks }); twitterStream.sample(); TimeUnit.SECONDS.sleep(10); twitterStream.shutdown(); } void consume( Consumer onStatus, Consumer onException) { TwitterStream twitterStream = new TwitterStreamFactory().getInstance(); twitterStream.addListener(new StatusListener() { @Override public void onStatus(Status status) { onStatus.accept(status); } @Override public void onDeletionNotice(StatusDeletionNotice statusDeletionNotice) { //... } @Override public void onTrackLimitationNotice(int numberOfLimitedStatuses) { //... } @Override public void onScrubGeo(long userId, long upToStatusId) { //... } @Override public void onStallWarning(StallWarning warning) { //... } @Override public void onException(Exception ex) { onException.accept(ex); } }); twitterStream.sample(); } @Test public void sample_99() throws Exception { consume( status -> log.info("Status: {}", status), ex -> log.error("Error callback", ex) ); } Observable observe() { return Observable.create(subscriber -> { TwitterStream twitterStream = new TwitterStreamFactory().getInstance(); twitterStream.addListener(new StatusListener() { @Override public void onStatus(Status status) { if (subscriber.isUnsubscribed()) { twitterStream.shutdown(); } else { subscriber.onNext(status); } } @Override public void onDeletionNotice(StatusDeletionNotice statusDeletionNotice) { //... } @Override public void onTrackLimitationNotice(int numberOfLimitedStatuses) { //... } @Override public void onScrubGeo(long userId, long upToStatusId) { //... } @Override public void onStallWarning(StallWarning warning) { //... } @Override public void onException(Exception ex) { if (subscriber.isUnsubscribed()) { twitterStream.shutdown(); } else { subscriber.onError(ex); } } }); subscriber.add(Subscriptions.create(twitterStream::shutdown)); }); } @Test public void sample_150() throws Exception { observe().subscribe( status -> log.info("Status: {}", status), ex -> log.error("Error callback", ex) ); } @Test public void sample_162() throws Exception { Observable observable = status(); Subscription sub1 = observable.subscribe(); System.out.println("Subscribed 1"); Subscription sub2 = observable.subscribe(); System.out.println("Subscribed 2"); sub1.unsubscribe(); System.out.println("Unsubscribed 1"); sub2.unsubscribe(); System.out.println("Unsubscribed 2"); } private Observable status() { return Observable.create(subscriber -> { System.out.println("Establishing connection"); TwitterStream twitterStream = new TwitterStreamFactory().getInstance(); //... subscriber.add(Subscriptions.create(() -> { System.out.println("Disconnecting"); twitterStream.shutdown(); })); twitterStream.sample(); }); } @Test public void sample_186() throws Exception { Observable observable = status(); Observable lazy = observable.publish().refCount(); //... System.out.println("Before subscribers"); Subscription sub1 = lazy.subscribe(); System.out.println("Subscribed 1"); Subscription sub2 = lazy.subscribe(); System.out.println("Subscribed 2"); sub1.unsubscribe(); System.out.println("Unsubscribed 1"); sub2.unsubscribe(); System.out.println("Unsubscribed 2"); } @Test public void sample_206() throws Exception { final Observable tweets = status(); ConnectableObservable published = tweets.publish(); published.connect(); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch2/TwitterSubject.java ================================================ package com.oreilly.rxjava.ch2; import rx.Observable; import rx.subjects.PublishSubject; import twitter4j.*; class TwitterSubject { private final PublishSubject subject = PublishSubject.create(); public TwitterSubject() { TwitterStream twitterStream = new TwitterStreamFactory().getInstance(); twitterStream.addListener(new StatusListener() { @Override public void onStatus(Status status) { subject.onNext(status); } @Override public void onDeletionNotice(StatusDeletionNotice statusDeletionNotice) { //... } @Override public void onTrackLimitationNotice(int numberOfLimitedStatuses) { //... } @Override public void onScrubGeo(long userId, long upToStatusId) { //... } @Override public void onStallWarning(StallWarning warning) { //... } @Override public void onException(Exception ex) { subject.onError(ex); } }); twitterStream.sample(); } public Observable observe() { return subject; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Car.java ================================================ package com.oreilly.rxjava.ch3; class Car { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/CarPhoto.java ================================================ package com.oreilly.rxjava.ch3; class CarPhoto { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/CashTransfer.java ================================================ package com.oreilly.rxjava.ch3; import java.math.BigDecimal; class CashTransfer { BigDecimal getAmount() { return BigDecimal.ONE; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/CassandraFactStore.java ================================================ package com.oreilly.rxjava.ch3; import rx.Observable; class CassandraFactStore implements FactStore { @Override public Observable observe() { return Observable.just(new ReservationEvent()); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Chapter3.java ================================================ package com.oreilly.rxjava.ch3; import com.oreilly.rxjava.util.Sleeper; import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.tuple.Pair; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import rx.functions.Func1; import twitter4j.Status; import java.math.BigDecimal; import java.math.BigInteger; import java.time.DayOfWeek; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.LongAdder; import static com.oreilly.rxjava.ch3.Sound.DAH; import static com.oreilly.rxjava.ch3.Sound.DI; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static rx.Observable.*; @Ignore public class Chapter3 { private static final Logger log = LoggerFactory.getLogger(Chapter3.class); @Test public void sample_6() throws Exception { Observable strings = empty(); Observable filtered = strings.filter(s -> s.startsWith("#")); } @Test public void sample_15() throws Exception { Observable strings = empty(); Observable comments = strings.filter(s -> s.startsWith("#")); Observable instructions = strings.filter(s -> s.startsWith(">")); Observable empty = strings.filter(String::isEmpty); } @Test public void sample_26() throws Exception { Observable tweets = empty(); Observable dates = tweets.map(new Func1() { @Override public Date call(Status status) { return status.getCreatedAt(); } }); Observable dates2 = tweets.map((Status status) -> status.getCreatedAt()); Observable dates3 = tweets.map((status) -> status.getCreatedAt()); Observable dates4 = tweets.map(Status::getCreatedAt); } @Test public void sample_49() throws Exception { Observable tweets = empty(); Observable instants = tweets .map(Status::getCreatedAt) .map((Date d) -> d.toInstant()); } @Test public void sample_57() throws Exception { just(8, 9, 10) .filter(i -> i % 3 > 0) .map(i -> "#" + i * 10) .filter(s -> s.length() < 4); } @Test public void sample_66() throws Exception { just(8, 9, 10) .doOnNext(i -> System.out.println("A: " + i)) .filter(i -> i % 3 > 0) .doOnNext(i -> System.out.println("B: " + i)) .map(i -> "#" + i * 10) .doOnNext(s -> System.out.println("C: " + s)) .filter(s -> s.length() < 4) .subscribe(s -> System.out.println("D: " + s)); } @Test public void sample_79() throws Exception { Observable numbers = just(1, 2, 3, 4); numbers.map(x -> x * 2); numbers.filter(x -> x != 10); //equivalent numbers.flatMap(x -> just(x * 2)); numbers.flatMap(x -> (x != 10) ? just(x) : empty()); } @Test public void sample_111() throws Exception { Observable customers = Observable.just(new Customer()); Observable orders = customers .flatMap(customer -> Observable.from(customer.getOrders())); } @Test public void sample_119() throws Exception { Observable customers = Observable.just(new Customer()); Observable orders = customers .map(Customer::getOrders) .flatMap(Observable::from); } @Test public void sample_127() throws Exception { Observable customers = Observable.just(new Customer()); Observable orders = customers .flatMapIterable(Customer::getOrders); } void store(UUID id) { upload(id).subscribe( bytes -> { }, //ignore e -> log.error("Error", e), () -> rate(id) ); } Observable upload(UUID id) { return Observable.just(42L); } Observable rate(UUID id) { return Observable.just(new Rating()); } @Test public void sample_155() throws Exception { UUID id = UUID.randomUUID(); upload(id) .flatMap( bytes -> Observable.empty(), e -> Observable.error(e), () -> rate(id) ); } Observable toMorseCode(char ch) { switch (ch) { case 'a': return just(DI, DAH); case 'b': return just(DAH, DI, DI, DI); case 'c': return just(DAH, DI, DAH, DI); case 'd': return just(DAH, DI, DI); case 'e': return just(DI); case 'f': return just(DI, DI, DAH, DI); case 'g': return just(DAH, DAH, DI); case 'h': return just(DI, DI, DI, DI); case 'i': return just(DI, DI); case 'j': return just(DI, DAH, DAH, DAH); case 'k': return just(DAH, DI, DAH); case 'l': return just(DI, DAH, DI, DI); case 'm': return just(DAH, DAH); case 'n': return just(DAH, DI); case 'o': return just(DAH, DAH, DAH); case 'p': return just(DI, DAH, DAH, DI); case 'q': return just(DAH, DAH, DI, DAH); case 'r': return just(DI, DAH, DI); case 's': return just(DI, DI, DI); case 't': return just(DAH); case 'u': return just(DI, DI, DAH); case 'v': return just(DI, DI, DI, DAH); case 'w': return just(DI, DAH, DAH); case 'x': return just(DAH, DI, DI, DAH); case 'y': return just(DAH, DI, DAH, DAH); case 'z': return just(DAH, DAH, DI, DI); case '0': return just(DAH, DAH, DAH, DAH, DAH); case '1': return just(DI, DAH, DAH, DAH, DAH); case '2': return just(DI, DI, DAH, DAH, DAH); case '3': return just(DI, DI, DI, DAH, DAH); case '4': return just(DI, DI, DI, DI, DAH); case '5': return just(DI, DI, DI, DI, DI); case '6': return just(DAH, DI, DI, DI, DI); case '7': return just(DAH, DAH, DI, DI, DI); case '8': return just(DAH, DAH, DAH, DI, DI); case '9': return just(DAH, DAH, DAH, DAH, DI); default: return empty(); } } @Test public void sample_213() throws Exception { just('S', 'p', 'a', 'r', 't', 'a') .map(Character::toLowerCase) .flatMap(this::toMorseCode); } @Test public void sample_218() throws Exception { Observable .just("Lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit") .delay(word -> timer(word.length(), SECONDS)) .subscribe(System.out::println); SECONDS.sleep(15); } Observable loadRecordsFor(DayOfWeek dow) { switch (dow) { case SUNDAY: return interval(90, MILLISECONDS) .take(5) .map(i -> "Sun-" + i); case MONDAY: return interval(65, MILLISECONDS) .take(5) .map(i -> "Mon-" + i); default: throw new IllegalArgumentException("Illegal: " + dow); } } @Test public void sample_249() throws Exception { Observable .just(DayOfWeek.SUNDAY, DayOfWeek.MONDAY) .concatMap(this::loadRecordsFor); } @Test public void sample_258() throws Exception { List veryLargeList = Arrays.asList(new User(), new User(), new User(), new User()); Observable profiles = Observable .from(veryLargeList) .flatMap(User::loadProfile); } @Test public void sample_286() throws Exception { final WeatherStation station = new BasicWeatherStation(); Observable temperatureMeasurements = station.temperature(); Observable windMeasurements = station.wind(); temperatureMeasurements .zipWith(windMeasurements, (temperature, wind) -> new Weather(temperature, wind)); } @Test public void sample_298() throws Exception { Observable oneToEight = Observable.range(1, 8); Observable ranks = oneToEight .map(Object::toString); Observable files = oneToEight .map(x -> 'a' + x - 1) .map(ascii -> (char) ascii.intValue()) .map(ch -> Character.toString(ch)); Observable squares = files .flatMap(file -> ranks.map(rank -> file + rank)); } @Test public void sample_312() throws Exception { Observable nextTenDays = Observable .range(1, 10) .map(i -> LocalDate.now().plusDays(i)); Observable possibleVacations = Observable .just(City.Warsaw, City.London, City.Paris) .flatMap(city -> nextTenDays.map(date -> new Vacation(city, date)) .flatMap(vacation -> Observable.zip( vacation.weather().filter(Weather::isSunny), vacation.cheapFlightFrom(City.NewYork), vacation.cheapHotel(), (w, f, h) -> vacation ))); } @Test public void sample_332() throws Exception { Observable red = interval(10, TimeUnit.MILLISECONDS); Observable green = interval(10, TimeUnit.MILLISECONDS); Observable.zip( red.timestamp(), green.timestamp(), (r, g) -> r.getTimestampMillis() - g.getTimestampMillis() ).forEach(System.out::println); } @Test public void sample_345() throws Exception { Observable.combineLatest( interval(17, MILLISECONDS).map(x -> "S" + x), interval(10, MILLISECONDS).map(x -> "F" + x), (s, f) -> f + ":" + s ).forEach(System.out::println); Sleeper.sleep(Duration.ofSeconds(2)); } @Test public void sample_355() throws Exception { Observable fast = interval(10, MILLISECONDS) .map(x -> "F" + x) .delay(100, MILLISECONDS) .startWith("FX"); Observable slow = interval(17, MILLISECONDS).map(x -> "S" + x); slow .withLatestFrom(fast, (s, f) -> s + ":" + f) .forEach(System.out::println); } @Test public void sample_367() throws Exception { Observable .just(1, 2) .startWith(0) .subscribe(System.out::println); } Observable stream(int initialDelay, int interval, String name) { return Observable .interval(initialDelay, interval, MILLISECONDS) .map(x -> name + x) .doOnSubscribe(() -> log.info("Subscribe to " + name)) .doOnUnsubscribe(() -> log.info("Unsubscribe from " + name)); } @Test public void sample_375() throws Exception { Observable.amb( stream(100, 17, "S"), stream(200, 10, "F") ).subscribe(log::info); } @Test public void sample_393() throws Exception { stream(100, 17, "S") .ambWith(stream(200, 10, "F")) .subscribe(log::info); } @Test public void sample_400() throws Exception { //BROKEN! Observable progress = transferFile(); LongAdder total = new LongAdder(); progress.subscribe(total::add); Sleeper.sleep(Duration.ofSeconds(10)); } private Observable transferFile() { return Observable .interval(500, MILLISECONDS) .map(x -> RandomUtils.nextLong(10, 30)) .take(100); } @Test public void sample_419() throws Exception { Observable progress = transferFile(); Observable totalProgress = progress .scan((total, chunk) -> total + chunk); totalProgress .toBlocking() .subscribe(System.out::println); } @Test public void sample_431() throws Exception { Observable factorials = Observable .range(2, 100) .scan(BigInteger.ONE, (big, cur) -> big.multiply(BigInteger.valueOf(cur))); } @Test public void sample_440() throws Exception { Observable transfers = Observable.just(new CashTransfer()); Observable total1 = transfers .reduce(BigDecimal.ZERO, (totalSoFar, transfer) -> totalSoFar.add(transfer.getAmount())); Observable total2 = transfers .map(CashTransfer::getAmount) .reduce(BigDecimal.ZERO, BigDecimal::add); } @Test public void sample_456() throws Exception { Observable> all = Observable .range(10, 20) .reduce(new ArrayList<>(), (list, item) -> { list.add(item); return list; }); } @Test public void sample_463() throws Exception { Observable> all = Observable .range(10, 20) .collect(ArrayList::new, List::add); } @Test public void sample_470() throws Exception { Observable str = Observable .range(1, 10) .collect( StringBuilder::new, (sb, x) -> sb.append(x).append(", ")) .map(StringBuilder::toString); } private Observable randomInts() { Observable randomInts = Observable.create(subscriber -> { Random random = new Random(); while (!subscriber.isUnsubscribed()) { subscriber.onNext(random.nextInt(1000)); } }); return randomInts; } @Test public void sample_490() throws Exception { final Observable randomInts = randomInts(); Observable uniqueRandomInts = randomInts .distinct() .take(10); } @Test public void sample_499() throws Exception { Observable tweets = Observable.empty(); Observable distinctUserIds = tweets .map(status -> status.getUser().getId()) .distinct(); } @Test public void sample_508() throws Exception { Observable tweets = Observable.empty(); Observable distinctUserIds = tweets .distinct(status -> status.getUser().getId()); } @Test public void sample_516() throws Exception { Observable measurements = Observable.empty(); Observable tempChanges = measurements .distinctUntilChanged(Weather::getTemperature); } @Test public void sample_524() throws Exception { Observable.range(1, 5).take(3); // [1, 2, 3] Observable.range(1, 5).skip(3); // [4, 5] Observable.range(1, 5).skip(5); // [] } @Test public void sample_531() throws Exception { Observable.range(1, 5).takeLast(2); // [4, 5] Observable.range(1, 5).skipLast(2); // [1, 2, 3] } @Test public void sample_537() throws Exception { Observable.range(1, 5).takeUntil(x -> x == 3); // [1, 2, 3] Observable.range(1, 5).takeWhile(x -> x != 3); // [1, 2] } @Test public void sample_543() throws Exception { Observable size = Observable .just('A', 'B', 'C', 'D') .reduce(0, (sizeSoFar, ch) -> sizeSoFar + 1); } @Test public void sample_550() throws Exception { Observable numbers = Observable.range(1, 5); numbers.all(x -> x != 4); // [false] numbers.exists(x -> x == 4); // [true] numbers.contains(4); // [true] } @Test public void sample_559() throws Exception { Observable veryLong = Observable .range(0, 1_000) .map(x -> new Data()); final Observable ends = Observable.concat( veryLong.take(5), veryLong.takeLast(5) ); } @Test public void sample_570() throws Exception { Observable fromCache = loadFromCache(); Observable fromDb = loadFromDb(); Observable found = Observable .concat(fromCache, fromDb) .first(); } private Observable loadFromDb() { return Observable.just(new Car()); } private Observable loadFromCache() { return Observable.just(new Car()); } @Test public void sample_589() throws Exception { Observable trueFalse = Observable.just(true, false).repeat(); Observable upstream = Observable.range(30, 8); Observable downstream = upstream .zipWith(trueFalse, Pair::of) .filter(Pair::getRight) .map(Pair::getLeft); } @Test public void sample_600() throws Exception { Observable trueFalse = Observable.just(true, false).repeat(); Observable upstream = Observable.range(30, 8); upstream.zipWith(trueFalse, (t, bool) -> bool ? just(t) : empty()) .flatMap(obs -> obs); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/City.java ================================================ package com.oreilly.rxjava.ch3; enum City { Warsaw, London, Paris, NewYork } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/CustomOperators.java ================================================ package com.oreilly.rxjava.ch3; import org.apache.commons.lang3.tuple.Pair; import org.junit.Ignore; import org.junit.Test; import rx.Observable; import rx.Subscriber; import static rx.Observable.just; @Ignore public class CustomOperators { static Observable odd(Observable upstream) { Observable trueFalse = just(true, false).repeat(); return upstream .zipWith(trueFalse, Pair::of) .filter(Pair::getRight) .map(Pair::getLeft); } private Observable.Transformer odd() { Observable trueFalse = just(true, false).repeat(); return upstream -> upstream .zipWith(trueFalse, Pair::of) .filter(Pair::getRight) .map(Pair::getLeft); } @Test public void sample_618() throws Exception { //[A, B, C, D, E...] Observable alphabet = Observable .range(0, 'Z' - 'A' + 1) .map(c -> (char) ('A' + c)); //[A, C, E, G, I...] alphabet .compose(odd()) .forEach(System.out::println); } @Test public void sample_9() throws Exception { Observable .range(1, 1000) .filter(x -> x % 3 == 0) .distinct() .reduce((a, x) -> a + x) .map(Integer::toHexString) .subscribe(System.out::println); } @Test public void sample_59() throws Exception { Observable odd = Observable .range(1, 9) .lift(toStringOfOdd()); //Will emit: "1", "3", "5", "7" and "9" strings odd.subscribe(System.out::println); } Observable.Operator toStringOfOdd() { return new Observable.Operator() { private boolean odd = true; @Override public Subscriber call(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) { if(odd) { child.onNext(t.toString()); } else { request(1); } odd = !odd; } }; } }; } @Test public void sample_67() throws Exception { Observable .range(1, 9) .buffer(1, 2) .concatMapIterable(x -> x) .map(Object::toString); } @Test public void sample_112() throws Exception { Observable .range(1, 4) .repeat() .lift(toStringOfOdd()) .take(3) .subscribe( System.out::println, Throwable::printStackTrace, () -> System.out.println("Completed") ); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Customer.java ================================================ package com.oreilly.rxjava.ch3; import java.util.Arrays; import java.util.List; class Customer { List getOrders() { return Arrays.asList(new Order(), new Order()); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Data.java ================================================ package com.oreilly.rxjava.ch3; class Data { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/FactStore.java ================================================ package com.oreilly.rxjava.ch3; import rx.Observable; interface FactStore { Observable observe(); } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Flight.java ================================================ package com.oreilly.rxjava.ch3; class Flight { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Hotel.java ================================================ package com.oreilly.rxjava.ch3; class Hotel { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/LicensePlate.java ================================================ package com.oreilly.rxjava.ch3; class LicensePlate { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Licenses.java ================================================ package com.oreilly.rxjava.ch3; import org.junit.Ignore; import org.junit.Test; import rx.Observable; @Ignore public class Licenses { Observable cars() { return Observable.just(new CarPhoto()); } Observable recognize(CarPhoto photo) { return Observable.just(new LicensePlate()); } @Test public void sample_100() throws Exception { Observable cars = cars(); Observable> plates = cars.map(this::recognize); Observable plates2 = cars.flatMap(this::recognize); } Observable fastAlgo(CarPhoto photo) { //Fast but poor quality return Observable.just(new LicensePlate()); } Observable preciseAlgo(CarPhoto photo) { //Precise but can be expensive return Observable.just(new LicensePlate()); } Observable experimentalAlgo(CarPhoto photo) { //Unpredictable, running anyway return Observable.just(new LicensePlate()); } @Test public void sample_317() throws Exception { CarPhoto photo = new CarPhoto(); Observable all = Observable.merge( preciseAlgo(photo), fastAlgo(photo), experimentalAlgo(photo) ); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/OperatorMap.java ================================================ package com.oreilly.rxjava.ch3; import rx.Observable; import rx.Subscriber; import rx.functions.Func1; final class OperatorMap implements Observable.Operator { private final Func1 transformer; public OperatorMap(Func1 transformer) { this.transformer = transformer; } @Override public Subscriber call(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 { child.onNext(transformer.call(t)); } catch (Exception e) { onError(e); } } }; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Order.java ================================================ package com.oreilly.rxjava.ch3; class Order { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Profile.java ================================================ package com.oreilly.rxjava.ch3; class Profile { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Rating.java ================================================ package com.oreilly.rxjava.ch3; class Rating { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Reservation.java ================================================ package com.oreilly.rxjava.ch3; class Reservation { Reservation consume(ReservationEvent event) { //mutate myself return this; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/ReservationEvent.java ================================================ package com.oreilly.rxjava.ch3; import java.util.UUID; class ReservationEvent { private final UUID uuid = UUID.randomUUID(); public UUID getReservationUuid() { return uuid; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Reservations.java ================================================ package com.oreilly.rxjava.ch3; import org.junit.Ignore; import org.junit.Test; import rx.Observable; import rx.observables.GroupedObservable; import java.util.Optional; import java.util.UUID; @Ignore public class Reservations { @Test public void sample_9() throws Exception { FactStore factStore = new CassandraFactStore(); Observable facts = factStore.observe(); facts.subscribe(this::updateProjection); } void updateProjection(ReservationEvent event) { } private void store(UUID id, Reservation modified) { //... } Optional loadBy(UUID uuid) { //... return Optional.of(new Reservation()); } @Test public void sample_34() throws Exception { FactStore factStore = new CassandraFactStore(); Observable facts = factStore.observe(); facts .flatMap(this::updateProjectionAsync) .subscribe(); //... } Observable updateProjectionAsync(ReservationEvent event) { //possibly asynchronous return Observable.just(new ReservationEvent()); } @Test public void sample_52() throws Exception { FactStore factStore = new CassandraFactStore(); Observable facts = factStore.observe(); Observable> grouped = facts.groupBy(ReservationEvent::getReservationUuid); grouped.subscribe(byUuid -> { byUuid.subscribe(this::updateProjection); }); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Shakespeare.java ================================================ package com.oreilly.rxjava.ch3; import com.oreilly.rxjava.util.Sleeper; import org.apache.commons.lang3.tuple.Pair; import org.junit.Ignore; import org.junit.Test; import rx.Observable; import java.time.Duration; import java.util.Random; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static rx.Observable.just; @Ignore public class Shakespeare { Observable speak(String quote, long millisPerChar) { String[] tokens = quote.replaceAll("[:,]", "").split(" "); Observable words = Observable.from(tokens); Observable absoluteDelay = words .map(String::length) .map(len -> len * millisPerChar) .scan((total, current) -> total + current); return words .zipWith(absoluteDelay.startWith(0L), Pair::of) .flatMap(pair -> just(pair.getLeft()) .delay(pair.getRight(), MILLISECONDS)); } @Test public void sample_28() throws Exception { Observable alice = speak( "To be, or not to be: that is the question", 110); Observable bob = speak( "Though this be madness, yet there is method in't", 90); Observable jane = speak( "There are more things in Heaven and Earth, " + "Horatio, than are dreamt of in your philosophy", 100); Observable .merge( alice.map(w -> "Alice: " + w), bob.map(w -> "Bob: " + w), jane.map(w -> "Jane: " + w) ) .subscribe(System.out::println); Sleeper.sleep(Duration.ofSeconds(10)); } @Test public void sample_52() throws Exception { Observable alice = speak( "To be, or not to be: that is the question", 110); Observable bob = speak( "Though this be madness, yet there is method in't", 90); Observable jane = speak( "There are more things in Heaven and Earth, " + "Horatio, than are dreamt of in your philosophy", 100); Random rnd = new Random(); Observable> quotes = just( alice.map(w -> "Alice: " + w), bob.map(w -> "Bob: " + w), jane.map(w -> "Jane: " + w)) .flatMap(innerObs -> just(innerObs) .delay(rnd.nextInt(5), SECONDS)); Observable .switchOnNext(quotes) .subscribe(System.out::println); Sleeper.sleep(Duration.ofSeconds(10)); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Sound.java ================================================ package com.oreilly.rxjava.ch3; enum Sound { DI, DAH } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/User.java ================================================ package com.oreilly.rxjava.ch3; import rx.Observable; class User { Observable loadProfile() { //Make HTTP request... return Observable.just(new Profile()); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Vacation.java ================================================ package com.oreilly.rxjava.ch3; import rx.Observable; import java.time.LocalDate; class Vacation { private final City where; private final LocalDate when; Vacation(City where, LocalDate when) { this.where = where; this.when = when; } public Observable weather() { //... return Observable.just(new Weather(new Temperature(), new Wind())); } public Observable cheapFlightFrom(City from) { //... return Observable.just(new Flight()); } public Observable cheapHotel() { //... return Observable.just(new Hotel()); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/Weather.java ================================================ package com.oreilly.rxjava.ch3; class Weather { private final Temperature temperature; public Weather(Temperature temperature, Wind wind) { //... this.temperature = temperature; } public boolean isSunny() { return true; } Temperature getTemperature() { return temperature; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch3/WeatherStation.java ================================================ package com.oreilly.rxjava.ch3; import rx.Observable; interface WeatherStation { Observable temperature(); Observable wind(); } class BasicWeatherStation implements WeatherStation { @Override public Observable temperature() { return Observable.just(new Temperature()); } @Override public Observable wind() { return Observable.just(new Wind()); } } class Temperature {} class Wind {} ================================================ FILE: src/test/java/com/oreilly/rxjava/ch4/Book.java ================================================ package com.oreilly.rxjava.ch4; class Book { public String getTitle() { return "Title"; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch4/Chapter4.java ================================================ package com.oreilly.rxjava.ch4; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.oreilly.rxjava.util.Sleeper; import org.apache.commons.lang3.RandomUtils; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import rx.Scheduler; import rx.observables.BlockingObservable; import rx.schedulers.Schedulers; import java.time.Duration; import java.util.Collections; import java.util.List; import java.util.concurrent.*; @Ignore public class Chapter4 { private static final Logger log = LoggerFactory.getLogger(Chapter4.class); private final PersonDao personDao = new PersonDao(); private int orderBookLength; @Test public void sample_9() throws Exception { List people = personDao.listPeople(); String json = marshal(people); } @Test public void sample_20() throws Exception { Observable peopleStream = personDao.listPeople2(); Observable> peopleList = peopleStream.toList(); BlockingObservable> peopleBlocking = peopleList.toBlocking(); List people = peopleBlocking.single(); } private String marshal(List people) { return people.toString(); } @Test public void sample_34() throws Exception { List people = personDao .listPeople2() .toList() .toBlocking() .single(); } void bestBookFor(Person person) { Book book; try { book = recommend(person); } catch (Exception e) { book = bestSeller(); } display(book.getTitle()); } private Book bestSeller() { return new Book(); } private Book recommend(Person person) { return new Book(); } void display(String title) { //... } void bestBookFor2(Person person) { Observable recommended = recommend2(person); Observable bestSeller = bestSeller2(); Observable book = recommended.onErrorResumeNext(bestSeller); Observable title = book.map(Book::getTitle); title.subscribe(this::display); } void bestBookFor3(Person person) { recommend2(person) .onErrorResumeNext(bestSeller2()) .map(Book::getTitle) .subscribe(this::display); } private Observable bestSeller2() { return Observable.fromCallable(Book::new); } private Observable recommend2(Person person) { return Observable.fromCallable(Book::new); } @Test public void sample_89() throws Exception { Observable .interval(10, TimeUnit.MILLISECONDS) .map(x -> getOrderBookLength()) .distinctUntilChanged(); } private int getOrderBookLength() { return RandomUtils.nextInt(5, 10); } Observable observeNewItems() { return Observable .interval(1, TimeUnit.SECONDS) .flatMapIterable(x -> query()) .distinct(); } List query() { //take snapshot of file system directory //or database table return Collections.emptyList(); } @Test public void sample_118() throws Exception { ThreadFactory threadFactory = new ThreadFactoryBuilder() .setNameFormat("MyPool-%d") .build(); Executor executor = new ThreadPoolExecutor( 10, //corePoolSize 10, //maximumPoolSize 0L, TimeUnit.MILLISECONDS, //keepAliveTime, unit new LinkedBlockingQueue<>(1000), //workQueue threadFactory ); Scheduler scheduler = Schedulers.from(executor); } @Test public void sample_136() throws Exception { ExecutorService executor = Executors.newFixedThreadPool(10); } private final long start = System.currentTimeMillis(); void log(Object label) { System.out.println( System.currentTimeMillis() - start + "\t| " + Thread.currentThread().getName() + "\t| " + label); } @Test public void sample_141() throws Exception { Scheduler scheduler = Schedulers.immediate(); Scheduler.Worker worker = scheduler.createWorker(); log("Main start"); worker.schedule(() -> { log(" Outer start"); sleepOneSecond(); worker.schedule(() -> { log(" Inner start"); sleepOneSecond(); log(" Inner end"); }); log(" Outer end"); }); log("Main end"); worker.unsubscribe(); } @Test public void sample_175() throws Exception { Scheduler scheduler = Schedulers.immediate(); Scheduler.Worker worker = scheduler.createWorker(); log("Main start"); worker.schedule(() -> { log(" Outer start"); sleepOneSecond(); worker.schedule(() -> { log(" Middle start"); sleepOneSecond(); worker.schedule(() -> { log(" Inner start"); sleepOneSecond(); log(" Inner end"); }); log(" Middle end"); }); log(" Outer end"); }); log("Main end"); } private void sleepOneSecond() { Sleeper.sleep(Duration.ofSeconds(1)); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch4/Flight.java ================================================ package com.oreilly.rxjava.ch4; class Flight { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch4/Flights.java ================================================ package com.oreilly.rxjava.ch4; import org.apache.commons.lang3.tuple.Pair; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import rx.schedulers.Schedulers; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; import static java.util.stream.Collectors.toList; import static rx.Observable.fromCallable; @Ignore public class Flights { private static final Logger log = LoggerFactory.getLogger(Flights.class); Flight lookupFlight(String flightNo) { //... return new Flight(); } Passenger findPassenger(long id) { //... return new Passenger(); } Ticket bookTicket(Flight flight, Passenger passenger) { //... return new Ticket(); } SmtpResponse sendEmail(Ticket ticket) { //... return new SmtpResponse(); } @Test public void sample_29() throws Exception { Flight flight = lookupFlight("LOT 783"); Passenger passenger = findPassenger(42); Ticket ticket = bookTicket(flight, passenger); sendEmail(ticket); } Observable rxLookupFlight(String flightNo) { return Observable.defer(() -> Observable.just(lookupFlight(flightNo))); } Observable rxFindPassenger(long id) { return Observable.defer(() -> Observable.just(findPassenger(id))); } @Test public void sample_49() throws Exception { Observable flight = rxLookupFlight("LOT 783"); Observable passenger = rxFindPassenger(42); Observable ticket = flight.zipWith(passenger, (f, p) -> bookTicket(f, p)); ticket.subscribe(this::sendEmail); } @Test public void sample_67() throws Exception { rxLookupFlight("LOT 783") .subscribeOn(Schedulers.io()) .timeout(100, TimeUnit.MILLISECONDS); } @Test public void sample_76() throws Exception { Observable flight = rxLookupFlight("LOT 783").subscribeOn(Schedulers.io()); Observable passenger = rxFindPassenger(42).subscribeOn(Schedulers.io()); Observable ticket = flight .zipWith(passenger, (Flight f, Passenger p) -> Pair.of(f, p)) .flatMap(pair -> rxBookTicket(pair.getLeft(), pair.getRight())); } private Observable rxBookTicket(Flight left, Passenger right) { return Observable.just(new Ticket()); } @Test public void sample_85() throws Exception { Observable flight = rxLookupFlight("LOT 783").subscribeOn(Schedulers.io()); Observable passenger = rxFindPassenger(42).subscribeOn(Schedulers.io()); Observable ticket = flight .zipWith(passenger, this::rxBookTicket) .flatMap(obs -> obs); } @Test public void sample_97() throws Exception { List tickets = Arrays.asList(new Ticket(), new Ticket(), new Ticket()); List failures = new ArrayList<>(); for (Ticket ticket : tickets) { try { sendEmail(ticket); } catch (Exception e) { log.warn("Failed to send {}", ticket, e); failures.add(ticket); } } } @Test public void sample_120() throws Exception { List tickets = Arrays.asList(new Ticket(), new Ticket(), new Ticket()); List>> tasks = tickets .stream() .map(ticket -> Pair.of(ticket, sendEmailAsync(ticket))) .collect(toList()); List failures = tasks.stream() .flatMap(pair -> { try { Future future = pair.getRight(); future.get(1, TimeUnit.SECONDS); return Stream.empty(); } catch (Exception e) { Ticket ticket = pair.getLeft(); log.warn("Failed to send {}", ticket, e); return Stream.of(ticket); } }) .collect(toList()); } private Future sendEmailAsync(Ticket ticket) { return CompletableFuture.supplyAsync(() -> sendEmail(ticket)); } @Test public void sample_152() throws Exception { List tickets = Arrays.asList(new Ticket(), new Ticket(), new Ticket()); //WARNING: code is sequential despite utilizing thread pool tickets .stream() .map(ticket -> Pair.of(ticket, sendEmailAsync(ticket))) .map(pair -> { try { return pair.getRight().get(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } }) .collect(toList()); } Observable rxSendEmail(Ticket ticket) { //unusual synchronous Observable return fromCallable(() -> sendEmail(ticket)); } @Test public void sample_177() throws Exception { List tickets = Arrays.asList(new Ticket(), new Ticket(), new Ticket()); List failures = Observable.from(tickets) .flatMap(ticket -> rxSendEmail(ticket) .flatMap(response -> Observable.empty()) .doOnError(e -> log.warn("Failed to send {}", ticket, e)) .onErrorReturn(err -> ticket)) .toList() .toBlocking() .single(); } @Test public void sample_191() throws Exception { List tickets = Arrays.asList(new Ticket(), new Ticket(), new Ticket()); Observable .from(tickets) .flatMap(ticket -> rxSendEmail(ticket) .ignoreElements() .doOnError(e -> log.warn("Failed to send {}", ticket, e)) .subscribeOn(Schedulers.io())); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch4/Item.java ================================================ package com.oreilly.rxjava.ch4; class Item { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch4/JmsConsumer.java ================================================ package com.oreilly.rxjava.ch4; import org.springframework.jms.annotation.JmsListener; import org.springframework.messaging.Message; import org.springframework.stereotype.Component; import rx.Observable; import rx.subjects.PublishSubject; @Component class JmsConsumer { private final PublishSubject subject = PublishSubject.create(); @JmsListener(destination = "orders", concurrency="1") public void newOrder(Message msg) { subject.onNext(msg); } Observable observe() { return subject; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch4/Messaging.java ================================================ package com.oreilly.rxjava.ch4; import org.apache.activemq.ActiveMQConnectionFactory; import org.apache.activemq.command.ActiveMQTopic; import org.junit.Ignore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import rx.Subscriber; import rx.Subscription; import rx.subscriptions.Subscriptions; import javax.jms.*; import static javax.jms.Session.AUTO_ACKNOWLEDGE; @Ignore public class Messaging { private static final Logger log = LoggerFactory.getLogger(Messaging.class); void connect() { ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616"); Observable txtMessages = observe(connectionFactory, new ActiveMQTopic("orders")) .cast(TextMessage.class) .flatMap(m -> { try { return Observable.just(m.getText()); } catch (JMSException e) { return Observable.error(e); } }); } public Observable observe(ConnectionFactory connectionFactory, Topic topic) { return Observable.create(subscriber -> { try { subscribeThrowing(subscriber, connectionFactory, topic); } catch (JMSException e) { subscriber.onError(e); } }); } private void subscribeThrowing(Subscriber subscriber, ConnectionFactory connectionFactory, Topic orders) throws JMSException { Connection connection = connectionFactory.createConnection(); Session session = connection.createSession(true, AUTO_ACKNOWLEDGE); MessageConsumer consumer = session.createConsumer(orders); consumer.setMessageListener(subscriber::onNext); subscriber.add(onUnsubscribe(connection)); connection.start(); } private Subscription onUnsubscribe(Connection connection) { return Subscriptions.create(() -> { try { connection.close(); } catch (Exception e) { log.error("Can't close", e); } }); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch4/Passenger.java ================================================ package com.oreilly.rxjava.ch4; class Passenger { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch4/Person.java ================================================ package com.oreilly.rxjava.ch4; class Person { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch4/PersonDao.java ================================================ package com.oreilly.rxjava.ch4; import rx.Observable; import java.util.Arrays; import java.util.List; import static rx.Observable.defer; import static rx.Observable.from; class PersonDao { private static final int PAGE_SIZE = 10; Observable allPeople(int initialPage) { return defer(() -> from(listPeople(initialPage))) .concatWith(defer(() -> allPeople(initialPage + 1))); } void allPeople() { Observable allPages = Observable .range(0, Integer.MAX_VALUE) .map(this::listPeople) .takeWhile(list -> !list.isEmpty()) .concatMap(Observable::from); } List listPeople() { return query("SELECT * FROM PEOPLE"); } List listPeople(int initialPage) { return query("SELECT * FROM PEOPLE OFFSET ? MAX ?", initialPage * 10, PAGE_SIZE); } Observable listPeople2() { final List people = query("SELECT * FROM PEOPLE"); return Observable.from(people); } public Observable listPeople3() { return defer(() -> Observable.from(query("SELECT * FROM PEOPLE"))); } private List query(String sql, Object... args) { //... return Arrays.asList(new Person(), new Person()); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch4/RxGroceries.java ================================================ package com.oreilly.rxjava.ch4; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import java.math.BigDecimal; class RxGroceries { private static final Logger log = LoggerFactory.getLogger(RxGroceries.class); private final long start = System.currentTimeMillis(); void log(Object label) { System.out.println( System.currentTimeMillis() - start + "\t| " + Thread.currentThread().getName() + "\t| " + label); } Observable purchase(String productName, int quantity) { return Observable.fromCallable(() -> doPurchase(productName, quantity)); } BigDecimal doPurchase(String productName, int quantity) { log("Purchasing " + quantity + " " + productName); //real logic here log("Done " + quantity + " " + productName); BigDecimal priceForProduct = BigDecimal.ONE; return priceForProduct; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch4/Schedulers.java ================================================ package com.oreilly.rxjava.ch4; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.apache.commons.lang3.tuple.Pair; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import rx.Scheduler; import java.math.BigDecimal; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadFactory; import static java.util.concurrent.Executors.newFixedThreadPool; import static java.util.concurrent.TimeUnit.SECONDS; @Ignore public class Schedulers { private static final Logger log = LoggerFactory.getLogger(Schedulers.class); ExecutorService poolA = newFixedThreadPool(10, threadFactory("Sched-A-%d")); Scheduler schedulerA = rx.schedulers.Schedulers.from(poolA); ExecutorService poolB = newFixedThreadPool(10, threadFactory("Sched-B-%d")); Scheduler schedulerB = rx.schedulers.Schedulers.from(poolB); ExecutorService poolC = newFixedThreadPool(10, threadFactory("Sched-C-%d")); Scheduler schedulerC = rx.schedulers.Schedulers.from(poolC); private final long start = System.currentTimeMillis(); void log(Object label) { System.out.println( System.currentTimeMillis() - start + "\t| " + Thread.currentThread().getName() + "\t| " + label); } private ThreadFactory threadFactory(String pattern) { return new ThreadFactoryBuilder() .setNameFormat(pattern) .build(); } Observable simple() { return Observable.create(subscriber -> { log("Subscribed"); subscriber.onNext("A"); subscriber.onNext("B"); subscriber.onCompleted(); }); } @Test public void sample_215() throws Exception { log("Starting"); final Observable obs = simple(); log("Created"); obs .subscribeOn(schedulerA) .subscribe( x -> log("Got " + x), Throwable::printStackTrace, () -> log("Completed") ); log("Exiting"); } @Test public void sample_33() throws Exception { //Don't do this Observable obs = Observable.create(subscriber -> { log("Subscribed"); Runnable code = () -> { subscriber.onNext("A"); subscriber.onNext("B"); subscriber.onCompleted(); }; new Thread(code, "Async").start(); }); } @Test public void sample_77() throws Exception { log("Starting"); Observable obs = simple(); log("Created"); obs .subscribeOn(schedulerA) //many other operators .subscribeOn(schedulerB) .subscribe( x -> log("Got " + x), Throwable::printStackTrace, () -> log("Completed") ); log("Exiting"); } @Test public void sample_103() throws Exception { log("Starting"); final Observable obs = simple(); log("Created"); obs .doOnNext(this::log) .map(x -> x + '1') .doOnNext(this::log) .map(x -> x + '2') .subscribeOn(schedulerA) .doOnNext(this::log) .subscribe( x -> log("Got " + x), Throwable::printStackTrace, () -> log("Completed") ); log("Exiting"); } private final RxGroceries rxGroceries = new RxGroceries(); @Test public void sample_122() throws Exception { Observable totalPrice = Observable .just("bread", "butter", "milk", "tomato", "cheese") .subscribeOn(schedulerA) //BROKEN!!! .map(prod -> rxGroceries.doPurchase(prod, 1)) .reduce(BigDecimal::add) .single(); } @Test public void sample_135() throws Exception { final Observable totalPrice = Observable .just("bread", "butter", "milk", "tomato", "cheese") .subscribeOn(schedulerA) .flatMap(prod -> rxGroceries.purchase(prod, 1)) .reduce(BigDecimal::add) .single(); } @Test public void sample_145() throws Exception { Observable totalPrice = Observable .just("bread", "butter", "milk", "tomato", "cheese") .flatMap(prod -> rxGroceries .purchase(prod, 1) .subscribeOn(schedulerA)) .reduce(BigDecimal::add) .single(); } @Test public void sample_157() throws Exception { Observable totalPrice = Observable .just("bread", "butter", "egg", "milk", "tomato", "cheese", "tomato", "egg", "egg") .groupBy(prod -> prod) .flatMap(grouped -> grouped .count() .map(quantity -> { String productName = grouped.getKey(); return Pair.of(productName, quantity); })) .flatMap(order -> rxGroceries .purchase(order.getKey(), order.getValue()) .subscribeOn(schedulerA)) .reduce(BigDecimal::add) .single(); } @Test public void sample_177() throws Exception { log("Starting"); final Observable obs = simple(); log("Created"); obs .doOnNext(x -> log("Found 1: " + x)) .observeOn(schedulerA) .doOnNext(x -> log("Found 2: " + x)) .subscribe( x -> log("Got 1: " + x), Throwable::printStackTrace, () -> log("Completed") ); log("Exiting"); } @Test public void sample_194() throws Exception { log("Starting"); final Observable obs = simple(); log("Created"); obs .doOnNext(x -> log("Found 1: " + x)) .observeOn(schedulerB) .doOnNext(x -> log("Found 2: " + x)) .observeOn(schedulerC) .doOnNext(x -> log("Found 3: " + x)) .subscribeOn(schedulerA) .subscribe( x -> log("Got 1: " + x), Throwable::printStackTrace, () -> log("Completed") ); log("Exiting"); } @Test public void sample_214() throws Exception { log("Starting"); Observable obs = Observable.create(subscriber -> { log("Subscribed"); subscriber.onNext("A"); subscriber.onNext("B"); subscriber.onNext("C"); subscriber.onNext("D"); subscriber.onCompleted(); }); log("Created"); obs .subscribeOn(schedulerA) .flatMap(record -> store(record).subscribeOn(schedulerB)) .observeOn(schedulerC) .subscribe( x -> log("Got: " + x), Throwable::printStackTrace, () -> log("Completed") ); log("Exiting"); } Observable store(String s) { return Observable.create(subscriber -> { log("Storing " + s); //hard work subscriber.onNext(UUID.randomUUID()); subscriber.onCompleted(); }); } @Test public void sample_248() throws Exception { Observable .just('A', 'B') .delay(1, SECONDS, schedulerA) .subscribe(this::log); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch4/SimplifiedHandlerScheduler.java ================================================ package com.oreilly.rxjava.ch4; import android.os.Handler; import android.os.Looper; import rx.Scheduler; import rx.Subscription; import rx.functions.Action0; import rx.internal.schedulers.ScheduledAction; import rx.subscriptions.CompositeSubscription; import rx.subscriptions.Subscriptions; import java.util.concurrent.TimeUnit; public final class SimplifiedHandlerScheduler extends Scheduler { @Override public Worker createWorker() { return new HandlerWorker(); } static class HandlerWorker extends Worker { private final Handler handler = new Handler(Looper.getMainLooper()); @Override public Subscription schedule(final Action0 action) { return schedule(action, 0, TimeUnit.MILLISECONDS); } private final CompositeSubscription compositeSubscription = new CompositeSubscription(); @Override public void unsubscribe() { compositeSubscription.unsubscribe(); } @Override public boolean isUnsubscribed() { return compositeSubscription.isUnsubscribed(); } @Override public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { if (compositeSubscription.isUnsubscribed()) { return Subscriptions.unsubscribed(); } final ScheduledAction scheduledAction = new ScheduledAction(action); scheduledAction.addParent(compositeSubscription); compositeSubscription.add(scheduledAction); handler.postDelayed(scheduledAction, unit.toMillis(delayTime)); scheduledAction.add(Subscriptions.create(() -> handler.removeCallbacks(scheduledAction))); return scheduledAction; } } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch4/SmtpResponse.java ================================================ package com.oreilly.rxjava.ch4; class SmtpResponse { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch4/Ticket.java ================================================ package com.oreilly.rxjava.ch4; class Ticket { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/Chapter5.java ================================================ package com.oreilly.rxjava.ch5; import io.netty.buffer.ByteBuf; import io.reactivex.netty.protocol.http.client.HttpClient; import io.reactivex.netty.protocol.http.client.HttpClientResponse; import org.junit.Ignore; import org.junit.Test; import rx.Observable; import java.net.URL; import static java.nio.charset.StandardCharsets.UTF_8; @Ignore public class Chapter5 { @Test public void sample_9() throws Exception { Observable response = HttpClient .newClient("example.com", 80) .createGet("/") .flatMap(HttpClientResponse::getContent); response .map(bb -> bb.toString(UTF_8)) .subscribe(System.out::println); } @Test public void sample_22() throws Exception { Observable sources = Observable.just(new URL("http://www.google.com")); Observable packets = sources .flatMap(url -> HttpClient .newClient(url.getHost(), url.getPort()) .createGet(url.getPath())) .flatMap(HttpClientResponse::getContent); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/CompletableFutures.java ================================================ package com.oreilly.rxjava.ch5; import org.apache.commons.lang3.tuple.Pair; import org.junit.Ignore; import org.junit.Test; import rx.Observable; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Collections; import java.util.List; import java.util.concurrent.*; import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.function.UnaryOperator.identity; @Ignore public class CompletableFutures { User findById(long id) { return new User(); } GeoLocation locate() { return new GeoLocation(); } Ticket book(Flight flight) { return new Ticket(); } @Test public void sample_21() throws Exception { long id = 42; ExecutorService pool = Executors.newFixedThreadPool(10); List agencies = Collections.singletonList(new SomeTravelAgency()); User user = findById(id); GeoLocation location = locate(); ExecutorCompletionService ecs = new ExecutorCompletionService<>(pool); agencies.forEach(agency -> ecs.submit(() -> agency.search(user, location))); Future firstFlight = ecs.poll(5, SECONDS); Flight flight = firstFlight.get(); book(flight); } CompletableFuture findByIdAsync(long id) { return CompletableFuture.supplyAsync(() -> findById(id)); } CompletableFuture locateAsync() { return CompletableFuture.supplyAsync(this::locate); } CompletableFuture bookAsync(Flight flight) { return CompletableFuture.supplyAsync(() -> book(flight)); } Observable rxFindById(long id) { return Util.observe(findByIdAsync(id)); } Observable rxLocate() { return Util.observe(locateAsync()); } Observable rxBook(Flight flight) { return Util.observe(bookAsync(flight)); } @Test public void sample_63() throws Exception { long id = 42; List agencies = Collections.singletonList(new SomeTravelAgency()); CompletableFuture user = findByIdAsync(id); CompletableFuture location = locateAsync(); CompletableFuture ticketFuture = user .thenCombine(location, (User us, GeoLocation loc) -> agencies .stream() .map(agency -> agency.searchAsync(us, loc)) .reduce((f1, f2) -> f1.applyToEither(f2, identity()) ) .get() ) .thenCompose(identity()) .thenCompose(this::bookAsync); } @Test public void sample_80() throws Exception { CompletableFuture timeFuture = CompletableFuture.completedFuture(System.currentTimeMillis()); CompletableFuture zoneFuture = CompletableFuture.completedFuture(ZoneId.of("GMT")); CompletableFuture instantFuture = timeFuture .thenApply(time -> Instant.ofEpochMilli(time)); CompletableFuture zdtFuture = instantFuture .thenCombine(zoneFuture, (instant, zoneId) -> ZonedDateTime.ofInstant(instant, zoneId)); } @Test public void sample_96() throws Exception { List agencies = Collections.singletonList(new SomeTravelAgency()); User us = new User(); GeoLocation loc = new GeoLocation(); agencies .stream() .map(agency -> agency.searchAsync(us, loc)) .reduce((f1, f2) -> f1.applyToEither(f2, identity()) ) .get(); } @Test public void sample_111() throws Exception { CompletableFuture primaryFuture = CompletableFuture.completedFuture(new User()); CompletableFuture secondaryFuture = CompletableFuture.completedFuture(new User()); CompletableFuture ageFuture = primaryFuture .applyToEither(secondaryFuture, user -> user.getBirth()); } @Test public void sample_123() throws Exception { CompletableFuture flightFuture = CompletableFuture.completedFuture(new Flight()); CompletableFuture ticketFuture = flightFuture .thenCompose(flight -> bookAsync(flight)); } @Test public void sample_145() throws Exception { long id = 42; Observable agencies = agencies(); Observable user = rxFindById(id); Observable location = rxLocate(); Observable ticket = user .zipWith(location, (us, loc) -> agencies .flatMap(agency -> agency.rxSearch(us, loc)) .first() ) .flatMap(x -> x) .flatMap(this::rxBook); } private Observable agencies() { return Observable.just(new SomeTravelAgency()); } @Test public void sample_168() throws Exception { Observable user = Observable.just(new User()); Observable location = Observable.just(new GeoLocation()); Observable agencies = agencies(); Observable ticket = user .zipWith(location, (usr, loc) -> Pair.of(usr, loc)) .flatMap(pair -> agencies .flatMap(agency -> { User usr = pair.getLeft(); GeoLocation loc = pair.getRight(); return agency.rxSearch(usr, loc); })) .first() .flatMap(this::rxBook); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/EurUsdCurrencyTcpServer.java ================================================ package com.oreilly.rxjava.ch5; import io.netty.handler.codec.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.reactivex.netty.protocol.tcp.server.TcpServer; import rx.Observable; import java.math.BigDecimal; import java.util.concurrent.TimeUnit; import static java.nio.charset.StandardCharsets.UTF_8; class EurUsdCurrencyTcpServer { private static final BigDecimal RATE = new BigDecimal("1.06448"); public static void main(final String[] args) { TcpServer .newServer(8080) .pipelineConfigurator(pipeline -> { pipeline.addLast(new LineBasedFrameDecoder(1024)); pipeline.addLast(new StringDecoder(UTF_8)); }) .start(connection -> { Observable output = connection .getInput() .map(BigDecimal::new) .flatMap(eur -> eurToUsd(eur)); return connection.writeAndFlushOnEach(output); }) .awaitShutdown(); } static Observable eurToUsd(BigDecimal eur) { return Observable .just(eur.multiply(RATE)) .map(amount -> eur + " EUR is " + amount + " USD\n") .delay(1, TimeUnit.SECONDS); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/Flight.java ================================================ package com.oreilly.rxjava.ch5; class Flight { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/GeoLocation.java ================================================ package com.oreilly.rxjava.ch5; class GeoLocation { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/HttpHandler.java ================================================ package com.oreilly.rxjava.ch5; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; import static java.nio.charset.StandardCharsets.UTF_8; @ChannelHandler.Sharable class HttpHandler extends ChannelInboundHandlerAdapter { private static final Logger log = LoggerFactory.getLogger(HttpHandler.class); @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { if (msg instanceof HttpRequest) { sendResponse(ctx); } } private void sendResponse(ChannelHandlerContext ctx) { final DefaultFullHttpResponse response = new DefaultFullHttpResponse( HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer("OK".getBytes(UTF_8))); response.headers().add("Content-length", 2); ctx.writeAndFlush(response); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { log.error("Error", cause); ctx.close(); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/HttpInitializer.java ================================================ package com.oreilly.rxjava.ch5; import io.netty.channel.ChannelInitializer; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpServerCodec; class HttpInitializer extends ChannelInitializer { private final HttpHandler httpHandler = new HttpHandler(); @Override public void initChannel(SocketChannel ch) { ch .pipeline() .addLast(new HttpServerCodec()) .addLast(httpHandler); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/HttpTcpNettyServer.java ================================================ package com.oreilly.rxjava.ch5; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; class HttpTcpNettyServer { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { new ServerBootstrap() .option(ChannelOption.SO_BACKLOG, 50_000) .group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new HttpInitializer()) .bind(8080) .sync() .channel() .closeFuture() .sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/HttpTcpRxNettyServer.java ================================================ package com.oreilly.rxjava.ch5; import io.netty.handler.codec.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.reactivex.netty.protocol.tcp.server.TcpServer; import rx.Observable; import static java.nio.charset.StandardCharsets.UTF_8; class HttpTcpRxNettyServer { public static final Observable RESPONSE = Observable.just( "HTTP/1.1 200 OK\r\n" + "Content-length: 2\r\n" + "\r\n" + "OK"); public static void main(final String[] args) { TcpServer .newServer(8080) .pipelineConfigurator(pipeline -> { pipeline.addLast(new LineBasedFrameDecoder(128)); pipeline.addLast(new StringDecoder(UTF_8)); }) .start(connection -> { Observable output = connection .getInput() .flatMap(line -> { if (line.isEmpty()) { return RESPONSE; } else { return Observable.empty(); } }); return connection.writeAndFlushOnEach(output); }) .awaitShutdown(); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/Postgres.java ================================================ package com.oreilly.rxjava.ch5; import org.junit.Ignore; import org.junit.Test; import org.postgresql.PGNotification; import org.postgresql.jdbc2.AbstractJdbc2Connection; import org.postgresql.jdbc42.Jdbc42Connection; import rx.Observable; import rx.observers.Subscribers; import rx.subscriptions.Subscriptions; import java.sql.*; import java.util.Arrays; import java.util.concurrent.TimeUnit; @Ignore public class Postgres { @Test public void sample_37() throws Exception { try ( Connection conn = DriverManager.getConnection("jdbc:h2:mem:"); Statement stat = conn.createStatement(); ResultSet rs = stat.executeQuery("SELECT 2 + 2 AS total") ) { if (rs.next()) { System.out.println(rs.getInt("total")); assert rs.getInt("total") == 4; } } } @Test public void sample_55() throws Exception { try (Connection connection = DriverManager.getConnection("jdbc:postgresql:db")) { try (Statement statement = connection.createStatement()) { statement.execute("LISTEN my_channel"); } Jdbc42Connection pgConn = (Jdbc42Connection) connection; pollForNotifications(pgConn); } } void pollForNotifications(Jdbc42Connection pgConn) throws Exception { while (!Thread.currentThread().isInterrupted()) { final PGNotification[] notifications = pgConn.getNotifications(); if (notifications != null) { for (final PGNotification notification : notifications) { System.out.println( notification.getName() + ": " + notification.getParameter()); } } TimeUnit.MILLISECONDS.sleep(100); } } Observable observe(String channel, long pollingPeriod) { return Observable.create(subscriber -> { try { Connection connection = DriverManager .getConnection("jdbc:postgresql:db"); subscriber.add(Subscriptions.create(() -> closeQuietly(connection))); listenOn(connection, channel); Jdbc42Connection pgConn = (Jdbc42Connection) connection; pollForNotifications(pollingPeriod, pgConn) .subscribe(Subscribers.wrap(subscriber)); } catch (Exception e) { subscriber.onError(e); } }).share(); } void listenOn(Connection connection, String channel) throws SQLException { try (Statement statement = connection.createStatement()) { statement.execute("LISTEN " + channel); } } void closeQuietly(Connection connection) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } Observable pollForNotifications( long pollingPeriod, AbstractJdbc2Connection pgConn) { return Observable .interval(0, pollingPeriod, TimeUnit.MILLISECONDS) .flatMap(x -> tryGetNotification(pgConn)) .filter(arr -> arr != null) .flatMapIterable(Arrays::asList); } Observable tryGetNotification( AbstractJdbc2Connection pgConn) { try { return Observable.just(pgConn.getNotifications()); } catch (SQLException e) { return Observable.error(e); } } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/RestCurrencyServer.java ================================================ package com.oreilly.rxjava.ch5; import io.reactivex.netty.protocol.http.server.HttpServer; import rx.Observable; import java.math.BigDecimal; class RestCurrencyServer { private static final BigDecimal RATE = new BigDecimal("1.06448"); public static void main(final String[] args) { HttpServer .newServer(8080) .start((req, resp) -> { String amountStr = req.getDecodedPath().substring(1); BigDecimal amount = new BigDecimal(amountStr); Observable response = Observable .just(amount) .map(eur -> eur.multiply(RATE)) .map(usd -> "{\"EUR\": " + amount + ", " + "\"USD\": " + usd + "}"); return resp.writeString(response); }) .awaitShutdown(); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/RxNettyHttpServer.java ================================================ package com.oreilly.rxjava.ch5; import io.reactivex.netty.protocol.http.server.HttpServer; import rx.Observable; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; class RxNettyHttpServer { private static final Observable RESPONSE_OK = Observable.just("OK"); public static void main(String[] args) { HttpServer .newServer(8086) .start((req, resp) -> resp .setHeader(CONTENT_LENGTH, 2) .writeStringAndFlushOnEach(RESPONSE_OK) ).awaitShutdown(); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/SingleThread.java ================================================ package com.oreilly.rxjava.ch5; import org.apache.commons.io.IOUtils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; class SingleThread { public static final byte[] RESPONSE = ( "HTTP/1.1 200 OK\r\n" + "Content-length: 2\r\n" + "\r\n" + "OK").getBytes(); public static void main(String[] args) throws IOException { final ServerSocket serverSocket = new ServerSocket(8080, 100); while (!Thread.currentThread().isInterrupted()) { final Socket client = serverSocket.accept(); handle(client); } } private static void handle(Socket client) { try { while (!Thread.currentThread().isInterrupted()) { readFullRequest(client); client.getOutputStream().write(RESPONSE); } } catch (Exception e) { e.printStackTrace(); IOUtils.closeQuietly(client); } } private static void readFullRequest(Socket client) throws IOException { BufferedReader reader = new BufferedReader( new InputStreamReader(client.getInputStream())); String line = reader.readLine(); while (line != null && !line.isEmpty()) { line = reader.readLine(); } } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/Singles.java ================================================ package com.oreilly.rxjava.ch5; import com.ning.http.client.AsyncCompletionHandler; import com.ning.http.client.AsyncHttpClient; import com.ning.http.client.Response; import org.junit.Ignore; import org.junit.Test; import org.springframework.jdbc.core.JdbcTemplate; import org.w3c.dom.Document; import rx.Observable; import rx.Single; import rx.SingleSubscriber; import rx.schedulers.Schedulers; import java.io.IOException; import java.time.Instant; @Ignore public class Singles { @Test public void sample_6() throws Exception { Single single = Single.just("Hello, world!"); single.subscribe(System.out::println); Single error = Single.error(new RuntimeException("Opps!")); error .observeOn(Schedulers.io()) .subscribe( System.out::println, Throwable::printStackTrace ); } AsyncHttpClient asyncHttpClient = new AsyncHttpClient(); Single fetch(String address) { return Single.create(subscriber -> asyncHttpClient .prepareGet(address) .execute(handler(subscriber))); } AsyncCompletionHandler handler(SingleSubscriber subscriber) { return new AsyncCompletionHandler() { public Response onCompleted(Response response) { subscriber.onSuccess(response); return response; } public void onThrowable(Throwable t) { subscriber.onError(t); } }; } @Test public void sample_55() throws Exception { Single example = fetch("http://www.example.com") .flatMap(this::body); String b = example.toBlocking().value(); } Single body(Response response) { return Single.create(subscriber -> { try { subscriber.onSuccess(response.getResponseBody()); } catch (IOException e) { subscriber.onError(e); } }); } //Same functionality as body(): Single body2(Response response) { return Single.fromCallable(() -> response.getResponseBody()); } private final JdbcTemplate jdbcTemplate = new JdbcTemplate(); Single content(int id) { return Single.fromCallable(() -> jdbcTemplate .queryForObject( "SELECT content FROM articles WHERE id = ?", String.class, id)) .subscribeOn(Schedulers.io()); } Single likes(int id) { //asynchronous HTTP request to social media website return Single.just(7); } Single updateReadCount() { //only side effect, no return value in Single return Single.just(null); } @Test public void sample_98() throws Exception { Single doc = Single.zip( content(123), likes(123), updateReadCount(), (con, lks, vod) -> buildHtml(con, lks) ); } Document buildHtml(String content, int likes) { //... return null; } @Test public void sample_113() throws Exception { Single single = Single.create(subscriber -> { System.out.println("Subscribing"); subscriber.onSuccess("42"); }); Single cachedSingle = single .toObservable() .cache() .toSingle(); cachedSingle.subscribe(System.out::println); cachedSingle.subscribe(System.out::println); } @Test public void sample_129() throws Exception { Single emptySingle = Observable.empty().toSingle(); Single doubleSingle = Observable.just(1, 2).toSingle(); } @Test public void sample_138() throws Exception { Single ignored = Single .just(1) .toObservable() .ignoreElements() //PROBLEM .toSingle(); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/Ticket.java ================================================ package com.oreilly.rxjava.ch5; class Ticket { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/TravelAgency.java ================================================ package com.oreilly.rxjava.ch5; import rx.Observable; import java.util.concurrent.CompletableFuture; interface TravelAgency { Flight search(User user, GeoLocation location); default CompletableFuture searchAsync(User user, GeoLocation location) { return CompletableFuture.supplyAsync(() -> search(user, location)); } default Observable rxSearch(User user, GeoLocation location) { return Observable.fromCallable(() -> search(user, location)); } } class SomeTravelAgency implements TravelAgency { @Override public Flight search(User user, GeoLocation location) { return new Flight(); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/User.java ================================================ package com.oreilly.rxjava.ch5; import java.time.LocalDate; class User { LocalDate getBirth() { return LocalDate.now().minusYears(30); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch5/Util.java ================================================ package com.oreilly.rxjava.ch5; import rx.Observable; import java.util.List; import java.util.concurrent.CompletableFuture; class Util { static Observable observe(CompletableFuture future) { return Observable.create(subscriber -> { future.whenComplete((value, exception) -> { if (exception != null) { subscriber.onError(exception); } else { subscriber.onNext(value); subscriber.onCompleted(); } }); }); } static CompletableFuture toFuture(Observable observable) { CompletableFuture promise = new CompletableFuture<>(); observable .single() .subscribe( promise::complete, promise::completeExceptionally ); return promise; } static CompletableFuture> toFutureList(Observable observable) { return toFuture(observable.toList()); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch6/Backpressure.java ================================================ package com.oreilly.rxjava.ch6; import com.oreilly.rxjava.util.Sleeper; import org.apache.commons.dbutils.ResultSetIterator; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import rx.Subscriber; import rx.observables.SyncOnSubscribe; import rx.schedulers.Schedulers; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.time.Duration; @Ignore public class Backpressure { private static final Logger log = LoggerFactory.getLogger(Backpressure.class); private Observable dishes() { Observable dishes = Observable .range(1, 1_000_000_000) .map(Dish::new); return dishes; } @Test public void sample_18() throws Exception { Observable .range(1, 1_000_000_000) .map(Dish::new) .subscribe(x -> { System.out.println("Washing: " + x); sleepMillis(50); }); } @Test public void sample_32() throws Exception { final Observable dishes = dishes(); dishes .observeOn(Schedulers.io()) .subscribe(x -> { System.out.println("Washing: " + x); sleepMillis(50); }); } private void sleepMillis(int millis) { Sleeper.sleep(Duration.ofMillis(millis)); } Observable myRange(int from, int count) { return Observable.create(subscriber -> { int i = from; while (i < from + count) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(i++); } else { return; } } subscriber.onCompleted(); }); } @Test public void sample_65() throws Exception { myRange(1, 1_000_000_000) .map(Dish::new) .observeOn(Schedulers.io()) .subscribe(x -> { System.out.println("Washing: " + x); sleepMillis(50); }, Throwable::printStackTrace ); } @Test public void sample_78() throws Exception { Observable .range(1, 10) .subscribe(new Subscriber() { @Override public void onStart() { request(3); } @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Integer integer) { } }); } @Test public void sample_94() throws Exception { Observable .range(1, 10) .subscribe(new Subscriber() { { { request(3); } } @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Integer integer) { } }); } @Test public void sample_136() throws Exception { Observable .range(1, 10) .subscribe(new Subscriber() { @Override public void onStart() { request(1); } @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Integer integer) { request(1); log.info("Next {}", integer); } //onCompleted, onError... }); } @Test public void sample_173() throws Exception { myRange(1, 1_000_000_000) .map(Dish::new) .onBackpressureBuffer() //.onBackpressureBuffer(1000, () -> log.warn("Buffer full")) //.onBackpressureDrop(dish -> log.warn("Throw away {}", dish)) .observeOn(Schedulers.io()) .subscribe(x -> { System.out.println("Washing: " + x); sleepMillis(50); }); } @Test public void sample_189() throws Exception { Connection connection = null; PreparedStatement statement = connection.prepareStatement("SELECT ..."); statement.setFetchSize(1000); ResultSet rs = statement.executeQuery(); Observable result = Observable .from(ResultSetIterator.iterable(rs)) .doAfterTerminate(() -> { try { rs.close(); statement.close(); connection.close(); } catch (SQLException e) { log.warn("Unable to close", e); } }); } @Test public void sample_213() throws Exception { Observable.OnSubscribe onSubscribe = SyncOnSubscribe.createStateless( observer -> observer.onNext(Math.random()) ); Observable rand = Observable.create(onSubscribe); } @Test public void sample_224() throws Exception { Observable.OnSubscribe onSubscribe = SyncOnSubscribe.createStateful( () -> 0L, (cur, observer) -> { observer.onNext(cur); return cur + 1; } ); Observable naturals = Observable.create(onSubscribe); } @Test public void sample_238() throws Exception { Observable naturals = Observable.create(subscriber -> { long cur = 0; while (!subscriber.isUnsubscribed()) { System.out.println("Produced: " + cur); subscriber.onNext(cur++); } }); } @Test public void sample_249() throws Exception { ResultSet resultSet = null; //... Observable.OnSubscribe onSubscribe = SyncOnSubscribe.createSingleState( () -> resultSet, (rs, observer) -> { try { rs.next(); observer.onNext(toArray(rs)); } catch (SQLException e) { observer.onError(e); } }, rs -> { try { //Also close Statement, Connection, etc. rs.close(); } catch (SQLException e) { log.warn("Unable to close", e); } } ); Observable records = Observable.create(onSubscribe); } private Object[] toArray(ResultSet rs) { //TODO return new Object[] {}; } @Test public void sample_284() throws Exception { Observable source = Observable.range(1, 1_000); source.subscribe(this::store); source .flatMap(this::store) .subscribe(uuid -> log.debug("Stored: {}", uuid)); source .flatMap(this::store) .buffer(100) .subscribe( hundredUuids -> log.debug("Stored: {}", hundredUuids)); } Observable store(int x) { return Observable.empty(); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch6/Chapter6.java ================================================ package com.oreilly.rxjava.ch6; import org.junit.Ignore; import org.junit.Test; import rx.Observable; import java.time.Duration; import java.time.LocalTime; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; import java.util.Random; import java.util.stream.Collectors; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static rx.Observable.empty; import static rx.Observable.just; @Ignore public class Chapter6 { @Test public void sample_9() throws Exception { long startTime = System.currentTimeMillis(); Observable .interval(7, MILLISECONDS) .timestamp() .sample(1, SECONDS) .map(ts -> ts.getTimestampMillis() - startTime + "ms: " + ts.getValue()) .take(5) .subscribe(System.out::println); } @Test public void sample_25() throws Exception { Observable delayedNames = delayedNames(); delayedNames .sample(1, SECONDS) .subscribe(System.out::println); } @Test public void sample_37() throws Exception { Observable delayedNames = delayedNames(); delayedNames .concatWith(delayedCompletion()) .sample(1, SECONDS) .subscribe(System.out::println); } private Observable delayedNames() { Observable names = just("Mary", "Patricia", "Linda", "Barbara", "Elizabeth", "Jennifer", "Maria", "Susan", "Margaret", "Dorothy"); Observable absoluteDelayMillis = just(0.1, 0.6, 0.9, 1.1, 3.3, 3.4, 3.5, 3.6, 4.4, 4.8) .map(d -> (long) (d * 1_000)); final Observable delayedNames = names .zipWith(absoluteDelayMillis, (n, d) -> just(n) .delay(d, MILLISECONDS)) .flatMap(o -> o); return delayedNames; } static Observable delayedCompletion() { return Observable.empty().delay(1, SECONDS); } @Test public void sample_64() throws Exception { Observable obs = Observable.interval(20, MILLISECONDS); //equivalent: obs.sample(1, SECONDS); obs.sample(Observable.interval(1, SECONDS)); } @Test public void sample_73() throws Exception { Observable delayedNames = delayedNames(); delayedNames .throttleFirst(1, SECONDS) .subscribe(System.out::println); } @Test public void sample_93() throws Exception { Observable .range(1, 7) //1, 2, 3, ... 7 .buffer(3) .subscribe((List list) -> { System.out.println(list); } ); } Repository repository = new SomeRepository(); @Test public void sample_105() throws Exception { Observable events = Observable.range(1, 100).map(x -> new Record()); events .subscribe(repository::store); //vs. events .buffer(10) .subscribe(repository::storeAll); } @Test public void sample_120() throws Exception { Random random = new Random(); Observable .defer(() -> just(random.nextGaussian())) .repeat(1000) .buffer(100, 1) .map(this::averageOfList) .subscribe(System.out::println); } private double averageOfList(List list) { return list .stream() .collect(Collectors.averagingDouble(x -> x)); } @Test public void sample_139() throws Exception { Observable> odd = Observable .range(1, 7) .buffer(1, 2); odd.subscribe(System.out::println); } @Test public void sample_147() throws Exception { Observable odd = Observable .range(1, 7) .buffer(1, 2) .flatMapIterable(list -> list); } @Test public void sample_155() throws Exception { Observable delayedNames = delayedNames(); delayedNames .buffer(1, SECONDS) .subscribe(System.out::println); } @Test public void sample_164() throws Exception { Observable keyEvents = empty(); Observable eventPerSecond = keyEvents .buffer(1, SECONDS) .map(List::size); } @Test public void sample_173() throws Exception { Observable insideBusinessHours = Observable .interval(1, SECONDS) .filter(x -> isBusinessHour()) .map(x -> Duration.ofMillis(100)); Observable outsideBusinessHours = Observable .interval(5, SECONDS) .filter(x -> !isBusinessHour()) .map(x -> Duration.ofMillis(200)); Observable openings = Observable.merge( insideBusinessHours, outsideBusinessHours); Observable upstream = empty(); Observable> samples = upstream .buffer(openings); Observable> samples2 = upstream .buffer( openings, duration -> empty() .delay(duration.toMillis(), MILLISECONDS)); } private static final LocalTime BUSINESS_START = LocalTime.of(9, 0); private static final LocalTime BUSINESS_END = LocalTime.of(17, 0); private boolean isBusinessHour() { ZoneId zone = ZoneId.of("Europe/Warsaw"); ZonedDateTime zdt = ZonedDateTime.now(zone); LocalTime localTime = zdt.toLocalTime(); return !localTime.isBefore(BUSINESS_START) && !localTime.isAfter(BUSINESS_END); } @Test public void sample_216() throws Exception { Observable keyEvents = empty(); Observable> windows = keyEvents.window(1, SECONDS); Observable eventPerSecond = windows .flatMap(eventsInSecond -> eventsInSecond.count()); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch6/Debounce.java ================================================ package com.oreilly.rxjava.ch6; import org.junit.Ignore; import org.junit.Test; import rx.Observable; import rx.observables.ConnectableObservable; import java.math.BigDecimal; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static rx.Observable.defer; @Ignore public class Debounce { private final TradingPlatform tradingPlatform = new TradingPlatform(); @Test public void sample_225() throws Exception { Observable prices = tradingPlatform.pricesOf("NFLX"); Observable debounced = prices.debounce(100, MILLISECONDS); prices .debounce(x -> { boolean goodPrice = x.compareTo(BigDecimal.valueOf(150)) > 0; return Observable .empty() .delay(goodPrice? 10 : 100, MILLISECONDS); }); } @Test public void sample_242() throws Exception { Observable .interval(99, MILLISECONDS) .debounce(100, MILLISECONDS); } @Test public void sample_249() throws Exception { Observable .interval(99, MILLISECONDS) .debounce(100, MILLISECONDS) .timeout(1, SECONDS); } @Test public void sample_48() throws Exception { ConnectableObservable upstream = Observable .interval(99, MILLISECONDS) .publish(); upstream .debounce(100, MILLISECONDS) .timeout(1, SECONDS, upstream.take(1)); upstream.connect(); } @Test public void sample_60() throws Exception { final Observable upstream = Observable.interval(99, MILLISECONDS); upstream .debounce(100, MILLISECONDS) .timeout(1, SECONDS, upstream .take(1) .concatWith( upstream.debounce(100, MILLISECONDS))); } @Test public void sample_72() throws Exception { final Observable upstream = Observable.interval(99, MILLISECONDS); upstream .debounce(100, MILLISECONDS) .timeout(1, SECONDS, upstream .take(1) .concatWith( upstream .debounce(100, MILLISECONDS) .timeout(1, SECONDS, upstream))); } Observable timedDebounce(Observable upstream) { Observable onTimeout = upstream .take(1) .concatWith(defer(() -> timedDebounce(upstream))); return upstream .debounce(100, MILLISECONDS) .timeout(1, SECONDS, onTimeout); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch6/Dish.java ================================================ package com.oreilly.rxjava.ch6; class Dish { private final byte[] oneKb = new byte[1_024]; private final int id; Dish(int id) { this.id = id; System.out.println("Created: " + id); } public String toString() { return String.valueOf(id); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch6/KeyEvent.java ================================================ package com.oreilly.rxjava.ch6; class KeyEvent { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch6/Record.java ================================================ package com.oreilly.rxjava.ch6; class Record { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch6/Repository.java ================================================ package com.oreilly.rxjava.ch6; import java.util.List; interface Repository { void store(Record record); void storeAll(List records); } class SomeRepository implements Repository { @Override public void store(Record record) { } @Override public void storeAll(List records) { } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch6/TeleData.java ================================================ package com.oreilly.rxjava.ch6; class TeleData { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch6/TradingPlatform.java ================================================ package com.oreilly.rxjava.ch6; import rx.Observable; import java.math.BigDecimal; import static java.util.concurrent.TimeUnit.MILLISECONDS; class TradingPlatform { Observable pricesOf(String ticker) { return Observable .interval(50, MILLISECONDS) .flatMap(this::randomDelay) .map(this::randomStockPrice) .map(BigDecimal::valueOf); } Observable randomDelay(long x) { return Observable .just(x) .delay((long) (Math.random() * 100), MILLISECONDS); } double randomStockPrice(long x) { return 100 + Math.random() * 10 + (Math.sin(x / 100.0)) * 60.0; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch7/Agreement.java ================================================ package com.oreilly.rxjava.ch7; class Agreement { boolean postalMailRequired() { return true; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch7/Chapter7.java ================================================ package com.oreilly.rxjava.ch7; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import rx.schedulers.TimeInterval; import java.math.BigInteger; import java.time.Duration; import java.time.Instant; import java.time.LocalDate; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import static java.time.Month.*; import static java.util.concurrent.TimeUnit.MILLISECONDS; @Ignore public class Chapter7 { private static final Logger log = LoggerFactory.getLogger(Chapter7.class); @Test public void sample_9() throws Exception { Observable .create(subscriber -> { try { subscriber.onNext(1 / 0); } catch (Exception e) { subscriber.onError(e); } }) //BROKEN, missing onError() callback .subscribe(System.out::println); } @Test public void sample_29() throws Exception { Observable .create(subscriber -> { try { subscriber.onNext(1 / 0); } catch (Exception e) { subscriber.onError(e); } }) .subscribe( System.out::println, throwable -> log.error("That escalated quickly", throwable)); } @Test public void sample_45() throws Exception { Observable.create(subscriber -> { try { subscriber.onNext(1 / 0); } catch (Exception e) { subscriber.onError(e); } }); Observable.create(subscriber -> subscriber.onNext(1 / 0)); Observable.fromCallable(() -> 1 / 0); } @Test public void sample_60() throws Exception { Observable .just(1, 0) .map(x -> 10 / x); Observable .just("Lorem", null, "ipsum") .filter(String::isEmpty); } @Test public void sample_71() throws Exception { Observable .just(1, 0) .flatMap(x -> (x == 0) ? Observable.error(new ArithmeticException("Zero :-(")) : Observable.just(10 / x) ); } private final PrintHouse printHouse = new PrintHouse(); @Test public void sample_81() throws Exception { Observable person = Observable.just(new Person()); Observable insurance = Observable.just(new InsuranceContract()); Observable health = person.flatMap(this::checkHealth); Observable income = person.flatMap(this::determineIncome); Observable score = Observable .zip(health, income, (h, i) -> asses(h, i)) .map(this::translate); Observable agreement = Observable.zip( insurance, score.filter(Score::isHigh), this::prepare); Observable mail = agreement .filter(Agreement::postalMailRequired) .flatMap(this::print) .flatMap(printHouse::deliver); } private Observable print(Agreement agreement) { return Observable.just(agreement); } private Agreement prepare(InsuranceContract contract, Score score) { return new Agreement(); } private Score translate(BigInteger score) { return new Score(); } private BigInteger asses(Health h, Income i) { return BigInteger.ONE; } private Observable determineIncome(Person person) { return Observable.error(new RuntimeException("Foo")); } private Observable checkHealth(Person person) { return Observable.just(new Health()); } @Test public void sample_129() throws Exception { Observable person = Observable.just(new Person()); Observable income = person .flatMap(this::determineIncome) .onErrorReturn(error -> Income.no()); } public Observable sample_137() throws Exception { Person person = new Person(); try { return determineIncome(person); } catch (Exception e) { return Observable.just(Income.no()); } } @Test public void sample_147() throws Exception { Observable person = Observable.just(new Person()); Observable income = person .flatMap(this::determineIncome) .onErrorResumeNext(person.flatMap(this::guessIncome)); } private Observable guessIncome(Person person) { //... return Observable.just(new Income(1)); } @Test public void sample_161() throws Exception { Observable person = Observable.just(new Person()); Observable income = person .flatMap(this::determineIncome) .flatMap( Observable::just, th -> Observable.empty(), Observable::empty) .concatWith(person.flatMap(this::guessIncome)) .first(); } @Test public void sample_175() throws Exception { Observable person = Observable.just(new Person()); Observable income = person .flatMap(this::determineIncome) .flatMap( Observable::just, th -> person.flatMap(this::guessIncome), Observable::empty); } @Test public void sample_187() throws Exception { Observable person = Observable.just(new Person()); Observable income = person .flatMap(this::determineIncome) .onErrorResumeNext(th -> { if (th instanceof NullPointerException) { return Observable.error(th); } else { return person.flatMap(this::guessIncome); } }); } Observable confirmation() { Observable delayBeforeCompletion = Observable .empty() .delay(200, MILLISECONDS); return Observable .just(new Confirmation()) .delay(100, MILLISECONDS) .concatWith(delayBeforeCompletion); } @Test public void sample_215() throws Exception { confirmation() .timeout(210, MILLISECONDS) .forEach( System.out::println, th -> { if ((th instanceof TimeoutException)) { System.out.println("Too long"); } else { th.printStackTrace(); } } ); } Observable nextSolarEclipse(LocalDate after) { return Observable .just( LocalDate.of(2016, MARCH, 9), LocalDate.of(2016, SEPTEMBER, 1), LocalDate.of(2017, FEBRUARY, 26), LocalDate.of(2017, AUGUST, 21), LocalDate.of(2018, FEBRUARY, 15), LocalDate.of(2018, JULY, 13), LocalDate.of(2018, AUGUST, 11), LocalDate.of(2019, JANUARY, 6), LocalDate.of(2019, JULY, 2), LocalDate.of(2019, DECEMBER, 26)) .skipWhile(date -> !date.isAfter(after)) .zipWith( Observable.interval(500, 50, MILLISECONDS), (date, x) -> date); } @Test public void sample_253() throws Exception { nextSolarEclipse(LocalDate.of(2016, SEPTEMBER, 1)) .timeout( () -> Observable.timer(1000, TimeUnit.MILLISECONDS), date -> Observable.timer(100, MILLISECONDS)); } @Test public void sample_262() throws Exception { Observable> intervals = nextSolarEclipse(LocalDate.of(2016, JANUARY, 1)) .timeInterval(); } @Test public void sample_271() throws Exception { Observable timestamps = Observable .fromCallable(() -> dbQuery()) .doOnSubscribe(() -> log.info("subscribe()")) .doOnRequest(c -> log.info("Requested {}", c)) .doOnNext(instant -> log.info("Got: {}", instant)); timestamps .zipWith(timestamps.skip(1), Duration::between) .map(Object::toString) .subscribe(log::info); } private Instant dbQuery() { return Instant.now(); } @Test public void sample_291() throws Exception { Observable obs = Observable .error(new RuntimeException("Swallowed")) .doOnError(th -> log.warn("onError", th)) .onErrorReturn(th -> "Fallback"); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch7/Confirmation.java ================================================ package com.oreilly.rxjava.ch7; class Confirmation { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch7/Monitoring.java ================================================ package com.oreilly.rxjava.ch7; import com.codahale.metrics.Counter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Slf4jReporter; import com.codahale.metrics.Timer; import org.junit.Ignore; import org.junit.Test; import org.slf4j.LoggerFactory; import rx.Observable; import java.util.concurrent.TimeUnit; @Ignore public class Monitoring { private MetricRegistry metricRegistry; @Test public void sample_9() throws Exception { metricRegistry = new MetricRegistry(); Slf4jReporter reporter = Slf4jReporter .forRegistry(metricRegistry) .outputTo(LoggerFactory.getLogger(Monitoring.class)) .build(); reporter.start(1, TimeUnit.SECONDS); } @Test public void sample_26() throws Exception { final Observable observable = Observable.range(1, 100); final Counter items = metricRegistry.counter("items"); observable .doOnNext(x -> items.inc()) .subscribe(/* ... */); } Observable makeNetworkCall(long x) { //... return Observable.just(10L); } @Test public void sample_38() throws Exception { final Observable observable = Observable.range(1, 100); Counter counter = metricRegistry.counter("counter"); observable .doOnNext(x -> counter.inc()) .flatMap(this::makeNetworkCall) .doOnNext(x -> counter.dec()) .subscribe(/* ... */); } @Test public void sample_55() throws Exception { final Observable observable = Observable.range(1, 100); Counter counter = metricRegistry.counter("counter"); observable .flatMap(x -> makeNetworkCall(x) .doOnSubscribe(counter::inc) .doOnTerminate(counter::dec) ) .subscribe(/* ... */); } @Test public void sample_69() throws Exception { Timer timer = metricRegistry.timer("timer"); Timer.Context ctx = timer.time(); //some lengthy operation... ctx.stop(); } @Test public void sample_78() throws Exception { Observable external = Observable.just(42L); Timer timer = metricRegistry.timer("timer"); Observable externalWithTimer = Observable .defer(() -> Observable.just(timer.time())) .flatMap(timerCtx -> external.doOnCompleted(timerCtx::stop)); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch7/MyService.java ================================================ package com.oreilly.rxjava.ch7; import rx.Observable; import java.time.LocalDate; interface MyService { Observable externalCall(); } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch7/MyServiceWithTimeout.java ================================================ package com.oreilly.rxjava.ch7; import rx.Observable; import rx.Scheduler; import java.time.LocalDate; import java.util.concurrent.TimeUnit; class MyServiceWithTimeout implements MyService { private final MyService delegate; private final Scheduler scheduler; MyServiceWithTimeout(MyService d, Scheduler s) { this.delegate = d; this.scheduler = s; } @Override public Observable externalCall() { return delegate .externalCall() .timeout(1, TimeUnit.SECONDS, Observable.empty(), scheduler); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch7/Person.java ================================================ package com.oreilly.rxjava.ch7; class Person { } class Health { } class Score { boolean isHigh() { return true; } } class InsuranceContract { } class Income { public Income(int i) { } static Income no() { return new Income(0); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch7/PrintHouse.java ================================================ package com.oreilly.rxjava.ch7; import rx.Observable; class PrintHouse { Observable deliver(Agreement agreement) { return Observable.just(new TrackingId()); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch7/RetryTimeouts.java ================================================ package com.oreilly.rxjava.ch7; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import java.util.concurrent.TimeoutException; import static java.util.concurrent.TimeUnit.SECONDS; @Ignore public class RetryTimeouts { private static final Logger log = LoggerFactory.getLogger(RetryTimeouts.class); Observable risky() { return Observable.fromCallable(() -> { if (Math.random() < 0.1) { Thread.sleep((long) (Math.random() * 2000)); return "OK"; } else { throw new RuntimeException("Transient"); } }); } @Test public void sample_281() throws Exception { risky() .timeout(1, SECONDS) .doOnError(th -> log.warn("Will retry", th)) .retry() .subscribe(log::info); } @Test public void sample_291() throws Exception { risky().cache().retry(); //BROKEN } @Test public void sample_296() throws Exception { Observable .defer(() -> risky()) .retry(); } @Test public void sample_303() throws Exception { risky() .timeout(1, SECONDS) .retry(10); } @Test public void sample_310() throws Exception { risky() .timeout(1, SECONDS) .retry((attempt, e) -> attempt <= 10 && !(e instanceof TimeoutException)); } @Test public void sample_66() throws Exception { risky() .timeout(1, SECONDS) // .retryWhen(failures -> failures.take(10)) .retryWhen(failures -> failures.delay(1, SECONDS)); } private static final int ATTEMPTS = 11; @Test public void sample_74() throws Exception { risky() .timeout(1, SECONDS) .retryWhen(failures -> failures .zipWith(Observable.range(1, ATTEMPTS), (err, attempt) -> attempt < ATTEMPTS ? Observable.timer(1, SECONDS) : Observable.error(err)) .flatMap(x -> x) ); } @Test public void sample_89() throws Exception { risky() .timeout(1, SECONDS) .retryWhen(failures -> failures .zipWith(Observable.range(1, ATTEMPTS), this::handleRetryAttempt) .flatMap(x -> x) ); } Observable handleRetryAttempt(Throwable err, int attempt) { switch (attempt) { case 1: return Observable.just(42L); case ATTEMPTS: return Observable.error(err); default: long expDelay = (long) Math.pow(2, attempt - 2); return Observable.timer(expDelay, SECONDS); } } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch7/Testing.java ================================================ package com.oreilly.rxjava.ch7; import com.google.common.io.Files; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import rx.Notification; import rx.Observable; import rx.Scheduler; import rx.observables.BlockingObservable; import rx.observables.SyncOnSubscribe; import rx.observers.TestSubscriber; import rx.plugins.RxJavaPlugins; import rx.plugins.RxJavaSchedulersHook; import rx.schedulers.Schedulers; import rx.schedulers.TestScheduler; import java.io.File; import java.io.FileNotFoundException; import java.time.LocalDate; import java.util.List; import java.util.concurrent.TimeUnit; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.AssertionsForClassTypes.failBecauseExceptionWasNotThrown; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static rx.Notification.Kind.OnError; import static rx.Notification.Kind.OnNext; import static rx.Observable.fromCallable; @Ignore public class Testing { @Test public void sample_9() throws Exception { TestScheduler sched = Schedulers.test(); Observable fast = Observable .interval(10, MILLISECONDS, sched) .map(x -> "F" + x) .take(3); Observable slow = Observable .interval(50, MILLISECONDS, sched) .map(x -> "S" + x); Observable stream = Observable.concat(fast, slow); stream.subscribe(System.out::println); System.out.println("Subscribed"); } @Test public void sample_31() throws Exception { TestScheduler sched = Schedulers.test(); TimeUnit.SECONDS.sleep(1); System.out.println("After one second"); sched.advanceTimeBy(25, MILLISECONDS); TimeUnit.SECONDS.sleep(1); System.out.println("After one more second"); sched.advanceTimeBy(75, MILLISECONDS); TimeUnit.SECONDS.sleep(1); System.out.println("...and one more"); sched.advanceTimeTo(200, MILLISECONDS); } @Test public void shouldApplyConcatMapInOrder() throws Exception { List list = Observable .range(1, 3) .concatMap(x -> Observable.just(x, -x)) .map(Object::toString) .toList() .toBlocking() .single(); assertThat(list).containsExactly("1", "-1", "2", "-2", "3", "-3"); } @Test public void sample_65() throws Exception { File file = new File("404.txt"); BlockingObservable fileContents = fromCallable(() -> Files.toString(file, UTF_8)) .toBlocking(); try { fileContents.single(); failBecauseExceptionWasNotThrown(FileNotFoundException.class); } catch (RuntimeException expected) { assertThat(expected) .hasCauseInstanceOf(FileNotFoundException.class); } } @Test public void sample_87() throws Exception { Observable> notifications = Observable .just(3, 0, 2, 0, 1, 0) .concatMapDelayError(x -> fromCallable(() -> 100 / x)) .materialize(); List kinds = notifications .map(Notification::getKind) .toList() .toBlocking() .single(); assertThat(kinds).containsExactly(OnNext, OnNext, OnNext, OnError); } @Test public void sample_107() throws Exception { Observable obs = Observable .just(3, 0, 2, 0, 1, 0) .concatMapDelayError(x -> Observable.fromCallable(() -> 100 / x)); TestSubscriber ts = new TestSubscriber<>(); obs.subscribe(ts); ts.assertValues(33, 50, 100); ts.assertError(ArithmeticException.class); //Fails (!) } private MyServiceWithTimeout mockReturning( Observable result, TestScheduler testScheduler) { MyService mock = mock(MyService.class); given(mock.externalCall()).willReturn(result); return new MyServiceWithTimeout(mock, testScheduler); } @Test public void timeoutWhenServiceNeverCompletes() throws Exception { //given TestScheduler testScheduler = Schedulers.test(); MyService mock = mockReturning( Observable.never(), testScheduler); TestSubscriber ts = new TestSubscriber<>(); //when mock.externalCall().subscribe(ts); //then testScheduler.advanceTimeBy(950, MILLISECONDS); ts.assertNoTerminalEvent(); testScheduler.advanceTimeBy(100, MILLISECONDS); ts.assertCompleted(); ts.assertNoValues(); } @Test public void valueIsReturnedJustBeforeTimeout() throws Exception { //given TestScheduler testScheduler = Schedulers.test(); Observable slow = Observable .timer(950, MILLISECONDS, testScheduler) .map(x -> LocalDate.now()); MyService myService = mockReturning(slow, testScheduler); TestSubscriber ts = new TestSubscriber<>(); //when myService.externalCall().subscribe(ts); //then testScheduler.advanceTimeBy(930, MILLISECONDS); ts.assertNotCompleted(); ts.assertNoValues(); testScheduler.advanceTimeBy(50, MILLISECONDS); ts.assertCompleted(); ts.assertValueCount(1); } private final TestScheduler testScheduler = new TestScheduler(); @Before public void alwaysUseTestScheduler() { RxJavaPlugins .getInstance() .registerSchedulersHook(new RxJavaSchedulersHook() { @Override public Scheduler getComputationScheduler() { return testScheduler; } @Override public Scheduler getIOScheduler() { return testScheduler; } @Override public Scheduler getNewThreadScheduler() { return testScheduler; } }); } Observable naturals1() { return Observable.create(subscriber -> { long i = 0; while (!subscriber.isUnsubscribed()) { subscriber.onNext(i++); } }); } Observable naturals2() { return Observable.create( SyncOnSubscribe.createStateful( () -> 0L, (cur, observer) -> { observer.onNext(cur); return cur + 1; } )); } @Test public void sample_222() throws Exception { TestSubscriber ts = new TestSubscriber<>(0); naturals1() .take(10) .subscribe(ts); ts.assertNoValues(); ts.requestMore(100); ts.assertValueCount(10); ts.assertCompleted(); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch7/TrackingId.java ================================================ package com.oreilly.rxjava.ch7; class TrackingId { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/Android.java ================================================ package com.oreilly.rxjava.ch8; import android.app.Activity; import android.util.Log; import android.view.View; import android.widget.*; import com.oreilly.rxjava.ch8.rxandroid.AndroidSchedulers; import com.oreilly.rxjava.ch8.rxbinding.RxTextView; import com.oreilly.rxjava.ch8.rxbinding.RxView; import com.oreilly.rxjava.ch8.rxbinding.TextViewAfterTextChangeEvent; import org.apache.commons.lang3.tuple.Pair; import org.junit.Ignore; import org.junit.Test; import rx.Observable; import rx.functions.Action1; import rx.functions.Func1; import rx.functions.Func2; import rx.schedulers.Schedulers; import java.util.List; import java.util.concurrent.TimeUnit; import static android.content.ContentValues.TAG; @Ignore public class Android extends Activity { private final MeetupApi meetup = new ApiFactory().meetup(); @Test public void sample_9() throws Exception { Button button = null; //... button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { meetup .listCities(52.229841, 21.011736) .concatMapIterable(extractCities()) .map(toCityName()) .toList() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( putOnListView(), displayError()); } //... }); } //Cities::getResults Func1> extractCities() { return new Func1>() { @Override public Iterable call(Cities cities) { return cities.getResults(); } }; } //City::getCity Func1 toCityName() { return new Func1() { @Override public String call(City city) { return city.getCity(); } }; } //cities -> listView.setAdapter(...) Action1> putOnListView() { ListView listView = null; //... return new Action1>() { @Override public void call(List cities) { listView.setAdapter(new ArrayAdapter(Android.this, -1 /*R.layout.list*/, cities)); } }; } //throwable -> {...} Action1 displayError() { return new Action1() { @Override public void call(Throwable throwable) { Log.e(TAG, "Error", throwable); Toast.makeText(Android.this, "Unable to load cities", Toast.LENGTH_SHORT).show(); } }; } @Test public void sample_98() throws Exception { Button button = new Button(null); RxView .clicks(button) .flatMap(c -> meetup.listCities(52.229841, 21.011736)) .delay(2, TimeUnit.SECONDS) .concatMapIterable(extractCities()) .map(toCityName()) .toList() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( putOnListView(), displayError()); } Func1> listCities(final double lat, final double lon) { return new Func1>() { @Override public Observable call(Void aVoid) { return meetup.listCities(lat, lon); } }; } @Test public void sample_121() throws Exception { EditText latText = null;//... EditText lonText = null;//... Observable latChanges = RxTextView .afterTextChangeEvents(latText) .flatMap(toDouble()); Observable lonChanges = RxTextView .afterTextChangeEvents(lonText) .flatMap(toDouble()); Observable cities = Observable .combineLatest(latChanges, lonChanges, toPair()) .debounce(1, TimeUnit.SECONDS) .flatMap(listCitiesNear()); } Func1> toDouble() { return new Func1>() { @Override public Observable call(TextViewAfterTextChangeEvent e) { String s = e.editable().toString(); try { return Observable.just(Double.parseDouble(s)); } catch (NumberFormatException ex) { return Observable.empty(); } } }; } //return Pair::new Func2> toPair() { return new Func2>() { @Override public Pair call(Double lat, Double lon) { return Pair.of(lat, lon); } }; } //return latLon -> meetup.listCities(latLon.first, latLon.second) Func1, Observable> listCitiesNear() { return new Func1, Observable>() { @Override public Observable call(Pair latLon) { return meetup.listCities(latLon.getLeft(), latLon.getRight()); } }; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/ApiFactory.java ================================================ package com.oreilly.rxjava.ch8; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import retrofit2.Retrofit; import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; import retrofit2.converter.jackson.JacksonConverterFactory; class ApiFactory { GeoNames geoNames() { return new Retrofit.Builder() .baseUrl("http://api.geonames.org") .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .addConverterFactory(JacksonConverterFactory.create(objectMapper())) .build() .create(GeoNames.class); } MeetupApi meetup() { Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.meetup.com/") .addCallAdapterFactory( RxJavaCallAdapterFactory.create()) .addConverterFactory( JacksonConverterFactory.create(objectMapper())) .build(); return retrofit.create(MeetupApi.class); } private ObjectMapper objectMapper() { ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setPropertyNamingStrategy( PropertyNamingStrategy.SNAKE_CASE); objectMapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); return objectMapper; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/Chapter8.java ================================================ package com.oreilly.rxjava.ch8; import com.couchbase.client.java.CouchbaseAsyncCluster; import com.couchbase.client.java.document.AbstractDocument; import com.couchbase.client.java.document.json.JsonArray; import com.mongodb.client.model.Filters; import com.mongodb.client.model.Projections; import com.mongodb.rx.client.MongoClients; import com.mongodb.rx.client.MongoCollection; import com.mongodb.rx.client.MongoDatabase; import org.apache.camel.CamelContext; import org.apache.camel.Message; import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.rx.ReactiveCamel; import org.bson.Document; import org.junit.Ignore; import org.junit.Test; import rx.Observable; import java.math.BigDecimal; import java.time.Instant; import java.time.LocalDateTime; import java.time.Month; import java.util.*; import java.util.concurrent.TimeUnit; import static com.oreilly.rxjava.ch8.GeoNames.log; import static java.time.Month.APRIL; import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.stream.Collectors.toList; @Ignore public class Chapter8 { @Test public void sample_9() throws Exception { final ApiFactory api = new ApiFactory(); MeetupApi meetup = api.meetup(); GeoNames geoNames = api.geoNames(); double warsawLat = 52.229841; double warsawLon = 21.011736; Observable cities = meetup.listCities(warsawLat, warsawLon); Observable cityObs = cities.concatMapIterable(Cities::getResults); Observable map = cityObs .filter(city -> city.distanceTo(warsawLat, warsawLon) < 50) .map(City::getCity); Observable totalPopulation = meetup .listCities(warsawLat, warsawLon) .concatMapIterable(Cities::getResults) .filter(city -> city.distanceTo(warsawLat, warsawLon) < 50) .map(City::getCity) .flatMap(geoNames::populationOf) .reduce(0L, (x, y) -> x + y); } @Test public void sample_35() throws Exception { CouchbaseAsyncCluster cluster = CouchbaseAsyncCluster.create(); cluster .openBucket("travel-sample") .flatMap(cl -> cl.get("route_14197") .map(AbstractDocument::content) .map(jsonObject -> jsonObject.getArray("schedule"))) .concatMapIterable(JsonArray::toList) .cast(Map.class) .filter(m -> ((Number) m.get("day")).intValue() == 0) .map(m -> m.get("flight").toString()) .subscribe(flight -> System.out.println(flight)); } @Test public void sample_55() throws Exception { MongoCollection monthsColl = com.mongodb.rx.client.MongoClients .create() .getDatabase("rx") .getCollection("months"); Observable .from(Month.values()) .map(month -> new Document() .append("name", month.name()) .append("days_not_leap", month.length(false)) .append("days_leap", month.length(true)) ) .toList() .flatMap(monthsColl::insertMany) .flatMap(s -> monthsColl.find().toObservable()) .toBlocking() .subscribe(System.out::println); } @Test public void sample_105() throws Exception { final MongoDatabase db = MongoClients .create() .getDatabase("rx"); Observable days = db.getCollection("months") .find(Filters.eq("name", APRIL.name())) .projection(Projections.include("days_not_leap")) .first() .map(doc -> doc.getInteger("days_not_leap")); Observable carManufactured = db.getCollection("cars") .find(Filters.eq("owner.name", "Smith")) .first() .map(doc -> doc.getDate("manufactured")) .map(Date::toInstant); Observable pricePerDay = dailyPrice(LocalDateTime.now()); Observable insurance = Observable .zip(days, carManufactured, pricePerDay, (d, man, price) -> { //Create insurance return new Insurance(); }); } private Observable dailyPrice(LocalDateTime date) { return Observable.just(BigDecimal.TEN); } @Test public void sample_121() throws Exception { CamelContext camel = new DefaultCamelContext(); ReactiveCamel reactiveCamel = new ReactiveCamel(camel); reactiveCamel .toObservable("file:/home/user/tmp") .subscribe(e -> log.info("New file: {}", e)); } @Test public void sample_136() throws Exception { CamelContext camel = new DefaultCamelContext(); ReactiveCamel reactiveCamel = new ReactiveCamel(camel); reactiveCamel .toObservable("kafka:localhost:9092?topic=demo&groupId=rx") .map(Message::getBody) .subscribe(e -> log.info("Message: {}", e)); } @Test public void sample_148() throws Exception { List people = Collections.emptyList(); //... List sorted = people .parallelStream() .filter(p -> p.getAge() >= 18) .map(Person::getFirstName) .sorted(Comparator.comparing(String::toLowerCase)) .collect(toList()); //DON'T DO THIS people .parallelStream() .forEach(this::publishOverJms); } private void publishOverJms(Person person) { //... } @Test public void sample_170() throws Exception { Observable .range(0, Integer.MAX_VALUE) .map(Picture::new) .distinct() .distinct(Picture::getTag) .sample(1, TimeUnit.SECONDS) .subscribe(System.out::println); } @Test public void sample_182() throws Exception { Observable .range(0, Integer.MAX_VALUE) .map(Picture::new) .window(1, TimeUnit.SECONDS) .flatMap(Observable::count) .subscribe(System.out::println); } @Test public void sample_192() throws Exception { Observable .range(0, Integer.MAX_VALUE) .map(Picture::new) .window(10, TimeUnit.SECONDS) .flatMap(Observable::distinct); } @Test public void sample_201() throws Exception { Observable incidents = Observable.empty(); //... Observable danger = incidents .buffer(1, TimeUnit.SECONDS) .map((List oneSecond) -> oneSecond .stream() .filter(Incident::isHIghPriority) .count() > 5); } @Test public void sample_213() throws Exception { Observable fast = Observable .interval(10, MICROSECONDS) .map(Picture::new); Observable slow = Observable .interval(11, MICROSECONDS) .map(Picture::new); Observable .zip(fast, slow, (f, s) -> f + " : " + s); Observable .zip( fast.onBackpressureDrop(), slow.onBackpressureDrop(), (f, s) -> f + " : " + s); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/Cities.java ================================================ package com.oreilly.rxjava.ch8; import java.util.List; public class Cities { private List results; public List getResults() { return results; } public void setResults(List results) { this.results = results; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/City.java ================================================ package com.oreilly.rxjava.ch8; public class City { private String city; private String country; private Double distance; private Integer id; private Double lat; private String localizedCountryName; private Double lon; private Integer memberCount; private Integer ranking; private String zip; public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public Double getDistance() { return distance; } public void setDistance(Double distance) { this.distance = distance; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Double getLat() { return lat; } public void setLat(Double lat) { this.lat = lat; } public String getLocalizedCountryName() { return localizedCountryName; } public void setLocalizedCountryName(String localizedCountryName) { this.localizedCountryName = localizedCountryName; } public Double getLon() { return lon; } public void setLon(Double lon) { this.lon = lon; } public Integer getMemberCount() { return memberCount; } public void setMemberCount(Integer memberCount) { this.memberCount = memberCount; } public Integer getRanking() { return ranking; } public void setRanking(Integer ranking) { this.ranking = ranking; } public String getZip() { return zip; } public void setZip(String zip) { this.zip = zip; } public double distanceTo(double lat, double lon) { //TODO Use correct formula return 0.0; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/GeoNames.java ================================================ package com.oreilly.rxjava.ch8; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import retrofit2.http.GET; import retrofit2.http.Query; import rx.Observable; import rx.schedulers.Schedulers; public interface GeoNames { Logger log = LoggerFactory.getLogger(GeoNames.class); default Observable populationOf(String query) { return search(query) .concatMapIterable(SearchResult::getGeonames) .map(Geoname::getPopulation) .filter(p -> p != null) .singleOrDefault(0) .doOnError(th -> log.warn("Falling back to 0 for {}", query, th)) .onErrorReturn(th -> 0) .subscribeOn(Schedulers.io()); } default Observable search(String query) { return search(query, 1, "LONG", "some_user"); } @GET("/searchJSON") Observable search( @Query("q") String query, @Query("maxRows") int maxRows, @Query("style") String style, @Query("username") String username ); } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/Geoname.java ================================================ package com.oreilly.rxjava.ch8; public class Geoname { private String lat; private String lng; private Integer geonameId; private Integer population; private String countryCode; private String name; public String getLat() { return lat; } public void setLat(String lat) { this.lat = lat; } public String getLng() { return lng; } public void setLng(String lng) { this.lng = lng; } public Integer getGeonameId() { return geonameId; } public void setGeonameId(Integer geonameId) { this.geonameId = geonameId; } public Integer getPopulation() { return population; } public void setPopulation(Integer population) { this.population = population; } public String getCountryCode() { return countryCode; } public void setCountryCode(String countryCode) { this.countryCode = countryCode; } public String getName() { return name; } public void setName(String name) { this.name = name; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/Incident.java ================================================ package com.oreilly.rxjava.ch8; class Incident { boolean isHIghPriority() { return true; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/Insurance.java ================================================ package com.oreilly.rxjava.ch8; class Insurance { } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/MeetupApi.java ================================================ package com.oreilly.rxjava.ch8; import retrofit2.http.GET; import retrofit2.http.Query; import rx.Observable; public interface MeetupApi { @GET("/2/cities") Observable listCities( @Query("lat") double lat, @Query("lon") double lon ); } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/Person.java ================================================ package com.oreilly.rxjava.ch8; class Person { int getAge() { return 42; } String getFirstName() { return "Smith"; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/Picture.java ================================================ package com.oreilly.rxjava.ch8; class Picture { private final byte[] blob = new byte[128 * 1024]; private final long tag; Picture(long tag) { this.tag = tag; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Picture)) return false; Picture picture = (Picture) o; return tag == picture.tag; } long getTag() { return tag; } @Override public int hashCode() { return (int) (tag ^ (tag >>> 32)); } @Override public String toString() { return Long.toString(tag); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/SearchResult.java ================================================ package com.oreilly.rxjava.ch8; import java.util.ArrayList; import java.util.List; class SearchResult { private List geonames = new ArrayList<>(); public List getGeonames() { return geonames; } public void setGeonames(List geonames) { this.geonames = geonames; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/hystrix/BlockingCmd.java ================================================ package com.oreilly.rxjava.ch8.hystrix; import com.netflix.hystrix.HystrixCommand; import com.netflix.hystrix.HystrixCommandGroupKey; import org.apache.commons.io.IOUtils; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.charset.StandardCharsets; class BlockingCmd extends HystrixCommand { public BlockingCmd() { super(HystrixCommandGroupKey.Factory.asKey("SomeGroup")); } @Override protected String run() throws IOException { final URL url = new URL("http://www.example.com"); try (InputStream input = url.openStream()) { return IOUtils.toString(input, StandardCharsets.UTF_8); } } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/hystrix/Book.java ================================================ package com.oreilly.rxjava.ch8.hystrix; class Book { } class Rating { private final Book book; Rating(Book book) { this.book = book; } public Book getBook() { return book; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/hystrix/CitiesCmd.java ================================================ package com.oreilly.rxjava.ch8.hystrix; import com.netflix.hystrix.HystrixCommandGroupKey; import com.netflix.hystrix.HystrixObservableCommand; import com.oreilly.rxjava.ch8.Cities; import com.oreilly.rxjava.ch8.MeetupApi; import rx.Observable; class CitiesCmd extends HystrixObservableCommand { private final MeetupApi api; private final double lat; private final double lon; protected CitiesCmd(MeetupApi api, double lat, double lon) { super(HystrixCommandGroupKey.Factory.asKey("Meetup")); this.api = api; this.lat = lat; this.lon = lon; } @Override protected Observable construct() { return api.listCities(lat, lon); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/hystrix/FetchManyRatings.java ================================================ package com.oreilly.rxjava.ch8.hystrix; import com.netflix.hystrix.HystrixCommandGroupKey; import com.netflix.hystrix.HystrixObservableCommand; import rx.Observable; import java.util.Collection; class FetchManyRatings extends HystrixObservableCommand { private final Collection books; protected FetchManyRatings(Collection books) { super(HystrixCommandGroupKey.Factory.asKey("Books")); this.books = books; } @Override protected Observable construct() { return fetchManyRatings(books); } private Observable fetchManyRatings(Collection books) { return Observable .from(books) .map(Rating::new); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/hystrix/FetchRatingsCollapser.java ================================================ package com.oreilly.rxjava.ch8.hystrix; import com.netflix.hystrix.*; import rx.functions.Func1; import java.util.Collection; import java.util.List; import static com.netflix.hystrix.HystrixObservableCollapser.Setter.withCollapserKey; import static java.util.stream.Collectors.toList; public class FetchRatingsCollapser extends HystrixObservableCollapser { private final Book book; public FetchRatingsCollapser(Book book) { super(withCollapserKey(HystrixCollapserKey.Factory.asKey("Books")) .andCollapserPropertiesDefaults(HystrixCollapserProperties.Setter() .withTimerDelayInMilliseconds(20) .withMaxRequestsInBatch(50) ) .andScope(Scope.GLOBAL)); this.book = book; } public Book getRequestArgument() { return book; } protected HystrixObservableCommand createCommand( Collection> requests) { List books = requests.stream() .map(c -> c.getArgument()) .collect(toList()); return new FetchManyRatings(books); } protected void onMissingResponse(HystrixCollapser.CollapsedRequest r) { r.setException(new RuntimeException("Not found for: " + r.getArgument())); } protected Func1 getRequestArgumentKeySelector() { return x -> x; } protected Func1 getBatchReturnTypeToResponseTypeMapper() { return x -> x; } protected Func1 getBatchReturnTypeKeySelector() { return Rating::getBook; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/hystrix/Hystrix.java ================================================ package com.oreilly.rxjava.ch8.hystrix; import com.netflix.hystrix.HystrixCommandKey; import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet; import com.netflix.hystrix.metric.HystrixCommandCompletionStream; import com.oreilly.rxjava.ch8.Cities; import com.oreilly.rxjava.ch8.MeetupApi; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import static com.netflix.hystrix.HystrixEventType.FAILURE; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; import static org.eclipse.jetty.servlet.ServletContextHandler.NO_SESSIONS; import static org.mockito.ArgumentMatchers.anyDouble; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; @Ignore public class Hystrix { private static final Logger log = LoggerFactory.getLogger(Hystrix.class); @Test public void sample_17() throws Exception { String string = new BlockingCmd().execute(); Future future = new BlockingCmd().queue(); Observable eager = new BlockingCmd().observe(); Observable lazy = new BlockingCmd().toObservable(); } @Test public void sample_28() throws Exception { Observable retried = new BlockingCmd() .toObservable() .doOnError(ex -> log.warn("Error ", ex)) .retryWhen(ex -> ex.delay(500, MILLISECONDS)) .timeout(3, SECONDS); } @Test public void sample_44() throws Exception { MeetupApi api = mock(MeetupApi.class); given(api.listCities(anyDouble(), anyDouble())).willReturn( Observable .error(new RuntimeException("Broken")) .doOnSubscribe(() -> log.debug("Invoking")) .delay(2, SECONDS) ); Observable .interval(50, MILLISECONDS) .doOnNext(x -> log.debug("Requesting")) .flatMap(x -> new CitiesCmd(api, 52.229841, 21.011736) .toObservable() .onErrorResumeNext(ex -> Observable.empty()), 5); } Observable allBooks() { return Observable.just(new Book()); } Observable fetchRating(Book book) { return Observable.just(new Rating(book)); } @Test public void sample_69() throws Exception { Observable ratings = allBooks() .flatMap(this::fetchRating); } @Test public void sample_83() throws Exception { final Book book = new Book(); Observable ratingObservable = new FetchRatingsCollapser(book).toObservable(); } @Test public void sample_82() throws Exception { HystrixCommandCompletionStream .getInstance(HystrixCommandKey.Factory.asKey("FetchRating")) .observe() .filter(e -> e.getEventCounts().getCount(FAILURE) > 0) .window(1, TimeUnit.SECONDS) .flatMap(Observable::count) .subscribe(x -> log.info("{} failures/s", x)); } @Test public void sample_98() throws Exception { ServletContextHandler context = new ServletContextHandler(NO_SESSIONS); HystrixMetricsStreamServlet servlet = new HystrixMetricsStreamServlet(); context.addServlet(new ServletHolder(servlet), "/hystrix.stream"); Server server = new Server(8080); server.setHandler(context); server.start(); server.join(); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxandroid/AndroidSchedulers.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.oreilly.rxjava.ch8.rxandroid; import android.os.Looper; import rx.Scheduler; import rx.annotations.Experimental; import java.util.concurrent.atomic.AtomicReference; /** Android-specific Schedulers. */ public final class AndroidSchedulers { private static final AtomicReference INSTANCE = new AtomicReference<>(); private final Scheduler mainThreadScheduler; private static AndroidSchedulers getInstance() { for (;;) { AndroidSchedulers current = INSTANCE.get(); if (current != null) { return current; } current = new AndroidSchedulers(); if (INSTANCE.compareAndSet(null, current)) { return current; } } } private AndroidSchedulers() { RxAndroidSchedulersHook hook = RxAndroidPlugins.getInstance().getSchedulersHook(); Scheduler main = hook.getMainThreadScheduler(); if (main != null) { mainThreadScheduler = main; } else { mainThreadScheduler = new LooperScheduler(Looper.getMainLooper()); } } /** A {@link Scheduler} which executes actions on the Android UI thread. */ public static Scheduler mainThread() { return getInstance().mainThreadScheduler; } /** A {@link Scheduler} which executes actions on {@code looper}. */ public static Scheduler from(Looper looper) { if (looper == null) throw new NullPointerException("looper == null"); return new LooperScheduler(looper); } /** * Resets the current {@link AndroidSchedulers} instance. * This will re-init the cached schedulers on the next usage, * which can be useful in testing. */ @Experimental public static void reset() { INSTANCE.set(null); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxandroid/LooperScheduler.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.oreilly.rxjava.ch8.rxandroid; import android.os.Handler; import android.os.Looper; import android.os.Message; import rx.Scheduler; import rx.Subscription; import rx.exceptions.OnErrorNotImplementedException; import rx.functions.Action0; import rx.plugins.RxJavaPlugins; import rx.subscriptions.Subscriptions; import java.util.concurrent.TimeUnit; class LooperScheduler extends Scheduler { private final Handler handler; LooperScheduler(Looper looper) { handler = new Handler(looper); } LooperScheduler(Handler handler) { this.handler = handler; } @Override public Worker createWorker() { return new HandlerWorker(handler); } static class HandlerWorker extends Worker { private final Handler handler; private final RxAndroidSchedulersHook hook; private volatile boolean unsubscribed; HandlerWorker(Handler handler) { this.handler = handler; this.hook = RxAndroidPlugins.getInstance().getSchedulersHook(); } @Override public void unsubscribe() { unsubscribed = true; handler.removeCallbacksAndMessages(this /* token */); } @Override public boolean isUnsubscribed() { return unsubscribed; } @Override public Subscription schedule(Action0 action, long delayTime, TimeUnit unit) { if (unsubscribed) { return Subscriptions.unsubscribed(); } action = hook.onSchedule(action); ScheduledAction scheduledAction = new ScheduledAction(action, handler); Message message = Message.obtain(handler, scheduledAction); message.obj = this; // Used as token for unsubscription operation. handler.sendMessageDelayed(message, unit.toMillis(delayTime)); if (unsubscribed) { handler.removeCallbacks(scheduledAction); return Subscriptions.unsubscribed(); } return scheduledAction; } @Override public Subscription schedule(final Action0 action) { return schedule(action, 0, TimeUnit.MILLISECONDS); } } static final class ScheduledAction implements Runnable, Subscription { private final Action0 action; private final Handler handler; private volatile boolean unsubscribed; ScheduledAction(Action0 action, Handler handler) { this.action = action; this.handler = handler; } @Override public void run() { try { action.call(); } catch (Throwable e) { // nothing to do but print a System error as this is fatal and there is nowhere else to throw this IllegalStateException ie; if (e instanceof OnErrorNotImplementedException) { ie = new IllegalStateException("Exception thrown on Scheduler.Worker thread. Add `onError` handling.", e); } else { ie = new IllegalStateException("Fatal Exception thrown on Scheduler.Worker thread.", e); } RxJavaPlugins.getInstance().getErrorHandler().handleError(ie); Thread thread = Thread.currentThread(); thread.getUncaughtExceptionHandler().uncaughtException(thread, ie); } } @Override public void unsubscribe() { unsubscribed = true; handler.removeCallbacks(this); } @Override public boolean isUnsubscribed() { return unsubscribed; } } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxandroid/MainActivity.java ================================================ package com.oreilly.rxjava.ch8.rxandroid; import android.app.Activity; import android.os.Bundle; import rx.Observable; import rx.Subscription; import rx.subscriptions.CompositeSubscription; import java.util.concurrent.TimeUnit; public class MainActivity extends Activity { private final byte[] blob = new byte[32 * 1024 * 1024]; private final CompositeSubscription allSubscriptions = new CompositeSubscription(); @Override protected void onCreate(Bundle savedInstanceState) { //... Subscription subscription = Observable .interval(100, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe(x -> { // text.setText(Long.toString(x)); }); allSubscriptions.add(subscription); } @Override protected void onDestroy() { super.onDestroy(); allSubscriptions.unsubscribe(); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxandroid/MainThreadSubscription.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.oreilly.rxjava.ch8.rxandroid; import android.os.Looper; import rx.Subscription; import rx.functions.Action0; import java.util.concurrent.atomic.AtomicBoolean; /** * A {@linkplain Subscription subscription} which ensures its {@linkplain #onUnsubscribe() * unsubscribe action} is executed on the main thread. When unsubscription occurs on a different * thread than the main thread, the action is posted to run on the main thread as soon as possible. *

* Instances of this class are useful in creating observables which interact with APIs that can * only be used on the main thread, such as UI objects. *

* A {@link #verifyMainThread() convenience method} is also provided for validating whether code * is being called on the main thread. Calls to this method along with instances of this class are * commonly used when creating custom observables using the following pattern: *


 * @Override public void call(Subscriber subscriber) {
 *   MainThreadSubscription.verifyMainThread();
 *
 *   // TODO set up behavior
 *
 *   subscriber.add(new MainThreadSubscription() {
 *     @Override public void onUnsubscribe() {
 *       // TODO undo behavior
 *     }
 *   });
 * }
 * 
*/ public abstract class MainThreadSubscription implements Subscription { /** * Verify that the calling thread is the Android main thread. *

* Calls to this method are usually preconditions for subscription behavior which instances of * this class later undo. See the class documentation for an example. * * @throws IllegalStateException when called from any other thread. */ public static void verifyMainThread() { if (Looper.myLooper() != Looper.getMainLooper()) { throw new IllegalStateException( "Expected to be called on the main thread but was " + Thread.currentThread().getName()); } } private final AtomicBoolean unsubscribed = new AtomicBoolean(); @Override public final boolean isUnsubscribed() { return unsubscribed.get(); } @Override public final void unsubscribe() { if (unsubscribed.compareAndSet(false, true)) { if (Looper.myLooper() == Looper.getMainLooper()) { onUnsubscribe(); } else { AndroidSchedulers.mainThread().createWorker().schedule(new Action0() { @Override public void call() { onUnsubscribe(); } }); } } } protected abstract void onUnsubscribe(); } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxandroid/RxAndroidPlugins.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.oreilly.rxjava.ch8.rxandroid; import rx.annotations.Experimental; import java.util.concurrent.atomic.AtomicReference; /** * Registry for plugin implementations that allows global override and handles the retrieval of * correct implementation based on order of precedence: *

    *
  1. plugin registered globally via {@code register} methods in this class
  2. *
  3. default implementation
  4. *
*/ public final class RxAndroidPlugins { private static final RxAndroidPlugins INSTANCE = new RxAndroidPlugins(); public static RxAndroidPlugins getInstance() { return INSTANCE; } private final AtomicReference schedulersHook = new AtomicReference<>(); RxAndroidPlugins() { } /** * Reset any explicit or default-set hooks. *

* Note: This should only be used for testing purposes. */ @Experimental public void reset() { schedulersHook.set(null); } /** * Retrieves the instance of {@link RxAndroidSchedulersHook} to use based on order of * precedence as defined in the {@link RxAndroidPlugins} class header. *

* Override the default by calling {@link #registerSchedulersHook(RxAndroidSchedulersHook)} or by * setting the property {@code rxandroid.plugin.RxAndroidSchedulersHook.implementation} with the * full classname to load. */ public RxAndroidSchedulersHook getSchedulersHook() { if (schedulersHook.get() == null) { schedulersHook.compareAndSet(null, RxAndroidSchedulersHook.getDefaultInstance()); // We don't return from here but call get() again in case of thread-race so the winner will // always get returned. } return schedulersHook.get(); } /** * Registers an {@link RxAndroidSchedulersHook} implementation as a global override of any * injected or default implementations. * * @throws IllegalStateException if called more than once or after the default was initialized * (if usage occurs before trying to register) */ public void registerSchedulersHook(RxAndroidSchedulersHook impl) { if (!schedulersHook.compareAndSet(null, impl)) { throw new IllegalStateException( "Another strategy was already registered: " + schedulersHook.get()); } } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxandroid/RxAndroidSchedulersHook.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.oreilly.rxjava.ch8.rxandroid; import rx.Scheduler; import rx.functions.Action0; public class RxAndroidSchedulersHook { private static final RxAndroidSchedulersHook DEFAULT_INSTANCE = new RxAndroidSchedulersHook(); public static RxAndroidSchedulersHook getDefaultInstance() { return DEFAULT_INSTANCE; } /** * Scheduler to return from {@link AndroidSchedulers#mainThread()} or {@code null} if default * should be used. *

* This instance should be or behave like a stateless singleton. */ public Scheduler getMainThreadScheduler() { return null; } /** * Invoked before the Action is handed over to the scheduler. Can be used for * wrapping/decorating/logging. The default is just a passthrough. * * @param action action to schedule * @return wrapped action to schedule */ public Action0 onSchedule(Action0 action) { return action; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/RxTextView.java ================================================ package com.oreilly.rxjava.ch8.rxbinding; import android.widget.TextView; import com.oreilly.rxjava.ch8.rxbinding.internal.Functions; import rx.Observable; import rx.functions.Action1; import rx.functions.Func1; import javax.annotation.Nonnull; import static com.oreilly.rxjava.ch8.rxbinding.internal.Preconditions.checkNotNull; /** * Static factory methods for creating {@linkplain Observable observables} and {@linkplain Action1 * actions} for {@link TextView}. */ public final class RxTextView { /** * Create an observable of editor actions on {@code view}. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. *

* Warning: The created observable uses {@link TextView.OnEditorActionListener} to * observe actions. Only one observable can be used for a view at a time. */ @Nonnull public static Observable editorActions(@Nonnull TextView view) { checkNotNull(view, "view == null"); return editorActions(view, Functions.FUNC1_ALWAYS_TRUE); } /** * Create an observable of editor actions on {@code view}. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. *

* Warning: The created observable uses {@link TextView.OnEditorActionListener} to * observe actions. Only one observable can be used for a view at a time. * * @param handled Function invoked each occurrence to determine the return value of the * underlying {@link TextView.OnEditorActionListener}. */ @Nonnull public static Observable editorActions(@Nonnull TextView view, @Nonnull Func1 handled) { checkNotNull(view, "view == null"); checkNotNull(handled, "handled == null"); return Observable.create(new TextViewEditorActionOnSubscribe(view, handled)); } /** * Create an observable of editor action events on {@code view}. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. *

* Warning: The created observable uses {@link TextView.OnEditorActionListener} to * observe actions. Only one observable can be used for a view at a time. */ @Nonnull public static Observable editorActionEvents(@Nonnull TextView view) { checkNotNull(view, "view == null"); return editorActionEvents(view, Functions.FUNC1_ALWAYS_TRUE); } /** * Create an observable of editor action events on {@code view}. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. *

* Warning: The created observable uses {@link TextView.OnEditorActionListener} to * observe actions. Only one observable can be used for a view at a time. * * @param handled Function invoked each occurrence to determine the return value of the * underlying {@link TextView.OnEditorActionListener}. */ @Nonnull public static Observable editorActionEvents(@Nonnull TextView view, @Nonnull Func1 handled) { checkNotNull(view, "view == null"); checkNotNull(handled, "handled == null"); return Observable.create(new TextViewEditorActionEventOnSubscribe(view, handled)); } /** * Create an observable of character sequences for text changes on {@code view}. *

* Warning: Values emitted by this observable are mutable and owned by the host * {@code TextView} and thus are not safe to cache or delay reading (such as by observing * on a different thread). If you want to cache or delay reading the items emitted then you must * map values through a function which calls {@link String#valueOf} or * {@link CharSequence#toString() .toString()} to create a copy. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. *

* Note: A value will be emitted immediately on subscribe. */ @Nonnull public static Observable textChanges(@Nonnull TextView view) { checkNotNull(view, "view == null"); return Observable.create(new TextViewTextOnSubscribe(view)); } /** * Create an observable of text change events for {@code view}. *

* Warning: Values emitted by this observable contain a mutable * {@link CharSequence} owned by the host {@code TextView} and thus are not safe to cache * or delay reading (such as by observing on a different thread). If you want to cache or delay * reading the items emitted then you must map values through a function which calls * {@link String#valueOf} or {@link CharSequence#toString() .toString()} to create a copy. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. *

* Note: A value will be emitted immediately on subscribe. */ @Nonnull public static Observable textChangeEvents(@Nonnull TextView view) { checkNotNull(view, "view == null"); return Observable.create(new TextViewTextChangeEventOnSubscribe(view)); } /** * Create an observable of before text change events for {@code view}. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. *

* Note: A value will be emitted immediately on subscribe. */ @Nonnull public static Observable beforeTextChangeEvents( @Nonnull TextView view) { checkNotNull(view, "view == null"); return Observable.create(new TextViewBeforeTextChangeEventOnSubscribe(view)); } /** * Create an observable of after text change events for {@code view}. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. *

* Note: A value will be emitted immediately on subscribe. */ @Nonnull public static Observable afterTextChangeEvents( @Nonnull TextView view) { checkNotNull(view, "view == null"); return Observable.create(new TextViewAfterTextChangeEventOnSubscribe(view)); } /** * An action which sets the text property of {@code view} with character sequences. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. */ @Nonnull public static Action1 text(@Nonnull final TextView view) { checkNotNull(view, "view == null"); return new Action1() { @Override public void call(CharSequence text) { view.setText(text); } }; } /** * An action which sets the text property of {@code view} string resource IDs. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. */ @Nonnull public static Action1 textRes(@Nonnull final TextView view) { checkNotNull(view, "view == null"); return new Action1() { @Override public void call(Integer textRes) { view.setText(textRes); } }; } /** * An action which sets the error property of {@code view} with character sequences. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. */ @Nonnull public static Action1 error(@Nonnull final TextView view) { checkNotNull(view, "view == null"); return new Action1() { @Override public void call(CharSequence text) { view.setError(text); } }; } /** * An action which sets the error property of {@code view} string resource IDs. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. */ @Nonnull public static Action1 errorRes(@Nonnull final TextView view) { checkNotNull(view, "view == null"); return new Action1() { @Override public void call(Integer textRes) { view.setError(view.getContext().getResources().getText(textRes)); } }; } /** * An action which sets the hint property of {@code view} with character sequences. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. */ @Nonnull public static Action1 hint(@Nonnull final TextView view) { checkNotNull(view, "view == null"); return new Action1() { @Override public void call(CharSequence hint) { view.setHint(hint); } }; } /** * An action which sets the hint property of {@code view} string resource IDs. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. */ @Nonnull public static Action1 hintRes(@Nonnull final TextView view) { checkNotNull(view, "view == null"); return new Action1() { @Override public void call(Integer hintRes) { view.setHint(hintRes); } }; } /** * An action which sets the color property of {@code view} with color integer. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. */ @Nonnull public static Action1 color(@Nonnull final TextView view) { checkNotNull(view, "view == null"); return new Action1() { @Override public void call(Integer color) { view.setTextColor(color); } }; } private RxTextView() { throw new AssertionError("No instances."); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/RxView.java ================================================ package com.oreilly.rxjava.ch8.rxbinding; import android.view.View; import rx.Observable; import rx.functions.Action1; import javax.annotation.Nonnull; import static com.oreilly.rxjava.ch8.rxbinding.internal.Preconditions.checkNotNull; /** * Static factory methods for creating {@linkplain Observable observables} and {@linkplain Action1 * actions} for {@link View}. */ public final class RxView { /** * Create an observable which emits on {@code view} click events. The emitted value is * unspecified and should only be used as notification. *

* Warning: The created observable keeps a strong reference to {@code view}. Unsubscribe * to free this reference. *

* Warning: The created observable uses {@link View#setOnClickListener} to observe * clicks. Only one observable can be used for a view at a time. */ @Nonnull public static Observable clicks(@Nonnull View view) { checkNotNull(view, "view == null"); return Observable.create(new ViewClickOnSubscribe(view)); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/TextViewAfterTextChangeEvent.java ================================================ package com.oreilly.rxjava.ch8.rxbinding; import android.content.Context; import android.text.Editable; import android.widget.TextView; import javax.annotation.Nonnull; import javax.annotation.Nullable; /** * An after text-change event on a view. *

* Warning: Instances keep a strong reference to the view. Operators that cache * instances have the potential to leak the associated {@link Context}. */ public final class TextViewAfterTextChangeEvent extends ViewEvent { @Nonnull public static TextViewAfterTextChangeEvent create(@Nonnull TextView view, @Nullable Editable editable) { return new TextViewAfterTextChangeEvent(view, editable); } private final Editable editable; private TextViewAfterTextChangeEvent(@Nonnull TextView view, @Nullable Editable editable) { super(view); this.editable = editable; } @Nullable public Editable editable() { return editable; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof TextViewAfterTextChangeEvent)) return false; TextViewAfterTextChangeEvent other = (TextViewAfterTextChangeEvent) o; return other.view() == view() && editable.equals(other.editable); } @Override public int hashCode() { int result = 17; result = result * 37 + view().hashCode(); result = result * 37 + editable.hashCode(); return result; } @Override public String toString() { return "TextViewAfterTextChangeEvent{editable=" + editable + ", view=" + view() + '}'; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/TextViewAfterTextChangeEventOnSubscribe.java ================================================ package com.oreilly.rxjava.ch8.rxbinding; import android.text.Editable; import android.text.TextWatcher; import android.widget.TextView; import com.oreilly.rxjava.ch8.rxandroid.MainThreadSubscription; import rx.Observable; import rx.Subscriber; import static com.oreilly.rxjava.ch8.rxandroid.MainThreadSubscription.verifyMainThread; final class TextViewAfterTextChangeEventOnSubscribe implements Observable.OnSubscribe { final TextView view; TextViewAfterTextChangeEventOnSubscribe(TextView view) { this.view = view; } @Override public void call(final Subscriber subscriber) { verifyMainThread(); final TextWatcher watcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(TextViewAfterTextChangeEvent.create(view, s)); } } }; subscriber.add(new MainThreadSubscription() { @Override protected void onUnsubscribe() { view.removeTextChangedListener(watcher); } }); view.addTextChangedListener(watcher); // Emit initial value. subscriber.onNext(TextViewAfterTextChangeEvent.create(view, view.getEditableText())); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/TextViewBeforeTextChangeEvent.java ================================================ package com.oreilly.rxjava.ch8.rxbinding; import android.content.Context; import android.widget.TextView; import javax.annotation.Nonnull; /** * A before text-change event on a view. *

* Warning: Instances keep a strong reference to the view. Operators that cache * instances have the potential to leak the associated {@link Context}. */ public final class TextViewBeforeTextChangeEvent extends ViewEvent { @Nonnull public static TextViewBeforeTextChangeEvent create(@Nonnull TextView view, @Nonnull CharSequence text, int start, int count, int after) { return new TextViewBeforeTextChangeEvent(view, text, start, count, after); } private final CharSequence text; private final int start; private final int count; private final int after; private TextViewBeforeTextChangeEvent(@Nonnull TextView view, @Nonnull CharSequence text, int start, int count, int after) { super(view); this.text = text; this.start = start; this.count = count; this.after = after; } @Nonnull public CharSequence text() { return text; } public int start() { return start; } public int count() { return count; } public int after() { return after; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof TextViewAfterTextChangeEvent)) return false; TextViewBeforeTextChangeEvent other = (TextViewBeforeTextChangeEvent) o; return other.view() == view() && text.equals(other.text) && start == other.start && count == other.count && after == other.after; } @Override public int hashCode() { int result = 17; result = result * 37 + view().hashCode(); result = result * 37 + text.hashCode(); result = result * 37 + start; result = result * 37 + count; result = result * 37 + after; return result; } @Override public String toString() { return "TextViewBeforeTextChangeEvent{text=" + text + ", start=" + start + ", count=" + count + ", after=" + after + ", view=" + view() + '}'; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/TextViewBeforeTextChangeEventOnSubscribe.java ================================================ package com.oreilly.rxjava.ch8.rxbinding; import android.text.Editable; import android.text.TextWatcher; import android.widget.TextView; import com.oreilly.rxjava.ch8.rxandroid.MainThreadSubscription; import rx.Observable; import rx.Subscriber; import static com.oreilly.rxjava.ch8.rxandroid.MainThreadSubscription.verifyMainThread; final class TextViewBeforeTextChangeEventOnSubscribe implements Observable.OnSubscribe { final TextView view; TextViewBeforeTextChangeEventOnSubscribe(TextView view) { this.view = view; } @Override public void call(final Subscriber subscriber) { verifyMainThread(); final TextWatcher watcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(TextViewBeforeTextChangeEvent.create(view, s, start, count, after)); } } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { } }; subscriber.add(new MainThreadSubscription() { @Override protected void onUnsubscribe() { view.removeTextChangedListener(watcher); } }); view.addTextChangedListener(watcher); // Emit initial value. subscriber.onNext(TextViewBeforeTextChangeEvent.create(view, view.getText(), 0, 0, 0)); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/TextViewEditorActionEvent.java ================================================ package com.oreilly.rxjava.ch8.rxbinding; import android.view.KeyEvent; import android.widget.TextView; import javax.annotation.Nullable; public final class TextViewEditorActionEvent extends ViewEvent { public static TextViewEditorActionEvent create( TextView view, int actionId, @Nullable KeyEvent keyEvent) { return new TextViewEditorActionEvent(view, actionId, keyEvent); } private final int actionId; @Nullable private final KeyEvent keyEvent; private TextViewEditorActionEvent( TextView view, int actionId, @Nullable KeyEvent keyEvent) { super(view); this.actionId = actionId; this.keyEvent = keyEvent; } public int actionId() { return actionId; } @Nullable public KeyEvent keyEvent() { return keyEvent; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof TextViewEditorActionEvent)) return false; TextViewEditorActionEvent other = (TextViewEditorActionEvent) o; return other.view() == view() && other.actionId == actionId && (other.keyEvent != null ? other.keyEvent.equals(keyEvent) : keyEvent == null); } @Override public int hashCode() { int result = 17; result = result * 37 + view().hashCode(); result = result * 37 + actionId; result = result * 37 + (keyEvent != null ? keyEvent.hashCode() : 0); return result; } @Override public String toString() { return "TextViewEditorActionEvent{view=" + view() + ", actionId=" + actionId + ", keyEvent=" + keyEvent + '}'; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/TextViewEditorActionEventOnSubscribe.java ================================================ package com.oreilly.rxjava.ch8.rxbinding; import android.view.KeyEvent; import android.widget.TextView; import com.oreilly.rxjava.ch8.rxandroid.MainThreadSubscription; import rx.Observable; import rx.Subscriber; import rx.functions.Func1; import static com.oreilly.rxjava.ch8.rxandroid.MainThreadSubscription.verifyMainThread; final class TextViewEditorActionEventOnSubscribe implements Observable.OnSubscribe { final TextView view; final Func1 handled; TextViewEditorActionEventOnSubscribe(TextView view, Func1 handled) { this.view = view; this.handled = handled; } @Override public void call(final Subscriber subscriber) { verifyMainThread(); TextView.OnEditorActionListener listener = new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent keyEvent) { TextViewEditorActionEvent event = TextViewEditorActionEvent.create(v, actionId, keyEvent); if (handled.call(event)) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(event); } return true; } return false; } }; subscriber.add(new MainThreadSubscription() { @Override protected void onUnsubscribe() { view.setOnEditorActionListener(null); } }); view.setOnEditorActionListener(listener); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/TextViewEditorActionOnSubscribe.java ================================================ package com.oreilly.rxjava.ch8.rxbinding; import android.view.KeyEvent; import android.widget.TextView; import com.oreilly.rxjava.ch8.rxandroid.MainThreadSubscription; import rx.Observable; import rx.Subscriber; import rx.functions.Func1; import static com.oreilly.rxjava.ch8.rxandroid.MainThreadSubscription.verifyMainThread; final class TextViewEditorActionOnSubscribe implements Observable.OnSubscribe { final TextView view; final Func1 handled; TextViewEditorActionOnSubscribe(TextView view, Func1 handled) { this.view = view; this.handled = handled; } @Override public void call(final Subscriber subscriber) { verifyMainThread(); TextView.OnEditorActionListener listener = new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { if (handled.call(actionId)) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(actionId); } return true; } return false; } }; subscriber.add(new MainThreadSubscription() { @Override protected void onUnsubscribe() { view.setOnEditorActionListener(null); } }); view.setOnEditorActionListener(listener); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/TextViewTextChangeEvent.java ================================================ package com.oreilly.rxjava.ch8.rxbinding; import android.content.Context; import android.widget.TextView; import javax.annotation.Nonnull; /** * A text-change event on a view. *

* Warning: Instances keep a strong reference to the view. Operators that cache * instances have the potential to leak the associated {@link Context}. */ public final class TextViewTextChangeEvent extends ViewEvent { @Nonnull public static TextViewTextChangeEvent create(@Nonnull TextView view, @Nonnull CharSequence text, int start, int before, int count) { return new TextViewTextChangeEvent(view, text, start, before, count); } private final CharSequence text; private final int start; private final int before; private final int count; private TextViewTextChangeEvent(@Nonnull TextView view, @Nonnull CharSequence text, int start, int before, int count) { super(view); this.text = text; this.start = start; this.before = before; this.count = count; } @Nonnull public CharSequence text() { return text; } public int start() { return start; } public int before() { return before; } public int count() { return count; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof TextViewTextChangeEvent)) return false; TextViewTextChangeEvent other = (TextViewTextChangeEvent) o; return other.view() == view() && text.equals(other.text) && start == other.start && before == other.before && count == other.count; } @Override public int hashCode() { int result = 17; result = result * 37 + view().hashCode(); result = result * 37 + text.hashCode(); result = result * 37 + start; result = result * 37 + before; result = result * 37 + count; return result; } @Override public String toString() { return "TextViewTextChangeEvent{text=" + text + ", start=" + start + ", before=" + before + ", count=" + count + ", view=" + view() + '}'; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/TextViewTextChangeEventOnSubscribe.java ================================================ package com.oreilly.rxjava.ch8.rxbinding; import android.text.Editable; import android.text.TextWatcher; import android.widget.TextView; import com.oreilly.rxjava.ch8.rxandroid.MainThreadSubscription; import rx.Observable; import rx.Subscriber; import static com.oreilly.rxjava.ch8.rxandroid.MainThreadSubscription.verifyMainThread; final class TextViewTextChangeEventOnSubscribe implements Observable.OnSubscribe { final TextView view; TextViewTextChangeEventOnSubscribe(TextView view) { this.view = view; } @Override public void call(final Subscriber subscriber) { verifyMainThread(); final TextWatcher watcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(TextViewTextChangeEvent.create(view, s, start, before, count)); } } @Override public void afterTextChanged(Editable s) { } }; subscriber.add(new MainThreadSubscription() { @Override protected void onUnsubscribe() { view.removeTextChangedListener(watcher); } }); view.addTextChangedListener(watcher); // Emit initial value. subscriber.onNext(TextViewTextChangeEvent.create(view, view.getText(), 0, 0, 0)); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/TextViewTextOnSubscribe.java ================================================ package com.oreilly.rxjava.ch8.rxbinding; import android.text.Editable; import android.text.TextWatcher; import android.widget.TextView; import com.oreilly.rxjava.ch8.rxandroid.MainThreadSubscription; import rx.Observable; import rx.Subscriber; import static com.oreilly.rxjava.ch8.rxandroid.MainThreadSubscription.verifyMainThread; final class TextViewTextOnSubscribe implements Observable.OnSubscribe { final TextView view; TextViewTextOnSubscribe(TextView view) { this.view = view; } @Override public void call(final Subscriber subscriber) { verifyMainThread(); final TextWatcher watcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(s); } } @Override public void afterTextChanged(Editable s) { } }; subscriber.add(new MainThreadSubscription() { @Override protected void onUnsubscribe() { view.removeTextChangedListener(watcher); } }); view.addTextChangedListener(watcher); // Emit initial value. subscriber.onNext(view.getText()); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/ViewClickOnSubscribe.java ================================================ package com.oreilly.rxjava.ch8.rxbinding; import android.view.View; import com.oreilly.rxjava.ch8.rxandroid.MainThreadSubscription; import rx.Observable; import rx.Subscriber; import static com.oreilly.rxjava.ch8.rxandroid.MainThreadSubscription.verifyMainThread; final class ViewClickOnSubscribe implements Observable.OnSubscribe { final View view; ViewClickOnSubscribe(View view) { this.view = view; } @Override public void call(final Subscriber subscriber) { verifyMainThread(); View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View v) { if (!subscriber.isUnsubscribed()) { subscriber.onNext(null); } } }; subscriber.add(new MainThreadSubscription() { @Override protected void onUnsubscribe() { view.setOnClickListener(null); } }); view.setOnClickListener(listener); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/ViewEvent.java ================================================ package com.oreilly.rxjava.ch8.rxbinding; import android.content.Context; import android.view.View; import javax.annotation.Nonnull; import static com.oreilly.rxjava.ch8.rxbinding.internal.Preconditions.checkNotNull; /** * A target view on which an event occurred (e.g., click). *

* Warning: Instances keep a strong reference to the view. Operators that cache * instances have the potential to leak the associated {@link Context}. */ public abstract class ViewEvent { private final T view; protected ViewEvent(@Nonnull T view) { this.view = checkNotNull(view, "view == null"); } /** The view from which this event occurred. */ @Nonnull public T view() { return view; } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/internal/Functions.java ================================================ package com.oreilly.rxjava.ch8.rxbinding.internal; import rx.functions.Func0; import rx.functions.Func1; public final class Functions { private static final Always ALWAYS_TRUE = new Always<>(true); public static final Func0 FUNC0_ALWAYS_TRUE = ALWAYS_TRUE; public static final Func1 FUNC1_ALWAYS_TRUE = ALWAYS_TRUE; private static final class Always implements Func1, Func0 { private final T value; Always(T value) { this.value = value; } @Override public T call(Object o) { return value; } @Override public T call() { return value; } } private Functions() { throw new AssertionError("No instances."); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch8/rxbinding/internal/Preconditions.java ================================================ /* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.oreilly.rxjava.ch8.rxbinding.internal; public final class Preconditions { public static void checkArgument(boolean assertion, String message) { if (!assertion) { throw new IllegalArgumentException(message); } } public static T checkNotNull(T value, String message) { if (value == null) { throw new NullPointerException(message); } return value; } private Preconditions() { throw new AssertionError("No instances."); } } ================================================ FILE: src/test/java/com/oreilly/rxjava/ch9/Chapter9.java ================================================ package com.oreilly.rxjava.ch9; import org.junit.Ignore; @Ignore public class Chapter9 { //no samples in this chapter } ================================================ FILE: src/test/java/com/oreilly/rxjava/util/Sleeper.java ================================================ package com.oreilly.rxjava.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.time.Duration; import java.util.Random; import java.util.concurrent.TimeUnit; public class Sleeper { private static final Logger log = LoggerFactory.getLogger(Sleeper.class); public static final Random RAND = new Random(); public static void sleep(Duration duration, Duration stdDev) { double randMillis = Math.max(0, duration.toMillis() + RAND.nextGaussian() * stdDev.toMillis()); sleep(Duration.ofMillis((long) randMillis)); } public static void sleep(Duration duration) { try { TimeUnit.MILLISECONDS.sleep(duration.toMillis()); } catch (InterruptedException e) { log.warn("Sleep interrupted", e); } } }