From 7de957dfc63276d92fd9b76f4c9d7e8799112dad Mon Sep 17 00:00:00 2001 From: Piotr Bobinski Date: Tue, 12 Sep 2017 15:33:26 +0200 Subject: [PATCH 1/7] Forked repo, slight change of repo structure, add my stuff --- .gitignore | 1 + .idea/Intro-To-RxJava.iml | 9 + .idea/compiler.xml | 16 + .idea/kotlinc.xml | 7 + ...n__io_reactivex_rxjava2_rxjava_unknown.xml | 13 + .idea/libraries/Maven__junit_junit_4_12.xml | 13 + ...__org_codehaus_groovy_groovy_all_2_4_4.xml | 13 + .../Maven__org_hamcrest_hamcrest_core_1_3.xml | 13 + ...ockframework_spock_core_1_1_groovy_2_4.xml | 13 + .idea/misc.xml | 10 + .idea/modules.xml | 9 + .idea/uiDesigner.xml | 124 +++ .idea/vcs.xml | 6 + .idea/workspace.xml | 876 ++++++++++++++++++ Part 1 - Getting Started/1. Why Rx.md | 49 + Part 1 - Getting Started/2. Key types.md | 246 +++++ .../3. Lifetime management.md | 154 +++ .../1. Creating a sequence.md | 305 ++++++ .../2. Reducing a sequence.md | 444 +++++++++ Part 2 - Sequence Basics/3. Inspection.md | 342 +++++++ Part 2 - Sequence Basics/4. Aggregation.md | 570 ++++++++++++ .../5. Transformation of sequences.md | 490 ++++++++++ .../1. Side effects.md | 332 +++++++ .../2. Leaving the monad.md | 328 +++++++ .../3. Advanced error handling.md | 237 +++++ .../4. Combining sequences.md | 676 ++++++++++++++ .../5. Time-shifted sequences.md | 606 ++++++++++++ .../6. Hot and Cold observables.md | 367 ++++++++ .../7. Custom operators.md | 407 ++++++++ .../1. Scheduling and threading.md | 395 ++++++++ Part 4 - Concurrency/2. Testing Rx.md | 230 +++++ .../3. Sequences of coincidence.md | 296 ++++++ Part 4 - Concurrency/4. Backpressure.md | 356 +++++++ README.md | 17 + exercises/exercises.iml | 20 + exercises/pom.xml | 30 + exercises/src/main/java/main/Book.java | 7 + exercises/src/test/java/main/BookTest.groovy | 18 + exercises/target/classes/main/Book.class | Bin 0 -> 251 bytes .../target/test-classes/main/BookTest.class | Bin 0 -> 5218 bytes .../itrx/chapter1/AsyncSubjectExample.java | 90 ++ .../itrx/chapter1/BehaviorSubjectExample.java | 115 +++ .../itrx/chapter1/PublishSubjectExample.java | 66 ++ .../itrx/chapter1/ReplaySubjectExample.java | 132 +++ .../java/itrx/chapter1/RxContractExample.java | 99 ++ .../itrx/chapter1/UnsubscribingExample.java | 133 +++ .../chapter2/aggregation/CollectExample.java | 67 ++ .../chapter2/aggregation/CountExample.java | 140 +++ .../chapter2/aggregation/FirstExample.java | 170 ++++ .../chapter2/aggregation/GroupByExample.java | 85 ++ .../chapter2/aggregation/LastExample.java | 161 ++++ .../chapter2/aggregation/NestExample.java | 62 ++ .../chapter2/aggregation/ReduceExample.java | 126 +++ .../chapter2/aggregation/ScanExample.java | 147 +++ .../chapter2/aggregation/SingleExample.java | 134 +++ .../aggregation/ToCollectionExample.java | 128 +++ .../chapter2/aggregation/ToMapExample.java | 315 +++++++ .../itrx/chapter2/creating/FromExample.java | 152 +++ .../creating/FunctionalUnfoldsExample.java | 160 ++++ .../creating/ObservableFactoriesExample.java | 225 +++++ .../itrx/chapter2/inspection/AllExample.java | 228 +++++ .../chapter2/inspection/ContainsExample.java | 79 ++ .../inspection/DefaultIfEmptyExample.java | 100 ++ .../chapter2/inspection/ElementAtExample.java | 99 ++ .../chapter2/inspection/ExistsExample.java | 99 ++ .../chapter2/inspection/IsEmptyExample.java | 79 ++ .../inspection/SequenceEqualExample.java | 136 +++ .../chapter2/reducing/DistinctExample.java | 225 +++++ .../itrx/chapter2/reducing/FilterExample.java | 72 ++ .../itrx/chapter2/reducing/IgnoreExample.java | 68 ++ .../chapter2/reducing/TakeSkipExample.java | 395 ++++++++ .../transforming/CastTypeOfExample.java | 146 +++ .../transforming/ConcatMapExample.java | 81 ++ .../chapter2/transforming/FlatMapExample.java | 238 +++++ .../transforming/FlatMapIterableExample.java | 173 ++++ .../chapter2/transforming/MapExample.java | 117 +++ .../transforming/MaterializeExample.java | 95 ++ .../TimestampTimeIntervalExample.java | 131 +++ .../itrx/chapter3/combining/AmbExample.java | 92 ++ .../combining/CombineLatestExample.java | 93 ++ .../chapter3/combining/ConcatExample.java | 151 +++ .../combining/MergeDelayErrorExample.java | 145 +++ .../itrx/chapter3/combining/MergeExample.java | 121 +++ .../chapter3/combining/RepeatExample.java | 196 ++++ .../chapter3/combining/StartWithExample.java | 66 ++ .../chapter3/combining/SwitchMapExample.java | 80 ++ .../combining/SwitchOnNextExample.java | 87 ++ .../itrx/chapter3/combining/ZipExample.java | 246 +++++ .../itrx/chapter3/custom/ComposeExample.java | 126 +++ .../itrx/chapter3/custom/LiftExample.java | 102 ++ .../chapter3/custom/SerializeExample.java | 197 ++++ .../itrx/chapter3/error/ResumeExample.java | 243 +++++ .../itrx/chapter3/error/RetryExample.java | 79 ++ .../itrx/chapter3/error/RetryWhenExample.java | 88 ++ .../itrx/chapter3/error/UsingExample.java | 105 +++ .../chapter3/hotandcold/CacheExample.java | 136 +++ .../itrx/chapter3/hotandcold/ColdExample.java | 93 ++ .../ConnectableObservableExample.java | 273 ++++++ .../chapter3/hotandcold/MulticastExample.java | 123 +++ .../chapter3/hotandcold/ReplayExample.java | 161 ++++ .../leaving/FirstLastSingleExample.java | 98 ++ .../itrx/chapter3/leaving/ForEachExample.java | 158 ++++ .../itrx/chapter3/leaving/FutureExample.java | 68 ++ .../chapter3/leaving/IterablesExample.java | 345 +++++++ .../sideeffects/AsObservableExample.java | 198 ++++ .../chapter3/sideeffects/DoOnExample.java | 193 ++++ .../sideeffects/MutablePipelineExample.java | 78 ++ .../sideeffects/SideEffectExample.java | 198 ++++ .../chapter3/timeshifted/BufferExample.java | 283 ++++++ .../chapter3/timeshifted/DebounceExample.java | 144 +++ .../chapter3/timeshifted/DelayExample.java | 137 +++ .../chapter3/timeshifted/SampleExample.java | 68 ++ .../timeshifted/TakeLastBufferExample.java | 119 +++ .../chapter3/timeshifted/ThrottleExample.java | 91 ++ .../chapter3/timeshifted/TimeoutExample.java | 203 ++++ .../backpressure/ConsumerSideExample.java | 128 +++ .../ControlledPullSubscriber.java | 84 ++ .../backpressure/NoBackpressureExample.java | 130 +++ .../backpressure/OnBackpressureExample.java | 153 +++ .../backpressure/OnRequestExample.java | 140 +++ .../backpressure/ReactivePullExample.java | 77 ++ .../coincidence/GroupJoinExample.java | 164 ++++ .../chapter4/coincidence/JoinExample.java | 195 ++++ .../chapter4/coincidence/WindowExample.java | 184 ++++ .../chapter4/scheduling/ObserveOnExample.java | 116 +++ .../chapter4/scheduling/SchedulerExample.java | 177 ++++ .../scheduling/SchedulersExample.java | 158 ++++ .../scheduling/SingleThreadedExample.java | 93 ++ .../scheduling/SubscribeOnExample.java | 165 ++++ .../scheduling/UnsubscribeOnExample.java | 93 ++ .../itrx/chapter4/testing/ExampleExample.java | 53 ++ .../testing/TestSchedulerExample.java | 248 +++++ .../testing/TestSubscriberExample.java | 55 ++ 133 files changed, 21110 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/Intro-To-RxJava.iml create mode 100644 .idea/compiler.xml create mode 100644 .idea/kotlinc.xml create mode 100644 .idea/libraries/Maven__io_reactivex_rxjava2_rxjava_unknown.xml create mode 100644 .idea/libraries/Maven__junit_junit_4_12.xml create mode 100644 .idea/libraries/Maven__org_codehaus_groovy_groovy_all_2_4_4.xml create mode 100644 .idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml create mode 100644 .idea/libraries/Maven__org_spockframework_spock_core_1_1_groovy_2_4.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/uiDesigner.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 Part 1 - Getting Started/1. Why Rx.md create mode 100644 Part 1 - Getting Started/2. Key types.md create mode 100644 Part 1 - Getting Started/3. Lifetime management.md create mode 100644 Part 2 - Sequence Basics/1. Creating a sequence.md create mode 100644 Part 2 - Sequence Basics/2. Reducing a sequence.md create mode 100644 Part 2 - Sequence Basics/3. Inspection.md create mode 100644 Part 2 - Sequence Basics/4. Aggregation.md create mode 100644 Part 2 - Sequence Basics/5. Transformation of sequences.md create mode 100644 Part 3 - Taming the sequence/1. Side effects.md create mode 100644 Part 3 - Taming the sequence/2. Leaving the monad.md create mode 100644 Part 3 - Taming the sequence/3. Advanced error handling.md create mode 100644 Part 3 - Taming the sequence/4. Combining sequences.md create mode 100644 Part 3 - Taming the sequence/5. Time-shifted sequences.md create mode 100644 Part 3 - Taming the sequence/6. Hot and Cold observables.md create mode 100644 Part 3 - Taming the sequence/7. Custom operators.md create mode 100644 Part 4 - Concurrency/1. Scheduling and threading.md create mode 100644 Part 4 - Concurrency/2. Testing Rx.md create mode 100644 Part 4 - Concurrency/3. Sequences of coincidence.md create mode 100644 Part 4 - Concurrency/4. Backpressure.md create mode 100644 README.md create mode 100644 exercises/exercises.iml create mode 100644 exercises/pom.xml create mode 100644 exercises/src/main/java/main/Book.java create mode 100644 exercises/src/test/java/main/BookTest.groovy create mode 100644 exercises/target/classes/main/Book.class create mode 100644 exercises/target/test-classes/main/BookTest.class create mode 100644 tests/java/itrx/chapter1/AsyncSubjectExample.java create mode 100644 tests/java/itrx/chapter1/BehaviorSubjectExample.java create mode 100644 tests/java/itrx/chapter1/PublishSubjectExample.java create mode 100644 tests/java/itrx/chapter1/ReplaySubjectExample.java create mode 100644 tests/java/itrx/chapter1/RxContractExample.java create mode 100644 tests/java/itrx/chapter1/UnsubscribingExample.java create mode 100644 tests/java/itrx/chapter2/aggregation/CollectExample.java create mode 100644 tests/java/itrx/chapter2/aggregation/CountExample.java create mode 100644 tests/java/itrx/chapter2/aggregation/FirstExample.java create mode 100644 tests/java/itrx/chapter2/aggregation/GroupByExample.java create mode 100644 tests/java/itrx/chapter2/aggregation/LastExample.java create mode 100644 tests/java/itrx/chapter2/aggregation/NestExample.java create mode 100644 tests/java/itrx/chapter2/aggregation/ReduceExample.java create mode 100644 tests/java/itrx/chapter2/aggregation/ScanExample.java create mode 100644 tests/java/itrx/chapter2/aggregation/SingleExample.java create mode 100644 tests/java/itrx/chapter2/aggregation/ToCollectionExample.java create mode 100644 tests/java/itrx/chapter2/aggregation/ToMapExample.java create mode 100644 tests/java/itrx/chapter2/creating/FromExample.java create mode 100644 tests/java/itrx/chapter2/creating/FunctionalUnfoldsExample.java create mode 100644 tests/java/itrx/chapter2/creating/ObservableFactoriesExample.java create mode 100644 tests/java/itrx/chapter2/inspection/AllExample.java create mode 100644 tests/java/itrx/chapter2/inspection/ContainsExample.java create mode 100644 tests/java/itrx/chapter2/inspection/DefaultIfEmptyExample.java create mode 100644 tests/java/itrx/chapter2/inspection/ElementAtExample.java create mode 100644 tests/java/itrx/chapter2/inspection/ExistsExample.java create mode 100644 tests/java/itrx/chapter2/inspection/IsEmptyExample.java create mode 100644 tests/java/itrx/chapter2/inspection/SequenceEqualExample.java create mode 100644 tests/java/itrx/chapter2/reducing/DistinctExample.java create mode 100644 tests/java/itrx/chapter2/reducing/FilterExample.java create mode 100644 tests/java/itrx/chapter2/reducing/IgnoreExample.java create mode 100644 tests/java/itrx/chapter2/reducing/TakeSkipExample.java create mode 100644 tests/java/itrx/chapter2/transforming/CastTypeOfExample.java create mode 100644 tests/java/itrx/chapter2/transforming/ConcatMapExample.java create mode 100644 tests/java/itrx/chapter2/transforming/FlatMapExample.java create mode 100644 tests/java/itrx/chapter2/transforming/FlatMapIterableExample.java create mode 100644 tests/java/itrx/chapter2/transforming/MapExample.java create mode 100644 tests/java/itrx/chapter2/transforming/MaterializeExample.java create mode 100644 tests/java/itrx/chapter2/transforming/TimestampTimeIntervalExample.java create mode 100644 tests/java/itrx/chapter3/combining/AmbExample.java create mode 100644 tests/java/itrx/chapter3/combining/CombineLatestExample.java create mode 100644 tests/java/itrx/chapter3/combining/ConcatExample.java create mode 100644 tests/java/itrx/chapter3/combining/MergeDelayErrorExample.java create mode 100644 tests/java/itrx/chapter3/combining/MergeExample.java create mode 100644 tests/java/itrx/chapter3/combining/RepeatExample.java create mode 100644 tests/java/itrx/chapter3/combining/StartWithExample.java create mode 100644 tests/java/itrx/chapter3/combining/SwitchMapExample.java create mode 100644 tests/java/itrx/chapter3/combining/SwitchOnNextExample.java create mode 100644 tests/java/itrx/chapter3/combining/ZipExample.java create mode 100644 tests/java/itrx/chapter3/custom/ComposeExample.java create mode 100644 tests/java/itrx/chapter3/custom/LiftExample.java create mode 100644 tests/java/itrx/chapter3/custom/SerializeExample.java create mode 100644 tests/java/itrx/chapter3/error/ResumeExample.java create mode 100644 tests/java/itrx/chapter3/error/RetryExample.java create mode 100644 tests/java/itrx/chapter3/error/RetryWhenExample.java create mode 100644 tests/java/itrx/chapter3/error/UsingExample.java create mode 100644 tests/java/itrx/chapter3/hotandcold/CacheExample.java create mode 100644 tests/java/itrx/chapter3/hotandcold/ColdExample.java create mode 100644 tests/java/itrx/chapter3/hotandcold/ConnectableObservableExample.java create mode 100644 tests/java/itrx/chapter3/hotandcold/MulticastExample.java create mode 100644 tests/java/itrx/chapter3/hotandcold/ReplayExample.java create mode 100644 tests/java/itrx/chapter3/leaving/FirstLastSingleExample.java create mode 100644 tests/java/itrx/chapter3/leaving/ForEachExample.java create mode 100644 tests/java/itrx/chapter3/leaving/FutureExample.java create mode 100644 tests/java/itrx/chapter3/leaving/IterablesExample.java create mode 100644 tests/java/itrx/chapter3/sideeffects/AsObservableExample.java create mode 100644 tests/java/itrx/chapter3/sideeffects/DoOnExample.java create mode 100644 tests/java/itrx/chapter3/sideeffects/MutablePipelineExample.java create mode 100644 tests/java/itrx/chapter3/sideeffects/SideEffectExample.java create mode 100644 tests/java/itrx/chapter3/timeshifted/BufferExample.java create mode 100644 tests/java/itrx/chapter3/timeshifted/DebounceExample.java create mode 100644 tests/java/itrx/chapter3/timeshifted/DelayExample.java create mode 100644 tests/java/itrx/chapter3/timeshifted/SampleExample.java create mode 100644 tests/java/itrx/chapter3/timeshifted/TakeLastBufferExample.java create mode 100644 tests/java/itrx/chapter3/timeshifted/ThrottleExample.java create mode 100644 tests/java/itrx/chapter3/timeshifted/TimeoutExample.java create mode 100644 tests/java/itrx/chapter4/backpressure/ConsumerSideExample.java create mode 100644 tests/java/itrx/chapter4/backpressure/ControlledPullSubscriber.java create mode 100644 tests/java/itrx/chapter4/backpressure/NoBackpressureExample.java create mode 100644 tests/java/itrx/chapter4/backpressure/OnBackpressureExample.java create mode 100644 tests/java/itrx/chapter4/backpressure/OnRequestExample.java create mode 100644 tests/java/itrx/chapter4/backpressure/ReactivePullExample.java create mode 100644 tests/java/itrx/chapter4/coincidence/GroupJoinExample.java create mode 100644 tests/java/itrx/chapter4/coincidence/JoinExample.java create mode 100644 tests/java/itrx/chapter4/coincidence/WindowExample.java create mode 100644 tests/java/itrx/chapter4/scheduling/ObserveOnExample.java create mode 100644 tests/java/itrx/chapter4/scheduling/SchedulerExample.java create mode 100644 tests/java/itrx/chapter4/scheduling/SchedulersExample.java create mode 100644 tests/java/itrx/chapter4/scheduling/SingleThreadedExample.java create mode 100644 tests/java/itrx/chapter4/scheduling/SubscribeOnExample.java create mode 100644 tests/java/itrx/chapter4/scheduling/UnsubscribeOnExample.java create mode 100644 tests/java/itrx/chapter4/testing/ExampleExample.java create mode 100644 tests/java/itrx/chapter4/testing/TestSchedulerExample.java create mode 100644 tests/java/itrx/chapter4/testing/TestSubscriberExample.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bfa6a22 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +# Created by .ignore support plugin (hsz.mobi) diff --git a/.idea/Intro-To-RxJava.iml b/.idea/Intro-To-RxJava.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/Intro-To-RxJava.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..515f050 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..1c24f9a --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__io_reactivex_rxjava2_rxjava_unknown.xml b/.idea/libraries/Maven__io_reactivex_rxjava2_rxjava_unknown.xml new file mode 100644 index 0000000..7c6c45c --- /dev/null +++ b/.idea/libraries/Maven__io_reactivex_rxjava2_rxjava_unknown.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__junit_junit_4_12.xml b/.idea/libraries/Maven__junit_junit_4_12.xml new file mode 100644 index 0000000..d411041 --- /dev/null +++ b/.idea/libraries/Maven__junit_junit_4_12.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_codehaus_groovy_groovy_all_2_4_4.xml b/.idea/libraries/Maven__org_codehaus_groovy_groovy_all_2_4_4.xml new file mode 100644 index 0000000..d70f739 --- /dev/null +++ b/.idea/libraries/Maven__org_codehaus_groovy_groovy_all_2_4_4.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml b/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml new file mode 100644 index 0000000..f58bbc1 --- /dev/null +++ b/.idea/libraries/Maven__org_hamcrest_hamcrest_core_1_3.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_spockframework_spock_core_1_1_groovy_2_4.xml b/.idea/libraries/Maven__org_spockframework_spock_core_1_1_groovy_2_4.xml new file mode 100644 index 0000000..71f722b --- /dev/null +++ b/.idea/libraries/Maven__org_spockframework_spock_core_1_1_groovy_2_4.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..481671d --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..8168f9f --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..e96534f --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..0b21292 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,876 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1505222212074 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No facets are configured + + + + + + + + groovy-2.4.7 + + + + + + + + 1.7 + + + + + + + + exercises + + + + + + + + 1.8 + + + + + + + + Maven: io.reactivex.rxjava2:rxjava:unknown + + + + + + + + \ No newline at end of file diff --git a/Part 1 - Getting Started/1. Why Rx.md b/Part 1 - Getting Started/1. Why Rx.md new file mode 100644 index 0000000..b7874e2 --- /dev/null +++ b/Part 1 - Getting Started/1. Why Rx.md @@ -0,0 +1,49 @@ +# PART 1 - Getting started + +## Why Rx + +> Users expect real time data. They want their tweets now. Their order confirmed now. They need prices accurate as of now. Their online games need to be responsive. As a developer, you demand fire-and-forget messaging. You don't want to be blocked waiting for a result. You want to have the result pushed to you when it is ready. Even better, when working with result sets, you want to receive individual results as they are ready. You do not want to wait for the entire set to be processed before you see the first row. The world has moved to push; users are waiting for us to catch up. Developers have tools to push data, this is easy. Developers need tools to react to push data + +Welcome to Rx. This book is based on [Rx.NET](http://msdn.microsoft.com/en-us/devlabs/gg577609)'s www.introtorx.com and it introduces beginners to [RxJava](https://github.com/ReactiveX/RxJava), the Netflix implementation of the original Microsoft library. Rx is a powerful tool that enables the solution of problems in an elegant declarative style, familiar to functional programmers. Rx has several benefits: + +* Unitive + * Queries in Rx are done in the same style as other libraries inspired by functional programming, such as Java streams. In Rx, one can use functional style transformations on event streams. +* Extensible + * RxJava can be extended with custom operators. Although Java does not allow for this to happen in an elegant way, RxJava offers all the extensibility one can find Rx implementations in other languages. +* Declarative + * Functional transformations are read in a declarative way. +* Composable + * Rx operators can be combined to produce more complicated operations. +* Transformative + * Rx operators can transform one type of data to another, reducing, mapping or expanding streams as needed. + + +## When is Rx appropriate? + +Rx is fit for composing and consuming sequences of events. We present some of the use cases for Rx, according to www.introtorx.com + +### Should use Rx + +* UI events like mouse move, button click +* Domain events like property changed, collection updated, "Order Filled", "Registration accepted" etc. +* Infrastructure events like from file watcher, system and WMI events +* Integration events like a broadcast from a message bus or a push event from WebSockets API or other low latency middleware like Nirvana +* Integration with a CEP engine like StreamInsight or StreamBase. + +### Could use Rx + +* Result of `Future` or equivalent pattern + +Those patterns are already well adopted and you may find that introducing Rx on top of that does not add to the development process. + +### Won't use Rx + +* Translating iterables to observables, just for the sake of working on them through an Rx library. + + +##### Continue reading + +| Previous | Next | +| --- | --- | +| | [Key types](/Part%201%20-%20Getting%20Started/2.%20Key%20types.md) | + diff --git a/Part 1 - Getting Started/2. Key types.md b/Part 1 - Getting Started/2. Key types.md new file mode 100644 index 0000000..7519602 --- /dev/null +++ b/Part 1 - Getting Started/2. Key types.md @@ -0,0 +1,246 @@ +# Key types + +Rx is based around two fundamental types, while several others expand the functionality around the core types. Those two core types are the `Observable` and the `Observer`, which will be introduced in this chapter. We will also introduce `Subject`s, which ease the learning curve. + +Rx builds upon the [Observer](http://en.wikipedia.org/wiki/Observer_pattern) pattern. It is not unique in doing so. Event handling already exists in Java (e.g. JavaFX's EventHandler). Those are simpler approaches, which suffer in comparison to Rx: + +* Events through event handlers are hard to compose. +* They cannot be queried over time +* They can lead to memory leaks +* These is no standard way of signaling completion. +* Require manual handling of concurrency and multithreading. + +## Observable + +[Observable](http://reactivex.io/RxJava/javadoc/rx/Observable) is the first core element that we will see. This class contains a lot of the implementation of Rx, including all of the core operators. We will be examining it step by step throughout this book. For now, we must understand the `Subscribe` method. Here is one key overload of the method: + +```java +public final Subscription subscribe(Subscriber subscriber) +``` + +This is the method that you use to receive the values emitted by the observable. As the values come to be pushed (through policies that we will discuss throughout this book), they are pushed to the subscriber, which is then responsible for the behaviour intended by the consumer. The `Subscriber` here is an implementation of the `Observer` interface. + +An observable pushes 3 kinds of events +* Values +* Completion, which indicates that no more values will be pushed. +* Errors, if something caused the sequence to fail. These events also imply termination. + + +## Observer + +We already saw one abstract implementation of the [Observer](http://reactivex.io/RxJava/javadoc/rx/Observer.html), `Subscriber`. `Subscriber` implements some extra functionality and should be used as the basis for our implementations of `Observer`. For now, it is simpler to first understand the interface. + +```java +interface Observer { + void onCompleted(); + void onError(java.lang.Throwable e); + void onNext(T t); +} +``` + +Those three methods are the behaviour that is executed every time the observable pushes a value. The observer will have its `onNext` called zero or more times, optionally followed by an `onCompleted` or an `onError`. No calls happen after a call to `onError` or `onCompleted`. + +When developing Rx code, you'll see a lot of `Observable`, but not so much of `Observer`. While it is important to understand the `Observer`, there are shorthands that remove the need to instantiate it yourself. + + +## Implementing Observable and Observer + +You could manually implement `Observer` or extend `Observable`. In reality that will usually be unnecessary, since Rx already provides all the building blocks you need. It is also dangerous, as interaction between parts of Rx includes conventions and internal plumbing that are not obvious to a beginner. It is both simpler and safer to use the many tools that Rx gives you for generating the functionality that you need. + +To subscribe to an observable, it is not necessary to provide instances of `Observer` at all. There are overloads to `subscribe` that simply take the functions to be executed for `onNext`, `onError` and `onSubscribe`, hiding away the instantiation of the corresponding `Observer`. It is not even necessary to provide each of those functions. You can provide a subset of them, i.e. just `onNext` or just `onNext` and `onError`. + +The introduction of lambda functions in Java 1.8 makes these overloads very convenient for the short examples that exist in this book. + +## Subject + +Subjects are an extension of the `Observable` that also implements the `Observer` interface. The idea may sound odd at first, but they make things a lot simpler in some cases. They can have events pushed to them (like observers), which they then push further to their own subscribers (like observables). This makes them ideal entry points into Rx code: when you have values coming in from outside of Rx, you can push them into a `Subject`, turning them into an observable. You can think of them as entry points to an Rx pipeline. + +`Subject` has two parameter types: the input type and the output type. This was designed so for the sake of abstraction and not because the common uses for subjects involve transforming values. There are transformation operators to do that, which we will see later. + +There are a few different implementations of `Subject`. We will now examine the most important ones and their differences. + +### PublishSubject + +`PublishSubject` is the most straight-forward kind of subject. When a value is pushed into a `PublishSubject`, the subject pushes it to every subscriber that is subscribed to it at that moment. + +```java +public static void main(String[] args) { + PublishSubject subject = PublishSubject.create(); + subject.onNext(1); + subject.subscribe(System.out::println); + subject.onNext(2); + subject.onNext(3); + subject.onNext(4); +} +``` +[Output](/tests/java/itrx/chapter1/PublishSubjectExample.java) +``` +2 +3 +4 +``` + +As we can see in the example, `1` isn't printed because we weren't subscribed when it was pushed. After we subscribed, we began receiving the values that were pushed to the subject. + +This is the first time we see `subscribe` being used, so it is worth paying attention to how it was used. In this case, we used the overload which expects one [Function](http://reactivex.io/RxJava/javadoc/rx/functions/Function.html) for the case of onNext. That function takes an argument of type `Integer` and returns nothing. Functions without a return type are also called actions. We can provide that function in different ways: +* we can supply an instance of `Action1`, +* implicitly create one using a [lambda expression](http://en.wikipedia.org/wiki/Anonymous_function#Java) or +* pass a reference to an existing method that fits the signature. +In this case, `System.out::println` has an overload that accepts `Object`, so we passed a reference to it. `subscribe` will call `println` with the arriving values as the argument. + +### ReplaySubject + +`ReplaySubject` has the special feature of caching all the values pushed to it. When a new subscription is made, the event sequence is replayed from the start for the new subscriber. After catching up, every subscriber receives new events as they come. + +```java +ReplaySubject s = ReplaySubject.create(); +s.subscribe(v -> System.out.println("Early:" + v)); +s.onNext(0); +s.onNext(1); +s.subscribe(v -> System.out.println("Late: " + v)); +s.onNext(2); +``` +[Output](/tests/java/itrx/chapter1/ReplaySubjectExample.java) +``` +Early:0 +Early:1 +Late: 0 +Late: 1 +Early:2 +Late: 2 +``` + +All the values are received by the subscribers, even though one was late. Also notice that the late subscriber had everything replayed to it before proceeding to the next value. + +Caching everything isn't always a good idea, as an observable sequence can run for a long time. There are ways to limit the size of the internal buffer. `ReplaySubject.createWithSize` limits the size of the buffer, while `ReplaySubject.createWithTime` limits how long an object can stay cached. + +```java +ReplaySubject s = ReplaySubject.createWithSize(2); +s.onNext(0); +s.onNext(1); +s.onNext(2); +s.subscribe(v -> System.out.println("Late: " + v)); +s.onNext(3); +``` +[Output](/tests/java/itrx/chapter1/ReplaySubjectExample.java) +``` +Late: 1 +Late: 2 +Late: 3 +``` + +Our late subscriber now missed the first value, which fell off the buffer of size 2. Similarily, old values fall off the buffer as time passes, when the subject is created with `createWithTime` + +```java +ReplaySubject s = ReplaySubject.createWithTime(150, TimeUnit.MILLISECONDS, + Schedulers.immediate()); +s.onNext(0); +Thread.sleep(100); +s.onNext(1); +Thread.sleep(100); +s.onNext(2); +s.subscribe(v -> System.out.println("Late: " + v)); +s.onNext(3); +``` +[Output](/tests/java/itrx/chapter1/ReplaySubjectExample.java) +``` +Late: 1 +Late: 2 +Late: 3 +``` + +Creating a `ReplaySubject` with time requires a `Scheduler`, which is Rx's way of keeping time. Feel free to ignore this for now, as we will properly introduce schedulers in the chapter about concurrency. + +`ReplaySubject.createWithTimeAndSize` limits both, which ever comes first. + +### BehaviorSubject + +`BehaviorSubject` only remembers the last value. It is similar to a `ReplaySubject` with a buffer of size 1. An initial value can be provided on creation, therefore guaranteeing that a value always will be available immediately on subscription. + +```java +BehaviorSubject s = BehaviorSubject.create(); +s.onNext(0); +s.onNext(1); +s.onNext(2); +s.subscribe(v -> System.out.println("Late: " + v)); +s.onNext(3); +``` +[Output](/tests/java/itrx/chapter1/BehaviorSubjectExample.java) +``` +Late: 2 +Late: 3 +``` + +The following example just completes, since that is the last event. + +```java +BehaviorSubject s = BehaviorSubject.create(); +s.onNext(0); +s.onNext(1); +s.onNext(2); +s.onCompleted(); +s.subscribe( + v -> System.out.println("Late: " + v), + e -> System.out.println("Error"), + () -> System.out.println("Completed") +); +``` + +An initial value is provided to be available if anyone subscribes before the first value is pushed. + +```java +BehaviorSubject s = BehaviorSubject.create(0); +s.subscribe(v -> System.out.println(v)); +s.onNext(1); +``` +[Output](/tests/java/itrx/chapter1/BehaviorSubjectExample.java) +``` +0 +1 +``` + +Since the defining role of a `BehaviorSubject` is to always have a value readily available, it is unusual to create one without an initial value. It is also unusual to terminate one. + +### AsyncSubject + +`AsyncSubject` also caches the last value. The difference now is that it doesn't emit anything until the sequence completes. Its use is to emit a single value and immediately complete. + +```java +AsyncSubject s = AsyncSubject.create(); +s.subscribe(v -> System.out.println(v)); +s.onNext(0); +s.onNext(1); +s.onNext(2); +s.onCompleted(); +``` +[Output](/tests/java/itrx/chapter1/AsyncSubjectExample.java) +``` +2 +``` + +Note that, if we didn't do `s.onCompleted();`, this example would have printed nothing. + +## Implicit contracts + +As we already mentioned, there are contracts in Rx that are not obvious in the code. An important one is that no events are emitted after a termination event (`onError` or `onCompleted`). The implemented subjects respect that, and the `subscribe` method also prevents some violations of the contract. + +```java +Subject s = ReplaySubject.create(); +s.subscribe(v -> System.out.println(v)); +s.onNext(0); +s.onCompleted(); +s.onNext(1); +s.onNext(2); +``` +[Output](/tests/java/itrx/chapter1/RxContractExample.java) +``` +0 +``` + +Safety nets like these are not guaranteed in the entirety of the implementation of Rx. It is best that you are mindful not to violate the contract, as this may lead to undefined behaviour. + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Why Rx](/Part%201%20-%20Getting%20Started/1.%20Why%20Rx.md) | [Lifetime management](/Part%201%20-%20Getting%20Started/3.%20Lifetime%20management.md) | diff --git a/Part 1 - Getting Started/3. Lifetime management.md b/Part 1 - Getting Started/3. Lifetime management.md new file mode 100644 index 0000000..2d1c4fe --- /dev/null +++ b/Part 1 - Getting Started/3. Lifetime management.md @@ -0,0 +1,154 @@ +# Lifetime management + +The idea behind Rx is that it is unknown *when* a sequence emits values or terminates, but you still have control over when you begin and stop accepting values. Subscriptions may be linked to allocated resources that you will want to release at the end of a sequence. Rx provides control over your subscriptions to enable you to do that. + +## Subscribing + +There are several overloads to `Observable.subscribe`, which are shorthands for the same thing. + +```java +Subscription subscribe() +Subscription subscribe(Action1 onNext) +Subscription subscribe(Action1 onNext, Action1 onError) +Subscription subscribe(Action1 onNext, Action1 onError, Action0 onComplete) +Subscription subscribe(Observer observer) +Subscription subscribe(Subscriber subscriber) +``` + +`subscribe()` consumes events but performs no actions. The overloads that take one or more `Action` will construct a `Subscriber` with the functions that you provide. Where you don't give an action, the event is practically ignored. + +In the following example, we handle the error of a sequence that failed. + +```java +Subject s = ReplaySubject.create(); +s.subscribe( + v -> System.out.println(v), + e -> System.err.println(e)); +s.onNext(0); +s.onError(new Exception("Oops")); +``` + +Output +``` +0 +java.lang.Exception: Oops +``` + +If we do not provide a function for error handling, an `OnErrorNotImplementedException` will be *thrown* at the point where `s.onError` is called, which is the producer's side. It happens here that the producer and the consumer are side-by-side, so we could do a traditional try-catch. However, on a compartmentalised system, the producer and the subscriber very often are in different places. Unless the consumer provides a handle for errors to `subscribe`, they will never know that an error has occured and that the sequence was terminated. + +## Unsubscribing + +You can also stop receiving values *before* a sequence terminates. Every `subscribe` overload returns an instance of `Subscription`, which is an interface with 2 methods: + +```java +boolean isUnsubscribed() +void unsubscribe() +``` + +Calling `unsubscribe` will stop events from being pushed to your observer. + +```java +Subject values = ReplaySubject.create(); +Subscription subscription = values.subscribe( + v -> System.out.println(v), + e -> System.err.println(e), + () -> System.out.println("Done") +); +values.onNext(0); +values.onNext(1); +subscription.unsubscribe(); +values.onNext(2); +``` +[Output](/tests/java/itrx/chapter1/UnsubscribingExample.java) +``` +0 +1 +``` + +Unsubscribing one observer does not interfere with other observers on the same observable. + +```java +Subject values = ReplaySubject.create(); +Subscription subscription1 = values.subscribe( + v -> System.out.println("First: " + v) +); +Subscription subscription2 = values.subscribe( + v -> System.out.println("Second: " + v) +); +values.onNext(0); +values.onNext(1); +subscription1.unsubscribe(); +System.out.println("Unsubscribed first"); +values.onNext(2); +``` +[Output](/tests/java/itrx/chapter1/UnsubscribingExample.java) +``` +First: 0 +Second: 0 +First: 1 +Second: 1 +Unsubscribed first +Second: 2 +``` + +## onError and onCompleted + +`onError` and `onCompleted` mean the termination of a sequence. An observable that complies with the Rx contract will not emit anything after either of those events. This is something to note both when consuming in Rx and when implementing your own observables. + +```java +Subject values = ReplaySubject.create(); +Subscription subscription1 = values.subscribe( + v -> System.out.println("First: " + v), + e -> System.out.println("First: " + e), + () -> System.out.println("Completed") +); +values.onNext(0); +values.onNext(1); +values.onCompleted(); +values.onNext(2); +``` +[Output](/tests/java/itrx/chapter1/RxContractExample.java) +``` +First: 0 +First: 1 +Completed +``` + +## Freeing resources + +A `Subscription` is tied to the resources it uses. For that reason, you should remember to dispose of subscriptions. You can create the binding between a `Subscription` and the necessary resources using the [Subscriptions](http://reactivex.io/RxJava/javadoc/rx/subscriptions/Subscriptions.html) factory. + +```java +Subscription s = Subscriptions.create(() -> System.out.println("Clean")); +s.unsubscribe(); +``` +[Output](/tests/java/itrx/chapter1/UnsubscribingExample.java) +``` +Clean +``` + +`Subscriptions.create` takes an action that will be executed on unsubscription to release the resources. There also are shorthand for common actions when creating a sequence. +* `Subscriptions.empty()` returns a `Subscription` that does nothing when disposed. This is useful when you are required to return an instance of `Subscription`, but your implementation doesn't actually need to release any resources. +* `Subscriptions.from(Subscription... subscriptions)` returns a `Subscription` that will dispose of multiple other subscriptions when it is disposed. +* `Subscriptions.unsubscribed()` returns a `Subscription` that is already disposed of. + +There are several implementations of `Subscription`. + +* `BooleanSubscription` +* `CompositeSubscription` +* `MultipleAssignmentSubscription` +* `RefCountSubscription` +* `SafeSubscriber` +* `Scheduler.Worker` +* `SerializedSubscriber` +* `SerialSubscription` +* `Subscriber` +* `TestSubscriber` + +We will see more of them later in this book. It is interesting to note that `Subscriber` also implements `Subscription`. This means that we can also use a reference to a `Subscriber` to terminate a subscription. + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Key types](/Part%201%20-%20Getting%20Started/2.%20Key%20types.md) | [Chapter 2](/Part%202%20-%20Sequence%20Basics/1.%20Creating%20a%20sequence.md) | diff --git a/Part 2 - Sequence Basics/1. Creating a sequence.md b/Part 2 - Sequence Basics/1. Creating a sequence.md new file mode 100644 index 0000000..a71693e --- /dev/null +++ b/Part 2 - Sequence Basics/1. Creating a sequence.md @@ -0,0 +1,305 @@ +# PART 2 - Sequence basics + +Now that you understand what Rx is in general, it is time to start creating and manipulating sequences. The original implementation of manipulating sequences was based on C#'s [LINQ](https://en.wikipedia.org/wiki/Language_Integrated_Query), which in turn was inspired from functional programming. Knowledge about either isn't necessary, but it would make the learning process a lot easier for the reader. Following the original www.introtorx.com, we too will divide operations into themes that generally go from the simpler to the more advanced. Most Rx operators manipulate existing sequences. But first, we will see how to create an `Observable` to begin with. + +# Creating a sequence + +In previous examples we used `Subject`s and manually pushed values into them to create a sequence. We used that sequence to demonstrate some key concepts and the first and most important Rx method, `subscribe`. In most cases, subjects are not the best way to create a new `Observable`. We will now see tidier ways to create observable sequences. + +## Simple factory methods + +### Observable.just + +The `just` method creates an `Observable` that will emit a predifined sequence of values, supplied on creation, and then terminate. + +```java +Observable values = Observable.just("one", "two", "three"); +Subscription subscription = values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") +); +``` +[Output](/tests/java/itrx/chapter2/creating/ObservableFactoriesExample.java) +``` +Received: one +Received: two +Received: three +Completed +``` + +### Observable.empty + +This observable will emit a single onCompleted and nothing else. + +```java +Observable values = Observable.empty(); +Subscription subscription = values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") +); +``` +[Output](/tests/java/itrx/chapter2/creating/ObservableFactoriesExample.java) +``` +Completed +``` + +### Observable.never + +This observable will never emit anything + +```java +Observable values = Observable.never(); +Subscription subscription = values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") +); +``` + +The [code above](/tests/java/itrx/chapter2/creating/ObservableFactoriesExample.java) will print nothing. Note that this doesn't mean that the program is blocking. In fact, it will terminate immediately. + +### Observable.error + +This observable will emit a single error event and terminate. + +```java +Observable values = Observable.error(new Exception("Oops")); +Subscription subscription = values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") +); +``` +[Output](/tests/java/itrx/chapter2/creating/ObservableFactoriesExample.java) +``` +Error: java.lang.Exception: Oops +``` + +### Observable.defer + +`defer` doesn't define a new kind of observable, but allows you to declare how an observable should be created every time a subscriber arrives. Consider how you would create an observable that returns the current time and terminates. You are emitting a single value, so it sounds like a case for `just`. + +```java +Observable now = Observable.just(System.currentTimeMillis()); + +now.subscribe(System.out::println); +Thread.sleep(1000); +now.subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter2/creating/ObservableFactoriesExample.java) +``` +1431443908375 +1431443908375 +``` + +Notice how the two subscribers, 1 second apart, see the same time. That is because the value for the time is aquired once: when execution reaches `just`. What you want is for the time to be aquired when a subscriber asks for it by subscribing. `defer` takes a function that will executed to create and return the `Observable`. The `Observable` returned by the function is also the `Observable` returned by `defer`. The important thing here is that this function will be executed again for every new subscription. + +```java +Observable now = Observable.defer(() -> + Observable.just(System.currentTimeMillis())); + +now.subscribe(System.out::println); +Thread.sleep(1000); +now.subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter2/creating/ObservableFactoriesExample.java) +``` +1431444107854 +1431444108858 +``` + +### Observable.create + +`create` is a very powerful function for creating observables. Let have a look at the signature. + +```java +static Observable create(Observable.OnSubscribe f) +``` + +The `Observable.OnSubscribe` is simpler than it looks. It is basically a function that takes a `Subscriber` for type `T`. Inside it we can manually determine the events that are pushed to the subscriber. + +```java +Observable values = Observable.create(o -> { + o.onNext("Hello"); + o.onCompleted(); +}); +Subscription subscription = values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") +); +``` +[Output](/tests/java/itrx/chapter2/creating/ObservableFactoriesExample.java) +``` +Received: Hello +Completed +``` + +When someone subscribes to the observable (here: `values`), the corresponding `Subscriber` instance is passed to your function. As the code is executed, values are being pushed to the subscriber. Note that you have to call `onCompleted` in the end by yourself, if you want the sequence to signal its completion. + +This method should be your preferred way of creating a custom observable, when none of the existing shorthands serve your purpose. The code is similar to how we created a `Subject` and pushed values to it, but there are a few important differences. First of all, the source of the events is neatly encapsulated and separated from unrelated code. Secondly, `Subject`s carry dangers that are not obvious: with a `Subject` you are managing state, and anyone with access to the instance can push values into it and alter the sequence. We will see more about this issue later on. + +Another key difference to using subjects is that the code is executed lazily, when and if an observer subscribes. In the example above, the code is run _not_ when the observable is created (because there is no `Subscriber` yet), but each time `subscribe` is called. This means that every value is generated again for each subscriber, similar to `ReplaySubject`. The end result is similar to a `ReplaySubject`, except that no caching takes place. However, if we had used a `ReplaySubject`, and the creation method was time-consuming, that would block the thread that executes the creation. You'd have to manually create a new thread to push values into the `Subject`. We're not presenting Rx's methods for concurrency yet, but there are convenient ways to make the execution of the `onSubscribe` function concurrently. + +You may have already noticed that you can trivially implement any of the previous observables using `Observable.create`. In fact, our example for `create` is equivalent to `Observable.just("hello")`. + +## Functional unfolds + +In functional programming it is common to create sequences of unrestricted or infinite length. RxJava has factory methods that create such sequences. + +### Observable.range + +A straight forward and familiar method to any functional programmer. It emits the specified range of integers. + +```java +Observable values = Observable.range(10, 15); +``` + +The [example](/tests/java/itrx/chapter2/creating/FunctionalUnfoldsExample.java) emits the values from 10 to 24 in sequence. + +### Observable.interval + +This function will create an _infinite_ sequence of ticks, separated by the specified time duration. + +```java +Observable values = Observable.interval(1000, TimeUnit.MILLISECONDS); +Subscription subscription = values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") +); +System.in.read(); +``` +[Output](/tests/java/itrx/chapter2/creating/FunctionalUnfoldsExample.java) +``` +Received: 0 +Received: 1 +Received: 2 +Received: 3 +... +``` + +This sequence will not terminate until we unsubscribe. + +We should note why the blocking read at the end is necessary. Without it, the program terminates without printing something. That's because our operations are non-blocking: we create an observable that will emit values _over time_, then we register the actions to execute if and when values arrive. None of that is blocking and the main thread proceeds to terminate. The timer that produces the ticks runs on its own thread, which does not prevent the JVM from terminating, killing the timer with it. + +### Observable.timer + +There are two overloads to `Observable.timer`. The first example creates an observable that waits a given amount of time, then emits `0L` and terminates. + +```java +Observable values = Observable.timer(1, TimeUnit.SECONDS); +Subscription subscription = values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") +); +``` +[Output](/tests/java/itrx/chapter2/creating/FunctionalUnfoldsExample.java) +``` +Received: 0 +Completed +``` + +The second one will wait a specified amount of time, then begin emitting like `interval` with the given frequency. + +```java +Observable values = Observable.timer(2, 1, TimeUnit.SECONDS); +Subscription subscription = values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") +); +``` +[Output](/tests/java/itrx/chapter2/creating/FunctionalUnfoldsExample.java) +``` +Received: 0 +Received: 1 +Received: 2 +... +``` + +The example above waits 2 seconds, then starts counting every 1 second. + + +## Transitioning into Observable + +There are well established tools for dealing with sequences, collections and asychronous events, which may not be directly compatible with Rx. Here we will discuss ways to turn their output into input for your Rx code. + +If you are using an asynchronous tool that uses event handlers, like JavaFX, you can use `Observable.create` to turn the streams into an observable + +```java +Observable events = Observable.create(o -> { + button2.setOnAction(new EventHandler() { + @Override public void handle(ActionEvent e) { + o.onNext(e) + } + }); +}) +``` + +Depending on what the event is, the event type (here `ActionEvent`) may be meaningful enough to be the type of your sequence. Very often you will want something else, like the contents of a field. The place to get the value is in the handler, while the GUI thread is blocked by the handler and the field value is relevant. There is no guarantee what the value will be by the time the value reaches the final `Subscriber`. On the other hand, a value moving though an observable should remain unchanged, if the pipeline is properly implemented. + +## Observable.from + +Much like most of the functions we've seen so far, you can turn any kind of input into an Rx observable with `create`. There are several shorthands for converting common types of input. + +`Future`s are part of the Java framework and you may come across them while using frameworks that use concurrency. They are a less powerful concept for concurrency than Rx, since they only return one value. Naturally, you may like to turn them into observables. + +```java +FutureTask f = new FutureTask(() -> { + Thread.sleep(2000); + return 21; +}); +new Thread(f).start(); + +Observable values = Observable.from(f); + +Subscription subscription = values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") +); +``` +[Output](/tests/java/itrx/chapter2/creating/FromExample.java) +``` +Received: 21 +Completed +``` + +The observable emits the result of the `FutureTask` when it is available and then terminates. If the task is canceled, the observable will emit a `java.util.concurrent.CancellationException` error. + +If you're interested in the results of the `Future` for a limited amount of time, you can provide a timeout period like this +```java +Observable values = Observable.from(f, 1000, TimeUnit.MILLISECONDS); +``` +If the `Future` has not completed in the specified amount of time, the observable will ignore it and fail with a `TimeoutException`. + +You can also turn any collection into an observable using the overloads of `Observable.from` that take arrays and iterables. This will result in every item in the collection being emitted and then a final onCompleted event. + +```java +Integer[] is = {1,2,3}; +Observable values = Observable.from(is); +Subscription subscription = values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") +); +``` +[Output](/tests/java/itrx/chapter2/creating/FromExample.java) +``` +Received: 1 +Received: 2 +Received: 3 +Completed +``` + +`Observable` is not interchangeable with `Iterable` or `Stream`. `Observable`s are push-based, i.e., the call to `onNext` causes the stack of handlers to execute all the way to the final subscriber method (unless specified otherwise). The other models are pull-based, which means that values are requested as soon as possible and execution blocks until the result is returned. + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Lifetime management](/Part%201%20-%20Getting%20Started/3.%20Lifetime%20management.md) | [Reducing a sequence](/Part%202%20-%20Sequence%20Basics/2.%20Reducing%20a%20sequence.md) | diff --git a/Part 2 - Sequence Basics/2. Reducing a sequence.md b/Part 2 - Sequence Basics/2. Reducing a sequence.md new file mode 100644 index 0000000..e22c0b0 --- /dev/null +++ b/Part 2 - Sequence Basics/2. Reducing a sequence.md @@ -0,0 +1,444 @@ +# Reducing a sequence + +The examples we've seen so far were all very small. Nothing should stop you from using Rx on a huge stream of realtime data, but what good would Rx be if it dumped the whole bulk of the data onto you, and force you handle it like you would otherwise? Here we will explore operators that can filter out irrelevant data, or reduce the data to the single value that you want. + +Most of the operators here will be familiar to anyone who has worked with Java's `Stream`s or functional programming in general. All the operators here return a new observable and do _not_ affect the original observable. This principle is present throughout Rx. Transformations of observables create a new observable every time and leave the original unaffected. Subscribers to the original observable should notice no change, but we will see in later chapters that guaranteeing this may require caution from the developer as well. + +### Marble diagrams + +This is an appropriate time to introduce to concept of marble diagrams. It is a popular way of explaining the operators in Rx, because of their intuitive and graphical nature. They are present a lot in the documentation of RxJava and it only makes sense that we take advantage of their explanatory nature. The format is mostly self-explanatory: time flows left to right, shapes represent values, a slash is an onCompletion, an X is an error. The operator is applied to the top sequence and the result is the sequence below. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/legend.png) + +## Filter + +`filter` takes a predicate function that makes a boolean decision for each value emitted. If the decision is `false`, the item is omitted from the filtered sequence. + +```java +public final Observable filter(Func1 predicate) +``` + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/filter.png) + +We will use `filter` to create a sequence of numbers and filter out all the even ones, keeping only odd values. + +```java +Observable values = Observable.range(0,10); +Subscription oddNumbers = values + .filter(v -> v % 2 == 0) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` + +[Output](/tests/java/itrx/chapter2/reducing/FilterExample.java) +``` +0 +2 +4 +6 +8 +Completed +``` + +## distinct and distinctUntilChanged + +`distinct` filters out any element that has already appeared in the sequence. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/distinct.png) + +```java +Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(1); + o.onNext(2); + o.onNext(3); + o.onNext(2); + o.onCompleted(); +}); + +Subscription subscription = values + .distinct() + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` +[Output](/tests/java/itrx/chapter2/reducing/DistinctExample.java) +``` +1 +2 +3 +Completed +``` + +An overload of distinct takes a key selector. For each item, the function generates a key and the key is then used to determine distinctiveness. + +```java +public final Observable distinct(Func1 keySelector) +``` + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/distinct.key.png) + +In this example, we use the first character as a key. + +```java +Observable values = Observable.create(o -> { + o.onNext("First"); + o.onNext("Second"); + o.onNext("Third"); + o.onNext("Fourth"); + o.onNext("Fifth"); + o.onCompleted(); +}); + +Subscription subscription = values + .distinct(v -> v.charAt(0)) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` +[Output](/tests/java/itrx/chapter2/reducing/DistinctExample.java) +``` +First +Second +Third +Completed +``` + +"Fourth" and "Fifth" were filtered out because their first character is 'F' and that has already appeared in "First". + +An experienced programmer already knows that this operator maintains a set internally with every unique value that passes through the observable and checks every new value against it. While Rx operators neatly hide these things, you should still be aware that an Rx operator can have a significant cost and consider what you are using it on. + +A variant of `distinct` is `distinctUntilChanged`. The difference is that only consecutive non-distinct values are filtered out. + +```java +public final Observable distinctUntilChanged() +public final Observable distinctUntilChanged(Func1 keySelector) +``` + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/distinctUntilChanged.png) + +```java +Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(1); + o.onNext(2); + o.onNext(3); + o.onNext(2); + o.onCompleted(); +}); + +Subscription subscription = values + .distinctUntilChanged() + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` +[Output](/tests/java/itrx/chapter2/reducing/DistinctExample.java) +``` +1 +2 +3 +2 +Completed +``` + +You can you use a key selector with `distinctUntilChanged`, as well. + +```java +Observable values = Observable.create(o -> { + o.onNext("First"); + o.onNext("Second"); + o.onNext("Third"); + o.onNext("Fourth"); + o.onNext("Fifth"); + o.onCompleted(); +}); + +Subscription subscription = values + .distinctUntilChanged(v -> v.charAt(0)) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` +[Output](/tests/java/itrx/chapter2/reducing/DistinctExample.java) +``` +First +Second +Third +Fourth +Completed +``` + +## ignoreElements + +`ignoreElements` will ignore every value, but lets pass through `onCompleted` and `onError`. + +```java +Observable values = Observable.range(0, 10); + +Subscription subscription = values + .ignoreElements() + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` +[Output](/tests/java/itrx/chapter2/reducing/IgnoreExample.java) +``` +Completed +``` + +`ignoreElements()` produces the same result as `filter(v -> false)` + + +## skip and take + +The next group of methods serve to cut the sequence at a specific point based on the item's index, and either take the first part or the second part. `take` takes the first n elements, while `skip` skips them. Note that neither function considers it an error if there are fewer items in the sequence than the specified index. + +```java +Observable take(int num) +``` + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/take.png) + +```java +Observable values = Observable.range(0, 5); + +Subscription first2 = values + .take(2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` + +[Output](/tests/java/itrx/chapter2/reducing/TakeSkipExample.java) +``` +0 +1 +Completed +``` + +Users of Java 8 streams should know the `take` operator as `limit`. The `limit` operator exists in Rx too, for symmetry purposes. It is an alias of `take`, but it lacks the richer overloads that we will soon see. + +`take` completes as soon as the n-th item is available. If an error occurs, the error will be forwarded, but not if it occurs after the cutting point. `take` doesn't care what happens in the observable after the n-th item. + +```java +Observable values = Observable.create(o -> { + o.onNext(1); + o.onError(new Exception("Oops")); +}); + +Subscription subscription = values + .take(1) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` +[Output](/tests/java/itrx/chapter2/reducing/TakeSkipExample.java) +``` +1 +Completed +``` + +`skip` returns the other half of a `take`. + +```java +Observable skip(int num) +``` + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/skip.png) + +```java +Observable values = Observable.range(0, 5); + +Subscription subscription = values + .skip(2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` +[Output](/tests/java/itrx/chapter2/reducing/TakeSkipExample.java) +``` +2 +3 +4 +Completed +``` + +There are overloads where the cutoff is a moment in time rather than place in the sequence. + +```java +Observable take(long time, java.util.concurrent.TimeUnit unit) +Observable skip(long time, java.util.concurrent.TimeUnit unit) +``` + +```java +Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + +Subscription subscription = values + .take(250, TimeUnit.MILLISECONDS) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` +[Output](/tests/java/itrx/chapter2/reducing/TakeSkipExample.java) +``` +0 +1 +Completed +``` + +## skipWhile and takeWhile + +`take` and `skip` work with predefined indices. If you want to "discover" the cutoff point as the values come, `takeWhile` and `skipWhile` will use a predicate instead. `takeWhile` takes items while a predicate function returns `true` + +```java +Observable takeWhile(Func1 predicate) +``` + +```java +Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + +Subscription subscription = values + .takeWhile(v -> v < 2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` +[Output](/tests/java/itrx/chapter2/reducing/TakeSkipExample.java) +``` +0 +1 +Completed +``` + +As you would expect, `skipWhile` returns the other half of the sequence + +```java +Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + +Subscription subscription = values + .skipWhile(v -> v < 2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` +[Output](/tests/java/itrx/chapter2/reducing/TakeSkipExample.java) +``` +2 +3 +4 +... +``` + +## skipLast and takeLast + +`skipLast` and `takeLast` work just like `take` and `skip`, with the difference that the point of reference is from the end. + +```java +Observable values = Observable.range(0,5); + +Subscription subscription = values + .skipLast(2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` +[Output](/tests/java/itrx/chapter2/reducing/TakeSkipExample.java) +``` +0 +1 +2 +Completed +``` + +By now you should be able to guess how `takeLast` is related to `skipLast`. There are overloads for both indices and time. + + +## takeUntil and skipUntil + +There are also two methods named `takeUntil` and `skipUntil`. `takeUntil` works exactly like `takeWhile` except that it takes items while the predictate is false. The same is true of `skipUntil`. + +Along with that, `takeUntil` and `skipUntil` each have a very interesting overload. The cutoff point is defined as the moment when _another_ observable emits an item. + +```java +public final Observable takeUntil(Observable other) +``` + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/takeUntil.png) + +```java +Observable values = Observable.interval(100,TimeUnit.MILLISECONDS); +Observable cutoff = Observable.timer(250, TimeUnit.MILLISECONDS); + +Subscription subscription = values + .takeUntil(cutoff) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` +[Output](/tests/java/itrx/chapter2/reducing/TakeSkipExample.java) +``` +0 +1 +Completed +``` + +As you may remember, `timer` here will wait 250ms and emit one event. This signals `takeUntil` to stop the sequence. Note that the signal can be of any type, since the actual value is not used. + +Once again `skipUntil` works by the same rules and returns the other half of the observable. Values are ignored until the signal comes to start letting values pass through. + +```java +Observable values = Observable.interval(100,TimeUnit.MILLISECONDS); +Observable cutoff = Observable.timer(250, TimeUnit.MILLISECONDS); + +Subscription subscription = values + .skipUntil(cutoff) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` +[Output](/tests/java/itrx/chapter2/reducing/TakeSkipExample.java) +``` +2 +3 +4 +... +``` + + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Creating a sequence](/Part%202%20-%20Sequence%20Basics/1.%20Creating%20a%20sequence.md) | [Inspection](/Part%202%20-%20Sequence%20Basics/3.%20Inspection.md) | diff --git a/Part 2 - Sequence Basics/3. Inspection.md b/Part 2 - Sequence Basics/3. Inspection.md new file mode 100644 index 0000000..3f95a2a --- /dev/null +++ b/Part 2 - Sequence Basics/3. Inspection.md @@ -0,0 +1,342 @@ +# Inspection + +In the previous chapter we just saw ways to filter out data that we don't care about. Sometimes what we want is information about the sequence rather than the values themselves. We will now introduce some methods that allow us to reason about a sequence. + +## all + +The `all` method establishes that every value emitted by an observable meets a criterion. Here's the signature and an example: + +```java +public final Observable all(Func1 predicate) +``` + +```java +Observable values = Observable.create(o -> { + o.onNext(0); + o.onNext(10); + o.onNext(10); + o.onNext(2); + o.onCompleted(); +}); + + +Subscription evenNumbers = values + .all(i -> i % 2 == 0) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` + +[Output](/tests/java/itrx/chapter2/inspection/AllExample.java) +``` +true +Completed +``` + +An interesting fact about this method is that it returns an observable with a single value, rather than the boolean value directly. This is because it is unknown how long it will take to establish whether the result should be true or false. Even though it completes as soon as it can know, that may take as long the source sequence itself. As soon as an item fails the predicate, `false` will be emitted. A value of `true` on the other hand cannot be emitted until the source sequence has completed and _all_ of the items are checked. Returning the decision inside an observable is a convenient way of making the operation non-blocking. We can see `all` failing as soon as possible in the next example: + +```java +Observable values = Observable.interval(150, TimeUnit.MILLISECONDS).take(5); + +Subscription subscription = values + .all(i -> i<3) // Will fail eventually + .subscribe( + v -> System.out.println("All: " + v), + e -> System.out.println("All: Error: " + e), + () -> System.out.println("All: Completed") + ); +Subscription subscription2 = values + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` + +[Output](/tests/java/itrx/chapter2/inspection/AllExample.java) +``` +0 +1 +2 +All: false +All: Completed +3 +4 +Completed +``` + +If the source observable emits an error, then `all` becomes irrelevant and the error passes through, terminating the sequence. + +```java +Observable values = Observable.create(o -> { + o.onNext(0); + o.onNext(2); + o.onError(new Exception()); +}); + +Subscription subscription = values + .all(i -> i % 2 == 0) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` + +[Output](/tests/java/itrx/chapter2/inspection/AllExample.java) +``` +Error: java.lang.Exception +``` + +If, however, the predicate fails, then `false` is emitted and the sequence terminates. Even if the source observable fails after that, the event is ignored, as required by the Rx contract (no events after a termination event). + +```java +Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onError(new Exception()); +}); + +Subscription subscription = values + .all(i -> i % 2 == 0) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` + +[Output](/tests/java/itrx/chapter2/inspection/AllExample.java) +``` +false +Completed +``` + +## exists + +The exists method returns an observable that will emit `true` if any of the values emitted by the observable make the predicate true. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/exists.png) + +```java +Observable values = Observable.range(0, 2); + +Subscription subscription = values + .exists(i -> i > 2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` + +[Output](/tests/java/itrx/chapter2/inspection/ExistsExample.java) +``` +false +Completed +``` + +Here our range didn't go high enough for the `i > 2` condition to succeed. If we extend our range in the same example with +```java +Observable values = Observable.range(0, 4); +``` +[We will get a successful result](/tests/java/itrx/chapter2/inspection/ExistsExample.java) +``` +true +Completed +``` + +## isEmpty + +This operator's result is a boolean value, indicating if an observable emitted values before completing or not. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/isEmpty.png) + +```java +Observable values = Observable.timer(1000, TimeUnit.MILLISECONDS); + +Subscription subscription = values + .isEmpty() + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` + +[Output](/tests/java/itrx/chapter2/inspection/IsEmptyExample.java) +``` +false +Completed +``` + +Falsehood is established as soon as the first value is emitted. `true` will be returned once the source observable has terminated. + +## contains + +The method `contains` establishes if a particular element is emitted by an observable. `contains` will use the `Object.equals` method to establish the equality. Just like previous operators, it emits its decision as soon as it can be established and immediately completes. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/contains.png) + +```java +Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + +Subscription subscription = values + .contains(4L) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` + +[Output](/tests/java/itrx/chapter2/inspection/ContainsExample.java) +``` +true +Completed +``` + +If we had used `contains(4)` where we used `contains(4L)`, nothing would be printed. That's because `4` and `4L` are not equal in Java. Our code would wait for the observable to complete before returning false, but the observable we used is infinite. + +## defaultIfEmpty + +If an empty sequence would cause you problems, rather than checking with `isEmpty` and handling the case, you can force an observable to emit a value on completion if it didn't emit anything before completing. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/defaultIfEmpty.png) + +```java +Observable values = Observable.empty(); + +Subscription subscription = values + .defaultIfEmpty(2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` + +[Output](/tests/java/itrx/chapter2/inspection/DefaultIfEmptyExample.java) +``` +2 +Completed +``` + +The default value is emitted only if no other values appeared and only on successful completion. If the source is not empty, the result is just the source observable. In the case of the error, the default value will _not_ be emitted before the error. + +```java +Observable values = Observable.error(new Exception()); + +Subscription subscription = values + .defaultIfEmpty(2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` + +Output +``` +Error: java.lang.Exception +``` + +## elementAt + +You can select exactly one element out of an observable using the `elementAt` method + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/elementAt.png) + +```java +Observable values = Observable.range(100, 10); + +Subscription subscription = values + .elementAt(2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` + +[Output](/tests/java/itrx/chapter2/inspection/ElementAtExample.java) +``` +102 +Completed +``` + +If the sequence doesn't have enough items, an `java.lang.IndexOutOfBoundsException` will be emitted. To avoid that specific case, we can provide a default value that will be emitted instead of an `IndexOutOfBoundsException`. + +```java +Observable values = Observable.range(100, 10); + +Subscription subscription = values + .elementAtOrDefault(22, 0) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` + +[Output](/tests/java/itrx/chapter2/inspection/ElementAtExample.java) +``` +0 +Completed +``` + +## sequenceEqual + +The last operator for this chapter establishes that two sequences are equal by comparing the values at the same index. Both the size of the sequences and the values must be equal. The function will either use `Object.equals` or the function that you supply to compare values. + +```java +Observable strings = Observable.just("1", "2", "3"); +Observable ints = Observable.just(1, 2, 3); + +Observable.sequenceEqual(strings, ints, (s,i) -> s.equals(i.toString())) +//Observable.sequenceEqual(strings, ints) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` + +[Output](/tests/java/itrx/chapter2/inspection/SequenceEqualExample.java) +``` +true +Completed +``` + +If we swap the operator for the one that is commented out, i.e, the one using the standard `Object.equals`, the result would be `false`. + +Failing is not part of the comparison. As soon as either sequence fails, the resulting observable forwards the error. + +```java +Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onError(new Exception()); +}); + +Observable.sequenceEqual(values, values) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); +``` + +[Output](/tests/java/itrx/chapter2/inspection/SequenceEqualExample.java) +``` +Error: java.lang.Exception +``` + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Reducing a sequence](/Part%202%20-%20Sequence%20Basics/2.%20Reducing%20a%20sequence.md) | [Aggregation](/Part%202%20-%20Sequence%20Basics/4.%20Aggregation.md) | + diff --git a/Part 2 - Sequence Basics/4. Aggregation.md b/Part 2 - Sequence Basics/4. Aggregation.md new file mode 100644 index 0000000..081324f --- /dev/null +++ b/Part 2 - Sequence Basics/4. Aggregation.md @@ -0,0 +1,570 @@ +# Aggregation + +We've seen how to cut away parts of a sequence that we don't want, how to get single values and how to inspect the contents of sequence. Those things can be seen as reasoning about the containing sequence. Now we will see how we can use the data in the sequence to derive new meaningful values. + +The methods we will see here resemble what is called catamorphism. In our case, it would mean that the methods consume the values in the sequence and compose them into one. However, they do not strictly meet the definition, as they don't return a single value. Rather, they return an observable that promises to emit a single value. + +If you've been reading through all of the examples, you should have noticed some repetition. To do away with that and to focus on what matters, we will now introduce a custom `Subscriber`, which we will use in our examples. + +```java +class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } +} +``` + +This is a very basic implementation that prints every event to the console, along with a helpful tag. + +### count + +Our first method is `count`. It serves the same purpose as `length` and `size`, found in most Java containers. This method will return an observable that waits until the sequence completes and emits the number of values encountered. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/count.png) + +```java +Observable values = Observable.range(0, 3); + +values + .subscribe(new PrintSubscriber("Values")); +values + .count() + .subscribe(new PrintSubscriber("Count")); +``` +[Output](/tests/java/itrx/chapter2/aggregation/CountExample.java) +``` +Values: 0 +Values: 1 +Values: 2 +Values: Completed +Count: 3 +Count: Completed +``` + +There is also `countLong` for sequences that may exceed the capacity of a standard integer. + +### first + +`first` will return an observable that emits only the first value in a sequence. It is similar to `take(1)`, except that it will emit `java.util.NoSuchElementException` if none is found. If you use the overload that takes a predicate, the first value that matches the predicate is returned. + +```java +Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + +values + .first(v -> v>5) + .subscribe(new PrintSubscriber("First")); +``` +[Output](/tests/java/itrx/chapter2/aggregation/FirstExample.java) +``` +First: 6 +``` + +Instead of getting a `java.util.NoSuchElementException`, you can use `firstOrDefault` to get a default value when the sequence is empty. + +### last + +`last` and `lastOrDefault` work in the same way as `first`, except that the item returned is the last item before the sequence completed. When using the overload with a predicate, the item returned is the last item that matched the predicate. We'll skip presenting examples, because they are trivial, but you can find them in the [example code](/tests/java/itrx/chapter2/aggregation/LastExample.java). + +### single + +`single` emits the only value in the sequence, or the only value that met predicate when one is given. It differs from `first` and `last` in that it does not ignore multiple matches. If multiple matches are found, it will emit an error. It can be used to assert that a sequence must only contain one such value. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/single.p.png) + +Remember that `single` must check the entire sequence to ensure your assertion. + +```java +Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + +values.take(10) + .single(v -> v == 5L) // Emits a result + .subscribe(new PrintSubscriber("Single1")); +values + .single(v -> v == 5L) // Never emits + .subscribe(new PrintSubscriber("Single2")); +``` + +[Output](/tests/java/itrx/chapter2/aggregation/SingleExample.java) +``` +Single1: 5 +Single1: Completed +``` + +Like in the previous methods, you can have a default value with `singleOrDefault` + +## Custom aggregators + +The methods we saw on this chapter so far don't seem that different from the ones in previous chapters. We will now see two very powerful methods that will greatly expand what we can do with an observable. Many of the methods we've seen so far can be implemented using those. + +### reduce + +You may have heard of `reduce` from [MapReduce] (https://en.wikipedia.org/wiki/MapReduce). Alternatively, you might have met it under the names "aggregate", "accumulate" or "fold". The general idea is that you produce a single value out of many by combining them two at a time. In its most basic overload, all you need is a function that combines two values into one. + +```java +public final Observable reduce(Func2 accumulator) +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/reduce.png) + +This is best explained with an example. Here we will calculate the sum of a sequence of integers: `0+1+2+3+4+...`. We will also calculate the minimum value for a different example; + +```java +Observable values = Observable.range(0,5); + +values + .reduce((i1,i2) -> i1+i2) + .subscribe(new PrintSubscriber("Sum")); +values + .reduce((i1,i2) -> (i1>i2) ? i2 : i1) + .subscribe(new PrintSubscriber("Min")); +``` + +[Output](/tests/java/itrx/chapter2/aggregation/ReduceExample.java) +``` +Sum: 10 +Sum: Completed +Min: 0 +Min: Completed +``` + +`reduce` in Rx is not identical to "reduce" in parallel systems. In the context of parallel systems, it implies that the pairs of values can be choosen arbitrarily so that multiple machines can work independently. In Rx, the `accumulator` function is applied in sequence from left to right (as seen on the marble diagram). Each time, the accumulator function combines the result of the previous step with the next value. This is more obvious in another overload: + +```java +public final Observable reduce(R initialValue, Func2 accumulator) +``` + +The accumulator returns a different type than the one in the observable. The first parameter for the accumulator is the previous partial result of the accumulation process and the second is the next value. To begin the process, an initial value is supplied. We will demonstrate the usefulness of this by reimplementing `count` + +```java +Observable values = Observable.just("Rx", "is", "easy"); + +values + .reduce(0, (acc,next) -> acc + 1) + .subscribe(new PrintSubscriber("Count")); +``` + +[Output](/tests/java/itrx/chapter2/aggregation/ReduceExample.java) +``` +Count: 3 +Count: Completed +``` + +We start with an accumulator of `0`, as we have counted 0 items. Every time a new item arrives, we return a new accumulator that is increased by one. The last value corresponds to the number of elements in the source sequence. + +`reduce` can be used to implement the functionality of most of the operators that emit a single value. It can not implement behaviour where a value is emitted before the source completes. So, you can implement `last` using `reduce`, but an implementation of `all` would not behave exactly like the original. + +### scan + +`scan` is very similar to `reduce`, with the key difference being that `scan` will emit all the intermediate results. + +```java +public final Observable scan(Func2 accumulator) +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/scan.png) + +In the case of our example for a sum, using `scan` will produce a running sum. + +```java +Observable values = Observable.range(0,5); + +values + .scan((i1,i2) -> i1+i2) + .subscribe(new PrintSubscriber("Sum")); +``` + +[Output](/tests/java/itrx/chapter2/aggregation/ScanExample.java) +``` +Sum: 0 +Sum: 1 +Sum: 3 +Sum: 6 +Sum: 10 +Sum: Completed +``` + +`scan` is more general than `reduce`, since `reduce` can be implemented with `scan`: `reduce(acc) = scan(acc).takeLast()` + +`scan` emits when the source emits and does not need the source to complete. We demonstrate that by implementing an observable that returns a running minimum: + +```java +Subject values = ReplaySubject.create(); + +values + .subscribe(new PrintSubscriber("Values")); +values + .scan((i1,i2) -> (i1` into a `List`. + +```java +Observable values = Observable.range(10,5); + +values + .reduce( + new ArrayList(), + (acc, value) -> { + acc.add(value); + return acc; + }) + .subscribe(v -> System.out.println(v)); +``` +[Output](/tests/java/itrx/chapter2/aggregation/ToCollectionExample.java) +``` +[10, 11, 12, 13, 14] +``` + +The code above has a problem with formality: `reduce` is meant to be a functional fold and such folds are not supposed to work on mutable accumulators. If we were to do this the "right" way, we would have to create a new instance of `ArrayList` for every new item, like this: + +```java +// Formally correct but very inefficient +.reduce( + new ArrayList(), + (acc, value) -> { + ArrayList newAcc = (ArrayList) acc.clone(); + newAcc.add(value); + return newAcc; + }) +``` + +#### collect + +The performance of creating a new collection for every new item is unacceptable. For that reason, Rx offers the `collect` operator, which does the same thing as `reduce`, only using a mutable accumulator this time. By using `collect` you document that you are not following the convention of immutability and you also simplify your code a little: + +```java +Observable values = Observable.range(10,5); + +values + .collect( + () -> new ArrayList(), + (acc, value) -> acc.add(value)) + .subscribe(v -> System.out.println(v)); +``` +[Output](/tests/java/itrx/chapter2/aggregation/CollectExample.java) +``` +[10, 11, 12, 13, 14] +``` + +Usually, you won't have to collect values manually. RxJava offers a variety of operators for collecting your sequence into a container. Those aggregators return an observable that will emit the corresponding collection when it is ready, just like what we did here. We will see such aggregators next. + +#### toList + +The example above could be [implemented as](/tests/java/itrx/chapter2/aggregation/ToCollectionExample.java) + +```java +Observable values = Observable.range(10,5); + +values + .toList() + .subscribe(v -> System.out.println(v)); +``` + +#### toSortedList + +The `toSortedList` aggregator works like `toList`, except that the resulting list is sorted. Here are the signatures: + +```java +public final Observable> toSortedList() +public final Observable> toSortedList( + Func2 sortFunction) +``` + +As we can see, we can either use the default comparison for the objects, or supply our own sorting function. The sorting function follows the semantics of `Comparator`'s [compare](https://docs.oracle.com/javase/7/docs/api/java/util/Comparator.html#compare%28T,%20T%29) method. + +In this example, we sort integers in reverse order with a custom sort function + +```java +Observable values = Observable.range(10,5); + +values + .toSortedList((i1,i2) -> i2 - i1) + .subscribe(v -> System.out.println(v)); +``` +[Output](/tests/java/itrx/chapter2/aggregation/ToCollectionExample.java) +``` +[14, 13, 12, 11, 10] +``` + +#### toMap + +`toMap` turns our sequence of `T` into a `Map`. There are 3 overloads + +```java +public final Observable> toMap( + Func1 keySelector) +public final Observable> toMap( + Func1 keySelector, + Func1 valueSelector) +public final Observable> toMap( + Func1 keySelector, + Func1 valueSelector, + Func0> mapFactory) +``` + +`keySelector` is a function that produces a key from a value. `valueSelector` produces from the emitted value the actual value that will be stored in the map. `mapFactory` creates the collection that will hold the items. + +Lets start with an example of the simplest overload. We want to map people to their age. First, we need a data structure to work on: + +```java +class Person { + public final String name; + public final Integer age; + public Person(String name, int age) { + this.name = name; + this.age = age; + } +} +``` +```java +Observable values = Observable.just( + new Person("Will", 25), + new Person("Nick", 40), + new Person("Saul", 35) +); + +values + .toMap(person -> person.name) + .subscribe(new PrintSubscriber("toMap")); +``` +[Output](/tests/java/itrx/chapter2/aggregation/ToMapExample.java) +``` +toMap: {Saul=Person@7cd84586, Nick=Person@30dae81, Will=Person@1b2c6ec2} +toMap: Completed +``` + +Now we will only use the age as a value + +```java +Observable values = Observable.just( + new Person("Will", 25), + new Person("Nick", 40), + new Person("Saul", 35) +); + +values + .toMap( + person -> person.name, + person -> person.age) + .subscribe(new PrintSubscriber("toMap")); +``` +[Output](/tests/java/itrx/chapter2/aggregation/ToMapExample.java) +``` +toMap: {Saul=35, Nick=40, Will=25} +toMap: Completed +``` + +If we want to be explicit about the container that will be used or initialise it, we can supply our own container: + +```java +values + .toMap( + person -> person.name, + person -> person.age, + () -> new HashMap()) + .subscribe(new PrintSubscriber("toMap")); +``` + +The container is provided as a factory function because a new container needs to be created for every new subscription. + +#### toMultimap + +When mapping, it is very common that many values share the same key. The datastructure that maps one key to multiple values is called a multimap and it is a map from keys to collections. This process can also be called "grouping". + +```java +public final Observable>> toMultimap( + Func1 keySelector) +public final Observable>> toMultimap( + Func1 keySelector, + Func1 valueSelector) +public final Observable>> toMultimap( + Func1 keySelector, + Func1 valueSelector, + Func0>> mapFactory) +public final Observable>> toMultimap( + Func1 keySelector, + Func1 valueSelector, + Func0>> mapFactory, + Func1> collectionFactory) +``` + +And here is an example where we group by age. + +```java +Observable values = Observable.just( + new Person("Will", 35), + new Person("Nick", 40), + new Person("Saul", 35) +); + +values + .toMultimap( + person -> person.age, + person -> person.name) + .subscribe(new PrintSubscriber("toMap")); +``` +[Output](/tests/java/itrx/chapter2/aggregation/ToMapExample.java) +``` +toMap: {35=[Will, Saul], 40=[Nick]} +toMap: Completed +``` + +The first three overloads are familiar from `toMap`. The fourth allows us to provide not only the `Map` but also the `Collection` that the values will be stored in. The key is provided as a parameter, in case we want to customise the corresponding collection based on the key. In this example we'll just ignore it. + +```java +Observable values = Observable.just( + new Person("Will", 35), + new Person("Nick", 40), + new Person("Saul", 35) +); + +values + .toMultimap( + person -> person.age, + person -> person.name, + () -> new HashMap(), + (key) -> new ArrayList()) + .subscribe(new PrintSubscriber("toMap")); +``` + +#### Note + +The operators just presented have actually limited use. It is tempting for a beginner to collect the data in a collection and process them in the traditional way. That should be avoided not just for didactic purposes, but because this practice defeats the advantages of using Rx in the first place. + + +### groupBy + +The last general function that we will see for now is `groupBy`. It is the Rx way of doing `toMultimap`. For each value, it calculates a key and groups the values into separate observables based on that key. + +```java +public final Observable> groupBy(Func1 keySelector) +``` + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/groupBy.png) + +The return value is an observable of `GroupedObservable`. Every time a new key is met, a new inner `GroupedObservable` will be emitted. That type is nothing more than a standard observable with a `getKey()` accessor, for getting the group's key. As values come from the source observable, they will be emitted by the observable with the corresponding key. + +The nested observables may complicate the signature, but they offer the advantage of allowing the groups to start emitting their items before the source observable has completed. + +In the next example, we will take a set of words and, for each starting letter, we will print the last word that occured. + +```java +Observable values = Observable.just( + "first", + "second", + "third", + "forth", + "fifth", + "sixth" +); + +values.groupBy(word -> word.charAt(0)) + .subscribe( + group -> group.last() + .subscribe(v -> System.out.println(group.getKey() + ": " + v)) + ); +``` + +The above example works, but it is a bad idea to have nested `subscribe`s. You can do the same with + +```java +Observable values = Observable.just( + "first", + "second", + "third", + "forth", + "fifth", + "sixth" +); + +values.groupBy(word -> word.charAt(0)) + .flatMap(group -> + group.last().map(v -> group.getKey() + ": " + v) + ) + .subscribe(v -> System.out.println(v)); +``` +[Output](/tests/java/itrx/chapter2/aggregation/GroupByExample.java) +``` +s: sixth +t: third +f: fifth +``` + +`map` and `flatMap` are unknown for now. We will introduce them properly in the next chapter. + +# Nested observables + +Nested observables may be confusing at first, but they are a powerful construct that has many uses. We borrow some nice examples, as outlined in www.introtorx.com + + + * Partitions of Data + * You may partition data from a single source so that it can easily be filtered and shared to many sources. Partitioning data may also be useful for aggregates as we have seen. This is commonly done with the `groupBy` operator. + * Online Game servers + * Consider a sequence of servers. New values represent a server coming online. The value itself is a sequence of latency values allowing the consumer to see real time information of quantity and quality of servers available. If a server went down then the inner sequence can signal that by completing. + * Financial data streams + * New markets or instruments may open and close during the day. These would then stream price information and could complete when the market closes. + * Chat Room + * Users can join a chat (outer sequence), leave messages (inner sequence) and leave a chat (completing the inner sequence). + * File watcher + * As files are added to a directory they could be watched for modifications (outer sequence). The inner sequence could represent changes to the file, and completing an inner sequence could represent deleting the file. + +### nest + +When dealing with nested observables, the `nest` operator becomes useful. It allows you to turn a non-nested observable into a nested one. `nest` takes a source observable and returns an observable that will emit the source observable and then terminate. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/nest.png) + +```java +Observable.range(0, 3) + .nest() + .subscribe(ob -> ob.subscribe(System.out::println)); +``` +[Output](/tests/java/itrx/chapter2/aggregation/NestExample.java) +``` +0 +1 +2 +``` + +Nesting observables to consume them doesn't make much sense. Towards the end of the pipeline, you'd rather flatten and simplify your observables, rather than nest them. Nesting is useful when you need to make a non-nested observable be of the same type as a nested observable that you have from elsewhere. Once they are of the same type, you can combine them, as we will see in the chapter about [combining sequences](/Part 3 - Taming the sequence/4. Combining sequences.md). + + + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Inspection](/Part%202%20-%20Sequence%20Basics/3.%20Inspection.md) | [Transformation of sequences](/Part%202%20-%20Sequence%20Basics/5.%20Transformation%20of%20sequences.md) | diff --git a/Part 2 - Sequence Basics/5. Transformation of sequences.md b/Part 2 - Sequence Basics/5. Transformation of sequences.md new file mode 100644 index 0000000..95bccd8 --- /dev/null +++ b/Part 2 - Sequence Basics/5. Transformation of sequences.md @@ -0,0 +1,490 @@ +# Transformation of sequences + +In this chapter we will see ways of changing the format of the data. In the real world, an observable may be of any type. It is uncommon that the data is already in format that we want them in. More likely, the values need to be expanded, trimmed, evaluated or simply replaced with something else. + +This will complete the three basic categories of operations. `map` and `flatMap` are the fundamental methods in the third category. In literature, you will often find them refered to as "bind", for reasons that are beyond the scope of this guide. +* Ana(morphism) `T` --> `Observable` +* Cata(morphism) `Observable` --> `T` +* Bind `Observable` --> `Observable` + +In the last chapter we introduced an implementation of `Subscriber` for convenience. We will continue to use it in the examples of this chapter. + +```java +class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } +} +``` + +### map + +The basic method for transformation is `map` (also known as "select" in SQL-inspired systems like LINQ). It takes a transformation function which takes an item and returns a new item of any type. The returned observable is composed of the values returned by the transformation function. + +```java +public final Observable map(Func1 func) +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/map.png) + +In the first example, we will take a sequence of integers and increase them by 3 + +```java +Observable values = Observable.range(0,4); + +values + .map(i -> i + 3) + .subscribe(new PrintSubscriber("Map")); +``` +[Output](/tests/java/itrx/chapter2/transforming/MapExample.java) +``` +Map: 3 +Map: 4 +Map: 5 +Map: 6 +Map: Completed +``` + +This was something we could do without `map`, for example by using `Observable.range(3,4)`. In the following, we will do something more practical. The producer will emit numeric values as a string, like many UIs often do, and then use `map` to convert them to a more processable integer format. + +```java +Observable values = + Observable.just("0", "1", "2", "3") + .map(Integer::parseInt); + +values.subscribe(new PrintSubscriber("Map")); +``` + +[Output](/tests/java/itrx/chapter2/transforming/MapExample.java) +``` +Map: 0 +Map: 1 +Map: 2 +Map: 3 +Map: Completed +``` + +This transformation is simple enough that we could also do it on the subscriber's side, but that would be a bad division of responsibilities. When developing the side of the producer, you want to present things in the neatest and most convenient way possible. You wouldn't dump the raw data and let the consumer figure it out. In our example, since we said that the API produces integers, it should do just that. Tranfomation operators allow us to convert the initial sequences into the API that we want to expose. + +### cast and ofType + +`cast` is a shorthand for the transformation of casting the items to a different type. If you had an `Observable` that you knew would only only emit values of type `T`, then it is just simpler to `cast` the observable, rather than do the casting in your lambda functions. + +```java +Observable values = Observable.just(0, 1, 2, 3); + +values + .cast(Integer.class) + .subscribe(new PrintSubscriber("Map")); +``` +[Output](/tests/java/itrx/chapter2/transforming/CastTypeOfExample.java) +``` +Map: 0 +Map: 1 +Map: 2 +Map: 3 +Map: Completed +``` + +The cast method will fail if not all of the items can be cast to the specified type. + +```java +Observable values = Observable.just(0, 1, 2, "3"); + +values + .cast(Integer.class) + .subscribe(new PrintSubscriber("Map")); +``` +[Output](/tests/java/itrx/chapter2/transforming/CastTypeOfExample.java) +``` +Map: 0 +Map: 1 +Map: 2 +Map: Error: java.lang.ClassCastException: Cannot cast java.lang.String to java.lang.Integer +``` + +If you would rather have such cases ignored, you can use the `ofType` method. This will filter our items that cannot be cast and then cast the sequence to the desired type. + +```java +Observable values = Observable.just(0, 1, "2", 3); + +values + .ofType(Integer.class) + .subscribe(new PrintSubscriber("Map")); +``` +[Output](/tests/java/itrx/chapter2/transforming/CastTypeOfExample.java) +``` +Map: 0 +Map: 1 +Map: 3 +Map: Completed +``` + +### timestamp and timeInterval + +The `timestamp` and `timeInterval` methods enable us to enrich our values with information about the asynchronous nature of sequences. `timestamp` transforms values into the `Timestamped` type, which contains the original value, along with a timestamp for when the event was emitted. + +```java +public final Observable> timestamp() +``` + +Here's an example: + +```java +Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + +values.take(3) + .timestamp() + .subscribe(new PrintSubscriber("Timestamp")); +``` +[Output](/tests/java/itrx/chapter2/transforming/TimestampTimeIntervalExample.java) +``` +Timestamp: Timestamped(timestampMillis = 1428611094943, value = 0) +Timestamp: Timestamped(timestampMillis = 1428611095037, value = 1) +Timestamp: Timestamped(timestampMillis = 1428611095136, value = 2) +Timestamp: Completed +``` + +The timestamp allows us to see that the items were emitted roughly 100ms apart (Java offers few guarantees on that). + +If we are more interested in how much time has passed since the last item, rather than the absolute moment in time when the items were emitted, we can use the `timeInterval` method. + +```java +public final Observable> timeInterval() +``` + +Using `timeInterval` in the same sequence as before: + +```java +Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + +values.take(3) + .timeInterval() + .subscribe(new PrintSubscriber("TimeInterval")); +``` +[Output](/tests/java/itrx/chapter2/transforming/TimestampTimeIntervalExample.java) +``` +TimeInterval: TimeInterval [intervalInMilliseconds=131, value=0] +TimeInterval: TimeInterval [intervalInMilliseconds=75, value=1] +TimeInterval: TimeInterval [intervalInMilliseconds=100, value=2] +TimeInterval: Completed +``` + +The information captured by `timestamp` and `timeInterval` is very useful for logging and debugging. It is Rx's way of acquiring information about the asynchronicity of sequences. + +### materialize and dematerialize + +Also useful for logging is `materialize`. `materialize` transforms a sequence into its metadata representation. + +```java +public final Observable> materialize() +``` + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/materialize.png) + +The notification type can represent any event, i.e. the emission of a value, an error or completion. Notice in the marble diagram above that the emission of "onCompleted" did not mean the end of the sequence, as the sequence actually ends afterwards. Here's an example + +```java +Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + +values.take(3) + .materialize() + .subscribe(new PrintSubscriber("Materialize")); +``` +[Output](/tests/java/itrx/chapter2/transforming/MaterializeExample.java) +``` +Materialize: [rx.Notification@a4c802e9 OnNext 0] +Materialize: [rx.Notification@a4c802ea OnNext 1] +Materialize: [rx.Notification@a4c802eb OnNext 2] +Materialize: [rx.Notification@18d48ace OnCompleted] +Materialize: Completed +``` + +The [Notification](http://reactivex.io/RxJava/javadoc/rx/Notification.html) type contains methods for determining the type of the event as well the carried value or `Throwable`, if any. + +`dematerialize` will reverse the effect of `materialize`, returning a materialized observable to its normal form. + +### flatMap + +`map` took one value and returned another, replacing items in the sequence one-for-one. `flatMap` will replace an item with any number of items, including zero or infinite items. `flatMap`'s transformation method takes values from the source observable and, for each of them, returns a new observable that emits the new values. + +```java +public final Observable flatMap(Func1> func) +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/flatMap.png) + +The observable returned by `flatMap` will emit all the values emitted by all the observables produced by the transformation function. Values from the same observable will be in order, but they may be interleaved with values from other observables. + +Let's start with a simple example, where `flatMap` is applied on an observable with a single value. `values` will emit a single value, `2`. `flatMap` will turn it into an observable that is the range between `0` and `2`. The values in this observable are emitted in the final observable. + +```java +Observable values = Observable.just(2); + +values + .flatMap(i -> Observable.range(0,i)) + .subscribe(new PrintSubscriber("flatMap")); +``` +[Output](/tests/java/itrx/chapter2/transforming/FlatMapExample.java) +``` +flatMap: 0 +flatMap: 1 +flatMap: Completed +``` + +When `flatMap` is applied on an observable with multiple values, each value will produce a new observable. `values` will emit `1`, `2` and `3`. The resulting observables will emit the values `[0]`, `[0,1]` and `[0,1,2]`, respectively. The values will be flattened together into one observable: the one that is returned by `flatMap`. + +```java +Observable values = Observable.range(1,3); + +values + .flatMap(i -> Observable.range(0,i)) + .subscribe(new PrintSubscriber("flatMap")); +``` +[Output](/tests/java/itrx/chapter2/transforming/FlatMapExample.java) +``` +flatMap: 0 +flatMap: 0 +flatMap: 1 +flatMap: 0 +flatMap: 1 +flatMap: 2 +flatMap: Completed +``` + +Much like `map`, `flatMap`'s input and output type are free to differ. In the next example, we will transform integers into `Character` + +```java +Observable values = Observable.just(1); + +values + .flatMap(i -> + Observable.just( + Character.valueOf((char)(i+64)) + )) + .subscribe(new PrintSubscriber("flatMap")); +``` + +This hasn't helped us more than the `map` operator. There is one key difference that we can exploit to get more out of the `flatMap` operator. While every value must result in an `Observable`, nothing prevents this observable from being empty. We can use that to silenty filter the sequence while transforming it at the same time. + +```java +Observable values = Observable.range(0,30); + +values + .flatMap(i -> { + if (0 < i && i <= 26) + return Observable.just(Character.valueOf((char)(i+64))); + else + return Observable.empty(); + }) + .subscribe(new PrintSubscriber("flatMap")); +``` +[Output](/tests/java/itrx/chapter2/transforming/FlatMapExample.java) +``` +flatMap: A +flatMap: B +flatMap: C +... +flatMap: X +flatMap: Y +flatMap: Z +flatMap: Completed +``` + +This example results in the entire alphabet being printed without errors, even though the initial range exceeds that of the alphabet. + +In our examples for `flatMap` so far, the values where in sequence: first all the values from the first observable, then all the values from the second observable. Though this seems intuitive, especially when coming from a synchronous environment, it is important to note that this is not always the case. The observable returned by `flatMap` emits values as soon as they are available. It just happened that in our examples, all of the observables had all of their values ready synchronously. To demonstrate, we construct asynchronous observables using the `interval` method. + +```java +Observable.just(100, 150) + .flatMap(i -> + Observable.interval(i, TimeUnit.MILLISECONDS) + .map(v -> i) + ) + .take(10) + .subscribe(new PrintSubscriber("flatMap")); +``` + +We started with the values 100 and 150, which we used as the interval period for the asynchronous observables created in `flatMap`. Since `interval` emits the numbers 1,2,3... in both cases, to better distinguish the two observables, we replaced those values with interval time that each observable operates on. + +[Output](/tests/java/itrx/chapter2/transforming/FlatMapExample.java) +``` +flatMap: 100 +flatMap: 150 +flatMap: 100 +flatMap: 100 +flatMap: 150 +flatMap: 100 +flatMap: 150 +flatMap: 100 +flatMap: 100 +flatMap: 150 +flatMap: Completed +``` + +We can see that the two observables are interleaved into one. + +### concatMap + +Even though `flatMap` shares its name with a very common operator in functional programming, we saw that it doesn't behave exactly like a functional programmer would expect. `flatMap` may interleave the supplied sequences. There is an operator that won't interleave the sequences and is called `concatMap`, because it is related to the [concat](/Part%203%20-%20Taming%20the%20sequence/4.%20Combining%20sequences.md#concat) operator that we will see later. + +```java +Observable.just(100, 150) + .concatMap(i -> + Observable.interval(i, TimeUnit.MILLISECONDS) + .map(v -> i) + .take(3)) + .subscribe( + System.out::println, + System.out::println, + () -> System.out.println("Completed")); +``` +[Output](/tests/java/itrx/chapter2/transforming/ConcatMapExample.java) +``` +100 +100 +100 +150 +150 +150 +Completed +``` + +We can see in the output that the two sequences are kept separate. Note that the `concatMap` operator only works with terminating sequences: it can't move on to the next sequence before the current sequence terminates. For that reason, we had to limit `interval`'s infinite sequence with `take`. + +### flatMapIterable + +`flatMap` and `concatMap` flatten a sequence of observables, as produced by their selector function, into one observable. We can also flatten a sequence of iterables with `flatMapIterable`. This is similar to `flatMap`, only our selector function creates iterables instead. + +Consider, instead of `Observable.range`, a function that produces the range as an iterable. +```java +public static Iterable range(int start, int count) { + List list = new ArrayList<>(); + for (int i=start ; i range(1, i)) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter2/transforming/FlatMapIterableExample.java) +``` +1 +1 +2 +1 +2 +3 +``` + +As expected, the 3 iterables that we created are flattened in a single observable sequence. + +As an Rx developer, you are advised to present your data as observable sequences and avoid mixing observables with iterables. However, when your data is already in the format of a collection, e.g. because standard Java operations returned them like that, it can be simpler or faster to just use them as they are without converting them first. `flatMapIterable` also eliminates the need to make a choice about interleaving or not: `flatMapIterable` doesn't interleave, just like you would expect from a synchronous `flatMap`. + +There is a second overload to `flatMapIterable` that allows you to combine every value in the iterable with the value that produced the iterable. + +```java +Observable.range(1, 3) + .flatMapIterable( + i -> range(1, i), + (ori, rv) -> ori * (Integer) rv) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter2/transforming/FlatMapIterableExample.java) +``` +1 +2 +4 +3 +6 +9 +``` + +Here, we multiplied every value in the iterable range with the value that seeded the range: `[1*1]`, `[1*2, 2*2]`, `[1*3, 2*3, 3*3]`. + +Java lacks a way to do `map` on its standard collections. It is therefore impossible to transform the iterable before the seeding value disappears (here, the `i` in `i -> range(1, i)`). Here, our iterable is just a list, so we could have just modified the iterable before returning it. However, if our iterable isn't a collection, we would have to either implement a `map` for iterables ourselves, or manually collect the modified values into a new collection and return that. This overload of `flatMapIterable` saves us from having to insert this ugliness in the middle of our pipeline. + +The concept of laziness isn't very common in Java, so you may be confused as to what kind of iterable isn't a collection. For the sake of example, consider the following iterable that generates a range lazily. It allows us to iterate over a range by calculating the next value from the previous one. In this way, we save the memory of storing the whole range. + +```java +public static class Range implements Iterable { + + private static class RangeIterator implements Iterator { + + private int next; + private final int end; + + RangeIterator(int start, int count) { + this.next = start; + this.end = start + count; + } + + @Override + public boolean hasNext() { + return next < end; + } + + @Override + public Integer next() { + return next++; + } + + } + + private final int start; + private final int count; + + public Range(int start, int count) { + this.start = start; + this.count = count; + } + + @Override + public Iterator iterator() { + return new RangeIterator(start, count); + } + +} +``` + +We can now iterate over a range and transform it without the need to store anything. + +```java +Observable.range(1, 3) + .flatMapIterable( + i -> new Range(1, i), + (ori, rv) -> ori * (Integer) rv) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter2/transforming/FlatMapIterableExample.java) +``` +1 +2 +4 +3 +6 +9 +``` + + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Aggregation](/Part%202%20-%20Sequence%20Basics/4.%20Aggregation.md) | [Chapter 3 - Taming the sequence](/Part%203%20-%20Taming%20the%20sequence/1.%20Side%20effects.md) | diff --git a/Part 3 - Taming the sequence/1. Side effects.md b/Part 3 - Taming the sequence/1. Side effects.md new file mode 100644 index 0000000..a4bcdeb --- /dev/null +++ b/Part 3 - Taming the sequence/1. Side effects.md @@ -0,0 +1,332 @@ +# PART 3 - Taming the sequence + +So far we've learned how to create observables and how to extract relevant data from observables. In this chapter we will go beyond what is necessary for simple examples and discuss more advanced functionality, as well as some good practices for using Rx in bigger applications. + +# Side effects + +Functions without side-effects interact with the rest of the program exclusively through their arguments and return values. When the operations within a function can affect the outcome of another function (or a subsequent call to the same function), we say that the function has side effects. Common side effects are writes to storage, logging, debugging or prints to a user interface. A more language-dependent form of side effect is the ability to modify the state of an object that is visible to other functions, which is something that Java considers legal. A function passed as an argument to an Rx operator can modify values in a wider scope, perform IO operations or update a display. + +Side effects can be very useful and are unavoidable in many cases. But they also have pitfalls. Rx developers are encouraged to avoid unnecessary side effects, and to have a clear intention when they do use them. While some cases are justified, abuse introduces unnecessary hazards. + +## Issues with side effects + +> Functional programming in general tries to avoid creating any side effects. Functions with side effects, especially which modify state, require the programmer to understand more than just the inputs and outputs of the function. The surface area they are required to understand needs to now extend to the history and context of the state being modified. This can greatly increase the complexity of a function, and thus make it harder to correctly understand and maintain. +> Side effects are not always accidental, nor are they always intentional. An easy way to reduce the accidental side effects is to reduce the surface area for change. The simple actions coders can take are to reduce the visibility or scope of state and to make what you can immutable. You can reduce the visibility of a variable by scoping it to a code block like a method. You can reduce visibility of class members by making them private or protected. By definition immutable data can't be modified so cannot exhibit side effects. These are sensible encapsulation rules that will dramatically improve the maintainability of your Rx code. + +We start with an example of an implementation with a side effect. Java doesn't allow references to non-final variables from lambdas (or anonymous implementations in general). However, the `final` keyword in Java protects only the reference and not the state of the referred object. Nothing stops you from modifying the state of objects from your lambda. Consider this simple counter, that is implemented as an object, rather than a primitive `int`. + +```java +class Inc { + private int count = 0; + public void inc() { + count++; + } + public int getCount() { + return count; + } +} +``` + +An instance of `Inc` can have its state modified even if it is declared as final. We are going to use this to index the items of an observable. Note that, while Java didn't force us to explicitly declare it as `final`, it would produce an error if we tried to change the reference while also using the reference in our lambda. + +```java +Observable values = Observable.just("No", "side", "effects", "please"); + +Inc index = new Inc(); +Observable indexed = + values.map(w -> { + index.inc(); + return w; + }); +indexed.subscribe(w -> System.out.println(index.getCount() + ": " + w)); +``` +[Output](/tests/java/itrx/chapter3/sideeffects/SideEffectExample.java) +``` +1: No +2: side +3: effects +4: please +``` + +So far it appears ok. Let's see what happens when we try to subscribe to that observable a second time. + +```java +Observable values = Observable.just("No", "side", "effects", "please"); + +Inc index = new Inc(); +Observable indexed = + values.map(w -> { + index.inc(); + return w; + }); +indexed.subscribe(w -> System.out.println("1st observer: " + index.getCount() + ": " + w)); +indexed.subscribe(w -> System.out.println("2nd observer: " + index.getCount() + ": " + w)); +``` +[Output](/tests/java/itrx/chapter3/sideeffects/SideEffectExample.java) +``` +1st observer: 1: No +1st observer: 2: side +1st observer: 3: effects +1st observer: 4: please +2nd observer: 5: No +2nd observer: 6: side +2nd observer: 7: effects +2nd observer: 8: please +``` + +The second subscriber sees the indexing starting at 5, which is non-sense. While the bug here is straight-forward to discover, side effects can lead to bugs which are a lot more subtle. + +## Composing data in a pipeline + +The safest way to use state in Rx is to include it in the data emitted. We can pair items with their indices using `scan`. + +```java +class Indexed { + public final int index; + public final T item; + public Indexed(int index, T item) { + this.index = index; + this.item = item; + } +} +``` + +```java +Observable values = Observable.just("No", "side", "effects", "please"); + +Observable> indexed = + values.scan( + new Indexed(0, null), + (prev,v) -> new Indexed(prev.index+1, v)) + .skip(1); +indexed.subscribe(w -> System.out.println("1st observer: " + w.index + ": " + w.item)); +indexed.subscribe(w -> System.out.println("2nd observer: " + w.index + ": " + w.item)); +``` +[Output](/tests/java/itrx/chapter3/sideeffects/SideEffectExample.java) +``` +1st observer: 1: No +1st observer: 2: side +1st observer: 3: effects +1st observer: 4: please +2nd observer: 1: No +2nd observer: 2: side +2nd observer: 3: effects +2nd observer: 4: please +``` + +The result now is valid. We removed the shared state between the two subscriptions and now they can't affect each other. + +## do + +There are cases where we do want a side effect, for example when logging. The `subscribe` method always has a side effect, otherwise it is not useful. We could put our logging in the body of a subscriber but then we would have two disadvantages: + +1. We are mixing the less interesting code of logging with the critical code of our subscription +2. If we wanted to log an intermediate state in our pipeline, e.g. before and after mapping, we would have to to introduce an additional subscription just for that, which won't necessarily see _exactly_ what the consumer saw and at the time when they saw it. + +The next family of methods helps us declare side effects in a tidier manner. + +```java +public final Observable doOnCompleted(Action0 onCompleted) +public final Observable doOnEach(Action1> onNotification) +public final Observable doOnEach(Observer observer) +public final Observable doOnError(Action1 onError) +public final Observable doOnNext(Action1 onNext) +public final Observable doOnTerminate(Action0 onTerminate) +``` + +As we can see, they take actions to perform when items are emitted. They also return the `Observable`, which means that we can use them between operators in our pipeline. In some cases, you could achieve the same result using `map` or `filter`. Using `doOn*` is better because it documents your intention to have a side effect. Here's an example + +```java +Observable values = Observable.just("side", "effects"); + +values + .doOnEach(new PrintSubscriber("Log")) + .map(s -> s.toUpperCase()) + .subscribe(new PrintSubscriber("Process")); +``` +[Output](/tests/java/itrx/chapter3/sideeffects/DoOnExample.java) +``` +Log: side +Process: SIDE +Log: effects +Process: EFFECTS +Log: Completed +Process: Completed +``` + +We reused our convenient `PrintSubscriber` from previous chapters. The "do" methods are not affected by the transformations later in the pipeline. We can log what our service produces regardless of what the consumer actually consumes. Consider the following service: + +```java +static Observable service() { + return Observable.just("First", "Second", "Third") + .doOnEach(new PrintSubscriber("Log")); +} +``` + +Then we use it: + +```java +service() + .map(s -> s.toUpperCase()) + .filter(s -> s.length() > 5) + .subscribe(new PrintSubscriber("Process")); +``` +[Output](/tests/java/itrx/chapter3/sideeffects/DoOnExample.java) +``` +Log: First +Log: Second +Process: SECOND +Log: Third +Log: Completed +Process: Completed +``` + +We logged everything that our service produced, even though the consumer modified and filtered the results. + +The differences between the different variants for "do" should be apparent by this point. In summary: +* `doOnEach` runs when any notification is emitted +* `doOnNext` runs when a value is emitted +* `doOnError` runs when the observable terminates with an error +* `doOnCompleted` runs when the observable terminates with no error +* `doOnTerminate` runs when the observable terminates + +One special note is the `onTerminate`, which runs _right before_ the observable terminates with either `onCompleted` or `onError`. There is also the method `finallyDo`, which will run _immediately after_ the observable terminates. + +## doOnSubscribe, doOnUnsubscribe + +```java +public final Observable doOnSubscribe(Action0 subscribe) +public final Observable doOnUnsubscribe(Action0 unsubscribe) +``` + +Subscription and unsubscription are not events that are emitted by an observable. They can still be seen as events in a general sense and you may want to perform some actions when they occur. Most likely, you'll be using them for logging purposes. + +```java +ReplaySubject subject = ReplaySubject.create(); +Observable values = subject + .doOnSubscribe(() -> System.out.println("New subscription")) + .doOnUnsubscribe(() -> System.out.println("Subscription over")); + +Subscription s1 = values.subscribe(new PrintSubscriber("1st")); +subject.onNext(0); +Subscription s2 = values.subscribe(new PrintSubscriber("2st")); +subject.onNext(1); +s1.unsubscribe(); +subject.onNext(2); +subject.onNext(3); +subject.onCompleted(); +``` +[Output](/tests/java/itrx/chapter3/sideeffects/DoOnExample.java) +``` +New subscription +1st: 0 +New subscription +2st: 0 +1st: 1 +2st: 1 +Subscription over +2st: 2 +2st: 3 +2st: Completed +Subscription over +``` + +## Encapsulating with AsObservable + +Rx is designed in the style of functional programming, but it exists within an object-oriented environment. We also have to protect against object-oriented dangers. Consider this naive implementation for a service that returns an observable. + +```java +public class BrakeableService { + public BehaviorSubject items = BehaviorSubject.create("Greet"); + public void play() { + items.onNext("Hello"); + items.onNext("and"); + items.onNext("goodbye"); + } +} +``` + +The code above does not prevent a naughty consumer from changing your `items` with one of their own. After that happens, subscriptions done before the change will no longer receive items, because you are not calling `onNext` on the right `Subject` any more. We obviously need to hide access to our `Subject` + +```java +public class BrakeableService { + private final BehaviorSubject items = BehaviorSubject.create("Greet"); + + public BehaviorSubject getValues() { + return items; + } + + public void play() { + items.onNext("Hello"); + items.onNext("and"); + items.onNext("goodbye"); + } +} +``` + +Now our reference is safe, but we are still exposing a reference to a `Subject`. Anyone can call `onNext` on our `Subject` and inject values in our sequence. We should only return `Observable`, which is an immutable object. `Subject`s extend `Observable` and we can cast our subject + +```java +public Observable getValuesUnsafe() { + return items; +} +``` + +Our API now looks safe, but it isn't. Nothing is stopping a user from discovering that our `Observable` is actually a `Subject` (e.g. using `instanceof`), casting it to a `Subject` and using it like previously. + +#### asObservable + +The idea behind the `asObservable` method is to wrap extensions of `Observable` into an actual `Observable` that can be safely shared, since `Observable` is immutable. + +```java +public Observable getValues() { + return items.asObservable(); +} +``` + +Now we have properly protected our `Subject`. This protection is not only against malicious attacks but also against mistakes. We have mentioned before that subjects should be avoided when alternatives exist, and now we've seen examples of why. Subjects introduce state to our observables. Calls to `onNext`, `onCompleted` and `onError` alter the sequence that consumers will see. Observables that are constructed with any of the factory methods or operators exposed on `Observable` are immutable, provided that we don't introduce side-effects ourselves, as we saw in [Issues with side effects](/Part%203%20-%20Taming%20the%20sequence/1.%20Side%20effects.md#issues-with-side-effects). + +You can find the full code of the examples discussed [here](/tests/java/itrx/chapter3/sideeffects/AsObservableExample.java) + +## Mutable elements cannot be protected + +As one might expect, an Rx pipeline forwards references to objects and doesn't create copies (unless we do so ourselves in the functions we supply). Modifications to the objects will be visible to every position in the pipeline that uses them. Consider the following mutable class: + +```java +class Data { + public int id; + public String name; + public Data(int id, String name) { + this.id = id; + this.name = name; + } +} +``` + +Now we show an observable of that type and two subscribers. + +```java +Observable data = Observable.just( + new Data(1, "Microsoft"), + new Data(2, "Netflix") +); + +data.subscribe(d -> d.name = "Garbage"); +data.subscribe(d -> System.out.println(d.id + ": " + d.name)); +``` +[Output](/tests/java/itrx/chapter3/sideeffects/MutablePipelineExample.java) +``` +1: Garbage +2: Garbage +``` + +The first subscriber is the first to be called for each item. Its action is to modify the data. Once the first subscriber is done, the same reference is also passed to the second subscriber, only now the data is changed in a way that was not declared in the producer. A developer needs to have a deep understanding of Rx, Java and their environment in order to reason about the sequence of modifications, and then argue that such code would run according to a plan. It is simpler to avoid mutable state altogether. Observables should be seen as a sequence notifications about resolved events. + + + + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Transformation of sequences](/Part%202%20-%20Sequence%20Basics/5.%20Transformation%20of%20sequences.md) | [Leaving the monad](/Part%203%20-%20Taming%20the%20sequence/2.%20Leaving%20the%20monad.md) | diff --git a/Part 3 - Taming the sequence/2. Leaving the monad.md b/Part 3 - Taming the sequence/2. Leaving the monad.md new file mode 100644 index 0000000..78746ce --- /dev/null +++ b/Part 3 - Taming the sequence/2. Leaving the monad.md @@ -0,0 +1,328 @@ +# Leaving the monad + +A [monad] (https://en.wikipedia.org/wiki/Monad_%28functional_programming%29) is an abstract concept from functional programming that is unfamiliar to most programmers. It is beyond the scope of this guide teaching monads. In www.introtorx.com we find a short definition: +> Monads are a kind of abstract data type constructor that encapsulate program logic instead of data in the domain model. + +Monads are of interest to us, because the observable is a monad. Rx code declares what needs to be done but the actual processing happens not when Rx statements are executed, but rather when values are emitted. Readers may find it interesting to read more about monads in general. For this guide, when refering to monads the reader only needs to think about the observable. + +## Why leave the monad + +There are two main reasons one may want to leave the monad. The first reason is that a new Rx developer will still be more comfortable in more traditional paradigms. Doing parts of the computation in a different paradigm may enable you to get some parts working, while you're still figuring out how to do things in Rx. The second reason is that we usually interact with components and libraries that weren't designed with Rx in mind. When refactoring existing code into Rx, it may be useful to have Rx behave in a blocking way. + +## BlockingObservable + +The first step to getting data out of an observable in a blocking manner is to transition to a [BlockingObservable](http://reactivex.io/RxJava/javadoc/rx/observables/BlockingObservable.html#). Any `Observable` can be converted to a `BlockingObservable` in one of two ways: +You can use the `Observable`'s `toBlocking` method +```java +public final BlockingObservable toBlocking() +``` +or the static factory of `BlockingObservable` +```java +public static BlockingObservable from(Observable o) +``` + +`BlockingObservable` does not extend the `Observable` and it can't be used with our usual Rx operators. It has its own implementations of a small set functions, which allow you to extract data out of an `Observable` in a blocking manner. Many of those methods are the blocking counterparts to methods that we have already seen. + +### forEach + +`Observable` has a method called `forEach`. `forEach` is defined as an alias to `subscribe`, with the main difference being that it doesn't return a `Subscription`. + +Consider this example + +```java +Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + +values + .take(5) + .forEach(v -> System.out.println(v)); +System.out.println("Subscribed"); +``` +[Output](/tests/java/itrx/chapter3/leaving/ForEachExample.java) +``` +Subscribed +0 +1 +2 +3 +4 +``` + +The code here behaves like `subscribe` would. First you register an observer (no overload for `forEach` accepts `Observer`, but the semantics are the same). Execution then proceeds to print "Subscribed" and exits our snippet. As values are emitted (the first one with a 100ms delay), they are passed to our observer for processing. + +`BlockingObservable` doesn't have a `subscribe` function, but it has `forEach`. Let's see the same example with `BLockingObservable` + +```java +Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + +values + .take(5) + .toBlocking() + .forEach(v -> System.out.println(v)); +System.out.println("Subscribed"); +``` +[Output](/tests/java/itrx/chapter3/leaving/ForEachExample.java) +``` +0 +1 +2 +3 +4 +Subscribed +``` + +We see here that the call to `forEach` blocked until the observable completed. Another difference is that there can be no handlers for `onError` and `onCompleted`. `onCompleted` is a given if the execution completes, while exceptions will be thrown into the runtime to be caught: + +```java +Observable values = Observable.error(new Exception("Oops")); + +try { + values + .take(5) + .toBlocking() + .forEach(v -> System.out.println(v)); +} +catch (Exception e) { + System.out.println("Caught: " + e.getMessage()); +} +System.out.println("Subscribed"); +``` +[Output](/tests/java/itrx/chapter3/leaving/ForEachExample.java) +``` +Caught: java.lang.Exception: Oops +Subscribed +``` + +### first, last, single + +`BlockingObservable` has methods for `first`, `last` and `single`, along with implementations for default values `firstOrDefault`, `lastOrDefault` and `singleOrDefault`. Having read about their [namesakes](/Part%202%20-%20Sequence%20Basics/4.%20Aggregation.md#first) in `Observable`, you already know what the returned value is. Once again, the difference is the blocking nature of the methods. They don't return an observable that will emit the value when it is available. Rather, they block until the value is available and return the value itself, without the surrounding observable. + +```java +Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + +long value = values + .take(5) + .toBlocking() + .first(i -> i>2); +System.out.println(value); +``` +[Output](/tests/java/itrx/chapter3/leaving/FirstLastSingleExample.java) +``` +3 +``` + +As we can see, the call to `first` blocked until a value was available, and only then was a value returned. + +Like with `forEach`, exceptions are thrown in the runtime to be caught + +```java +Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + +try { + long value = values + .take(5) + .toBlocking() + .single(i -> i>2); + System.out.println(value); +} +catch (Exception e) { + System.out.println("Caught: " + e); +} +``` +[Output](/tests/java/itrx/chapter3/leaving/FirstLastSingleExample.java) +``` +Caught: java.lang.IllegalArgumentException: Sequence contains too many elements +``` + +### To Iterable + +You can transform your observables to [iterables](https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html) throught a variety of methods on `BlockingObservable`. Iterables are pull-based, unlike Rx, which is push-based. That means that when the consumer is ready to consume a value, one is requested with `next()` on the iterable's [Iterator](https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html). The call to `next()` will either return a value immediately or block until one is ready. + +There are several ways to go from `BlockingObservable` to `Iterable` and each has a different behaviour. + +#### toIterable + +```java +public java.lang.Iterable toIterable() +``` +![](https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.toIterable.png) + +In this implementation, all the emitted values are collected and cached. Because of the caching, no items will be missed. The iterator gets the next value as soon as possible, either immediately if it has already occured, or it blocks until the next value becomes available. + +```java +Observable values = Observable.interval(500, TimeUnit.MILLISECONDS); + +Iterable iterable = values.take(5).toBlocking().toIterable(); +for (long l : iterable) { + System.out.println(l); +} +``` +[Output](/tests/java/itrx/chapter3/leaving/IterablesExample.java) +``` +0 +1 +2 +3 +4 +``` + +An interesting thing to note is that either `hasNext()` or `next()` block until the next notification is available. If the observable completes, `hasNext` returns `false` and `next` throws a `java.util.NoSuchElementException` + +#### next + +```java +public java.lang.Iterable next() +``` +![](https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.next.png) + +In this implementation values are not cached at all. The iterator will always wait for the next value and return that. + +```java +Observable values = Observable.interval(500, TimeUnit.MILLISECONDS); + +values.take(5) + .subscribe(v -> System.out.println("Emitted: " + v)); + +Iterable iterable = values.take(5).toBlocking().next(); +for (long l : iterable) { + System.out.println(l); + Thread.sleep(750); +} +``` +[Output](/tests/java/itrx/chapter3/leaving/IterablesExample.java) +``` +Emitted: 0 +0 +Emitted: 1 +Emitted: 2 +2 +Emitted: 3 +Emitted: 4 +4 + +``` + +In this example the consumer is slower than the producer and always misses the next value. The iterator gets the next after that. + +#### latest + +```java +public java.lang.Iterable latest() +``` + +The `latest` method is similar to `next`, with the difference that it will cache one value. The iterator only blocks if no events have been emitted by the observable since the last value was consumed. As long as there has been a new event, the iterator will return immediately with a value, or with the termination of the iteration. + +```java +Observable values = Observable.interval(500, TimeUnit.MILLISECONDS); + +values.take(5) + .subscribe(v -> System.out.println("Emitted: " + v)); + +Iterable iterable = values.take(5).toBlocking().latest(); +for (long l : iterable) { + System.out.println(l); + Thread.sleep(750); +} +``` +[Output](/tests/java/itrx/chapter3/leaving/IterablesExample.java) +``` +Emitted: 0 +0 +Emitted: 1 +1 +Emitted: 2 +Emitted: 3 +3 +Emitted: 4 +``` + +When using the `latest` iterator, values will be skipped if they are not pulled before the next event is emitted. If the consumer is faster than the producer, the iterator will block and wait for the next value. + +It is interesting here that 4 was never consumed. That was because an `onCompleted` followed immediately, resulting in the next pull seeing a terminated observable. The implicit `iterator.hasNext()` method reports a terminated observable without checking if the last value has been consumed. + +#### mostRecent + +```java +public java.lang.Iterable mostRecent(T initialValue) +``` +![](https://github.com/ReactiveX/RxJava/wiki/images/rx-operators/B.mostRecent.png) + +The `mostRecent` iterator never blocks. It caches a single value, therefore values may be skipped if the consumer is slow. Unlike `latest`, the last cached value is always returned, resulting in repetitions if the consumer is faster than the producer. To allow the `mostRecent` iterator to be completely non-blocking, an initial value is needed. That value is returned if the observable has not emitted any values yet. + +```java +Observable values = Observable.interval(500, TimeUnit.MILLISECONDS); + +values.take(5) + .subscribe(v -> System.out.println("Emitted: " + v)); + +Iterable iterable = values.take(5).toBlocking().mostRecent(-1L); +for (long l : iterable) { + System.out.println(l); + Thread.sleep(400); +} +``` +[Output](/tests/java/itrx/chapter3/leaving/IterablesExample.java) +``` +-1 +-1 +Emitted: 0 +0 +Emitted: 1 +1 +Emitted: 2 +2 +Emitted: 3 +3 +3 +Emitted: 4 +``` + +### Future + +A `BlockingObservable` can be presented as a [Future](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html) using the `toFuture` method. This method only creates an instance of `Future` and does not block. Execution blocks as necessary when getting the value. `Future` allows the consumer to decide how to approach an asynchronous operation. A `Future` is also capable of reporting errors in the operation. + +```java +Observable values = Observable.timer(500, TimeUnit.MILLISECONDS); + +values.subscribe(v -> System.out.println("Emitted: " + v)); + +Future future = values.toBlocking().toFuture(); +System.out.println(future.get()); +``` +[Output](/tests/java/itrx/chapter3/leaving/FutureExample.java) +``` +Emitted: 0 +0 +``` + +`Future`s that are created in this way expect that the observable will emit a single value, just like the `single` method does. If multiple items are emitted, the `Future` will report a `java.lang.IllegalArgumentException`. + + +## Locks + +### Deadlocks + +So far we were able to ignore potential deadlocks. Rx's non-blocking nature makes it harder to create unnecessary deadlocks. However, in this chapter we returned to blocking methods, thus bringing deadlocks to the forefront again. + +The example below would work as a non-blocking case. But because we used blocking operations, it will never unblock + +```java +ReplaySubject subject = ReplaySubject.create(); + +subject.toBlocking().forEach(v -> System.out.println(v)); +subject.onNext(1); +subject.onNext(2); +subject.onCompleted(); +``` + +`forEach` returns only after the termination of the sequence. However, the termination event requires `forEach` to return before being pushed. Therefore, `forEach` will never unblock. + +### Non-terminating sequences + +Some blocking ways to access observables, such as `last()`, require the observable to terminate to unblock. Others, like `first()`, require it to emit at least one event to unblock. Using those methods on `Observable` isn't a big danger, as they only return a non-terminating observable. These same `methods` on `BlockingObservable` can result in a permanent block if the consumer hasn't taken the time to enforce some guarantees, such as timeouts (we will see how this is done in [Timeshifter sequences](/Part 3 - Taming the sequence/5. Time-shifted sequences.md)). + + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Side effects](/Part%203%20-%20Taming%20the%20sequence/1.%20Side%20effects.md) | [Advanced error handling](/Part%203%20-%20Taming%20the%20sequence/3.%20Advanced%20error%20handling.md) | diff --git a/Part 3 - Taming the sequence/3. Advanced error handling.md b/Part 3 - Taming the sequence/3. Advanced error handling.md new file mode 100644 index 0000000..1688ccc --- /dev/null +++ b/Part 3 - Taming the sequence/3. Advanced error handling.md @@ -0,0 +1,237 @@ +# Advanced error handling + +We've already seen how we can handle an error in the observer. However, by that time, we are practically outside of the monad. There can be many kinds of errors and not every error is worth pushing all the way to the top. In standard Java, you can catch an exception at any level and decide if you want to handle it there or throw it further. Similarly in Rx, you can define behaviour based on errors without terminating the observable and forcing the observer to deal with everything. + +## Resume + +### onErrorReturn + +The `onErrorReturn` operator allows you to ignore an error and emit one final value before terminating (successfully this time). + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/onErrorReturn.png) + +In the next example, we will convert an error into a normal value to be printed: + +```java +Observable values = Observable.create(o -> { + o.onNext("Rx"); + o.onNext("is"); + o.onError(new Exception("adjective unknown")); +}); + +values + .onErrorReturn(e -> "Error: " + e.getMessage()) + .subscribe(v -> System.out.println(v)); +``` +[Output](/tests/java/itrx/chapter3/error/ResumeExample.java) +``` +Rx +is +Error: adjective unknown +``` + +### onErrorResumeNext + +The `onErrorResumeNext` allows you to resume a failed sequence with another sequence. The error will not appear in the resulting observable. + +```java +public final Observable onErrorResumeNext( + Observable resumeSequence) +public final Observable onErrorResumeNext( + Func1> resumeFunction) +``` + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/onErrorResumeNext.png) + +The first overload uses the same followup observable in every case. The second overload allows you to decide what the resume sequence should be based on the error that occurred. + +```java +Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onError(new Exception("Oops")); +}); + +values + .onErrorResumeNext(Observable.just(Integer.MAX_VALUE)) + .subscribe(new PrintSubscriber("with onError: ")); +``` +[Output](/tests/java/itrx/chapter3/error/ResumeExample.java) +``` +with onError: 1 +with onError: 2 +with onError: 2147483647 +with onError: Completed +``` + +There's nothing stopping the resumeSequence from failing as well. In fact, if you wanted to change the type of the error, you can return an observable that fails immediately. In standard Java, components may decide they can't handle an error and that they should re-throw it. In such cases, it is common wrap a new exception around the original error, thus providing additional context. You can do the same in Rx: + +```java +.onErrorResumeNext(e -> Observable.error(new UnsupportedOperationException(e))) +``` + +Now the sequence still fails, but you've wrapped the original error in a new error. + +### onExceptionResumeNext + +`onExceptionResumeNext` only has one difference to `onErrorResumeNext`: it only catches errors that are `Exception`s. + +```java +Observable values = Observable.create(o -> { + o.onNext("Rx"); + o.onNext("is"); + //o.onError(new Throwable() {}); // this won't be caught + o.onError(new Exception()); // this will be caught +}); + +values + .onExceptionResumeNext(Observable.just("hard")) + .subscribe(v -> System.out.println(v)); +``` + +## Retry + +If the error is non-deterministic, it may make sense to retry. `retry` re-subscribes to the source and emits everything again from the start. + +```java +public final Observable retry() +public final Observable retry(long count) +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/retry.png) + +If the error doesn't go away, `retry()` will lock us in an infinite loop of retries. The second overload limits the number of retries. If errors persist and the sequence fails n times, `retry(n)` will fail too. Let's see this in an example + +```java +Random random = new Random(); +Observable values = Observable.create(o -> { + o.onNext(random.nextInt() % 20); + o.onNext(random.nextInt() % 20); + o.onError(new Exception()); +}); + +values + .retry(1) + .subscribe(v -> System.out.println(v)); +``` +[Output](/tests/java/itrx/chapter3/error/RetryExample.java) +``` +0 +13 +9 +15 +java.lang.Exception +``` + +Here we've specified that we want to retry once. Our observable fails after two values, then tries again, fails again. The second time it fails the exception is allowed pass through. + +In this example, we have done something naughty: we have made our subscription stateful to demonstrate that the observable is restarted from the source: it produced different values the second time around. `retry` does not cache any elements like `replay`, nor would it make sense to do so. Retrying makes sense only if there are side effects, or if the observable is [hot](/Part%203%20-%20Taming%20the%20sequence/6.%20Hot%20and%20Cold%20observables.md). + +### retryWhen + +`retry` will restart the subscription as soon as the failure happens. If we need more control over this, we can use `retryWhen`. + +```java +public final Observable retryWhen( + Func1,? extends Observable> notificationHandler) +``` + +The argument to `retryWhen` is a function that takes an observable and returns another. The input observable emits all the errors that `retryWhen` encounters. The resulting observable signals when to retry: +* if it emits a value, `retryWhen` will retry, +* if it terminates with error, `retryWhen` will emit the error and not retry. +* if it terminates successfully, `retryWhen` will terminate successfully. + +Note that the type of the signaling observable and the actual values emitted don't matter. The values are discarded and the observable is only used for timing. + +In the next example, we will construct a retrying policy where we wait 100ms before retrying. + +```java +Observable source = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onError(new Exception("Failed")); +}); + +source.retryWhen((o) -> o + .take(2) + .delay(100, TimeUnit.MILLISECONDS)) + .timeInterval() + .subscribe( + System.out::println, + System.out::println); +``` +[Output](/tests/java/itrx/chapter3/error/RetryWhenExample.java) +``` +TimeInterval [intervalInMilliseconds=21, value=1] +TimeInterval [intervalInMilliseconds=0, value=2] +TimeInterval [intervalInMilliseconds=104, value=1] +TimeInterval [intervalInMilliseconds=0, value=2] +TimeInterval [intervalInMilliseconds=103, value=1] +TimeInterval [intervalInMilliseconds=0, value=2] +``` + +Our source observable emits 2 values and immediately fails. When that happens, the observable of failures inside `retryWhen` emits the error. We delay that emission by 100ms and send it back to signal a retry. `take(2)` guarantees that our signaling observable will terminate after we receive two errors. `retryWhen` sees the termination and doesn't retry after the second failures. + +## using + +The `using` operator is for creating observables from resources that need to be managed. It guarantees that your resources will be managed regardless of when and how subscriptions are terminated. If you were to just use `create`, you would have to do the managing in the traditional Java paradigm and inject it into Rx. `using` is a more natural way of managing resources in Rx. + +```java +public static final Observable using( + Func0 resourceFactory, + Func1> observableFactory, + Action1 disposeAction) +``` + +When a new subscription begins, `resourceFactory` leases the necessary resource. `observableFactory` uses that resource to produce items. When the resource is no longer needed, it is disposed of with the `disposeAction`. The dispose action is executed regardless of the way the subscription terminates (successfully or with a failure). + +In the next example, we pretend that a `string` is a resource that needs managing. + +```java +Observable values = Observable.using( + () -> { + String resource = "MyResource"; + System.out.println("Leased: " + resource); + return resource; + }, + (resource) -> { + return Observable.create(o -> { + for (Character c : resource.toCharArray()) + o.onNext(c); + o.onCompleted(); + }); + }, + (resource) -> System.out.println("Disposed: " + resource)); + +values + .subscribe( + v -> System.out.println(v), + e -> System.out.println(e)); +``` +[Output](/tests/java/itrx/chapter3/error/UsingExample.java) +``` +Leased: MyResource +M +y +R +e +s +o +u +r +c +e +Disposed: MyResource +``` + +When we subscribe to `values`, the resource factory function is called which returns `"MyResource"`. That string is used to produce an observable which emits all of the characters in the string. Once the subscription ends, the resource is disposed of. A `String` doesn't need any more managing than what the garbage collector will do. Resources may actually need such managing, e.g., database connections, opened files etc. + +It is important to note here that we are responsible for terminating the observable, just like we were when using the `create` method. With `create`, terminating is a matter of semantics. With `using`, not terminating defeats the point of using it in the first place. Only upon termination the resources will be released. If we had not called `o.onCompleted()`, the sequence would be assumed to be still active and needing its resources. + + + + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Leaving the monad](/Part%203%20-%20Taming%20the%20sequence/2.%20Leaving%20the%20monad.md) | [Combining sequences](/Part%203%20-%20Taming%20the%20sequence/4.%20Combining%20sequences.md) | diff --git a/Part 3 - Taming the sequence/4. Combining sequences.md b/Part 3 - Taming the sequence/4. Combining sequences.md new file mode 100644 index 0000000..17b6eae --- /dev/null +++ b/Part 3 - Taming the sequence/4. Combining sequences.md @@ -0,0 +1,676 @@ +# Combining sequences + +So far, we've seen most of the methods that allow us to create a sequence and transform it into what we want. However, most applications will have more than one source of input. We need a way a of combining sequences. We've already seen a few sequences that use more than one observable. In this chapter, we will see the most important operators that use multiple sequences to produce one. + +## Concatenation + +The most straight-forward combination of sequences is to have one run after the other. + +### concat + +The `concat` operator concatenates sequences one after the other. There are many overloads to `concat`, which allow you to provide source observables in different numbers and formats. + +```java +public static final Observable concat( + Observable> observables) +public static final Observable concat( + Observable t1, + Observable t2) +public static final Observable concat(Observable t1, + Observable t2, + Observable t3) +public static final Observable concat(Observable t1, + Observable t2, + Observable t3, + Observable t4) +// All the way to 10 observables +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/concat.png) + +Concatenating two (or more) given observables is straightforward. + +```java +Observable seq1 = Observable.range(0, 3); +Observable seq2 = Observable.range(10, 3); + +Observable.concat(seq1, seq2) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/ConcatExample.java) +``` +0 +1 +2 +10 +11 +12 +``` + +If the number of sequences to be combined is dynamic, you can provide an observable that emits the sequences to be concatenated. In this example, we will use our familiar `groupBy` to create a sequence that emits words starting with the same letter together. + +```java +Observable words = Observable.just( + "First", + "Second", + "Third", + "Fourth", + "Fifth", + "Sixth" +); + +Observable.concat(words.groupBy(v -> v.charAt(0))) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/ConcatExample.java) +``` +First +Fourth +Fifth +Second +Sixth +Third +``` + +`concat` behaves like the flattening phase of `concatMap`. In fact, `concatMap` is an alias for applying `map` and then `concat`. + +The `concatWith` operator is an alternative style of doing `concat`, which allows you to combine sequences one by one in a chain: + +```java +public void exampleConcatWith() { + Observable seq1 = Observable.range(0, 3); + Observable seq2 = Observable.range(10, 3); + Observable seq3 = Observable.just(20); + + seq1.concatWith(seq2) + .concatWith(seq3) + .subscribe(System.out::println); +} +``` +[Output](/tests/java/itrx/chapter3/combining/ConcatExample.java) +``` +0 +1 +2 +10 +11 +12 +20 +``` + +### repeat + +`repeat` allows you to concatenate a sequence after itself, either an infinite or a finite number of times. `repeat` doesn't cache the values to repeat them. When the time comes, it will start a new subscription and dispose of the old one. + +```java +public final Observable repeat() +public final Observable repeat(long count) +``` + +Its application is very simple + +```java +Observable words = Observable.range(0,2); + +words.repeat(2) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/RepeatExample.java) +``` +0 +1 +0 +1 +``` + + +### repeatWhen + +If you need more control than `repeat` gives, you can control when the repetition starts with the `repeatWhen` operator. The _when_ is defined by an observable that you provide. When the original sequence completes, it waits for the handling observable to emit something (the value is irrelevant) and only then does it repeat. If the handling observable terminates, that means that the repetitions should stop. + +It may be useful for the signal to know when a repetition has been completed. `repeatWhen` provides a special observable that emits `void` when a repetition terminates. You can use that observable to construct your signal. + +```java +public final Observable repeatWhen( + Func1,? extends Observable> notificationHandler) +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/repeatWhen.f.png) + +The argument of `repeatWhen` is a function that takes an observable and returns an observable. The types emitted by both objects do not matter. The input is the observable that signals the end of a repetition and the returned observable will be used to signal a restart. + +In the next example, we create our version of `repeat(n)` using `repeatWhen`. + +```java +Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + +values + .take(2) + .repeatWhen(ob -> { + return ob.take(2); + }) + .subscribe(new PrintSubscriber("repeatWhen")); +``` +[Output](/tests/java/itrx/chapter3/combining/RepeatExample.java) +``` +repeatWhen: 0 +repeatWhen: 1 +repeatWhen: 0 +repeatWhen: 1 +repeatWhen: Completed +``` + +Here the repetition happens immediately: `ob` emits when a repetition has ended, so the returned observable also emits right after a completed repetition. That signal the new repetition to begin. + +In the next example, we create sequence that repeats every two seconds, forever. + +```java +Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + +values + .take(5) + .repeatWhen((ob)-> { + ob.subscribe(); + return Observable.interval(2, TimeUnit.SECONDS); + }) + .subscribe(new PrintSubscriber("repeatWhen")); +``` + +Note that the sequence repeats every 2 seconds regardless of when it completed. That's because we created an independent `interval` observable that sends a signal every 2 seconds. In the next chapter, [Time-shifted sequences](/Part 3 - Taming the sequence/5. Time-shifted sequences.md), we will see ways of dealing with sequences in time with more control. + +Another thing to note is the `ob.subscribe()` statement, which appears to be useless. That is necessary because it forces `ob` to be created. In the current implementation of `repeatWhen`, if `ob` is not subscribed to, then repetitions never begin. + +### startWith + +`startWith` takes a sequence and concatenates it before the observable it is applied to. + +```java +public final Observable startWith(java.lang.Iterable values) +public final Observable startWith(Observable values) +public final Observable startWith(T t1) +public final Observable startWith(T t1, T t2) +public final Observable startWith(T t1, T t2, T t3) +// up to 10 values +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/startWith.png) + +Here an example + +```java +Observable values = Observable.range(0, 3); + +values.startWith(-1,-2) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/StartWithExample.java) +``` +-1 +-2 +0 +1 +2 +``` + +`startWith` is a shorthand for using `concat` with a `just` and our source sequence. + +```java +Observable.concat( + Observable.just(-1,-2,-3), + values) +// Same as +values.startWith(-1,-2,-3) +``` + + +## Concurrent sequences + +Observables aren't always emitting values at predictable moments in time. We will now see some operators intended for combining sequences that emit values concurrently. + +### amb + +`amb`takes a number of observables and returns the one that emits a value first. The rest are discarded. + +```java +public static final Observable amb( + java.lang.Iterable> sources) +public static final Observable amb( + Observable o1, + Observable o2) +public static final Observable amb( + Observable o1, + Observable o2, + Observable o3) +// Up to 10 observables +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/amb.png) + +In the following example, `amb` will mirror the second observable, because it waits less to start. + +```java +Observable.amb( + Observable.timer(100, TimeUnit.MILLISECONDS).map(i -> "First"), + Observable.timer(50, TimeUnit.MILLISECONDS).map(i -> "Second")) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/AmbExample.java) +``` +Second +``` + +It's usefulness may be not be obvious +> The amb feature can be useful if you have multiple cheap resources that can provide values, but latency is widely variable. For an example, you may have servers replicated around the world. Issuing a query is cheap for both the client to send and for the server to respond, however due to network conditions the latency is not predictable and varies considerably. Using the Amb operator, you can send the same request out to many servers and consume the result of the first that responds. _-Lee Cambell www.introtorx.com_ + +An alternative style of doing `amb` is the `ambWith` operator. `ambWith` allows you to combine the observables one by one in a chain. This is more convenient when using `amb` in the middle of a chain or operators. + +```java +Observable.timer(100, TimeUnit.MILLISECONDS).map(i -> "First") + .ambWith(Observable.timer(50, TimeUnit.MILLISECONDS).map(i -> "Second")) + .ambWith(Observable.timer(70, TimeUnit.MILLISECONDS).map(i -> "Third")) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/AmbExample.java) +``` +Second +``` + +### merge + +`merge` combines a set of observables into one. The resulting observable emits the values that the source observables emit, as they emit them. This means that values from different sequences can be mixed. + +```java +public static final Observable merge( + java.lang.Iterable> sequences) +public static final Observable merge( + java.lang.Iterable> sequences, + int maxConcurrent) +public static final Observable merge( + Observable> source) +public static final Observable merge( + Observable> source, + int maxConcurrent) +public static final Observable merge( + Observable t1, + Observable t2) +public static final Observable merge( + Observable t1, + Observable t2, + Observable t3) +... +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/merge.png) + +The many overloads are different ways of supplying a set of observables to merge. Here an example of what `merge` does + +```java +Observable.merge( + Observable.interval(250, TimeUnit.MILLISECONDS).map(i -> "First"), + Observable.interval(150, TimeUnit.MILLISECONDS).map(i -> "Second")) + .take(10) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/MergeExample.java) +``` +Second +First +Second +Second +First +Second +Second +First +Second +First +``` + +The difference between `concat` and `merge` is that `merge` does not wait for the current observable to terminate before moving to the next. `merge` subscribes to every observable available to it and emits items as they come. In that way, `merge` is similar to the flattening part of `flatMap`. + +Like other combinators that are static methods, `merge` has an alternative that allows you to merge sequences one by one in a chain. The operator is called `mergeWith` and the behaviour is the same. The following example has the same result as the one above. + +```java +Observable.interval(250, TimeUnit.MILLISECONDS).map(i -> "First") + .mergeWith(Observable.interval(150, TimeUnit.MILLISECONDS).map(i -> "Second")) + .take(10) + .subscribe(System.out::println); +``` + +### mergeDelayError + +With `merge`, as soon as any of the source sequences fails, the merged sequence fails as well. An alternative to that behaviour is `mergeDelayError`, which will postpone the emission of an error and continue to merge values from sequences that haven't failed. + +```java +public static final Observable mergeDelayError( + Observable> source) +public static final Observable mergeDelayError( + Observable t1, + Observable t2) +public static final Observable mergeDelayError( + Observable t1, + Observable t2, + Observable t3) +... +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/mergeDelayError.png) + +In the next example, we merge two observables which emit every 100ms. One fails early while the other observable continues to complete. + +```java +Observable failAt200 = + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(2), + Observable.error(new Exception("Failed"))); +Observable completeAt400 = + Observable.interval(100, TimeUnit.MILLISECONDS) + .take(4); + +Observable.mergeDelayError(failAt200, completeAt400) + .subscribe( + System.out::println, + System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/MergeDelayErrorExample.java) +``` +0 +0 +1 +1 +2 +3 +java.lang.Exception: Failed +``` + +In the beginning, both observables emit the same value. After value 1, the first sequence fails, and the merged sequence continues with values only from the second sequence. + +When merging more than two sequences, the merged sequence will go on until all of the sources have terminated, successfully or with an error. If more than one sequences fail, the error in the merged sequence will be of type `CompositeException` + +```java +Observable failAt200 = +Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(2), + Observable.error(new Exception("Failed"))); +Observable failAt300 = + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(3), + Observable.error(new Exception("Failed"))); +Observable completeAt400 = + Observable.interval(100, TimeUnit.MILLISECONDS) + .take(4); + +Observable.mergeDelayError(failAt200, failAt300, completeAt400) + .subscribe( + System.out::println, + System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/MergeDelayErrorExample.java) +``` +0 +0 +0 +1 +1 +1 +2 +2 +3 +rx.exceptions.CompositeException: 2 exceptions occurred. +``` + + + +### switchOnNext + +The `switchOnNext` operator takes an observable that emits observables. The returned observable emits items from the most recent observable. As soon as a new observable comes, the old one is discarded and values from the newer one are emitted. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchDo.png) + +```java +Observable.switchOnNext( + Observable.interval(100, TimeUnit.MILLISECONDS) + .map(i -> + Observable.interval(30, TimeUnit.MILLISECONDS) + .map(i2 -> i) + ) + ) + .take(9) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/SwitchOnNextExample.java) +``` +0 +0 +0 +1 +1 +1 +2 +2 +2 +``` + +This example may be a bit confusing. What we've done is creating an observable that creates a new observable every 100ms. Every created observable emits its number in the sequence every 30ms. After 100ms, each of those observables has had enough time to emit its number 3 times. Then a new observable is created, which causes them to be replaced by the new one. + +#### switchMap + +Where `flatMap` internally uses `merge` to combine the generated sequences and `concatMap` uses `concat`, there is `switchMap` to use `switchOnNext` for the flattening phase. + +```java +public final Observable switchMap(Func1> func) +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/switchMap.png) + +Every value from the source sequence is mapped through `func` to an observable. The values from the generated observable are emitted by the returned observable. Every time a new value arrives, `func` generates a new observable and switches to it, dropping the old one. The example we showed for `switchOnNext` can also be implemented with `switchMap`: + +```java +Observable.interval(100, TimeUnit.MILLISECONDS) + .switchMap(i -> + Observable.interval(30, TimeUnit.MILLISECONDS) + .map(l -> i)) + .take(9) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/SwitchMapExample.java) +``` +0 +0 +0 +1 +1 +1 +2 +2 +2 +``` + + +## Pairing sequences + +So far, we've seen operators which, in one way or the other, flattened multiple sequences into one of the same type. The next operators put the source sequences side-by-side and use the values to create a composite value. + +### zip + +`zip` is a very basic function out of functional programming. It takes two or more sequences and matches their values one-to-one by index. A function is required to combine the values. Unlike what you might expect from other environments, in RxJava `zip` doesn't default to combining all the values in a tuple. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/zip.png) + +In the next example, we have two sources that emit items at different rates. + +```java +Observable.zip( + Observable.interval(100, TimeUnit.MILLISECONDS) + .doOnNext(i -> System.out.println("Left emits " + i)), + Observable.interval(150, TimeUnit.MILLISECONDS) + .doOnNext(i -> System.out.println("Right emits " + i)), + (i1,i2) -> i1 + " - " + i2) + .take(6) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/ZipExample.java) +``` +Left emits 0 +Right emits 0 +0 - 0 +Left emits 1 +Right emits 1 +Left emits 2 +1 - 1 +Left emits 3 +Right emits 2 +2 - 2 +Left emits 4 +Left emits 5 +Right emits 3 +3 - 3 +Left emits 6 +Right emits 4 +4 - 4 +Left emits 7 +Right emits 5 +Left emits 8 +5 - 5 +``` + +As we can see, `zip` matched values based on index. + +`zip` has multiple overloads for zipping more than two sequences together. + +```java +public static final Observable zip( + java.lang.Iterable> ws, + FuncN zipFunction) +public static final Observable zip( + Observable> ws, + FuncN zipFunction) +public static final Observable zip( + Observable o1, + Observable o2, + Func2 zipFunction) +public static final Observable zip( + Observable o1, + Observable o2, + Observable o3, + Func3 zipFunction) +/// etc +``` + +When zipping more than two sequences, the operator will wait until all of the sources have emitted the next value before it emits the next zipped value. In the next example, we add another source with its own frequency again. + +```java +Observable.zip( + Observable.interval(100, TimeUnit.MILLISECONDS), + Observable.interval(150, TimeUnit.MILLISECONDS), + Observable.interval(050, TimeUnit.MILLISECONDS), + (i1,i2,i3) -> i1 + " - " + i2 + " - " + i3) + .take(6) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/ZipExample.java) +``` +0 - 0 - 0 +1 - 1 - 1 +2 - 2 - 2 +3 - 3 - 3 +4 - 4 - 4 +5 - 5 - 5 +``` + +The zipped sequence terminates when any of the source sequences terminates successfully. Further values from the other sequences will be ignored. We can see that in the next example, where we zip sequences of different sizes and count the elements in the zipped sequence. + +```java +Observable.zip( + Observable.range(0, 5), + Observable.range(0, 3), + Observable.range(0, 8), + (i1,i2,i3) -> i1 + " - " + i2 + " - " + i3) + .count() + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/ZipExample.java) +``` +3 +``` + +The zipped sequence contains as many elements as the shortest source sequence. + + +There is also the `zipWith` operator, which is an alternative style of zipping 2 sequences. `zipWith` allows you to zip in a chain, but it can be inconvenient for zipping more that two sequences. + +```java +Observable.interval(100, TimeUnit.MILLISECONDS) + .zipWith( + Observable.interval(150, TimeUnit.MILLISECONDS), + (i1,i2) -> i1 + " - " + i2) + .take(6) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/ZipExample.java) +``` +0 - 0 +1 - 1 +2 - 2 +3 - 3 +4 - 4 +5 - 5 +``` + +The `zipWith` also has an overload that allows you to zip your observable sequence with an iterable. + +```java +Observable.range(0, 5) + .zipWith( + Arrays.asList(0,2,4,6,8), + (i1,i2) -> i1 + " - " + i2) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/ZipExample.java) +``` +0 - 0 +1 - 2 +2 - 4 +3 - 6 +4 - 8 +``` + +### combineLatest + +Where `zip` uses indices, `combineLatest` will use time. Every time one of the observables being combined emits a value, that value is combined with the latest value by the other observable. Once again, a function is required to combine the values. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/combineLatest.png) + +```java +Observable.combineLatest( + Observable.interval(100, TimeUnit.MILLISECONDS) + .doOnNext(i -> System.out.println("Left emits")), + Observable.interval(150, TimeUnit.MILLISECONDS) + .doOnNext(i -> System.out.println("Right emits")), + (i1,i2) -> i1 + " - " + i2 + ) + .take(6) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/combining/CombineLatestExample.java) +``` +Left emits +Right emits +0 - 0 +Left emits +1 - 0 +Left emits +2 - 0 +Right emits +2 - 1 +Left emits +3 - 1 +Right emits +3 - 2 +``` + +As we can see, `combineLatest` first it waits for every sequence to have a value. After that, every value emitted by either observable results in a combined value being emitted. + +Just like every combinator we've seen in this chapter, there are overloads that allow to combine more than two sequences. + +I like to think of `combineLatest` as one event occuring in the context of another. `combineLatest` is very useful when consuming input from GUIs, where multiple stateful GUI controls affect the same output. Imagine a text input field, a paragraph that echoes the text and a checkbox that signals to capitalise it or not. Everytime the text field or the checkbox changes, `combineLatest` will combine the text with the decision to capitalise it or not. The end result is ready to be written to the output. + + + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Advanced error handling](/Part%203%20-%20Taming%20the%20sequence/3.%20Advanced%20error%20handling.md) | [Time-shifted sequences](/Part%203%20-%20Taming%20the%20sequence/5.%20Time-shifted%20sequences.md) | diff --git a/Part 3 - Taming the sequence/5. Time-shifted sequences.md b/Part 3 - Taming the sequence/5. Time-shifted sequences.md new file mode 100644 index 0000000..c14bafa --- /dev/null +++ b/Part 3 - Taming the sequence/5. Time-shifted sequences.md @@ -0,0 +1,606 @@ +# Time-shifted sequences + +One of the key features in Rx is that you don't know when items will be emitted. Some observables will emit everything immediately and synchronously(e.g. `range`), some emit on regular intervals, and some are hard or even impossible to predict. For example, mouse events and UDP packets simply arrive when they arrive. We need tools to decide what to do with those events, not only based on what they are, but also based on when they arrived and at what frequency. + +## Buffer + +`buffer` allows you to collect values and get them in bulks, rather than one at a time. The are several different ways of buffering values. + +### Complete, non-overlapping buffering + +First we will examine variants of buffer where every value is buffered exactly once, with no losses and no duplicates. + +#### buffer by count + +The simplest overload groups a fixed number of values together and emits the group when it's ready. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer3.png) + +```java +Observable.range(0, 10) + .buffer(4) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/BufferExample.java) +``` +[0, 1, 2, 3] +[4, 5, 6, 7] +[8, 9] +``` + +#### buffer by time + +The next overload allows you to buffer based on time. Time is divided into windows of equal length. Values are collected for the each window and at the end of each window the buffer is emited. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer5.png) + +In the next example, we produce values every 100ms and buffer them in windows of 250ms. + +```java +Observable.interval(100, TimeUnit.MILLISECONDS).take(10) + .buffer(250, TimeUnit.MILLISECONDS) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/BufferExample.java) +``` +[0, 1] +[2, 3] +[4, 5, 6] +[7, 8] +[9] +``` + +The size of a collection here depends on how many values were emitted in that timespan and not on a desired size. The collection can even be empty, if there where no events during the window. + +#### buffer by count and time + +You can use both a buffer size and a timespan to buffer values. The buffered values are emitted if either the buffer is full or if the time slot ends and a new one starts. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer6.png) + +```java +Observable.interval(100, TimeUnit.MILLISECONDS) + .take(10) + .buffer(250, TimeUnit.MILLISECONDS, 2) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/BufferExample.java) +``` +[0, 1] +[] +[2, 3] +[] +[4, 5] +[6] +[7, 8] +[] +[9] +``` + +We see a lot of empty lists here. This is because the buffer is emitted both when it reaches size 2 and when a time window closes. As we can see from our previous example, only two values belong in those windows. Since the buffer was emptied when it reached size 2, it is empty when the window closes. + +#### buffer with signal + +Instead of fixed points in time, you can also signal `buffer` with an observable to flush. Every time the signal emits onNext, the values in the buffer will be emitted will be emitted. Buffering with a signal can be very useful if you want to buffer values until the moment that you are ready for them. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer8.png) + +The following example does the same as `.buffer(250, TimeUnit.MILLISECONDS)` + +```java +Observable.interval(100, TimeUnit.MILLISECONDS).take(10) + .buffer(Observable.interval(250, TimeUnit.MILLISECONDS)) + .subscribe(System.out::println); +``` + +There is a variant for the way above, where you provide the signaling observable through a function: `.buffer(() -> Observable.interval(250, TimeUnit.MILLISECONDS))`. The difference here is that the function that creates the observable is executed when a subscription happens. You can use to start your signal when the subscription starts. + +### Overlapping buffers + +Every method for buffering that we've seen has an alternative that allows buffers to overloap or to leave out values. + +#### buffer by count + +When buffering based on the desired buffer size, you can also declare how far apart the beginings of each buffer are. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer4.png) + +As we can see in the marble diagram, we start a new buffer every 3 values but the buffer is limited to 2 values. Therefore, every third element is left out. You can also start the new buffer before the previous buffer closes. +* When `count` > `skip`, the buffers overlap +* When `count` < `skip`, elements are left out +* The case of `count` = `skip` is equivalent to the simpler case we saw in the previous subchapter. + +Here's an example in code, where the buffers overlap + +```java +Observable.range(0,10) + .buffer(4, 3) + .subscribe(System.out::println); +``` +Output +``` +[0, 1, 2, 3] +[3, 4, 5, 6] +[6, 7, 8, 9] +[9] +``` + +As we can see, a new buffer starts every 3 elements, and that buffer contains the next 4 elements. + +#### buffer by time + +We can do a very similar thing for the variant where buffering is based on a timespan. You decide how frequently to open new buffers and how long they should last. +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer7.png) +Once again, this allows you either to make your buffers overlap or leave out elements. +* When `timespan` > `timeshift`, the buffers overlap +* When `timespan` < `timeshift`, elements are left out +* The case of `timespan` = `timeshift` is equivalent to the simpler case we saw in the previous subchapter. + +In the next example we will create a new buffer every 200ms and have it collect for 350ms. That means that buffers overlap by 150ms. + +```java +Observable.interval(100, TimeUnit.MILLISECONDS).take(10) + .buffer(350, 200, TimeUnit.MILLISECONDS) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/BufferExample.java) +``` +[0, 1, 2] +[2, 3, 4] +[3, 4, 5, 6] +[5, 6, 7, 8] +[7, 8, 9] +[9] +``` + +#### buffer by signal + +The last and most powerful variant or `buffer` allows you to define the start and the end of buffers using signaling observables. +```java +public final Observable> buffer( + Observable bufferOpenings, + Func1> bufferClosingSelector) +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer2.png) + +This function takes two arguments. The first argument, `bufferOpenings`, is an observable. Every time this observable emits a value, a new buffer begins. Along with opening a new buffer, the value which it emitted is passed to the `bufferClosingSelector`, which is a function. This function uses the value to create a new observable, which will signal the end of the corresponding buffer when it emits its first onNext event. + +Let's see this in code: +```java +Observable.interval(100, TimeUnit.MILLISECONDS).take(10) + .buffer( + Observable.interval(250, TimeUnit.MILLISECONDS), + i -> Observable.timer(200, TimeUnit.MILLISECONDS)) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/BufferExample.java) +``` +[2, 3] +[4, 5] +[7, 8] +[9] +``` + +We've created an `Observable.interval`, which signals the opening of a new buffer every 250ms. Because observables created with `interval` do not immediately emit a value, and first buffer actually starts at 250ms and the values before were lost. For the closing of a buffer, we provided a lambda function that took every value emitted by `bufferOpenings`. The values generated by `interval` are the natural progression 0,1,2,3... but we don't actually use the value, because such an example would be too complicated. Instead, we just created an observable that waits 200ms and then emits a single value. That means that each buffer last exactly 200ms, similarily to buffering by time. + +### takeLastBuffer + +We have already seen the [takeLast](/Part%202%20-%20Sequence%20Basics/2.%20Reducing%20a%20sequence.md#skiplast-and-takelast) operator, which returns the last N number of items. Internally, `takeLast` needs to buffer items and re-emits them when the source sequence ends. The `takeLastBuffer` operator returns the last elements as one buffer. + +#### By count + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/takeLastBuffer.png) + +`takeLastBuffer` by count will emit the last `N` elements in a list. + +```java +Observable.range(0, 5) + .takeLastBuffer(2) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/TakeLastBufferExample.java) +``` +[3, 4] +``` + +#### By time + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/takeLastBuffer.tn.png) + +`takeLastBuffer` by time will emit, as a buffer, the items that were received during the specified timespan, which is measure from the end of the source sequence. + +```java +Observable.interval(100, TimeUnit.MILLISECONDS) + .take(5) + .takeLastBuffer(200, TimeUnit.MILLISECONDS) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/TakeLastBufferExample.java) +``` +[2, 3, 4] +``` + +#### By count and time + +The buffer emitted by this overload of `takeLastBuffer` will contain items that were emitted over the specified timespan before the end. If this window contains more than the specified number of items, the buffer will contain the last `N` items. + +```java +Observable.interval(100, TimeUnit.MILLISECONDS) + .take(5) + .takeLastBuffer(2, 200, TimeUnit.MILLISECONDS) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/TakeLastBufferExample.java) +``` +[3, 4] +``` + +As we saw in the previous example, the last 200ms include three values. With `.takeLastBuffer(2, 200, TimeUnit.MILLISECONDS)` we specified that we want values from the last 200ms, but no more than 2 values. For that reason, we only get the last two values. + + +## Delay + +`delay`, as the name suggests, will postpone the emission of values for a specified amount of time. The are two ways to do that. One is by storing values until you are ready to emit them. The other is to delay the subscription to observable. + +### delay + +The simplest overload of `delay` will delay every item by the same amount of time. You can think of it as delaying the beginning of the sequence, while maintaining the time intervals between successive elements. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/delay.png) + +Here's an example in code + +```java +Observable.interval(100, TimeUnit.MILLISECONDS).take(5) + .delay(1, TimeUnit.SECONDS) + .timeInterval() + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/DelayExample.java) +``` +TimeInterval [intervalInMilliseconds=1109, value=0] +TimeInterval [intervalInMilliseconds=94, value=1] +TimeInterval [intervalInMilliseconds=100, value=2] +TimeInterval [intervalInMilliseconds=100, value=3] +TimeInterval [intervalInMilliseconds=101, value=4] +``` + +We created 5 values spaced 100ms apart and then we delayed the sequence by 1s. We can see that the first value takes ~(1000 + 100)ms and the next values take 100ms each. + +You can also delay each value individually. +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/delay.o.png) +This overload takes a function which will create an observable for each item. When that observable emits onNext, the corresponding item is emitted in the delayed sequence. Here's some code: + +```java +Observable.interval(100, TimeUnit.MILLISECONDS).take(5) + .delay(i -> Observable.timer(i * 100, TimeUnit.MILLISECONDS)) + .timeInterval() + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/DelayExample.java) +``` +TimeInterval [intervalInMilliseconds=152, value=0] +TimeInterval [intervalInMilliseconds=173, value=1] +TimeInterval [intervalInMilliseconds=199, value=2] +TimeInterval [intervalInMilliseconds=201, value=3] +TimeInterval [intervalInMilliseconds=199, value=4] +``` + +The initial sequence is spaced 100ms apart, while the resulting is 200ms. If you remember, `interval` emits the numbers i = 1,2,3,etc. We delay each item `i` by `i*100`, so the first item is delayed by 100ms, then second by 200ms, the third by 300ms. The difference between the successive delays is 100ms. Added to the initial 100ms interval, that results in 200ms interval between items. + +### delaySubscription + +Rather than storing values and emitting them later, you can delay the subscription altogether. This will have a different effect depending on if the observable is hot or cold. This will be discussed more in the [Hot and cold observables](/Part 3 - Taming the sequence/6. Hot and Cold observables.md) chapter. For our examples so far, the observables are cold and subscription event is when the source observable is created (i.e. the begining of the sequence). What that means is that there is no difference in the sequences between delaying each item by the same amount and delaying the subscription. Since that is the case here, delaying the subscription is more efficient, since the operator doesn't need to buffer items internally. + +Let's see code for the different overloads for delaying a subscription +```java +Observable.interval(100, TimeUnit.MILLISECONDS).take(5) + .delaySubscription(1000, TimeUnit.MILLISECONDS) + .timeInterval() + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/DelayExample.java) +``` +TimeInterval [intervalInMilliseconds=1114, value=0] +TimeInterval [intervalInMilliseconds=92, value=1] +TimeInterval [intervalInMilliseconds=101, value=2] +TimeInterval [intervalInMilliseconds=100, value=3] +TimeInterval [intervalInMilliseconds=99, value=4] +``` + +What we see here is that the subscription of the `interval` observable (i.e. its creation) was delayed by 1000ms. After that, the sequence goes as defined. + +You can also delay the subscription based on a signaling observable through the following overload: +```java +public final Observable delaySubscription(Func0> subscriptionDelay) +``` + +The argument is a function that will create a new observable for each subscription. The subscription is delayed until the corresponding observable emits a value. The following example is equivalent to the one we've just seen. + +```java +Observable.interval(100, TimeUnit.MILLISECONDS).take(5) + .delaySubscription(() -> Observable.timer(1000, TimeUnit.MILLISECONDS)) + .timeInterval() + .subscribe(System.out::println); +``` + +### delay values and subscription + +The last method in this category allows you to delay both the subscription and each item individually. +```java +public final Observable delay( + Func0> subscriptionDelay, + Func1> itemDelay) +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/delay.oo.png) + +This combines two delay variants we've already seen into one. The first argument is a function that creates an observable that will signal when to perform the subscription. The second argument takes every item and decides how long it should be delayed. + +## Sample + +`sample` allows you to thin-out a sequence by dividing it into time windows and taking only one value out of each window. When each window ends, the last value within that window (if any) is emitted. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/sample.png) + +```java +Observable.interval(150, TimeUnit.MILLISECONDS) + .sample(1, TimeUnit.SECONDS) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/SampleExample.java) +``` +5 +12 +18 +... +``` + +The division of time doesn't have to be uniform. You can specify the end of each part with a signaling observable. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/sample.o.png) + +The following code does the same thing as before +```java +Observable.interval(150, TimeUnit.MILLISECONDS) + .sample(Observable.interval(1, TimeUnit.SECONDS)) + .subscribe(System.out::println); +``` + +## Throttling + +Throttling is also intended for thinning out a sequence. When the producer emits more values than we want and we don't need every sequential value, we can thin out the sequence by throttling it. + +### throttleFirst + +The `throttleFirst` operators filter out values relative to the values that were already accepted. After a value has been accepted, values will be rejected for the duration of the window. Once The window expires, the next value will be accepted and a new window starts. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleFirst.png) + +```java +Observable.interval(150, TimeUnit.MILLISECONDS) + .throttleFirst(1, TimeUnit.SECONDS) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/ThrottleExample.java) +``` +0 +7 +14 +... +``` + +Here, `interval` emits every 150ms. The values seen as output were emitted at `(i+1)*150`ms, relative to the start of the sequence. The first item is emitted at 150ms and is accepted by default. Now items are rejected for the next 1000ms. The first item after that comes at 1200ms. Again, items are rejected for the next 1000ms, so the next item comes at 2250ms. + +### throttleLast + +The `throttleLast` operator divides time at regular intervals, rather than relative to the last item. it emits the last value in each window, rather than the first after it. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/throttleLast.png) + +```java +Observable.interval(150, TimeUnit.MILLISECONDS) + .throttleLast(1, TimeUnit.SECONDS) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/ThrottleExample.java) +``` +5 +12 +18 +... +``` + +Here, a window starts with the creation of the sequence at 0ms. That window expires at 1000ms and the last value in that window was at 900ms. The next window last 1000ms until 2000ms. The last item in that window is at 1950. In the next window, the item is at 2850ms. + +## Debouncing + +In this operator, a time window starts every time a value is received. Once the window expires, the value is emitted. If, however, another value is received before the window expires, the previous value is rejected and the window restarts for the next value. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/debounce.s.png) + +Demonstrating this is a bit more complicated, since an `interval` observable will either have all of its values accepted or only its last value accepted (which is never if the observable is infinite). For that reason we will construct a more complicated observable. + +```java +Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .timeInterval() + .subscribe(System.out::println); +``` +Output +``` +TimeInterval [intervalInMilliseconds=110, value=0] +TimeInterval [intervalInMilliseconds=1, value=1] +TimeInterval [intervalInMilliseconds=98, value=2] +TimeInterval [intervalInMilliseconds=101, value=3] +TimeInterval [intervalInMilliseconds=502, value=4] +TimeInterval [intervalInMilliseconds=500, value=5] +TimeInterval [intervalInMilliseconds=499, value=6] +TimeInterval [intervalInMilliseconds=102, value=7] +TimeInterval [intervalInMilliseconds=99, value=8] +TimeInterval [intervalInMilliseconds=101, value=9] +``` + +As we can see here, our observable will emit 4 values in quick succession, then 3 values in greater intervals and finally 3 values in quick succession. The `scan` only serves to turn the values into the natural sequence, rather than 3 repetitions of 1,2,3. The reason the first two emissions are simultaneous is that that `scan` emits the initial value and the first value together. + +Now that we understand our source observable, let's `debounce` it: + +```java +Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .debounce(150, TimeUnit.MILLISECONDS) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/DebounceExample.java) +``` +3 +4 +5 +9 +``` + +We debounced with a window of 150ms. The bursts of emissions in our observable were faster than that (100ms), so only the last value in each burst passed through. During the slower part of our observable, all the values were accepted, because the 150ms window expired before the next value arrived. + +There is a `throttleWithTimeout` operator which has the same behaviour as the `debounce` operator that we just saw. One is practically an alias of the other, even though neither is officially declared as such in the documentation. + +You can also debounce based on a per item basis. In this case, you provide a function that calculates for each item how long the window should be after it. You signal that the window is using a new observable for each item +. When the observable terminates, the window expires. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/debounce.f.png) + +In the next example, the window size for each value `i` is `i*50`ms. + +```java +Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .debounce(i -> Observable.timer(i * 50, TimeUnit.MILLISECONDS)) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/DebounceExample.java) +``` +1 +3 +4 +5 +9 +``` + +Let's map each item to the length of its window and the time that the next item actually arrives + +Item | Calculated Window | Time until next value | Window < next value +---- | ----------------- | --------------------- | ------------------- +0 | 0 | 1 | +1 | 50 | 98 | Yes +2 | 100 | 101 | (timed operations in Java are not 100% accurate) +3 | 150 | 502 | Yes +4 | 200 | 500 | Yes +5 | 250 | 499 | Yes +6 | 300 | 102 | +7 | 350 | 99 | +8 | 400 | 101 | +9 | 450 | | Yes + +We can now see why the values turned out to be so. + + +This operator is useful against observables that undergo periods of uncertainty, where the value changes frequently from one non-definitive state to another. For example, imagine that you are monitoring the contents of a text field and you want to offer suggestions based on what the user is writting. You could recompute your suggestions on every keystroke, but that would be too noisy and too costly. If, instead, you `debounce` the changes to the text field, you will offer suggestions only when the user has paused or finished typing. + + +## Timeout + +`timeout` is used to detect observables that have remained inactive for a given amount of time. If a specified amount of time passes without the source emitting any items, `timeout` makes the observable fail with a `TimeoutException`. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timeout.1.png) + +We will reuse our composite observable from the examples of `debounce` to demonstrate `timeout`. + +```java +Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .timeout(200, TimeUnit.MILLISECONDS) + .subscribe( + System.out::println, + System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/TimeoutExample.java) +``` +0 +1 +2 +3 +java.util.concurrent.TimeoutException +``` + +The output mirrors the source observable for as long as values come more frequently than 200ms. As soon as a value takes more than that to arrive, an error is pushed. + +Instead of failing, you can provide a fallback observable. When a timeout occures, the resulting observable will switch to the fallback. The original observable will be ignored from then on, even if it resumes. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timeout.2.png) + +```java +Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .timeout(200, TimeUnit.MILLISECONDS, Observable.just(-1)) + .subscribe( + System.out::println, + System.out::println); +``` +[Output](/tests/java/itrx/chapter3/timeshifted/TimeoutExample.java) +``` +0 +1 +2 +3 +-1 +``` + +You can also specify the timeout window per item. In that case, you provide a function that creates an observable for each value. When the observable terminates, that is the signal for the timeout. If no values had been emitted until that, that triggers the timeout. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/timeout5.png) + +Here is the previous example, implemented using this overload. + +```java +Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .timeout(i -> Observable.timer(200, TimeUnit.MILLISECONDS)) + .subscribe( + System.out::println, + System.out::println); +``` +Again, you can provide a fallback observable with +```java +.timeout(i -> Observable.timer(200, TimeUnit.MILLISECONDS), Observable.just(-1)) +``` + +The output is the same as the previous two examples + + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Combining sequences](/Part%203%20-%20Taming%20the%20sequence/4.%20Combining%20sequences.md) | [Hot and cold observables](/Part%203%20-%20Taming%20the%20sequence/6.%20Hot%20and%20Cold%20observables.md) | diff --git a/Part 3 - Taming the sequence/6. Hot and Cold observables.md b/Part 3 - Taming the sequence/6. Hot and Cold observables.md new file mode 100644 index 0000000..4f87b03 --- /dev/null +++ b/Part 3 - Taming the sequence/6. Hot and Cold observables.md @@ -0,0 +1,367 @@ +# Hot and Cold observables + +Observable sequences come in two flavours, called "hot" and "cold", that have important differences. In this chapter, we will explain what each type is and what that means for you as an Rx developer. + +## Cold observables + +Cold observables are observables that run their sequence when and if they are subscribed to. They present the sequence from the start to each subscriber. An example of a cold observable would be `Observable.interval`. Regardless of when it is created and when it is subscribed to, it will generate the same sequence for every subscriber. + +```java +Observable cold = Observable.interval(200, TimeUnit.MILLISECONDS); + +cold.subscribe(i -> System.out.println("First: " + i)); +Thread.sleep(500); +cold.subscribe(i -> System.out.println("Second: " + i)); +``` +[Output](/tests/java/itrx/chapter3/hotandcold/ColdExample.java) +``` +First: 0 +First: 1 +First: 2 +Second: 0 +First: 3 +Second: 1 +First: 4 +Second: 2 +... +``` + +The two subscribers don't receive the same value at the same time, even though they are both subscribed to the same observable. They do see the same sequence, except that each of them sees it as having begun when they subscribed. + +The code samples that we've seen in this guide so far have been cold observables, because cold observables are easier to reason about. Every observable that is created with the `Observable.create` is a cold observable. That includes all the shorthands that we've seen, such as `just`, `range`, `timer` and `from`. + +Cold observables don't necessarily present the same sequence to each subscriber. If, for example, an observable connects to a database and emits the results of a query, the actual value will depend on the state of the database at the time of subscription. It is the fact that a subscriber will receive the whole query from the start that makes this observable cold. + +## Hot observables + +Hot observables emit values independent of individual subscriptions. They have their own timeline and events occur whether someone is listening or not. An example of this is mouse events. A mouse is generating events regardless of whether there is a subscription listening for them. When a subscription is made, the observer receives current events as they happen. You don't receive and you don't want to receive a recap of everything that the mouse has done since booting the system. When you unsubscribe, it doesn't stop your mouse from generating events either. You're just not receiving them. If you resubscribe, you will again see current events with no recap of what you've missed. + +## Publish + +There are ways to transform cold observables into hot and vice versa. Cold observables become hot with the `publish()` operator. + +```java +public final ConnectableObservable publish() +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/publishConnect.png) + +`publish` returns a `ConnectableObservable`, which is an extension of `Observable` with three additional methods +```java +public final Subscription connect() +public abstract void connect(Action1 connection) +public Observable refCount() +``` + +There is a variant that takes a selector that transforms a sequence before publishing it. +```java +public final Observable publish(Func1,? extends Observable> selector) +``` +The `selector` can do anything that we've learned to do on observables. The usefulness of this is that a single subscription is made for the selector, which can be reused as much as needed. Without this overload, reusing the observable could lead to multiple subscriptions. There is no way to guarantee that the subscriptions would happen at the same exact time and therefore see the exact same sequence. + +This method returns an `Observable` instead of a `ConnectableObservable`, so the connection functionality we are about to discuss does not apply there. + +### connect + +The `ConnectableObservable` will initially emit nothing. When calling `connect`, it will create a new subscription to its source observable (the one we called `publish` on). It will begin receiving events and pushing them to its subscribers. All of the subscribers will receive the same events at the same time, as they are practically sharing the same subscription: the one that `connect` created. + +```java +ConnectableObservable cold = Observable.interval(200, TimeUnit.MILLISECONDS).publish(); +cold.connect(); + +cold.subscribe(i -> System.out.println("First: " + i)); +Thread.sleep(500); +cold.subscribe(i -> System.out.println("Second: " + i)); +``` +[Output](/tests/java/itrx/chapter3/hotandcold/ConnectableObservableExample.java) +``` +First: 0 +First: 1 +First: 2 +Second: 2 +First: 3 +Second: 3 +First: 4 +Second: 4 +First: 5 +Second: 5 +``` + +### Disconnecting + +As we saw in `connect`'s signature, this method returns a `Subscription`, just like `Observable.subscribe` does. You can use that reference to terminate the `ConnectableObservable`'s subscription. That will stop events from being propagated to observers but it will not unsubscribe them from the `ConnectableObservable`. If you call `connect` again, the `ConnectableObservable` will start a new subscription and the old observers will begin receiving values again. + +```java +ConnectableObservable connectable = Observable.interval(200, TimeUnit.MILLISECONDS).publish(); +Subscription s = connectable.connect(); + +connectable.subscribe(i -> System.out.println(i)); + +Thread.sleep(1000); +System.out.println("Closing connection"); +s.unsubscribe(); + +Thread.sleep(1000); +System.out.println("Reconnecting"); +s = connectable.connect(); +``` +[Output](/tests/java/itrx/chapter3/hotandcold/ConnectableObservableExample.java) +``` +0 +1 +2 +3 +4 +Closing connection +Reconnecting +0 +1 +2 +... +``` + +When you restart by calling `connect` again, a new subscription will be created. If the source observable is cold, that means that the whole sequence is restarted. + +If instead of terminating the connection, you want to unsubscribe from the hot observable, you can use the `Subscription` returned by the `subscribe` method. + +```java +ConnectableObservable connectable = Observable.interval(200, TimeUnit.MILLISECONDS).publish(); +Subscription s = connectable.connect(); + +Subscription s1 = connectable.subscribe(i -> System.out.println("First: " + i)); +Thread.sleep(500); +Subscription s2 = connectable.subscribe(i -> System.out.println("Second: " + i)); + +Thread.sleep(500); +System.out.println("Unsubscribing second"); +s2.unsubscribe(); +``` +[Output](/tests/java/itrx/chapter3/hotandcold/ConnectableObservableExample.java) +``` +First: 0 +First: 1 +First: 2 +Second: 2 +First: 3 +Second: 3 +First: 4 +Second: 4 +Unsubscribing second +First: 5 +First: 6 +``` + +### refCount + +`ConnectableObservable.refCount` returns `Observable` that is connected as long as there are subscribers to it. + +```java +Observable cold = Observable.interval(200, TimeUnit.MILLISECONDS).publish().refCount(); + +Subscription s1 = cold.subscribe(i -> System.out.println("First: " + i)); +Thread.sleep(500); +Subscription s2 = cold.subscribe(i -> System.out.println("Second: " + i)); +Thread.sleep(500); +System.out.println("Unsubscribe second"); +s2.unsubscribe(); +Thread.sleep(500); +System.out.println("Unsubscribe first"); +s1.unsubscribe(); + +System.out.println("First connection again"); +Thread.sleep(500); +s1 = cold.subscribe(i -> System.out.println("First: " + i)); +``` +[Output](/tests/java/itrx/chapter3/hotandcold/ConnectableObservableExample.java) +``` +First: 0 +First: 1 +First: 2 +Second: 2 +First: 3 +Second: 3 +Unsubscribe second +First: 4 +First: 5 +First: 6 +Unsubscribe first +First connection again +First: 0 +First: 1 +First: 2 +First: 3 +First: 4 +``` + +We see here that the sequence doesn't start until there are subscribers to `refCount`. If they all go away, the connection stops. If more come later, a new connection starts. + + +## replay + +```java +public final ConnectableObservable replay() +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/replay.png) + +`replay` resembles the `ReplaySubject`. Upon connection, it will begin collecting values. Once a new observer subscribes to the observable, it will have all the collected values replayed to it. Once it has caught up, it will receive values in parallel to every other observer. + +```java +ConnectableObservable cold = Observable.interval(200, TimeUnit.MILLISECONDS).replay(); +Subscription s = cold.connect(); + +System.out.println("Subscribe first"); +Subscription s1 = cold.subscribe(i -> System.out.println("First: " + i)); +Thread.sleep(700); +System.out.println("Subscribe second"); +Subscription s2 = cold.subscribe(i -> System.out.println("Second: " + i)); +Thread.sleep(500); +``` +[Output](/tests/java/itrx/chapter3/hotandcold/ReplayExample.java) +``` +Subscribe first +First: 0 +First: 1 +First: 2 +Subscribe second +Second: 0 +Second: 1 +Second: 2 +First: 3 +Second: 3 +``` + +`replay` returns an `ConnectableObservable` like `publish`, so we can use the same ways to unsubscribe or create a `refCount` observable. + +There are 8 overloads for `replay`. +```java +ConnectableObservable replay() + Observable replay(Func1,? extends Observable> selector) + Observable replay(Func1,? extends Observable> selector, int bufferSize) + Observable replay(Func1,? extends Observable> selector, int bufferSize, long time, java.util.concurrent.TimeUnit unit) + Observable replay(Func1,? extends Observable> selector, long time, java.util.concurrent.TimeUnit unit) +ConnectableObservable replay(int bufferSize) +ConnectableObservable replay(int bufferSize, long time, java.util.concurrent.TimeUnit unit) +ConnectableObservable replay(long time, java.util.concurrent.TimeUnit unit) +``` + +They are different ways of providing one or more of 3 parameters: `bufferSize`, `selector` and `time` (plus `unit` for time). +* `bufferSize` determines the maximum amount of items to be stored and replayed. Upon subscription, the observable will replay the last `bufferSize` number of items. Older items are forgotten. This is useful for conserving memory. +* `time`, `unit` determines how old an element can be before being forgotten. Upon subscription, the observable will replay items that are newer than `time`. +* `selector` will transform the replayed observable, in the same way that `publish(selector)` works. + +Here's an example with `bufferSize` + +```java +ConnectableObservable source = Observable.interval(1000, TimeUnit.MILLISECONDS) + .take(5) + .replay(2); + +source.connect(); +Thread.sleep(4500); +source.subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/hotandcold/ReplayExample.java) +``` +2 +3 +4 +``` + +When we `connect`, the source begins emitting the sequence 0,1,2,3,4 in 1s intervals. We sleep for 4.5s before subscribing, which means that the source has emitted 0,1,2,3. 0 and 1 have fallen off the buffer, so only 2 and 3 are replayed. When 4 is emitted, we receive it normally. + +When providing a time window, items fall off the buffer based on time + +```java +ConnectableObservable source = Observable.interval(1000, TimeUnit.MILLISECONDS) + .take(5) + .replay(2000, TimeUnit.MILLISECONDS); + +source.connect(); +Thread.sleep(4500); +source.subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/hotandcold/ReplayExample.java) +``` +2 +3 +4 +``` + +## cache + +The `cache` operator has a similar function to `replay`, but hides away the `ConnectableObservable` and removes the managing of subscriptions. The internal `ConnectableObservable` is subscribed to when the first observer arrives. Subsequent subscribers have the previous values replayed to them from the cache and don't result in a new subscription to the source observable. + +```java +public final Observable cache() +public final Observable cache(int capacity) +``` +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/cache.png) + +```java +Observable obs = Observable.interval(100, TimeUnit.MILLISECONDS) + .take(5) + .cache(); + +Thread.sleep(500); +obs.subscribe(i -> System.out.println("First: " + i)); +Thread.sleep(300); +obs.subscribe(i -> System.out.println("Second: " + i)); +``` +[Outout](/tests/java/itrx/chapter3/hotandcold/CacheExample.java) +``` +First: 0 +First: 1 +First: 2 +Second: 0 +Second: 1 +Second: 2 +First: 3 +Second: 3 +First: 4 +Second: 4 +``` + +In this example, we see that the sequence begins not when the observable was created, but when the first subscriber arrived 500ms later. The second subscribers caught up with earlier values upon subscription and received future values normally. + +An important thing to note is that the internal `ConnectableObservable` doesn't unsubscribe if all the subscribers go away, like `refCount` would. Once the first subscriber arrives, the source observable will be observed and cached once and for all. This matters because we can't walk away from an infinite observable anymore. Values will continue to cached until the source terminates or we run out of memory. The overload that specifies capacity isn't a solution either, as the capacity is received as a hint for optimisation and won't actually restrict the size of our cache. + +```java +Observable obs = Observable.interval(100, TimeUnit.MILLISECONDS) + .take(5) + .doOnNext(System.out::println) + .cache() + .doOnSubscribe(() -> System.out.println("Subscribed")) + .doOnUnsubscribe(() -> System.out.println("Unsubscribed")); + +Subscription subscription = obs.subscribe(); +Thread.sleep(150); +subscription.unsubscribe(); +``` +[Outout](/tests/java/itrx/chapter3/hotandcold/CacheExample.java) +``` +Subscribed +0 +Unsubscribed +1 +2 +3 +4 +``` + +In this example, `doOnNext` prints the values as they are produced and cached from the source observable, while `doOnSubscribe` and `doOnUnsubscribe` show the subscribers after the caching. We see that the emission of values begins with the first subscription but ignores the fact that we unsubscribed. + + + + +## Multicast + +The `share` method is an alias for `Observable.publish().refCount()`. It allows your subscribers to share a subscription, which is kept for as long as there are subscribers. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/publishRefCount.png) + + + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Time-shifted sequences](/Part%203%20-%20Taming%20the%20sequence/5.%20Time-shifted%20sequences.md) | [Custom operators](/Part%203%20-%20Taming%20the%20sequence/7.%20Custom%20operators.md) | diff --git a/Part 3 - Taming the sequence/7. Custom operators.md b/Part 3 - Taming the sequence/7. Custom operators.md new file mode 100644 index 0000000..5123a19 --- /dev/null +++ b/Part 3 - Taming the sequence/7. Custom operators.md @@ -0,0 +1,407 @@ +# Custom operators + +RxJava offers a very large [operator set](http://reactivex.io/RxJava/javadoc/rx/Observable.html). Counting all the overloads, the number of operators on Rx is over 300. A smaller number of those is essential, meaning that you cannot achieve an Rx implementation without them. Many are there just for convenience and a self-descriptive name. For example, if `source.First(user -> user.isOnline())` didn't exist, we would still be able to do `source.filter(user -> user.isOnline()).First()`. + +Despite many convenience operators, the operator set of RxJava is still very basic. Rx offers the building blocks that you can combine into anything, but eventually you will want to define reuseable code for repeated cases. In standard Java, this would be done with custom classes and methods. In Rx, you would like the ability to design custom operators. For example, calculating a running average from a sequence of numbers may be very common in your financial application. That doesn't already exist in `Observable`, but you can make it yourself: + +```java +class AverageAcc { + public final int sum; + public final int count; + public AverageAcc(int sum, int count) { + this.sum = sum; + this.count = count; + } +} +``` +```java +source + .scan( + new AverageAcc(0,0), + (acc, v) -> new AverageAcc(acc.sum + v, acc.count + 1)) + .filter(acc -> acc.count > 0) + .map(acc -> acc.sum/(double)acc.count); +``` + +That does it, but it's not reusable. In a real financial application, you will probably want to do the same kind of processing over many different sequences. Even if you don't, it would still be nice to hide all this code behind a single phrase: "running average". Understandably, a Java developer's first instinct would be to make a function out of it: + +```java +public static Observable runningAverage(Observable source) { + return source + .scan( + new AverageAcc(0,0), + (acc, v) -> new AverageAcc(acc.sum + v, acc.count + 1)) + .filter(acc -> acc.count > 0) + .map(acc -> acc.sum/(double)acc.count); +} +``` + +And you can easily use it: + +```java +runningAverage(Observable.just(3, 5, 6, 4, 4)) + .subscribe(System.out::println); +``` +Output +``` +3.0 +4.0 +4.666666666666667 +4.5 +4.4 +``` + +The above example looks fine because it's simple. Let's do something a little more complicated with our custom operator. Let's take a phrase, turn it into a sequence of word lengths and calculate the running average for that. + +```java +runningAverage( + Observable.just("The brown fox jumped and I forget the rest") + .flatMap(phrase -> Observable.from(phrase.split(" "))) + .map(word -> word.length())) + .subscribe(System.out::println); +``` + +Once again, this works, but it doesn't look 100% Rx. Imagine if everything in Rx was done like the method which we designed (including all the existing operators). + +```java +subscribe( + lastOperator( + middleOperator( + firstOperator(source)))) +``` + +We're reading our pipeline in reverse! Yikes! + +## Chaining operators + +Rx has a particular style for applying operators, by chaining them, rather than nesting them. This style is not uncommon for objects whose methods return instances of the same type. This makes even more sense for immutable objects and can be found even in standard Java features, such as strings: +`String s = new String("Hi").toLowerCase().replace('a', 'c');` +This style allows you to see modifications in the order that they are applied, and it also looks neater when a lot of operators are used. + +Ideally, you would want your Rx operators to fit into the chain just like any other operator: +```java +Observable.range(0,10) + .map(i -> i*2) + .myOperator() + .subscribe(); +``` + +Many languages have ways of supporting this. Inconveniently, Java doesn't. You'd have to edit `Observable` itself to add your own methods. There's no point asking the RxJava team to add your idea to the operator set, since there are so many already and the RxJava team are conservative about adding yet another operator. You could `extend` `Observable` and add your own operators there. In that case, you'd no longer be able to share and combine libraries of operators. + +### compose + +There is a way of fitting a custom operator into the chain, with the `compose` method. + +```java +public Observable compose(Observable.Transformer transformer) +``` + +Aha! A `Transformer` interface! `Transformer` actually just an alias for the `Func1,Observable>` interface. It is a method that takes an `Observable` and returns an `Observable`, just like the one we made for calculating a running average. + +```java +Observable.just(3, 5, 6, 4, 4) + .compose(Main::runningAverage) + .subscribe(System.out::println); +``` + +Java doesn't let you reference a function by its name alone, so here we assumed the custom operator is in our Main class. We can see that now our operator fits perfecty into the chain, albeit with the boilerplate of calling `compose` first. For even better encapsulation, you should implement `Observable.Transformer` in a new class and move the whole implementation out of sight, along with its helper class(es). + +```java +public class RunningAverage implements Observable.Transformer { + private static class AverageAcc { + public final int sum; + public final int count; + public AverageAcc(int sum, int count) { + this.sum = sum; + this.count = count; + } + } + + @Override + public Observable call(Observable source) { + return source + .scan( + new AverageAcc(0,0), + (acc, v) -> new AverageAcc(acc.sum + v, acc.count + 1)) + .filter(acc -> acc.count > 0) + .map(acc -> acc.sum/(double)acc.count); + } +} +``` + +And we use it like this + +```java +source.compose(new RunningAverage()) +``` + +Most Rx operators are parameterisable. We can do this too. Let's extend the functionality of our operator with the possiblity to ignore values above a certain threshold. + +```java +public class RunningAverage implements Observable.Transformer { + private static class AverageAcc { + public final int sum; + public final int count; + public AverageAcc(int sum, int count) { + this.sum = sum; + this.count = count; + } + } + + final int threshold; + + public RunningAverage() { + this.threshold = Integer.MAX_VALUE; + } + + public RunningAverage(int threshold) { + this.threshold = threshold; + } + + @Override + public Observable call(Observable source) { + return source + .filter(i -> i< this.threshold) + .scan( + new AverageAcc(0,0), + (acc, v) -> new AverageAcc(acc.sum + v, acc.count + 1)) + .filter(acc -> acc.count > 0) + .map(acc -> acc.sum/(double)acc.count); + } +} +``` + +We just added the parameter as a field in the operator, added constructors for the uses that we cover and used the parameter in our Rx operations. Now we can do `source.compose(new RunningAverage(5))`, where, ideally, we would be calling `source.runningAverage(5)`. Java only lets us go this far. Rx is a functional paradigm, but Java is still primarily an object oriented language and quite conservative at that. + +You can a complete example of for this example operator [here](/tests/java/itrx/chapter3/custom/ComposeExample.java). + +### lift + +Internally, every Rx operator does 3 things + +1. It subscribes to the source and observes the values. +2. It transforms the observed sequence according to the operator's purpose. +3. It pushes the modified sequence to its own subscribers, by calling `onNext`, `onError` and `onCompleted`. + +The `compose` operator works with a method that makes an observable out of another. In doing so, it spares you the trouble of doing the 3 steps above manually: the intermediate subscribing and pushing is implicit within an Rx chain. That presumes that you can do the transformation by using existing operators. If the operators don't already exist, you need to do the processing in the traditional Java OOP way. This means extracting the values from the pipeline and re-pushing when processed. An `Observable.Transformer` that does this would include an explicit subscription to the source `Observable` and/or the explicit creation of a new `Observable` to be returned. + +You'll find that this is often just boilerplate, and that you can avoid some of it by going to a lower level. The `lift` operator is similar to `compose`, with the difference of transforming a `Subscriber`, instead of an `Observable`. + +```java +public final Observable lift(Observable.Operator lift) +``` + +And `Observable.Operator` is an alias for `Func1,Subscriber>`: a function that will transform a `Subscriber` into `Subscriber`. By dealing directly with `Subscriber` we avoid involving `Observable`. The boilerplate of subscribing to and creating `Observable` types will be handled by lift. + +If you studied the signature, there's something which seems backwards at first: to turn an `Observable` into `Observable`, we need a function that turns `Subscriber` into `Subscriber`. To understand why that is the case, remember that a subscription begins at the end of the chain and is propagated to the source. In other words, a subscription goes backwards through the chain of operators. Each operator receives a subscription (i.e. is subscribed to) and uses that subscription to create a subscription to the preceeding operator. + +In the next example, we will reimplement `map`, without using the existing implementation or any other existing operator. + +```java +class MyMap implements Observable.Operator { + + private Func1 transformer; + + public MyMap(Func1 transformer) { + this.transformer = transformer; + } + + @Override + public Subscriber call(Subscriber subscriber) { + return new Subscriber() { + + @Override + public void onCompleted() { + if (!subscriber.isUnsubscribed()) + subscriber.onCompleted(); + } + + @Override + public void onError(Throwable e) { + if (!subscriber.isUnsubscribed()) + subscriber.onError(e); + } + + @Override + public void onNext(T t) { + if (!subscriber.isUnsubscribed()) + subscriber.onNext(transformer.call(t)); + } + + }; + } +} +``` + +A `map` operator requires a function that transforms items from `T` to `R`. In our implementation, that's the `transformer` field. The key part is the `call` method. We receive a `Subscriber` that wants to receive items of type `R`. For that subscriber we create a new `Subscriber` that takes items of type `T`, transforms them to type `R` and pushes them to the `Subscriber`. `lift` handles the boilerplate of receiving the `Subscriber`, as well as using the created `Subscriber` to subscribe on the source observable. + +Using an `Observable.Operator` is as simple as using `Observable.Transformer`: +```java +Observable.range(0, 5) + .lift(new MyMap(i -> i + "!")) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter3/custom/LiftExample.java) +``` +0! +1! +2! +3! +4! +``` + +A class constructor in Java can't have its type parameters infered. A logical last step would be to make a method that can infer the types for us +```java +public static MyMap create(Func1 transformer) { + return new MyMap(transformer); +} +``` +And use like this +```java +Observable.range(0, 5) + .lift(MyMap.create(i -> i + "!")) + .subscribe(System.out::println); +``` + +When doing manual pushes to subscribers, like we do when implementing `Observable.Operator`, there are a few things to consider +* A subscriber is free to unsubscribe. Don't push without checking first: `!subscriber.isUnsubscribed()`. +* You are responsible for complying with the Rx contract: any number of `onNext` notifications, optionally followed by a single `onCompleted` or `onError`. +* If you need to perform asynchronous operations and scheduling, use the [Schedulers](/Part%204%20-%20Concurrency/1.%20Scheduling%20and%20threading.md#Schedulers) of Rx. That will allow your operator to become [testable](/Part%204%20-%20Concurrency/2.%20Testing%20Rx.md#testscheduler). + +### serialize + +If you can't guarantee that your operator will obey the Rx contract, for example because you push asynchronously from multiple sources, you can use the `serialize` operator. The `serialize` operator will turn an unreliable observable into a lawful, sequential observable. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/synchronize.png) + +Let's first create an observable that breaks the contract and subscribe to it. + +```java +Observable source = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onCompleted(); + o.onNext(3); + o.onCompleted(); +}); + +source.doOnUnsubscribe(() -> System.out.println("Unsubscribed")) + .subscribe( + System.out::println, + System.out::println, + () -> System.out.println("Completed")); +``` +[Output](/tests/java/itrx/chapter3/custom/SerializeExample.java) +``` +1 +2 +Completed +Unsubscribed +``` + +Despite what our observable tried to emit, the end result obeyed the Rx contract. That happened because `subscribe` terminated the subscription when it (very reasonably) thought that the sequence ended. This doesn't mean that the problem will always be taken care for us. There is also a method called `unsafeSubscribe`, which won't unsubscribe automatically. + +```java +Observable source = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onCompleted(); + o.onNext(3); + o.onCompleted(); +}); + +source.doOnUnsubscribe(() -> System.out.println("Unsubscribed")) + .unsafeSubscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("Completed"); + } + + @Override + public void onError(Throwable e) { + System.out.println(e); + } + + @Override + public void onNext(Integer t) { + System.out.println(t); + } +}); +``` +[Output](/tests/java/itrx/chapter3/custom/SerializeExample.java) +``` +1 +2 +Completed +3 +Completed +``` + +Our subscriber's intended behaviour was identical to the previous example (we created an instance of `Subscriber` because `unsafeSubscribe` doesn't have overloads that take lambdas). However, we can see here we weren't unsubscribed and we kept receiving notifications. + +`unsafeSubscribe` is unsafe in other regards as well, such as error handling. It's usefulness is limited. The documentation says that it should only be used for custom operators that use nested subscriptions. To protect such operators from receiving and illegal sequence, we can apply the `serialize` operator + +```java +Observable source = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onCompleted(); + o.onNext(3); + o.onCompleted(); + }) + .cast(Integer.class) + .serialize();; + + +source.doOnUnsubscribe(() -> System.out.println("Unsubscribed")) + .unsafeSubscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("Completed"); + } + + @Override + public void onError(Throwable e) { + System.out.println(e); + } + + @Override + public void onNext(Integer t) { + System.out.println(t); + } +}); +``` +[Output](/tests/java/itrx/chapter3/custom/SerializeExample.java) +``` +1 +2 +Completed +``` + +We see that, despite the fact that we did not unsubscribe, the illegal notifications were filtered out. + + + + +### Extra benefits of lift + +If the ability to use your custom operator in the chain like a standard operator is not convincing enough, using `lift` has one more unexpected advantage. Standard operators are also implemented using `lift`, which makes `lift` a hot method at runtime. JVM optimises for `lift` and operators that use `lift` receive a performance boost. That can include your operator, if you use `lift`. + + +## Choosing between `lift` and `compose` + +Both `lift` and `compose` are meta-operators, used for injecting a custom operator into the chain. In both cases, the custom operator can be implemented as a function or a class. +* `compose`: `Observable.Transformer` or `Func, Observable>` +* `lift`: `Observable.Operator` or `Func, Subscriber>` + +Theoretically, any operator can be implemented as both `Observable.Operator` and `Observable.Transformer`. The choice between the two is a question of convenience, and what kind of boilerplate you want to avoid. + +* If the custom operator is a composite of existing operators, `compose` is a natural fit. +* If the custom operator needs to extract values from the pipeline to process them and then push them back, `lift` is a better fit. + + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Hot and cold observables](/Part%203%20-%20Taming%20the%20sequence/6.%20Hot%20and%20Cold%20observables.md) | [Chapter 4 - Concurrency](/Part%204%20-%20Concurrency/1.%20Scheduling%20and%20threading.md) | diff --git a/Part 4 - Concurrency/1. Scheduling and threading.md b/Part 4 - Concurrency/1. Scheduling and threading.md new file mode 100644 index 0000000..c011a5f --- /dev/null +++ b/Part 4 - Concurrency/1. Scheduling and threading.md @@ -0,0 +1,395 @@ +# Scheduling and threading + +Because Rx is targeted at asynchronous systems and because Rx can naturally support multithreading, new Rx developers sometimes assume that Rx is multithreaded by default. It is important clarify before anything else that __Rx is single-threaded by default__. + +_Unless you specify otherwise_, every call to `onNext`/`onError`/`onCompleted` executes the entire chain of operators synchronously, including the actions of the final subscriber. We can see than in the following example: + +```java +final BehaviorSubject subject = BehaviorSubject.create(); +subject.subscribe(i -> { + System.out.println("Received " + i + " on " + Thread.currentThread().getId()); +}); + +int[] i = {1}; // naughty side-effects for examples only ;) +Runnable r = () -> { + synchronized(i) { + System.out.println("onNext(" + i[0] + ") on " + Thread.currentThread().getId()); + subject.onNext(i[0]++); + } +}; + +r.run(); // Execute on main thread +new Thread(r).start(); +new Thread(r).start(); +``` +[Output](/tests/java/itrx/chapter4/scheduling/SingleThreadedExample.java) +``` +onNext(1) on 1 +Received 1 on 1 +onNext(2) on 11 +Received 2 on 11 +onNext(3) on 12 +Received 3 on 12 +``` + +We see here that we called `onNext` on our subject from 3 different threads. Every time, the subscriber's action was executed on the same thread where the first `onNext` call came from. The same would be true no matter how many operators we had chained together. The value goes through the chain synchronously, unless we request otherwise. + + +## subscribeOn and observeOn + +`subscribeOn` and `observeOn` allow you to control the invocation of the subscription and the reception of notifications (what thread will call `onNext`/`onError`/`onCompleted` on your observer). + +```java +public final Observable observeOn(Scheduler scheduler) +public final Observable subscribeOn(Scheduler scheduler) +``` + +In Rx you don't juggle threads directly. Instead you wrap them in policies called `Scheduler`. We will see more on that later. + +### subscribeOn + +With `subscribeOn` you decide on what Scheduler the `Observable.create` is executed. Even if you're not calling `create` yourself, there is an internal equivalent to it. Consider the following example: + +```java +System.out.println("Main: " + Thread.currentThread().getId()); + +Observable.create(o -> { + System.out.println("Created on " + Thread.currentThread().getId()); + o.onNext(1); + o.onNext(2); + o.onCompleted(); + }) + //.subscribeOn(Schedulers.newThread()) + .subscribe(i -> { + System.out.println("Received " + i + " on " + Thread.currentThread().getId()); + }); + +System.out.println("Finished main: " + Thread.currentThread().getId()); +``` +[Output](/tests/java/itrx/chapter4/scheduling/SubscribeOnExample.java) +``` +Main: 1 +Created on 1 +Received 1 on 1 +Received 2 on 1 +Finished main: 1 +``` + +We see here that, not only is everything executed on the same thread, it is actually sequential: `subscribe` does not unblock until it has completed subscribing to (and thus creating) the observable, which includes executing the body of `create`'s lambda parameter. The calls to `onNext` within that lambda execute the entire chain of operators, all the way to the `println`. Effectively, subscribing on a `create`d observable is blocking. + +If you uncomment `.subscribeOn(Schedulers.newThread())`, the [output](/tests/java/itrx/chapter4/scheduling/SubscribeOnExample.java) now is +``` +Main: 1 +Finished main: 1 +Created on 11 +Received 1 on 11 +Received 2 on 11 +``` + +`Schedulers.newThread()` provided a new thread for our lambda function to run on. `subscribe` no longer blocks until `create`'s lambda is executed and the main thread is free to proceed. + +Some observables create their own threads regardless of you what you requested. For example, `Observable.interval` is asynchronous regardless. In such cases, `subscribeOn` will dictate on what thread to run the function which creates the resources, which typically won't be helpful. It gives you no control over what resources will be leased. + +```java +System.out.println("Main: " + Thread.currentThread().getId()); + +Observable.interval(100, TimeUnit.MILLISECONDS) + .subscribe(i -> { + System.out.println("Received " + i + " on " + Thread.currentThread().getId()); + }); + +System.out.println("Finished main: " + Thread.currentThread().getId()); +``` +[Output](/tests/java/itrx/chapter4/scheduling/SubscribeOnExample.java) +``` +Main: 1 +Finished main: 1 +Received 0 on 11 +Received 1 on 11 +Received 2 on 11 +``` + +### observeOn + +`observeOn` controls the other side of the pipeline. The creation and emission of values will work like normal, but the actions of your observer will be invoked on a different thread, as specified by the `Scheduler` policy. + +```java +Observable.create(o -> { + System.out.println("Created on " + Thread.currentThread().getId()); + o.onNext(1); + o.onNext(2); + o.onCompleted(); + }) + .observeOn(Schedulers.newThread()) + .subscribe(i -> + System.out.println("Received " + i + " on " + Thread.currentThread().getId())); +``` +[Output](/tests/java/itrx/chapter4/scheduling/ObserveOnExample.java) +``` +Created on 1 +Received 1 on 13 +Received 2 on 13 +``` + +Unlike `subscribeOn`, `observeOn`'s effect doesn't jump to the start of the pipeline. It just changes the thread for the operators that come after it. You can think of it as intercepting events and changing the thread for the rest of the chain. Here's an example for this: + +```java +Observable.create(o -> { + System.out.println("Created on " + Thread.currentThread().getId()); + o.onNext(1); + o.onNext(2); + o.onCompleted(); + }) + .doOnNext(i -> + System.out.println("Before " + i + " on " + Thread.currentThread().getId())) + .observeOn(Schedulers.newThread()) + .doOnNext(i -> + System.out.println("After " + i + " on " + Thread.currentThread().getId())) + .subscribe(); +``` +[Output](/tests/java/itrx/chapter4/scheduling/ObserveOnExample.java) +``` +Created on 1 +Before 1 on 1 +Before 2 on 1 +After 1 on 13 +After 2 on 13 +``` + +We can see here that events start on the thread that calls `onNext` and stay on that thread until they encounter the `observeOn` operator. After that, they continue on the new thread. This way you can assign different threading policies to different parts of an Rx pipeline. + +This is very useful if you, as the consumer of an observable, know that processing is time-consuming and you don't want to block the producing thread. A typical case for this is applications with a GUI. GUI libraries have a special thread that has the exclusive right to access GUI components (buttons etc). While the GUI thread is busy, everything becomes non-responsive. Handlers to GUI events are invoked on the GUI thread and everything the handler does will block the GUI thread. In order not to have your application freeze for the duration of the processing, you must move heavy processing to another thread. You can use `observeOn` to move away and again to return to the GUI thread, when the data is ready to be displayed. + +### unsubscribeOn + +As we have seen, some observables depend on resources which are leased on subscription and released when subscriptions end. Typically, releasing resources is cheap. In exceptional cases, where you need the unsubscription actions to not block or to specifically take place on a special thread, you can specify the scheduler that will execute those actions with `unsubscribeOn` + +```java +Observable source = Observable.using( + () -> { + System.out.println("Subscribed on " + Thread.currentThread().getId()); + return Arrays.asList(1,2); + }, + (ints) -> { + System.out.println("Producing on " + Thread.currentThread().getId()); + return Observable.from(ints); + }, + (ints) -> { + System.out.println("Unubscribed on " + Thread.currentThread().getId()); + } +); + +source + .unsubscribeOn(Schedulers.newThread()) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter4/scheduling/UnsubscribeOnExample.java) +``` +Subscribed on 1 +Producing on 1 +1 +2 +Unubscribed on 11 +``` + +The `using` method executes 3 functions, one that leases a resource, one that uses it and one the releases it. With `unsubscribeOn` we only affected the function that releases the resource. + + +## Schedulers + +The `observeOn` and `subscribeOn` methods take as an argument a [Scheduler](http://reactivex.io/RxJava/javadoc/rx/Scheduler.html). A `Scheduler`, as the name suggests, is a tool that can schedule individual actions to be performed. The specifics of how the action will be invoked depends on the implementation of the scheduler used. You can create your own implementation of scheduler, but most of time you'll find that RxJava already has you covered with a set of Schedulers for the common cases. You can get the existing implementations from the factory methods on [Schedulers](http://reactivex.io/RxJava/javadoc/rx/schedulers/Schedulers.html). + +The existing schedulers are as follows +* `immediate` executes the scheduled action synchronously. No actual scheduling takes place. +* `trampoline` queues work on the current thread to be executed after the current work completes. +* `newThread` creates a new thread for each scheduled unit of work. +* `computation` is intended for CPU work +* `io` is intended for IO work +* `test` is useful for testing and debugging. + +In the current implementation, `computation` and `io` schedulers aren't actually unique implementations. The point of this separation is to have unique instances, while also documenting your intent. + +Many of the Rx operators use schedulers internally. If you revisit the [Observable](http://reactivex.io/RxJava/javadoc/rx/Observable.html) operators that you've seen so far, you'll see that all the asynchronous operators have overloads that take a `Scheduler`. You can dictate exactly what scheduler each operator uses. You can also find in the documentation which scheduler they use when you don't provide one. + + +## Advanced features of schedulers + +The approaches and implementations for scheduling used in Rx schedulers aren't specific to Rx. In fact, they are quite standard and could be used without any Rx code. You typically won't have to use schedulers directly, except for when you are designing a custom asynchronous operator. Using schedulers in custom operators isn't only convenient, but it also allow asynchronous operators to become testable, as we will see in the next chapter. + +An implementation of `Scheduler` has two parts. One is the notion of time, which you can get through the `now()` method. Implementing time through the scheduler is going to prove useful when virtualising time for testing, but for now this feature isn't interesting. + +The interesting part is `createWorker()`, which returns a [Scheduler.Worker](http://reactivex.io/RxJava/javadoc/rx/Scheduler.Worker.html). A worker accepts actions and executes them sequentially on a single thread. In a way, a worker is a scheduler itself, but we will not refer to it as a scheduler to avoid confusion. + +### Scheduling an action + +The way to schedule a job on any `Scheduler` is to create a new worker for that scheduler and schedule actions on that worker. + +```java +Scheduler.Worker worker = scheduler.createWorker(); +worker.schedule( + () -> System.out.println("Action")); +``` + +The action is then queued to be executed on the thread that the worker is assigned to. + +As you would expect from a scheduler, you can also schedule actions to be executed in the future once or repeatedly with the following methods: +```java +Subscription schedule( + Action0 action, + long delayTime, + java.util.concurrent.TimeUnit unit) +Subscription schedulePeriodically( + Action0 action, + long initialDelay, + long period, + java.util.concurrent.TimeUnit unit) +``` + +```java +Scheduler scheduler = Schedulers.newThread(); +long start = System.currentTimeMillis(); +Scheduler.Worker worker = scheduler.createWorker(); +worker.schedule( + () -> System.out.println(System.currentTimeMillis()-start), + 5, TimeUnit.SECONDS); +worker.schedule( + () -> System.out.println(System.currentTimeMillis()-start), + 5, TimeUnit.SECONDS); +``` +[Output](/tests/java/itrx/chapter4/scheduling/SchedulerExample.java) +``` +5033 +5035 +``` + +We can see here that delay for the execution is measured from the moment of scheduling. The specified time is not a mandatory sleep period in between tasks. The worker can do work in the meantime, if there is work ready for execution. + +### Canceling work + +`Scheduler.Worker` extends `Subscription`. Calling the `unsubscribe` method on a worker will result in the queue being emptied and all pending tasks being cancelled. We can see that by modifying out previous example + +```java +Scheduler scheduler = Schedulers.newThread(); +long start = System.currentTimeMillis(); +Scheduler.Worker worker = scheduler.createWorker(); +worker.schedule( + () -> { + System.out.println(System.currentTimeMillis()-start); + worker.unsubscribe(); + }, + 5, TimeUnit.SECONDS); +worker.schedule( + () -> System.out.println(System.currentTimeMillis()-start), + 5, TimeUnit.SECONDS); +``` +[Output](/tests/java/itrx/chapter4/scheduling/SchedulerExample.java) +``` +5032 +``` + +The second task is never executed because the one before it cancelled everything. Actions that were in the process of being executed will be interrupted. In the next example, we will create a task that sleeps for 2000ms. 500ms after it has begun executing we cancel all work on the worker. This results in an `InterruptedException`. + +```java +Scheduler scheduler = Schedulers.newThread(); +long start = System.currentTimeMillis(); +Scheduler.Worker worker = scheduler.createWorker(); +worker.schedule(() -> { + try { + Thread.sleep(2000); + System.out.println("Action completed"); + } catch (InterruptedException e) { + System.out.println("Action interrupted"); + } +}); +Thread.sleep(500); +worker.unsubscribe(); +``` +[Output](/tests/java/itrx/chapter4/scheduling/SchedulerExample.java) +``` +Action interrupted +``` + +As we saw earlier in the signature of `schedule`, scheduling returns a `Subscription`. Rather that canceling all work, you can cancel individual tasks via that `Subscription` that was created while scheduling. + +## Existing schedulers + +### ImmediateScheduler + +`ImmediateScheduler` doesn't do any scheduling at all. Upon request for scheduling, the scheduler instead just executes the action synchronously and returns when the action is completed. Nested requests for scheduling will result in the actions being executed recursively. + +```java +Scheduler scheduler = Schedulers.immediate(); +Scheduler.Worker worker = scheduler.createWorker(); +worker.schedule(() -> { + System.out.println("Start"); + worker.schedule(() -> System.out.println("Inner")); + System.out.println("End"); +}); +``` +[Output](/tests/java/itrx/chapter4/scheduling/SchedulersExample.java) +``` +Start +Inner +End +``` + +### TrampolineScheduler + +The `TrampolineScheduler`'s worker is also synchronous but does not nest tasks. Instead, it begins with the initial task and any tasks scheduled while executing will be queued for after the current task has completed. + +``` +Scheduler scheduler = Schedulers.trampoline(); +Scheduler.Worker worker = scheduler.createWorker(); +worker.schedule(() -> { + System.out.println("Start"); + worker.schedule(() -> System.out.println("Inner")); + System.out.println("End"); +}); +``` +[Output](/tests/java/itrx/chapter4/scheduling/SchedulersExample.java) +``` +Start +End +Inner +``` + +The `TrampolineScheduler`'s worker executes every task on the thread that scheduled the first task. In this implementation, the first call to `schedule` is blocking until the queue is emptied. Any calls to `schedule` while executing will be non-blocking and the task will be executed by the thread is blocked. + +### NewThreadScheduler + +The `NewThreadScheduler` creates workers that each have their own thread. Every scheduled task will be executed on the thread corresponding to that particular worker. + +Let us first define a convenient method will make the following examples more readable. +```java +public static void printThread(String message) { + System.out.println(message + " on " + Thread.currentThread().getId()); +} +``` +Now we schedule work on a `NewThreadScheduler` worker and demonstrate that the worker is bound to a specific thread. +```java +printThread("Main"); +Scheduler scheduler = Schedulers.newThread(); +Scheduler.Worker worker = scheduler.createWorker(); +worker.schedule(() -> { + printThread("Start"); + worker.schedule(() -> printThread("Inner")); + printThread("End"); +}); +Thread.sleep(500); +worker.schedule(() -> printThread("Again")); +``` +[Output](/tests/java/itrx/chapter4/scheduling/SchedulersExample.java) +``` +Main on 1 +Start on 11 +End on 11 +Inner on 11 +Again on 11 +``` + + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Custom operators](/Part%203%20-%20Taming%20the%20sequence/7.%20Custom%20operators.md) | [Testing Rx](/Part%204%20-%20Concurrency/2.%20Testing%20Rx.md) | diff --git a/Part 4 - Concurrency/2. Testing Rx.md b/Part 4 - Concurrency/2. Testing Rx.md new file mode 100644 index 0000000..2849840 --- /dev/null +++ b/Part 4 - Concurrency/2. Testing Rx.md @@ -0,0 +1,230 @@ +# Testing + +When designing any system, you want guarantees about the correctness of the operations and that this quality does not regress as the system is modified throughout its lifetime. You'll design tests, which, ideally, should be automated. Modern software is backed by thorough unit tests and Rx code should be no different. + +Testing a synchronous piece of Rx code is as straight-forward as any unit test you are likely to find, using predefined sequences and [inspection](/Part 2 - Sequence Basics/3. Inspection.md). But what about asynchronous code? Consider testing the following piece of code: +```java +Observable.interval(1, TimeUnit.SECONDS) + .take(5) +``` +That is a sequence that takes 5 seconds to complete. That means every test that uses this sequence will take 5 seconds or more. That's not convenient at all, if you have thousands of tests to run. + +## TestScheduler + +The piece of code above isn't just time-consuming, it actually wastes all of that time doing nothing while waiting. If you could fast-forward the clock, that sequence would be evaluated almost instantly. You can't actually fast-forward your system's clock, but you can fast-forward a virtualised clock. It was a design decision in Rx to only use time through schedulers. This decision allows you to replace real time with a scheduler that virtualises time, called `TestScheduler`. + +The `TestScheduler` does scheduling in the same way as the schedulers that we saw in the chapter about [Scheduling and threading](/Part 4 - Concurrency/1. Scheduling and threading.md). It schedules actions to be executed either immediately or in the future. The difference is that time is frozen and only progresses upon request. We decide when time progresses and by how much. + +### advanceTimeTo + +As the name suggests, `advanceTimeTo` will execute all actions that are scheduled for up to a specific moment in time. That includes actions scheduled while the scheduler was being fast-forwarded, i.e. actions scheduled by other actions. + +```java +TestScheduler s = Schedulers.test(); + +s.createWorker().schedule( + () -> System.out.println("Immediate")); +s.createWorker().schedule( + () -> System.out.println("20s"), + 20, TimeUnit.SECONDS); +s.createWorker().schedule( + () -> System.out.println("40s"), + 40, TimeUnit.SECONDS); + +System.out.println("Advancing to 1ms"); +s.advanceTimeTo(1, TimeUnit.MILLISECONDS); +System.out.println("Virtual time: " + s.now()); + +System.out.println("Advancing to 10s"); +s.advanceTimeTo(10, TimeUnit.SECONDS); +System.out.println("Virtual time: " + s.now()); + +System.out.println("Advancing to 40s"); +s.advanceTimeTo(40, TimeUnit.SECONDS); +System.out.println("Virtual time: " + s.now()); +``` +[Output](/tests/java/itrx/chapter4/testing/TestSchedulerExample.java) +``` +Advancing to 1ms +Immediate +Virtual time: 1 +Advancing to 10s +Virtual time: 10000 +Advancing to 40s +20s +40s +Virtual time: 40000 +``` + +We scheduled 3 tasks: one to be executed immediately, and two to be executed in the future. Nothing happens until we advance time, including the tasks scheduled immediately. When we advance time, the scheduler synchronously executes all the tasks that were scheduled for that period of time, in the order of the time they were scheduled for. + +`advanceTimeTo` allows you to set time to any value, including one that is before the current time. This implementation decision can needlessly introduce bugs in the tests, so it is probably better to use the next method, when applicable. + +### advanceTimeBy + +`advanceTimeBy` advances time relative to the current moment in time. In every other regard, works like `advanceTimeTo`. + +```java +TestScheduler s = Schedulers.test(); + +s.createWorker().schedule( + () -> System.out.println("Immediate")); +s.createWorker().schedule( + () -> System.out.println("20s"), + 20, TimeUnit.SECONDS); +s.createWorker().schedule( + () -> System.out.println("40s"), + 40, TimeUnit.SECONDS); + +System.out.println("Advancing by 1ms"); +s.advanceTimeBy(1, TimeUnit.MILLISECONDS); +System.out.println("Virtual time: " + s.now()); + +System.out.println("Advancing by 10s"); +s.advanceTimeBy(10, TimeUnit.SECONDS); +System.out.println("Virtual time: " + s.now()); + +System.out.println("Advancing by 40s"); +s.advanceTimeBy(40, TimeUnit.SECONDS); +System.out.println("Virtual time: " + s.now()); +``` +[Output](/tests/java/itrx/chapter4/testing/TestSchedulerExample.java) +``` +Advancing by 1ms +Immediate +Virtual time: 1 +Advancing by 10s +Virtual time: 10001 +Advancing by 40s +20s +40s +Virtual time: 50001 +``` + +### triggerActions + +`triggerActions` does not advance time. It only executes actions that were scheduled to be executed up to the present. + +```java +TestScheduler s = Schedulers.test(); + +s.createWorker().schedule( + () -> System.out.println("Immediate")); +s.createWorker().schedule( + () -> System.out.println("20s"), + 20, TimeUnit.SECONDS); + +s.triggerActions(); +System.out.println("Virtual time: " + s.now()); +``` +[Output](/tests/java/itrx/chapter4/testing/TestSchedulerExample.java) +``` +Immediate +Virtual time: 0 +``` + +### Scheduling collisions + +There is nothing preventing actions from being scheduled for the same moment in time. When that happens, we have a scheduling collision. The order that two simultaneous tasks are executed is the same as the order in which they where scheduled. + +```java +TestScheduler s = Schedulers.test(); + +s.createWorker().schedule( + () -> System.out.println("First"), + 20, TimeUnit.SECONDS); +s.createWorker().schedule( + () -> System.out.println("Second"), + 20, TimeUnit.SECONDS); +s.createWorker().schedule( + () -> System.out.println("Third"), + 20, TimeUnit.SECONDS); + +s.advanceTimeTo(20, TimeUnit.SECONDS); +``` +[Output](/tests/java/itrx/chapter4/testing/TestSchedulerExample.java) +``` +First +Second +Third +``` + +## Testing + +Rx operators which involve asynchronous actions schedule those actions using a scheduler. If you take a look at all the operators in [Observable](http://reactivex.io/RxJava/javadoc/rx/Observable.html), you will see that such operators have overloads that take a scheduler. This is the way that you can supplement their real-time schedulers for your `TestScheduler`. + +Here is an [example](/tests/java/itrx/chapter4/testing/ExampleExample.java) where we will test the output of `Observable.interval` against what we expect it to emit. + +```java +@Test +public void test() { + TestScheduler scheduler = new TestScheduler(); + List expected = Arrays.asList(0L, 1L, 2L, 3L, 4L); + List result = new ArrayList<>(); + Observable + .interval(1, TimeUnit.SECONDS, scheduler) + .take(5) + .subscribe(i -> result.add(i)); + assertTrue(result.isEmpty()); + scheduler.advanceTimeBy(5, TimeUnit.SECONDS); + assertTrue(result.equals(expected)); +} +``` + +This is useful for testing small, self-contained pieces of Rx code, such as custom operators. A complete system may be using schedulers on its own, thus defeating our virtual time. [Lee Campbell suggested](http://www.introtorx.com/Content/v1.0.10621.0/16_TestingRx.html#TestingRx) abstracting over Rx's scheduler factories (`Schedulers`), with a provider of our own. When in debug-mode, our custom scheduler factory will replace all schedulers with a `TestScheduler`, which we will then use to control time throughout our system. + + +### TestSubscriber + +In the test above, we manually collected the values emitted and compared them against what we expected. This process is common enough in tests that Rx comes packaged with `TestSubscriber`, which will do that for us. Its event handlers will collect every notification received and make them available for us to inspect. With `TestSubscriber` our [previous test becomes](/tests/java/itrx/chapter4/testing/TestSubscriberExample.java): + +```java +@Test +public void test() { + TestScheduler scheduler = new TestScheduler(); + TestSubscriber subscriber = new TestSubscriber<>(); + List expected = Arrays.asList(0L, 1L, 2L, 3L, 4L); + Observable + .interval(1, TimeUnit.SECONDS, scheduler) + .take(5) + .subscribe(subscriber); + assertTrue(subscriber.getOnNextEvents().isEmpty()); + scheduler.advanceTimeBy(5, TimeUnit.SECONDS); + subscriber.assertReceivedOnNext(expected); +} +``` + +A `TestSubscriber` collects more than just values and exposes them through the following methods: + +```java +java.lang.Thread getLastSeenThread() +java.util.List> getOnCompletedEvents() +java.util.List getOnErrorEvents() +java.util.List getOnNextEvents() +``` + +There are two things to notice here. First is the `getLastSeenThread` method. A `TestSubscriber` checks on what thread it is notified and logs the most recent. That can be useful if, for example, you want to verify that an operation is/isn't executed on the GUI thread. Another interesting thing to notice is that there can be more than one termination event. That goes against how we defined our sequences in the begining of this guide. That is also the reason why the subscriber is capable of collecting multiple termination events: that would be a violation of the Rx contract and needs to be debugged. + +`TestSubscriber` provides shorthands for a few basic assertions: +```java +void assertNoErrors() +void assertReceivedOnNext(java.util.List items) +void assertTerminalEvent() +void assertUnsubscribed() +``` + +There is also a way to block execution until the observable, to which the `TestSubscriber` is subscribed, terminates. +```java +void awaitTerminalEvent() +void awaitTerminalEvent(long timeout, java.util.concurrent.TimeUnit unit) +void awaitTerminalEventAndUnsubscribeOnTimeout(long timeout, java.util.concurrent.TimeUnit unit) +``` + +Awaiting with a timeout will cause an exception if the observable fails to complete on time. + + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Scheduling and threading](/Part%204%20-%20Concurrency/1.%20Scheduling%20and%20threading.md) | [Sequences of coincidence](/Part%204%20-%20Concurrency/3.%20Sequences%20of%20coincidence.md) | diff --git a/Part 4 - Concurrency/3. Sequences of coincidence.md b/Part 4 - Concurrency/3. Sequences of coincidence.md new file mode 100644 index 0000000..aace31f --- /dev/null +++ b/Part 4 - Concurrency/3. Sequences of coincidence.md @@ -0,0 +1,296 @@ +# Sequences of coincidence + +Rx tries to avoid state outside of the pipeline. However, some things are inherently stateful. A server can be up or down, a mobile device may have access to wifi, a button is held down. In Rx, we see those as events with a duration and we call them windows. Other events that happen within those windows may need to be treated differently. For example, a mobile device will postpone network requests with low priority while using more expensive channels of communication. + +## Window + +With [buffer](https://github.com/Froussios/New-Intro-To-Rx/blob/master/Part%203%20-%20Taming%20the%20sequence/5.%20Time-shifted%20sequences.md#buffer), we saw an operator that can take a sequence and group values into chunks, based on a variety of overloads. The `window` operator has a one-to-one relationship with `buffer`. The main difference is that it doesn't return the groups in buffered chunks. Instead, it returns a sequence of sequences, each sequence corresponding to what would have been a buffer. This means that every emitted observable emits its values as soon as they appear in the source observable, rather than emitting them all at the end of the window. That relationship between `buffer` and `window` is immediately apparent by a quick look on the marble diagrams of two corresponding overloads: + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/buffer1.png) +With `window` this becomes: +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window1.png) + +If you are not already familiar with `buffer`, I strongly recommend that you begin with that. The overloads and resulting groupings are the same in both operators, but `buffer` is easier to understand and present examples for. Every `buffer` overload can be contructed from the `window` overload with the same arguments as such: +```java +source.buffer(...) +// same as +source.window(...).flatMap(w -> w.toList()) +``` + +### Window by count + +You can have windows with a fixed number of elements. Once the window has emitted the required number of elements, the observable terminates and a new one starts. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window3.png) + +You can also have skipping and overlapping windows like you do in `buffer` with `window(int count, int skip)`. When windows overlap they will be emitting values simultaneously, as can be seen in the next example. + +```java +Observable + .merge( + Observable.range(0, 5) + .window(3,1)) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter4/coincidence/WindowExample.java) +``` +0 +1 +1 +2 +2 +2 +3 +3 +3 +4 +4 +4 +``` + +We can see here that the inner observables are emitting the same item simultaneously. To more clearly see what each observable is emitting, let us format the output in a different way: + +```java +Observable.range(0, 5) + .window(3, 1) + .flatMap(o -> o.toList()) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter4/coincidence/WindowExample.java) +``` +[0, 1, 2] +[1, 2, 3] +[2, 3, 4] +[3, 4] +[4] +``` + +By turning the inner observables into lists, we see how closely related `window` is to `buffer`. + +### Window by time + +Rather than having windows of fixed size, you can have windows of a fixed duration in time. +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window5.png) + +You can contruct windows that overlap or skip elements, just like you would with `buffer`, with +```java +public final Observable> window(long timespan, long timeshift, java.util.concurrent.TimeUnit unit) +``` + +```java +Observable.interval(100, TimeUnit.MILLISECONDS) + .take(5) + .window(250, 100, TimeUnit.MILLISECONDS) + .flatMap(o -> o.toList()) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter4/coincidence/WindowExample.java) +``` +[0, 1] +[0, 1, 2] +[1, 2, 3] +[2, 3, 4] +[3, 4] +[4] +``` + +In this example, a new window begins every 100ms and lasts 250ms. The first window opens at time 0ms and remains open long enough to catch `[0, 1]` (interval emits the first value at time 100ms). Every subsequent window remains open long enough to catch the next 3 values, except for when the values stop. + +### Window with signal + +Lastly, you can define windows using another observable. Every time your signaling observable emits a value, the old window closes and a new one starts. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window1.png). + +Alternatively, to have overlapping windows, you can provide a function that uses the values emitted by your signaling observable to contruct another observable that will signal the closing of the window. When the observable terminates, the corresponding window closes. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/window2.png) + +Here's an example with overlapping windows based on signaling observables + +```java +Observable.interval(100, TimeUnit.MILLISECONDS) + .take(5) + .window( + Observable.interval(100, TimeUnit.MILLISECONDS), + o -> Observable.timer(250, TimeUnit.MILLISECONDS)) + .flatMap(o -> o.toList()) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter4/coincidence/WindowExample.java) +``` +[1, 2] +[2, 3] +[3, 4] +[4] +[] +``` + +This example is the same as the previous example: a new window opens every 100ms and lasts 250ms, with the exception that the first window starts at 100ms rather than 0ms. We see a difference in results, however. The window that begins at time 100ms does not catch the value that is emitted at 100ms, and the same goes for every other window. This happens because the `interval` event that begins the window fires just after the `interval` event that is the value. Even though the two events are simultaneous in theory, in practice there is no such thing. + +## Join + +`join` allows you to pair together items from two sequences. We've already seen `zip`, which pairs values based on their index. `join` allows you to pair values based on durations. Let's see the signature first: + +```java +public final Observable join( + Observable right, + Func1> leftDurationSelector, + Func1> rightDurationSelector, + Func2 resultSelector) +``` + +`join` combines two sequences, called "left" and "right". The method is not `stati`c and the left sequence is implied to be the one that `join` is being called on. In the signature, we can see two methods called `leftDurationSelector` and `rightDurationSelector`, which take as an argument an item of the respective sequence. They return an observable that defines a duration (i.e. a window), just like in the last overload of `window`. These windows are used to select values to be paired together. Values that are paired are passed to the `resultSelector` function which will combine them into a single value, like a `resultSelector` in `zip` does. That value will be emitted by `join`. + +The thing that makes `join` powerful, but also complicated to understand, is how values are selected to be paired. Every value that arrives in a sequence begins a window for itself. The corresponding duration selector decides when the window for each value will terminate. While the window is open, any value arriving in the opposite sequence will be paired with it. The process is symmetrical for the left and right sequences, so let's just consider a case where the items of only one sequence have windows. + +In the first example, the windows in the left sequence never close, while the windows in the right sequence are 0. + + + +```java +Observable left = + Observable.interval(100, TimeUnit.MILLISECONDS) + .map(i -> "L" + i); +Observable right = + Observable.interval(200, TimeUnit.MILLISECONDS) + .map(i -> "R" + i); + +left + .join( + right, + i -> Observable.never(), + i -> Observable.timer(0, TimeUnit.MILLISECONDS), + (l,r) -> l + " - " + r + ) + .take(10) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter4/coincidence/JoinExample.java) +``` +L0 - R0 +L1 - R0 +L0 - R1 +L1 - R1 +L2 - R1 +L3 - R1 +L0 - R2 +L1 - R2 +L2 - R2 +L3 - R2 +``` + +When a window for a left value never ends, what that means is that every value from the left sequence will be paired with every value that comes after it from the right sequence. Because here the right sequence has half the frequence of the left sequence, between two right values, two more windows have opened on the left. The first right value is paired with the first 2 left values, the second right value is paired with the first 4 left values, the third with 6 and so on. + +Lets change the example and see what happens when left and right emit every 100ms and left windows close after 150ms. What happens then is that every left window remains open long enough to catch two right values: one that is emitted at the same time and another after 100ms. + +```java +Observable left = + Observable.interval(100, TimeUnit.MILLISECONDS) + .map(i -> "L" + i); +Observable right = + Observable.interval(100, TimeUnit.MILLISECONDS) + .map(i -> "R" + i); + +left + .join( + right, + i -> Observable.timer(150, TimeUnit.MILLISECONDS), + i -> Observable.timer(0, TimeUnit.MILLISECONDS), + (l,r) -> l + " - " + r + ) + .take(10) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter4/coincidence/JoinExample.java) +``` +L0 - R0 +L0 - R1 +L1 - R1 +L1 - R2 +L2 - R2 +L2 - R3 +L3 - R3 +L3 - R4 +L4 - R4 +L4 - R5 +``` + +Both sequences have windows. Every value of a sequence is paired with: +* Any older value of the opposite sequence, if the window of the older value is still open +* Any newer value of the opposite sequence, if the window for this value is still open + + +## groupJoin + +As soon as it detected a pair, `join` passed the two values to the result selector and emitted the result. `groupJoin` takes it one step further. Let's start with the signature + +```java +public final Observable groupJoin( + Observable right, + Func1> leftDuration, + Func1> rightDuration, + Func2,? extends R> resultSelector) +``` + +The signature is the same as `join` exept for the `resultSelector`. Now the result selector takes an item from the left sequence and an observable of values from the right sequence. That observable will emit every right value that the left value is paired with. The pairing in `groupJoin` is symmetrical, just like `join`, but the contruction of results isn't. An alternative implementation of this method could have been if the argument of the `resultSelect` was a single `GroupedObservable`, where the left value is the key and the right values are being emitted. + +Lets revisit our example from `join` where the windows on the left never close. + +```java +Observable left = + Observable.interval(100, TimeUnit.MILLISECONDS) + .map(i -> "L" + i) + .take(6); +Observable right = + Observable.interval(200, TimeUnit.MILLISECONDS) + .map(i -> "R" + i) + .take(3); + +left + .groupJoin( + right, + i -> Observable.never(), + i -> Observable.timer(0, TimeUnit.MILLISECONDS), + (l, rs) -> rs.toList().subscribe(list -> System.out.println(l + ": " + list)) + ) + .subscribe(); +``` +[Output](/tests/java/itrx/chapter4/coincidence/GroupJoinExample.java) +``` +L0: [R0, R1, R2] +L1: [R0, R1, R2] +L2: [R1, R2] +L3: [R1, R2] +L4: [R2] +L5: [R2] +``` + +In the result selector, we have a left value and an observable of right values. We used that to print all the values from the right that were paired to each left value. If you go back to the example which used `join`, you'll see that the pairs are the same. What is changes is how they are made available to us in the `resultSelector`. + +You can implement `join` with `groupJoin` and `flatMap` +```java +.join( + right, + leftDuration + rightDuration, + (l,r) -> joinResultSelector(l,r) +) +// same as +.groupJoin( + right, + leftDuration + rightDuration, + (l, rs) -> rs.map(r -> joinResultSelector(l,r)) +) +.flatMap(i -> i) +``` + +You can also implement `groupJoin` with `join` and `groupBy`. Doing so would require you to contruct tuples as a result and do `groupBy` on the left part of the tuple. We will leave the code for this example to the reader's appetite for hands-on. + + +#### Continue reading + +| Previous | Next | +| --- | --- | +| [Testing Rx](/Part%204%20-%20Concurrency/2.%20Testing%20Rx.md) | [Backpressure](/Part%204%20-%20Concurrency/4.%20Backpressure.md) | diff --git a/Part 4 - Concurrency/4. Backpressure.md b/Part 4 - Concurrency/4. Backpressure.md new file mode 100644 index 0000000..395477c --- /dev/null +++ b/Part 4 - Concurrency/4. Backpressure.md @@ -0,0 +1,356 @@ +# Backpressure + +Rx leads events from one end of a pipeline to the other. The actions which take place on each end can be very dissimilar. What happens when the producer and the consumer require different amounts of time to process a value? In a synchronous model, this question isn't an issue. Consider the following example: + +```java +// Produce +Observable producer = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onCompleted(); +}); +// Consume +producer.subscribe(i -> { + try { + Thread.sleep(1000); + System.out.println(i); + } catch (Exception e) { } +}); +``` + +Here, the producer has its values ready and can emit them with no delay. The consumer is very slow by comparison, but this isn't going to cause problems, because the synchronous nature of the code above automatically regulates the rates of the producer and consumer. When `o.onNext(1);` is called, execution for the producer is blocked until the entire Rx chain completes. Only when that expression returns can the execution proceed to `o.onNext(2);`. + +This works like that only for synchronous execution. It is very common for the producer and the consumer to be asynchronous. So, what happens when a producer and a consumer operate asynchronously at different speeds? + +Let's first consider the traditional pull-based model, such as an iterator. In a pull-based model, the consumer requests the values. If the producer is slower, the consumer will block on request and resume when the next value arrives. If the producer is faster, then the producer will have idle time waiting for the consumer to request the next value. + +Rx push-based, not pull-based. In Rx, it is the producer that pushes values to the consumer when the values are ready. If the producer is slower, then the consumer will have idle time waiting for the next value to arrive. If the producer is faster, without any provisions, it will keep force-feeding data to consumer without ever knowing about the consumer's difficulties. + +```java +Observable.interval(1, TimeUnit.MILLISECONDS) + .observeOn(Schedulers.newThread()) + .subscribe( + i -> { + System.out.println(i); + try { + Thread.sleep(100); + } catch (Exception e) { } + }, + System.out::println); +``` +[Output](/tests/java/itrx/chapter4/backpressure/NoBackpressureExample.java) +``` +0 +1 +rx.exceptions.MissingBackpressureException +``` + +Here, the `MissingBackpressureException` is letting us know that the producer is too fast and the operators that we chained together can't deal with it. + +## Remedies for the consumer + +Some of the operators we've seen in previous chapters can help the consumer lessen the stress caused by too much input. + +#### Thin out the data + +The [sample](/Part 3 - Taming the sequence/5. Time-shifted sequences.md#sample) operator naturally allows you to specify a maximum rate of input, leaving out any excess data. +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/sample.o.png) + +```java +Observable.interval(1, TimeUnit.MILLISECONDS) + .observeOn(Schedulers.newThread()) + .sample(100, TimeUnit.MILLISECONDS) + .subscribe( + i -> { + System.out.println(i); + try { + Thread.sleep(100); + } catch (Exception e) { } + }, + System.out::println); +``` +[Output](/tests/java/itrx/chapter4/backpressure/ConsumerSideExample.java) +``` +82 +182 +283 +... +``` + +The are similar operators that can serve the same purpose. +* The [throttle](/Part 3 - Taming the sequence/5. Time-shifted sequences.md#throttling) family of operators also filters on rate, but allows you to specify in a different way which element to let through when stressed. +* [Debounce](/Part 3 - Taming the sequence/5. Time-shifted sequences.md#debouncing) does not cut the rate to a fixed maximum. Instead, it will completely remove every burst of information and replace it with a single value. + +#### Collect + +Instead of sampling the data, you can use `buffer` and `window` to collect overflowing data while the consumer is busy. This is useful if processing items in batches is faster. Alternatively, you can inspect the buffer to manually decide how many and which of the buffered items to process. + +In the example that we saw previously, the consumer processes single items and bulks at practically the same speed. Here we slowed down the producer to make the batches fit a line, but the principle remains the same. + +```java +Observable.interval(10, TimeUnit.MILLISECONDS) + .observeOn(Schedulers.newThread()) + .buffer(100, TimeUnit.MILLISECONDS) + .subscribe( + i -> { + System.out.println(i); + try { + Thread.sleep(100); + } catch (Exception e) { } + }, + System.out::println); +``` +[Output](/tests/java/itrx/chapter4/backpressure/ConsumerSideExample.java) +``` +[0, 1, 2, 3, 4, 5, 6, 7] +[8, 9, 10, 11, 12, 13, 14, 15, 16, 17] +[18, 19, 20, 21, 22, 23, 24, 25, 26, 27] +... +``` + + +## Reactive pull + +The above remedies are legitimate solutions to the problem. However, they aren't always the best way to deal with an overproducing observable. Sometimes the problem can be better handled on the side of the producer. Backpressure is a way for the pipeline to resist the emission of values. + +> Back pressure refers to pressure opposed to the desired flow of a fluid in a confined place such as a pipe. It is often caused by obstructions or tight bends in the confinement vessel along which it is moving, such as piping or air vents. _- Wikipedia_ + +RxJava has implemented a way for a subscriber to regulate the rate of an observable. The `Subscriber` has a `request(n)` method, with which it notifies the observable that it is ready to accept `n` more values. By calling `request` on the `onStart` method of your `Subscriber`, you establish reactive pull backpressure. This isn't a pull in the sense of a pull-based model: it doesn't return any values and will not block if values are not ready. Instead, it merely notifies the observable of how many values the `Subscriber` is ready to accept and to hold the rest. Subsequent calls to `request` will allow more values through. + +This is a `Subscriber` that takes values one at a time: +```java +class MySubscriber extends Subscriber { + @Override + public void onStart() { + request(1); + } + + @Override + public void onCompleted() { + ... + } + + @Override + public void onError(Throwable e) { + ... + } + + @Override + public void onNext(T n) { + ... + request(1); + } +} +``` + +The `request(1)` in `onStart` establishes backpressure and informs the observable that it should only emit the first value. After processing the value in `onNext`, we request the next item to be sent, if and when it is available. Calling `request(Long.MAX_VALUE)` disables backpressure. + +### doOnRequested + +Back when we were discussing the `doOn_` operators for [side effects](/Part%203%20-%20Taming%20the%20sequence/1.%20Side%20effects.md#do), we left out `doOnRequested`. +```java +public final Observable doOnRequest(Action1 onRequest) +``` +The `doOnRequested` meta-event happens when a subscriber requests for more items. The value supplied to the action is the number of items requested. + +At this moment, `doOnRequest` is in beta. In this book, we have been avoiding beta operators. We're making an exception here, because it enables us to peek into stable backpressure functionality that is otherwise hidden. Let's see what happens in a simple observable: + +```java +Observable.range(0, 3) + .doOnRequest(i -> System.out.println("Requested " + i)) + .subscribe(System.out::println); +``` +[Output](/tests/java/itrx/chapter4/backpressure/OnRequestExample.java) +``` +Requested 9223372036854775807 +0 +1 +2 +``` + +We see that `subscribe` requests the maximum number of items from the beginning. This means that `subscribe` doesn't resist values at all. Subscribe will only use backpressure if we provide a subscriber that implements backpressure. Here is a complete [example for a subscriber](/tests/java/itrx/chapter4/backpressure/ControlledPullSubscriber.java), which will allow us to control backpressure from the outside. + +```java +public class ControlledPullSubscriber extends Subscriber { + + private final Action1 onNextAction; + private final Action1 onErrorAction; + private final Action0 onCompletedAction; + + public ControlledPullSubscriber( + Action1 onNextAction, + Action1 onErrorAction, + Action0 onCompletedAction) { + this.onNextAction = onNextAction; + this.onErrorAction = onErrorAction; + this.onCompletedAction = onCompletedAction; + } + + public ControlledPullSubscriber( + Action1 onNextAction, + Action1 onErrorAction) { + this(onNextAction, onErrorAction, () -> {}); + } + + public ControlledPullSubscriber(Action1 onNextAction) { + this(onNextAction, e -> {}, () -> {}); + } + + @Override + public void onStart() { + request(0); + } + + @Override + public void onCompleted() { + onCompletedAction.call(); + } + + @Override + public void onError(Throwable e) { + onErrorAction.call(e); + } + + @Override + public void onNext(T t) { + onNextAction.call(t); + } + + public void requestMore(int n) { + request(n); + } +} +``` + +This simple implementation will not request values unless we manually make it do so with `requestMore`. + +```java +ControlledPullSubscriber puller = + new ControlledPullSubscriber(System.out::println); + +Observable.range(0, 3) + .doOnRequest(i -> System.out.println("Requested " + i)) + .subscribe(puller); + +puller.requestMore(2); +puller.requestMore(1); +``` +[Output](/tests/java/itrx/chapter4/backpressure/OnRequestExample.java) +``` +Requested 0 +Requested 2 +0 +1 +Requested 1 +2 +``` + +First we requested no emissions (our `ControlledPullSubscriber` does this in `onStart`). Then we requested 2 and we got 2 values, then we requested 1 and we got 1. + +Rx operators that use queues and buffers internally should use backpressure to avoid storing an infinite amount of values. Large-scale buffering should be left to operators that explicitly serve this purpose, such as `cache`, `buffer` etc. An example of an operator that needs to buffer items is `zip`: the first observable might emit two or more values before the second observable emits its next value. Such small asymmetries are expected even when the two sequences are supposed to have the same frequency. Needing to buffer a couple of items shouldn't cause the operator to fail. For that reason, `zip` has a small buffer of 128 items. + +```java +Observable.range(0, 300) + .doOnRequest(i -> System.out.println("Requested " + i)) + .zipWith( + Observable.range(10, 300), + (i1, i2) -> i1 + " - " + i2) + .take(300) + .subscribe(); +``` +[Output](/tests/java/itrx/chapter4/backpressure/OnRequestExample.java) +``` +Requested 128 +Requested 90 +Requested 90 +Requested 90 +``` + +The `zip` operator starts by requesting enough items to fill its buffer, and requests more when it consumes them. The details of how many items `zip` requests isn't interesting. What the reader should take away is the realization that some buffering and backpressure exist in Rx whether the developer requests for it or not. This gives an Rx pipeline some flexibility, where you might expect none. It might trick you into thinking that your code is solid, by silently saving small tests from failing, but you're not safe until you have explicitly declared behaviour with regard to backpressure. + + +## Backpressure policies + +Many Rx operators use backpressure internally to avoid overfilling their internal queues. This way, the problem of a slow consumer is propagated backwards in the chain of operators: if an operator stops accepting values, then the previous operator will fill its buffers until it stops accepting values too, and so on. Backpressure doesn't make the problem go away. It merely moves it where it may be handled better. We still need to decide what to do with the values of an overproducing observable. + +There are Rx operators that declare how you want to deal with situations where a subscriber cannot accept the values that are being emitted. + +### onBackpressureBuffer + +The `onBackpressureBuffer` operator with cause every value that can't be consumed to be stored until the observer can consume it. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/bp.obp.buffer.png) + +You can have a buffer of infinite size or a buffer with a maximum capacity. If the buffer overflows, the observable will fail. + +```java +Observable.interval(1, TimeUnit.MILLISECONDS) + .onBackpressureBuffer(1000) + .observeOn(Schedulers.newThread()) + .subscribe( + i -> { + System.out.println(i); + try { + Thread.sleep(100); + } catch (Exception e) { } + }, + System.out::println + ); +``` +[Output](/tests/java/itrx/chapter4/backpressure/OnBackpressureExample.java) +``` +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +rx.exceptions.MissingBackpressureException: Overflowed buffer of 1000 +``` + +What happens here is that the producer is 100 times faster than the consumer. We try to deal with that by buffering up to 1000 items. It is easy to calculate that, by the time that the consumer consumes the 11th item, the producer has produced 1100 items, well over our buffer's capacity. The sequence then fails, as it can't deal with the backpressure. + +### onBackpressureDrop + +The `onBackpressureDrop` operator discards items if they can't be received. + +![](https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/bp.obp.drop.png) + +```java +Observable.interval(1, TimeUnit.MILLISECONDS) + .onBackpressureDrop() + .observeOn(Schedulers.newThread()) + .subscribe( + i -> { + System.out.println(i); + try { + Thread.sleep(100); + } catch (Exception e) { } + }, + System.out::println); +``` +[Output](/tests/java/itrx/chapter4/backpressure/OnBackpressureExample.java) +``` +0 +1 +2 +... +126 +127 +12861 +12862 +... +``` + +What we see here is that the first 128 items where consumed normally, but then we jumped forward. The items in-between were dropped by `onBackPressureDrop`. Even though we did not request it, the first 128 items were still buffered, since `observeOn` uses a small buffer between switching threads. + + +| Previous | Next | +| --- | --- | +| [Sequences of coincidence](/Part%204%20-%20Concurrency/3.%20Sequences%20of%20coincidence.md) | | diff --git a/README.md b/README.md new file mode 100644 index 0000000..eadd1b8 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Intro to RxJava + +This guide aims to introduce a beginner reactive programmer to the complete power of the [RxJava](https://github.com/ReactiveX/RxJava) implementation of reactive programming for the JVM. It is based on the [IntroToRx](http://www.introtorx.com) guide for Rx.NET. + +No experience with either reactive or functional programming is needed to follow the book. Familiarity with the basics of Java is required. + +[Begin learning](/Part%201%20-%20Getting%20Started/1.%20Why%20Rx.md) + +### Structure + +The content of this book is meant to be read from start to finish. It is bigger than your average tutorial and smaller than an actual book. It begins with the basics and every subsequent chapter introduces increasingly advanced features and concepts. Sections of the book are intended to be self-containing and to-the-point, so that the book can be referred back to by non-beginners. + +The examples used in the book are also [available in compilable java files](/tests/java/itrx) in two formats: +* Examples that print to standard output (recommended for first-time readers) +* Silent, self-checking examples in the form of [JUnit](http://junit.org/) tests. +The readers are invited to study whichever style suits them best. + diff --git a/exercises/exercises.iml b/exercises/exercises.iml new file mode 100644 index 0000000..8bb97d8 --- /dev/null +++ b/exercises/exercises.iml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/pom.xml b/exercises/pom.xml new file mode 100644 index 0000000..4534c81 --- /dev/null +++ b/exercises/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + com.bobzone.rxjavatutorial + exercises + 1.0-SNAPSHOT + + + + io.reactivex.rxjava2 + rxjava + + + org.codehaus.groovy + groovy-all + 2.4.4 + + + org.spockframework + spock-core + 1.1-groovy-2.4 + test + + + + + \ No newline at end of file diff --git a/exercises/src/main/java/main/Book.java b/exercises/src/main/java/main/Book.java new file mode 100644 index 0000000..f6f883a --- /dev/null +++ b/exercises/src/main/java/main/Book.java @@ -0,0 +1,7 @@ +package main; + +public class Book { + + public Book() { + } +} diff --git a/exercises/src/test/java/main/BookTest.groovy b/exercises/src/test/java/main/BookTest.groovy new file mode 100644 index 0000000..24abb05 --- /dev/null +++ b/exercises/src/test/java/main/BookTest.groovy @@ -0,0 +1,18 @@ +package main + +import spock.lang.Specification +import spock.lang.Unroll + + +class BookTest extends Specification { + + @Unroll + def "this test should work because it does nothing"() { + given: + def book = null + when: + book = new Book() + then: + book != null + } +} diff --git a/exercises/target/classes/main/Book.class b/exercises/target/classes/main/Book.class new file mode 100644 index 0000000000000000000000000000000000000000..916ea7bd5c58e3d9412e864ab4c9068f4328eed0 GIT binary patch literal 251 zcmX|*!4APd5QhJ$YN=B325vZL#F03VI5ixI`?fYL)lI6rmy^W719&JgD`F@4cm8kw z+3f56cmf!p>7j(Wi-wDU;EYwKiV30A>23)2Om8J2h*TyQms2eBwTM$Dtw<*#-H2Qn z|ATh1S7$;kIti79Q>~99g16F_Jdtx{)~W{f58^6(*eEmjM+r^C!&GFua2X$DQlJfs rKW7ec@dB&EZus>;xpxQM7+7#wJ-!)o&_RVepJT>Xd19WZv0Csyf#fbE literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BookTest.class b/exercises/target/test-classes/main/BookTest.class new file mode 100644 index 0000000000000000000000000000000000000000..b518eeab0ef5c1a90911b2d8c6624fe6ae104c30 GIT binary patch literal 5218 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 literal 0 HcmV?d00001 diff --git a/tests/java/itrx/chapter1/AsyncSubjectExample.java b/tests/java/itrx/chapter1/AsyncSubjectExample.java new file mode 100644 index 0000000..5fe595c --- /dev/null +++ b/tests/java/itrx/chapter1/AsyncSubjectExample.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter1; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.observers.TestSubscriber; +import rx.subjects.AsyncSubject; + +public class AsyncSubjectExample { + + public void exampleLastValue() { + AsyncSubject s = AsyncSubject.create(); + s.subscribe(v -> System.out.println(v)); + s.onNext(0); + s.onNext(1); + s.onNext(2); + s.onCompleted(); + + // 2 + } + + public void exampleNoCompletion() { + AsyncSubject s = AsyncSubject.create(); + s.subscribe(v -> System.out.println(v)); + s.onNext(0); + s.onNext(1); + s.onNext(2); + } + + // + // Tests + // + + @Test + public void testLastValue() { + TestSubscriber tester = new TestSubscriber(); + + AsyncSubject s = AsyncSubject.create(); + s.subscribe(tester); + s.onNext(0); + s.onNext(1); + s.onNext(2); + s.onCompleted(); + + tester.assertReceivedOnNext(Arrays.asList(2)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testNoCompletion() { + TestSubscriber tester = new TestSubscriber(); + + AsyncSubject s = AsyncSubject.create(); + s.subscribe(tester); + s.onNext(0); + s.onNext(1); + s.onNext(2); + + tester.assertReceivedOnNext(Arrays.asList()); + assertTrue(tester.getOnCompletedEvents().size() == 0); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter1/BehaviorSubjectExample.java b/tests/java/itrx/chapter1/BehaviorSubjectExample.java new file mode 100644 index 0000000..6be1e92 --- /dev/null +++ b/tests/java/itrx/chapter1/BehaviorSubjectExample.java @@ -0,0 +1,115 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter1; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.observers.TestSubscriber; +import rx.subjects.BehaviorSubject; + +public class BehaviorSubjectExample { + + public void exampleLate() { + BehaviorSubject s = BehaviorSubject.create(); + s.onNext(0); + s.onNext(1); + s.onNext(2); + s.subscribe(v -> System.out.println("Late: " + v)); + s.onNext(3); + + // Late: 2 + // Late: 3 + } + + public void exampleCompleted() { + BehaviorSubject s = BehaviorSubject.create(); + s.onNext(0); + s.onNext(1); + s.onNext(2); + s.onCompleted(); + s.subscribe( + v -> System.out.println("Late: " + v), + e -> System.out.println("Error"), + () -> System.out.println("Completed") + ); + } + + public void exampleInitialvalue() { + BehaviorSubject s = BehaviorSubject.create(0); + s.subscribe(v -> System.out.println(v)); + s.onNext(1); + + // 0 + // 1 + } + + + // + // Tests + // + + @Test + public void testLate() { + TestSubscriber tester = new TestSubscriber(); + + BehaviorSubject s = BehaviorSubject.create(); + s.onNext(0); + s.onNext(1); + s.onNext(2); + s.subscribe(tester); + s.onNext(3); + + tester.assertReceivedOnNext(Arrays.asList(2,3)); + } + + @Test + public void testCompleted() { + TestSubscriber tester = new TestSubscriber(); + + BehaviorSubject s = BehaviorSubject.create(); + s.onNext(0); + s.onNext(1); + s.onNext(2); + s.onCompleted(); + s.subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList()); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testInitialvalue() { + TestSubscriber tester = new TestSubscriber(); + + BehaviorSubject s = BehaviorSubject.create(0); + s.subscribe(tester); + s.onNext(1); + + tester.assertReceivedOnNext(Arrays.asList(0,1)); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter1/PublishSubjectExample.java b/tests/java/itrx/chapter1/PublishSubjectExample.java new file mode 100644 index 0000000..e771477 --- /dev/null +++ b/tests/java/itrx/chapter1/PublishSubjectExample.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter1; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.observers.TestSubscriber; +import rx.subjects.PublishSubject; + +public class PublishSubjectExample { + + public void example() { + PublishSubject subject = PublishSubject.create(); + subject.onNext(1); + subject.subscribe(System.out::println); + subject.onNext(2); + subject.onNext(3); + subject.onNext(4); + + // 2 + // 3 + // 4 + } + + + // + // Test + // + + @Test + public void test() { + TestSubscriber tester = new TestSubscriber<>(); + + PublishSubject subject = PublishSubject.create(); + subject.onNext(1); + subject.subscribe(tester); + subject.onNext(2); + subject.onNext(3); + subject.onNext(4); + + tester.assertReceivedOnNext(Arrays.asList(2,3,4)); + } + +} diff --git a/tests/java/itrx/chapter1/ReplaySubjectExample.java b/tests/java/itrx/chapter1/ReplaySubjectExample.java new file mode 100644 index 0000000..4d01da6 --- /dev/null +++ b/tests/java/itrx/chapter1/ReplaySubjectExample.java @@ -0,0 +1,132 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter1; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; +import rx.subjects.ReplaySubject; + +public class ReplaySubjectExample { + + public void exampleEarlyLate() { + ReplaySubject s = ReplaySubject.create(); + s.subscribe(v -> System.out.println("Early:" + v)); + s.onNext(0); + s.onNext(1); + s.subscribe(v -> System.out.println("Late: " + v)); + s.onNext(2); + + // Early:0 + // Early:1 + // Late: 0 + // Late: 1 + // Early:2 + // Late: 2 + } + + public void exampleWithSize() { + ReplaySubject s = ReplaySubject.createWithSize(2); + s.onNext(0); + s.onNext(1); + s.onNext(2); + s.subscribe(v -> System.out.println("Late: " + v)); + s.onNext(3); + + // Late: 1 + // Late: 2 + // Late: 3 + } + + public void exampleWithTime() throws InterruptedException { + ReplaySubject s = ReplaySubject.createWithTime(150, TimeUnit.MILLISECONDS, Schedulers.immediate()); + s.onNext(0); + Thread.sleep(100); + s.onNext(1); + Thread.sleep(100); + s.onNext(2); + s.subscribe(v -> System.out.println("Late: " + v)); + s.onNext(3); + + // Late: 1 + // Late: 2 + // Late: 3 + } + + + // + // Test + // + + @Test + public void testEarlyLate() { + TestSubscriber tester = new TestSubscriber(); + + ReplaySubject s = ReplaySubject.create(); + s.subscribe(tester); + s.onNext(0); + s.onNext(1); + s.subscribe(tester); + s.onNext(2); + + tester.assertReceivedOnNext(Arrays.asList(0, 1, 0, 1, 2, 2)); + } + + @Test + public void testWithSize() { + TestSubscriber tester = new TestSubscriber(); + + ReplaySubject s = ReplaySubject.createWithSize(2); + s.onNext(0); + s.onNext(1); + s.onNext(2); + s.subscribe(tester); + s.onNext(3); + + tester.assertReceivedOnNext(Arrays.asList(1,2,3)); + } + + @Test + public void testWithTime() { + TestSubscriber tester = new TestSubscriber(); + TestScheduler scheduler = Schedulers.test(); + + ReplaySubject s = ReplaySubject.createWithTime(150, TimeUnit.MILLISECONDS, scheduler); + s.onNext(0); + scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); + s.onNext(1); + scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); + s.onNext(2); + s.subscribe(tester); + s.onNext(3); + + tester.assertReceivedOnNext(Arrays.asList(1,2,3)); + } + + +} diff --git a/tests/java/itrx/chapter1/RxContractExample.java b/tests/java/itrx/chapter1/RxContractExample.java new file mode 100644 index 0000000..4d15a25 --- /dev/null +++ b/tests/java/itrx/chapter1/RxContractExample.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter1; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.observers.TestSubscriber; +import rx.subjects.ReplaySubject; +import rx.subjects.Subject; + +public class RxContractExample { + + public void example() { + Subject s = ReplaySubject.create(); + s.subscribe(v -> System.out.println(v)); + s.onNext(0); + s.onCompleted(); + s.onNext(1); + s.onNext(2); + + // 0 + } + + public void examplePrintCompletion() { + Subject values = ReplaySubject.create(); + values.subscribe( + v -> System.out.println(v), + e -> System.out.println(e), + () -> System.out.println("Completed") + ); + values.onNext(0); + values.onNext(1); + values.onCompleted(); + values.onNext(2); + + // 0 + // 1 + } + + + // + // Test + // + + @Test + public void test() { + TestSubscriber tester = new TestSubscriber(); + + Subject s = ReplaySubject.create(); + s.subscribe(tester); + s.onNext(0); + s.onCompleted(); + s.onNext(1); + s.onNext(2); + + tester.assertReceivedOnNext(Arrays.asList(0)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testPrintCompletion() { + TestSubscriber tester = new TestSubscriber(); + + Subject values = ReplaySubject.create(); + values.subscribe(tester); + values.onNext(0); + values.onNext(1); + values.onCompleted(); + values.onNext(2); + + tester.assertReceivedOnNext(Arrays.asList(0,1)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter1/UnsubscribingExample.java b/tests/java/itrx/chapter1/UnsubscribingExample.java new file mode 100644 index 0000000..3d67086 --- /dev/null +++ b/tests/java/itrx/chapter1/UnsubscribingExample.java @@ -0,0 +1,133 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter1; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Subscription; +import rx.observers.TestSubscriber; +import rx.subjects.ReplaySubject; +import rx.subjects.Subject; +import rx.subscriptions.Subscriptions; + +public class UnsubscribingExample { + + public void exampleUnsubscribe() { + Subject values = ReplaySubject.create(); + Subscription subscription = values.subscribe( + v -> System.out.println(v), + e -> System.err.println(e), + () -> System.out.println("Done")); + values.onNext(0); + values.onNext(1); + subscription.unsubscribe(); + values.onNext(2); + + // 0 + // 1 + } + + public void exampleIndependentSubscriptions() { + Subject values = ReplaySubject.create(); + Subscription subscription1 = values.subscribe(v -> System.out + .println("First: " + v)); + values.subscribe(v -> System.out.println("Second: " + v)); + values.onNext(0); + values.onNext(1); + subscription1.unsubscribe(); + System.out.println("Unsubscribed first"); + values.onNext(2); + + // First: 0 + // Second: 0 + // First: 1 + // Second: 1 + // Unsubscribed first + // Second: 2 + } + + public void exampleUnsubscribeAction() { + Subscription s = Subscriptions + .create(() -> System.out.println("Clean")); + s.unsubscribe(); + + // Clean + } + + + // + // Tests + // + + @Test + public void testUnsubscribe() { + TestSubscriber tester = new TestSubscriber(); + + Subject values = ReplaySubject.create(); + Subscription subscription = values.subscribe(tester); + values.onNext(0); + values.onNext(1); + subscription.unsubscribe(); + values.onNext(2); + + tester.assertReceivedOnNext(Arrays.asList(0, 1)); + tester.assertUnsubscribed(); + } + + @Test + public void testIndependentSubscriptions() { + TestSubscriber tester1 = new TestSubscriber(); + TestSubscriber tester2 = new TestSubscriber(); + + Subject values = ReplaySubject.create(); + Subscription subscription1 = values.subscribe(tester1); + Subscription subscription2 = values.subscribe(tester2); + values.onNext(0); + values.onNext(1); + subscription1.unsubscribe(); + values.onNext(2); + + tester1.assertReceivedOnNext(Arrays.asList(0, 1)); + tester2.assertReceivedOnNext(Arrays.asList(0, 1, 2)); + tester1.assertUnsubscribed(); + assertFalse(tester2.isUnsubscribed()); + + subscription2.unsubscribe(); + } + + @Test + public void testUnsubscribeAction() { + boolean[] ran = { false }; + + Subscription s = Subscriptions.create(() -> ran[0] = true); + s.unsubscribe(); + + assertTrue(ran[0]); + } + +} diff --git a/tests/java/itrx/chapter2/aggregation/CollectExample.java b/tests/java/itrx/chapter2/aggregation/CollectExample.java new file mode 100644 index 0000000..592a979 --- /dev/null +++ b/tests/java/itrx/chapter2/aggregation/CollectExample.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.aggregation; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class CollectExample { + + public void example() { + Observable values = Observable.range(10,5); + + values + .collect( + () -> new ArrayList(), + (acc, value) -> acc.add(value)) + .subscribe(v -> System.out.println(v)); + + // [10, 11, 12, 13, 14] + } + + @Test + public void test() { + TestSubscriber> tester = new TestSubscriber<>(); + + Observable values = Observable.range(10,5); + + values + .collect( + () -> new ArrayList(), + (acc, value) -> acc.add(value)) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList( + Arrays.asList(10, 11, 12, 13, 14) + )); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter2/aggregation/CountExample.java b/tests/java/itrx/chapter2/aggregation/CountExample.java new file mode 100644 index 0000000..a01890a --- /dev/null +++ b/tests/java/itrx/chapter2/aggregation/CountExample.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.aggregation; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; + +public class CountExample { + + private class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } + } + + + public void example() { + Observable values = Observable.range(0, 3); + + values + .subscribe(new PrintSubscriber("Values")); + values + .count() + .subscribe(new PrintSubscriber("Count")); + + // Values: 0 + // Values: 1 + // Values: 2 + // Values: Completed + // Count: 3 + // Count: Completed + } + + public void exampleCountLong() { + Observable values = Observable.range(0, 3); + + values + .subscribe(new PrintSubscriber("Values")); + values + .countLong() + .subscribe(new PrintSubscriber("Count")); + + // Values: 0 + // Values: 1 + // Values: 2 + // Values: Completed + // Count: 3 + // Count: Completed + } + + + // + // Tests + // + + @Test + public void test() { + TestSubscriber testerSource = new TestSubscriber<>(); + TestSubscriber testerCount = new TestSubscriber<>(); + + Observable values = Observable.range(0, 3); + + values + .subscribe(testerSource); + values + .count() + .subscribe(testerCount); + + testerSource.assertReceivedOnNext(Arrays.asList(0,1,2)); + testerSource.assertTerminalEvent(); + testerSource.assertNoErrors(); + + testerCount.assertReceivedOnNext(Arrays.asList(3)); + testerCount.assertTerminalEvent(); + testerCount.assertNoErrors(); + } + + @Test + public void testCountLong() { + TestSubscriber testerSource = new TestSubscriber<>(); + TestSubscriber testerCount = new TestSubscriber<>(); + + Observable values = Observable.range(0, 3); + + values + .subscribe(testerSource); + values + .countLong() + .subscribe(testerCount); + + testerSource.assertReceivedOnNext(Arrays.asList(0,1,2)); + testerSource.assertTerminalEvent(); + testerSource.assertNoErrors(); + + testerCount.assertReceivedOnNext(Arrays.asList(3L)); + testerCount.assertTerminalEvent(); + testerCount.assertNoErrors(); + } + + + +} diff --git a/tests/java/itrx/chapter2/aggregation/FirstExample.java b/tests/java/itrx/chapter2/aggregation/FirstExample.java new file mode 100644 index 0000000..4c9f59d --- /dev/null +++ b/tests/java/itrx/chapter2/aggregation/FirstExample.java @@ -0,0 +1,170 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.aggregation; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class FirstExample { + + private class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } + } + + public void exampleFirst() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + values + .first() + .subscribe(new PrintSubscriber("First")); + + // 0 + } + + public void exampleFirstWithPredicate() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + values + .first(v -> v>5) + .subscribe(new PrintSubscriber("First")); + + // 6 + } + + public void exampleFirstOrDefault() { + Observable values = Observable.empty(); + + values + .firstOrDefault(-1L) + .subscribe(new PrintSubscriber("First")); + + // -1 + } + + public void exampleFirstOrDefaultWithPredicate() { + Observable values = Observable.empty(); + + values + .firstOrDefault(-1L, v -> v>5) + .subscribe(new PrintSubscriber("First")); + + // -1 + } + + + // + // Tests + // + + @Test + public void testFirst() { + TestSubscriber tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + + values + .first() + .subscribe(tester); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + tester.assertReceivedOnNext(Arrays.asList(0L)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testFirstWithPredicate() { + TestSubscriber tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + + values + .first(v -> v>5) + .subscribe(tester); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + tester.assertReceivedOnNext(Arrays.asList(6L)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testFirstOrDefault() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.empty(); + + values + .firstOrDefault(-1L) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(-1L)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testFirstOrDefaultWithPredicate() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.empty(); + + values + .firstOrDefault(-1L, v -> v>5) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(-1L)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} + + diff --git a/tests/java/itrx/chapter2/aggregation/GroupByExample.java b/tests/java/itrx/chapter2/aggregation/GroupByExample.java new file mode 100644 index 0000000..0adcb6f --- /dev/null +++ b/tests/java/itrx/chapter2/aggregation/GroupByExample.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.aggregation; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class GroupByExample { + + public void exampleGroupBy() { + Observable values = Observable.just( + "first", + "second", + "third", + "forth", + "fifth", + "sixth" + ); + + values.groupBy(word -> word.charAt(0)) + .flatMap(group -> + group.last().map(v -> group.getKey() + ": " + v) + ) + .subscribe(v -> System.out.println(v)); + + // s: sixth + // t: third + // f: fifth + } + + + // + // Tests + // + + @Test + public void testGroupBy() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.just( + "first", + "second", + "third", + "forth", + "fifth", + "sixth" + ); + + values.groupBy(word -> word.charAt(0)) + .flatMap(group -> + group.last().map(v -> group.getKey() + ": " + v) + ) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList("s: sixth", "t: third", "f: fifth")); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } +} + + diff --git a/tests/java/itrx/chapter2/aggregation/LastExample.java b/tests/java/itrx/chapter2/aggregation/LastExample.java new file mode 100644 index 0000000..fd759f9 --- /dev/null +++ b/tests/java/itrx/chapter2/aggregation/LastExample.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.aggregation; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; + +public class LastExample { + + private class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } + } + + public void exampleLast() { + Observable values = Observable.range(0,10); + + values + .last() + .subscribe(new PrintSubscriber("Last")); + + // 9 + } + + public void exampleLastWithPredicate() { + Observable values = Observable.range(0,10); + + values + .last(v -> v<5) + .subscribe(new PrintSubscriber("Last")); + + // 4 + } + + public void exampleLastOrDefault() { + Observable values = Observable.empty(); + + values + .lastOrDefault(-1) + .subscribe(new PrintSubscriber("Last")); + + // -1 + } + + public void exampleLastOrDefaultWithPredicate() { + Observable values = Observable.empty(); + + values + .lastOrDefault(-1, v -> v>5) + .subscribe(new PrintSubscriber("Last")); + + // -1 + } + + + // + // Tests + // + + @Test + public void testLast() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.range(0,10); + + values + .last() + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(9)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testLastWithPredicate() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.range(0,10); + + values + .last(v -> v<5) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(4)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testLastOrDefault() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.empty(); + + values + .lastOrDefault(-1) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(-1)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testLastOrDefaultWithPredicate() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.empty(); + + values + .lastOrDefault(-1, v -> v<5) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(-1)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} + + diff --git a/tests/java/itrx/chapter2/aggregation/NestExample.java b/tests/java/itrx/chapter2/aggregation/NestExample.java new file mode 100644 index 0000000..cfa027d --- /dev/null +++ b/tests/java/itrx/chapter2/aggregation/NestExample.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.aggregation; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class NestExample { + + public void example() { + Observable.range(0, 3) + .nest() + .subscribe(ob -> ob.subscribe(System.out::println)); + + // 0 + // 1 + // 2 + } + + + // + // Test + // + + @Test + public void test() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable.range(0, 3) + .nest() + .subscribe(ob -> ob.subscribe(tester)); + + tester.assertReceivedOnNext(Arrays.asList(0,1,2)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter2/aggregation/ReduceExample.java b/tests/java/itrx/chapter2/aggregation/ReduceExample.java new file mode 100644 index 0000000..1592ee9 --- /dev/null +++ b/tests/java/itrx/chapter2/aggregation/ReduceExample.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.aggregation; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; + +public class ReduceExample { + + private class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } + } + + + public void example() { + Observable values = Observable.range(0,5); + + values + .reduce((i1,i2) -> i1+i2) + .subscribe(new PrintSubscriber("Sum")); + values + .reduce((i1,i2) -> (i1>i2) ? i2 : i1) + .subscribe(new PrintSubscriber("Min")); + + // Sum: 10 + // Sum: Completed + // Min: 0 + // Min: Completed + } + + public void exampleWithAccumulator() { + Observable values = Observable.just("Rx", "is", "easy"); + + values + .reduce(0, (acc,next) -> acc + 1) + .subscribe(new PrintSubscriber("Count")); + + // Count: 3 + // Count: Completed + } + + + // + // Tests + // + + @Test + public void test() { + TestSubscriber testerSum = new TestSubscriber<>(); + TestSubscriber testerMin = new TestSubscriber<>(); + + Observable values = Observable.range(0,5); + + values + .reduce((i1,i2) -> i1+i2) + .subscribe(testerSum); + values + .reduce((i1,i2) -> (i1>i2) ? i2 : i1) + .subscribe(testerMin); + + testerSum.assertReceivedOnNext(Arrays.asList(10)); + testerSum.assertTerminalEvent(); + testerSum.assertNoErrors(); + testerMin.assertReceivedOnNext(Arrays.asList(0)); + testerMin.assertTerminalEvent(); + testerMin.assertNoErrors(); + } + + @Test + public void testWithAccumulator() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.just("Rx", "is", "easy"); + + values + .reduce(0, (acc,next) -> acc + 1) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(3)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} + + diff --git a/tests/java/itrx/chapter2/aggregation/ScanExample.java b/tests/java/itrx/chapter2/aggregation/ScanExample.java new file mode 100644 index 0000000..018921d --- /dev/null +++ b/tests/java/itrx/chapter2/aggregation/ScanExample.java @@ -0,0 +1,147 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.aggregation; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; +import rx.subjects.ReplaySubject; +import rx.subjects.Subject; + +public class ScanExample { + + private class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } + } + + + public void exampleRunningSum() { + Observable values = Observable.range(0,5); + + values + .scan((i1,i2) -> i1+i2) + .subscribe(new PrintSubscriber("Sum")); + + // Sum: 0 + // Sum: 1 + // Sum: 3 + // Sum: 6 + // Sum: 10 + // Sum: Completed + } + + public void exampleRunningMin() { + Subject values = ReplaySubject.create(); + + values + .subscribe(new PrintSubscriber("Values")); + values + .scan((i1,i2) -> (i1 tester = new TestSubscriber<>(); + + Observable values = Observable.range(0,5); + + values + .scan((i1,i2) -> i1+i2) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0,1,3,6,10)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testRunningMin() { + TestSubscriber testerSource = new TestSubscriber<>(); + TestSubscriber testerScan = new TestSubscriber<>(); + + Subject values = ReplaySubject.create(); + + values + .subscribe(testerSource); + values + .scan((i1,i2) -> (i1{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } + } + + public void exampleSingle() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + values.take(10) + .single(v -> v == 5L) // Emits a result + .subscribe(new PrintSubscriber("Single1")); + values + .single(v -> v == 5L) // Never emits + .subscribe(new PrintSubscriber("Single2")); + + // Single1: 5 + // Single1: Completed + } + + public void exampleSingleOrDefault() { + Observable values = Observable.empty(); + + values + .singleOrDefault(-1) + .subscribe(new PrintSubscriber("SingleOrDefault")); + + // SingleOrDefault: -1 + // SingleOrDefault: Completed + } + + + // + // Tests + // + + @Test + public void testSingle() { + TestSubscriber tester1 = new TestSubscriber<>(); + TestSubscriber tester2 = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + + Subscription s1 = values.take(10) + .single(v -> v == 5L) // Emits a result + .subscribe(tester1); + Subscription s2 = values + .single(v -> v == 5L) // Never emits + .subscribe(tester2); + + scheduler.advanceTimeBy(2, TimeUnit.SECONDS); + + tester1.assertReceivedOnNext(Arrays.asList(5L)); + tester1.assertTerminalEvent(); + tester1.assertNoErrors(); + tester2.assertReceivedOnNext(Arrays.asList()); + assertEquals(tester2.getOnCompletedEvents().size(), 0); + tester2.assertNoErrors(); + + s1.unsubscribe(); + s2.unsubscribe(); + } + + @Test + public void testSingleOrDefault() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.empty(); + + values + .singleOrDefault(-1) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(-1)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } +} + + diff --git a/tests/java/itrx/chapter2/aggregation/ToCollectionExample.java b/tests/java/itrx/chapter2/aggregation/ToCollectionExample.java new file mode 100644 index 0000000..f33c687 --- /dev/null +++ b/tests/java/itrx/chapter2/aggregation/ToCollectionExample.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.aggregation; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class ToCollectionExample { + + public void exampleCustom() { + Observable values = Observable.range(10,5); + + values + .reduce( + new ArrayList(), + (acc, value) -> { + acc.add(value); + return acc; + }) + .subscribe(v -> System.out.println(v)); + + // [10, 11, 12, 13, 14] + } + + public void exampleToList() { + Observable values = Observable.range(10,5); + + values + .toList() + .subscribe(v -> System.out.println(v)); + + // [10, 11, 12, 13, 14] + } + + public void exampleToSortedList() { + Observable values = Observable.range(10,5); + + values + .toSortedList((i1,i2) -> i2 - i1) + .subscribe(v -> System.out.println(v)); + + // [14, 13, 12, 11, 10] + } + + + // + // Tests + // + + @Test + public void testCustom() { + TestSubscriber> tester = new TestSubscriber<>(); + + Observable values = Observable.range(10,5); + + values + .reduce( + new ArrayList(), + (acc, value) -> { + acc.add(value); + return acc; + }) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(Arrays.asList(10, 11, 12, 13, 14))); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testToList() { + TestSubscriber> tester = new TestSubscriber<>(); + + Observable values = Observable.range(10,5); + + values + .toList() + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(Arrays.asList(10, 11, 12, 13, 14))); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testToSortedList() { + TestSubscriber> tester = new TestSubscriber<>(); + + Observable values = Observable.range(10,5); + + values + .toSortedList((i1,i2) -> i2 - i1) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(Arrays.asList(14, 13, 12, 11, 10))); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} + + diff --git a/tests/java/itrx/chapter2/aggregation/ToMapExample.java b/tests/java/itrx/chapter2/aggregation/ToMapExample.java new file mode 100644 index 0000000..5d064f5 --- /dev/null +++ b/tests/java/itrx/chapter2/aggregation/ToMapExample.java @@ -0,0 +1,315 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.aggregation; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; + +public class ToMapExample { + + private class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } + } + + private static class Person { + public final String name; + public final Integer age; + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Person) { + Person o = (Person) obj; + return this.name == o.name && + this.age == o.age; + } + return false; + } + } + + + public void exampleToMap() { + Observable values = Observable.just( + new Person("Will", 25), + new Person("Nick", 40), + new Person("Saul", 35) + ); + + values + .toMap(person -> person.name) + .subscribe(new PrintSubscriber("toMap")); + + // toMap: {Saul=Person@7cd84586, Nick=Person@30dae81, Will=Person@1b2c6ec2} + // toMap: Completed + } + + public void exampleToMapWithSelector() { + Observable values = Observable.just( + new Person("Will", 25), + new Person("Nick", 40), + new Person("Saul", 35) + ); + + values + .toMap( + person -> person.name, + person -> person.age) + .subscribe(new PrintSubscriber("toMap")); + + // toMap: {Saul=35, Nick=40, Will=25} + // toMap: Completed + } + + public void exampleToMapWithCustomContainer() { + Observable values = Observable.just( + new Person("Will", 25), + new Person("Nick", 40), + new Person("Saul", 35) + ); + + values + .toMap( + person -> person.name, + person -> person.age, + () -> new HashMap()) + .subscribe(new PrintSubscriber("toMap")); + + // toMap: {Saul=35, Nick=40, Will=25} + // toMap: Completed + } + + public void exampleToMultimap() { + Observable values = Observable.just( + new Person("Will", 35), + new Person("Nick", 40), + new Person("Saul", 35) + ); + + values + .toMultimap( + person -> person.age, + person -> person.name) + .subscribe(new PrintSubscriber("toMap")); + + // toMap: {35=[Will, Saul], 40=[Nick]} + // toMap: Completed + } + + public void exampleToMultimapWithCustomContainers() { + Observable values = Observable.just( + new Person("Will", 35), + new Person("Nick", 40), + new Person("Saul", 35) + ); + + values + .toMultimap( + person -> person.age, + person -> person.name, + () -> new HashMap<>(), + (key) -> new ArrayList<>()) + .subscribe(new PrintSubscriber("toMap")); + + // toMap: {35=[Will, Saul], 40=[Nick]} + // toMap: Completed + } + + + // + // Tests + // + + @SuppressWarnings("serial") + @Test + public void testToMap() { + TestSubscriber> tester = new TestSubscriber<>(); + + Person will = new Person("Will", 25); + Person nick = new Person("Nick", 40); + Person saul = new Person("Saul", 35); + + Observable values = Observable.just( + will, nick, saul + ); + + values + .toMap(person -> person.name) + .subscribe(tester); + + assertEquals(tester.getOnNextEvents(), Arrays.asList(new HashMap() {{ + this.put(will.name, will); + this.put(nick.name, nick); + this.put(saul.name, saul); + }})); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + +// toMap: {Saul=Person@7cd84586, Nick=Person@30dae81, Will=Person@1b2c6ec2} +// toMap: Completed + } + + @SuppressWarnings("serial") + @Test + public void testToMapWithSelector() { + TestSubscriber> tester = new TestSubscriber<>(); + + Person will = new Person("Will", 25); + Person nick = new Person("Nick", 40); + Person saul = new Person("Saul", 35); + + Observable values = Observable.just( + will, nick, saul + ); + + values + .toMap( + person -> person.name, + person -> person.age) + .subscribe(tester); + + assertEquals(tester.getOnNextEvents(), Arrays.asList(new HashMap() {{ + this.put(will.name, will.age); + this.put(nick.name, nick.age); + this.put(saul.name, saul.age); + }})); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @SuppressWarnings("serial") + @Test + public void testToMapWithCustomContainer() { + TestSubscriber> tester = new TestSubscriber<>(); + + Person will = new Person("Will", 25); + Person nick = new Person("Nick", 40); + Person saul = new Person("Saul", 35); + + Observable values = Observable.just( + will, nick, saul + ); + + values + .toMap( + person -> person.name, + person -> person.age, + () -> new HashMap()) + .subscribe(tester); + + assertEquals(tester.getOnNextEvents(), Arrays.asList(new HashMap() {{ + this.put(will.name, will.age); + this.put(nick.name, nick.age); + this.put(saul.name, saul.age); + }})); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @SuppressWarnings("serial") + @Test + public void testToMultimap() { + TestSubscriber>> tester = new TestSubscriber<>(); + + Person will = new Person("Will", 35); + Person nick = new Person("Nick", 40); + Person saul = new Person("Saul", 35); + + Observable values = Observable.just( + will, nick, saul + ); + + values + .toMultimap( + person -> person.age, + person -> person.name) + .subscribe(tester); + + assertEquals(tester.getOnNextEvents(), Arrays.asList(new HashMap>() {{ + this.put(35, Arrays.asList(will.name, saul.name)); + this.put(40, Arrays.asList(nick.name)); + }})); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @SuppressWarnings("serial") + @Test + public void testToMultimapWithCustomContainers() { + TestSubscriber>> tester = new TestSubscriber<>(); + + Person will = new Person("Will", 35); + Person nick = new Person("Nick", 40); + Person saul = new Person("Saul", 35); + + Observable values = Observable.just( + will, nick, saul + ); + + values + .toMultimap( + person -> person.age, + person -> person.name, + () -> new HashMap<>(), + (key) -> new ArrayList<>()) + .subscribe(tester); + + assertEquals(tester.getOnNextEvents(), Arrays.asList(new HashMap>() {{ + this.put(35, Arrays.asList(will.name, saul.name)); + this.put(40, Arrays.asList(nick.name)); + }})); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} + + diff --git a/tests/java/itrx/chapter2/creating/FromExample.java b/tests/java/itrx/chapter2/creating/FromExample.java new file mode 100644 index 0000000..71be04b --- /dev/null +++ b/tests/java/itrx/chapter2/creating/FromExample.java @@ -0,0 +1,152 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.creating; + +import java.util.Arrays; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class FromExample { + + public void exampleFromFuture() { + FutureTask f = new FutureTask(() -> { + Thread.sleep(2000); + return 21; + }); + new Thread(f).start(); + + Observable values = Observable.from(f); + + values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // Received: 21 + // Completed + } + + public void exampleFromFutureTimeout() { + FutureTask f = new FutureTask(() -> { + Thread.sleep(2000); + return 21; + }); + new Thread(f).start(); + + Observable values = Observable.from(f, 1000, TimeUnit.MILLISECONDS); + + values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // Error: java.util.concurrent.TimeoutException + } + + public void exampleFromArray() { + Integer[] is = {1,2,3}; + Observable values = Observable.from(is); + values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // Received: 1 + // Received: 2 + // Received: 3 + // Completed + } + + public void exampleFromIterable() { + Iterable input = Arrays.asList(1,2,3); + Observable values = Observable.from(input); + values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // Received: 1 + // Received: 2 + // Received: 3 + // Completed + } + + + // + // Tests + // + + @Test + public void testFromFuture() { + TestSubscriber tester = new TestSubscriber(); + + FutureTask f = new FutureTask(() -> { + return 21; + }); + new Thread(f).start(); + + Observable values = Observable.from(f); + + values.subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(21)); + tester.assertNoErrors(); + tester.assertTerminalEvent(); + } + + @Test + public void testFromArray() { + TestSubscriber tester = new TestSubscriber(); + + Integer[] input = {1,2,3}; + Observable values = Observable.from(input); + values.subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(input)); + tester.assertNoErrors(); + tester.assertTerminalEvent(); + } + + @Test + public void testFromIterable() { + TestSubscriber tester = new TestSubscriber(); + + Iterable input = Arrays.asList(1,2,3); + Observable values = Observable.from(input); + values.subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(1,2,3)); + tester.assertNoErrors(); + tester.assertTerminalEvent(); + } + +} diff --git a/tests/java/itrx/chapter2/creating/FunctionalUnfoldsExample.java b/tests/java/itrx/chapter2/creating/FunctionalUnfoldsExample.java new file mode 100644 index 0000000..050ef2f --- /dev/null +++ b/tests/java/itrx/chapter2/creating/FunctionalUnfoldsExample.java @@ -0,0 +1,160 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.creating; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscription; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class FunctionalUnfoldsExample { + + public void exampleRange() { + Observable values = Observable.range(10, 15); + values.subscribe(System.out::println); + + // 10 + // ... + // 24 + } + + public void exampleInterval() throws IOException { + Observable values = Observable.interval(1000, TimeUnit.MILLISECONDS); + values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + System.in.read(); + + // Received: 0 + // Received: 1 + // Received: 2 + // Received: 3 + // ... + } + + public void exampleTimer() throws IOException { + Observable values = Observable.timer(1, TimeUnit.SECONDS); + values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + System.in.read(); + + // Received: 0 + // Completed + } + + public void exampleTimerWithRepeat() throws IOException { + Observable values = Observable.timer(2, 1, TimeUnit.SECONDS); + values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + System.in.read(); + + // Received: 0 + // Received: 1 + // Received: 2 + // ... + } + + + // + // Tests + // + + @Test + public void testRange() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.range(10, 15); + values.subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(10,11,12,13,14,15,16,17,18,19,20,21,22,23,24)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testInterval() { + TestSubscriber tester = new TestSubscriber(); + TestScheduler scheduler = Schedulers.test(); + + Observable values = Observable.interval(1000, TimeUnit.MILLISECONDS, scheduler); + Subscription subscription = values.subscribe(tester); + scheduler.advanceTimeBy(4500, TimeUnit.MILLISECONDS); + + tester.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L)); + tester.assertNoErrors(); + assertEquals(tester.getOnCompletedEvents().size(), 0); + + subscription.unsubscribe(); + } + + @Test + public void testTimer() { + TestSubscriber tester = new TestSubscriber(); + TestScheduler scheduler = Schedulers.test(); + + Observable values = Observable.timer(1, TimeUnit.SECONDS, scheduler); + Subscription subscription = values.subscribe(tester); + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + tester.assertReceivedOnNext(Arrays.asList(0L)); + tester.assertNoErrors(); + tester.assertTerminalEvent(); + + subscription.unsubscribe(); + } + + @Test + public void testTimerWithRepeat() { + TestSubscriber tester = new TestSubscriber(); + TestScheduler scheduler = Schedulers.test(); + + Observable values = Observable.timer(2, 1, TimeUnit.SECONDS, scheduler); + Subscription subscription = values.subscribe(tester); + + scheduler.advanceTimeBy(6, TimeUnit.SECONDS); + + tester.assertReceivedOnNext(Arrays.asList(0L,1L,2L,3L,4L)); + tester.assertNoErrors(); + assertEquals(tester.getOnCompletedEvents().size(), 0); // Hasn't terminated + + subscription.unsubscribe(); + } + +} diff --git a/tests/java/itrx/chapter2/creating/ObservableFactoriesExample.java b/tests/java/itrx/chapter2/creating/ObservableFactoriesExample.java new file mode 100644 index 0000000..4077fda --- /dev/null +++ b/tests/java/itrx/chapter2/creating/ObservableFactoriesExample.java @@ -0,0 +1,225 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.creating; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class ObservableFactoriesExample { + + public void exampleJust() { + Observable values = Observable.just("one", "two", "three"); + values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // Received: one + // Received: two + // Received: three + // Completed + } + + public void exampleEmpty() { + Observable values = Observable.empty(); + values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // Completed + } + + public void exampleNever() { + Observable values = Observable.never(); + values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + } + + public void exampleError() { + Observable values = Observable.error(new Exception("Oops")); + values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // Error: java.lang.Exception: Oops + } + + public void exampleShouldDefer() throws InterruptedException { + Observable now = Observable.just(System.currentTimeMillis()); + + now.subscribe(System.out::println); + Thread.sleep(1000); + now.subscribe(System.out::println); + + // 1431443908375 + // 1431443908375 + } + + public void exampleDefer() throws InterruptedException { + Observable now = Observable.defer(() -> + Observable.just(System.currentTimeMillis())); + + now.subscribe(System.out::println); + Thread.sleep(1000); + now.subscribe(System.out::println); + + // 1431444107854 + // 1431444108858 + } + + public void exampleCreate() { + Observable values = Observable.create(o -> { + o.onNext("Hello"); + o.onCompleted(); + }); + + values.subscribe( + v -> System.out.println("Received: " + v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // Received: Hello + // Completed + } + + + // + // Tests + // + + @Test + public void testJust() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.just("one", "two", "three"); + values.subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList("one", "two", "three")); + tester.assertNoErrors(); + tester.assertTerminalEvent(); + } + + @Test + public void testEmpty() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.empty(); + values.subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList()); + tester.assertNoErrors(); + tester.assertTerminalEvent(); + } + + @Test + public void testNever() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.never(); + values.subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList()); + tester.assertNoErrors(); + assertEquals(tester.getOnCompletedEvents().size(), 0); + } + + @Test + public void testError() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.error(new Exception("Oops")); + values.subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList()); + tester.assertTerminalEvent(); + assertEquals(tester.getOnErrorEvents().size(), 1); + assertEquals(tester.getOnCompletedEvents().size(), 0); + } + + @Test + public void testShouldDefer() throws InterruptedException { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester1 = new TestSubscriber<>(); + TestSubscriber tester2 = new TestSubscriber<>(); + + Observable now = Observable.just(scheduler.now()); + + now.subscribe(tester1); + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + now.subscribe(tester2); + + assertEquals(tester1.getOnNextEvents().get(0), + tester2.getOnNextEvents().get(0)); + } + + @Test + public void testDefer() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester1 = new TestSubscriber<>(); + TestSubscriber tester2 = new TestSubscriber<>(); + + Observable now = Observable.defer(() -> + Observable.just(scheduler.now())); + + now.subscribe(tester1); + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + now.subscribe(tester2); + + assertTrue(tester1.getOnNextEvents().get(0) < + tester2.getOnNextEvents().get(0)); + } + + @Test + public void testCreate() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.create(o -> { + o.onNext("Hello"); + o.onCompleted(); + }); + values.subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList("Hello")); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } +} diff --git a/tests/java/itrx/chapter2/inspection/AllExample.java b/tests/java/itrx/chapter2/inspection/AllExample.java new file mode 100644 index 0000000..9fe812b --- /dev/null +++ b/tests/java/itrx/chapter2/inspection/AllExample.java @@ -0,0 +1,228 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.inspection; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscription; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class AllExample { + + public void exampleAll() { + Observable values = Observable.create(o -> { + o.onNext(0); + o.onNext(10); + o.onNext(10); + o.onNext(2); + o.onCompleted(); + }); + + + values + .all(i -> i % 2 == 0) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // true + // Completed + } + + public void exampleAllEarlyFalse() { + Observable values = Observable.interval(150, TimeUnit.MILLISECONDS).take(5); + + Subscription subscription = values + .all(i -> i<3) + .subscribe( + v -> System.out.println("All: " + v), + e -> System.out.println("All: Error: " + e), + () -> System.out.println("All: Completed") + ); + Subscription subscription2 = values + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + subscription.unsubscribe(); + subscription2.unsubscribe(); + + // 0 + // 1 + // 2 + // All: false + // All: Completed + // 3 + // 4 + // Completed + } + + public void exampleAllError() { + Observable values = Observable.create(o -> { + o.onNext(0); + o.onNext(2); + o.onError(new Exception()); + }); + + values + .all(i -> i % 2 == 0) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // Error: java.lang.Exception + } + + public void exampleAllErrorAfterComplete() { + Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onError(new Exception()); + }); + + values + .all(i -> i % 2 == 0) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // false + // Completed + } + + + + // + // Tests for examples + // + + @Test + public void testAll() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.create(o -> { + o.onNext(0); + o.onNext(10); + o.onNext(10); + o.onNext(2); + o.onCompleted(); + }); + + + values + .all(i -> i % 2 == 0) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(true)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testAllEarlyFalse() { + TestSubscriber testerSrc = new TestSubscriber(); + TestSubscriber testerAll = new TestSubscriber(); + TestScheduler scheduler = Schedulers.test(); + + Observable values = + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .take(5); + + Subscription subscription = values + .all(i -> i<3) + .subscribe(testerAll); + Subscription subscription2 = values + .subscribe(testerSrc); + + scheduler.advanceTimeBy(450, TimeUnit.MILLISECONDS); + + testerAll.assertReceivedOnNext(Arrays.asList(false)); + testerAll.assertTerminalEvent(); + testerAll.assertNoErrors(); + testerSrc.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L)); + + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + + testerSrc.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L)); + testerSrc.assertTerminalEvent(); + testerSrc.assertNoErrors(); + + subscription.unsubscribe(); + subscription2.unsubscribe(); + } + + @Test + public void testAllError() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.create(o -> { + o.onNext(0); + o.onNext(2); + o.onError(new Exception()); + }); + + values + .all(i -> i % 2 == 0) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList()); + tester.assertTerminalEvent(); + assertEquals(tester.getOnErrorEvents().size(), 1); + } + + @Test + public void testAllErrorAfterComplete() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onError(new Exception()); + }); + + values + .all(i -> i % 2 == 0) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(false)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter2/inspection/ContainsExample.java b/tests/java/itrx/chapter2/inspection/ContainsExample.java new file mode 100644 index 0000000..031ebf9 --- /dev/null +++ b/tests/java/itrx/chapter2/inspection/ContainsExample.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.inspection; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscription; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class ContainsExample { + + public void exampleContains() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + Subscription subscription = values + .contains(4L) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + subscription.unsubscribe(); + + // true + // Completed + } + + + // + // Test + // + + @Test + public void test() { + TestSubscriber tester = new TestSubscriber(); + TestScheduler scheduler = Schedulers.test(); + + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + + Subscription subscription = values + .contains(4L) + .subscribe(tester); + + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + subscription.unsubscribe(); + + tester.assertReceivedOnNext(Arrays.asList(true)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter2/inspection/DefaultIfEmptyExample.java b/tests/java/itrx/chapter2/inspection/DefaultIfEmptyExample.java new file mode 100644 index 0000000..7c4b513 --- /dev/null +++ b/tests/java/itrx/chapter2/inspection/DefaultIfEmptyExample.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.inspection; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class DefaultIfEmptyExample { + + public void exampleDefaultIfEmpty() { + Observable values = Observable.empty(); + + values + .defaultIfEmpty(2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // 2 + // Completed + } + + public void exampleDefaultIfEmptyError() { + Observable values = Observable.error(new Exception()); + + values + .defaultIfEmpty(2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // Error: java.lang.Exception + } + + + // + // Tests + // + + @Test + public void testDefaultIfEmpty() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.empty(); + + values + .defaultIfEmpty(2) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(2)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testDefaultIfEmptyError() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.error(new Exception()); + + values + .defaultIfEmpty(2) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList()); + tester.assertTerminalEvent(); + assertEquals(tester.getOnErrorEvents().size(), 1); + } + +} diff --git a/tests/java/itrx/chapter2/inspection/ElementAtExample.java b/tests/java/itrx/chapter2/inspection/ElementAtExample.java new file mode 100644 index 0000000..3e07e8c --- /dev/null +++ b/tests/java/itrx/chapter2/inspection/ElementAtExample.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.inspection; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class ElementAtExample { + + public void exampleElementAt() { + Observable values = Observable.range(100, 10); + + values + .elementAt(2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // 102 + // Completed + } + + public void exampleElementAtOrDefault() { + Observable values = Observable.range(100, 10); + + values + .elementAtOrDefault(22, 0) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // 0 + // Completed + } + + + // + // Tests + // + + @Test + public void testElementAt() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.range(100, 10); + + values + .elementAt(2) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(102)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testElementAtOrDefault() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.range(100, 10); + + values + .elementAtOrDefault(22, 0) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter2/inspection/ExistsExample.java b/tests/java/itrx/chapter2/inspection/ExistsExample.java new file mode 100644 index 0000000..2b2d250 --- /dev/null +++ b/tests/java/itrx/chapter2/inspection/ExistsExample.java @@ -0,0 +1,99 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.inspection; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class ExistsExample { + + public void exampleFalse() { + Observable values = Observable.range(0, 2); + + values + .exists(i -> i > 2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // false + // Completed + } + + public void exampleTrue() { + Observable values = Observable.range(0, 4); + + values + .exists(i -> i > 2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // true + // Completed + } + + + // + // Tests + // + + @Test + public void testFalse() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.range(0, 2); + + values + .exists(i -> i > 2) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(false)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testTrue() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.range(0, 4); + + values + .exists(i -> i > 2) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(true)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter2/inspection/IsEmptyExample.java b/tests/java/itrx/chapter2/inspection/IsEmptyExample.java new file mode 100644 index 0000000..a474721 --- /dev/null +++ b/tests/java/itrx/chapter2/inspection/IsEmptyExample.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.inspection; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscription; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class IsEmptyExample { + + public void exampleIsEmpty() { + Observable values = Observable.timer(1000, TimeUnit.MILLISECONDS); + + Subscription subscription = values + .isEmpty() + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + subscription.unsubscribe(); + + // false + // Completed + } + + + // + // Test + // + + @Test + public void testIsEmpty() { + TestSubscriber tester = new TestSubscriber(); + TestScheduler scheduler = Schedulers.test(); + + Observable values = Observable.timer(1000, TimeUnit.MILLISECONDS, scheduler); + + Subscription subscription = values + .isEmpty() + .subscribe(tester); + + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + subscription.unsubscribe(); + + tester.assertReceivedOnNext(Arrays.asList(false)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter2/inspection/SequenceEqualExample.java b/tests/java/itrx/chapter2/inspection/SequenceEqualExample.java new file mode 100644 index 0000000..8bd8ef8 --- /dev/null +++ b/tests/java/itrx/chapter2/inspection/SequenceEqualExample.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.inspection; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class SequenceEqualExample { + + public void exampleSequenceEqualTrue() { + Observable strings = Observable.just("1", "2", "3"); + Observable ints = Observable.just(1, 2, 3); + + Observable.sequenceEqual(strings, ints, (s,i) -> s.equals(i.toString())) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // true + // Completed + } + + public void exampleSequenceEqualFalse() { + Observable strings = Observable.just("1", "2", "3"); + Observable ints = Observable.just(1, 2, 3); + + Observable.sequenceEqual(strings, ints) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // false + // Completed + } + + public void exampleSequenceEqualError() { + Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onError(new Exception()); + }); + + Observable.sequenceEqual(values, values) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // Error: java.lang.Exception + } + + + // + // Tests + // + + @Test + public void testSequenceEqualTrue() { + TestSubscriber tester = new TestSubscriber(); + + Observable strings = Observable.just("1", "2", "3"); + Observable ints = Observable.just(1, 2, 3); + + Observable.sequenceEqual(strings, ints, (s,i) -> s.equals(i.toString())) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(true)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testSequenceEqualFalse() { + TestSubscriber tester = new TestSubscriber(); + + Observable strings = Observable.just("1", "2", "3"); + Observable ints = Observable.just(1, 2, 3); + + Observable.sequenceEqual(strings, ints) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(false)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testSequenceEqualError() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onError(new Exception()); + }); + + Observable.sequenceEqual(values, values) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList()); + tester.assertTerminalEvent(); + assertEquals(tester.getOnErrorEvents().size(), 1); + } + +} diff --git a/tests/java/itrx/chapter2/reducing/DistinctExample.java b/tests/java/itrx/chapter2/reducing/DistinctExample.java new file mode 100644 index 0000000..49007f4 --- /dev/null +++ b/tests/java/itrx/chapter2/reducing/DistinctExample.java @@ -0,0 +1,225 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.reducing; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class DistinctExample { + + public void exampleDistinct() { + Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(1); + o.onNext(2); + o.onNext(3); + o.onNext(2); + o.onCompleted(); + }); + + values + .distinct() + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // 1 + // 2 + // 3 + // Completed + } + + public void exampleDistinctKey() { + Observable values = Observable.create(o -> { + o.onNext("First"); + o.onNext("Second"); + o.onNext("Third"); + o.onNext("Fourth"); + o.onNext("Fifth"); + o.onCompleted(); + }); + + values + .distinct(v -> v.charAt(0)) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // First + // Second + // Third + // Completed + } + + public void exampleDistinctUntilChanged() { + Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(1); + o.onNext(2); + o.onNext(3); + o.onNext(2); + o.onCompleted(); + }); + + values + .distinctUntilChanged() + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // 1 + // 2 + // 3 + // 2 + // Completed + } + + public void exampleDistinctUntilChangedKey() { + Observable values = Observable.create(o -> { + o.onNext("First"); + o.onNext("Second"); + o.onNext("Third"); + o.onNext("Fourth"); + o.onNext("Fifth"); + o.onCompleted(); + }); + + values + .distinctUntilChanged(v -> v.charAt(0)) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // First + // Second + // Third + // Fourth + // Completed + } + + + // + // Tests + // + + @Test + public void testDistinct() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(1); + o.onNext(2); + o.onNext(3); + o.onNext(2); + o.onCompleted(); + }); + + values + .distinct() + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(1,2,3)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testDistinctKey() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.create(o -> { + o.onNext("First"); + o.onNext("Second"); + o.onNext("Third"); + o.onNext("Fourth"); + o.onNext("Fifth"); + o.onCompleted(); + }); + + values + .distinct(v -> v.charAt(0)) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList("First", "Second", "Third")); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testDistinctUntilChanged() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(1); + o.onNext(2); + o.onNext(3); + o.onNext(2); + o.onCompleted(); + }); + + values + .distinctUntilChanged() + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(1,2,3,2)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testDistinctUntilChangedKey() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.create(o -> { + o.onNext("First"); + o.onNext("Second"); + o.onNext("Third"); + o.onNext("Fourth"); + o.onNext("Fifth"); + o.onCompleted(); + }); + + values + .distinctUntilChanged(v -> v.charAt(0)) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList("First", "Second", "Third", "Fourth")); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter2/reducing/FilterExample.java b/tests/java/itrx/chapter2/reducing/FilterExample.java new file mode 100644 index 0000000..405ddc6 --- /dev/null +++ b/tests/java/itrx/chapter2/reducing/FilterExample.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.reducing; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class FilterExample { + + + public void example() { + Observable values = Observable.range(0,10); + values + .filter(v -> v % 2 == 0) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // 0 + // 2 + // 4 + // 6 + // 8 + // Completed + } + + + // + // Test + // + + @Test + public void test() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.range(0,10); + values + .filter(v -> v % 2 == 0) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0,2,4,6,8)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter2/reducing/IgnoreExample.java b/tests/java/itrx/chapter2/reducing/IgnoreExample.java new file mode 100644 index 0000000..57b3216 --- /dev/null +++ b/tests/java/itrx/chapter2/reducing/IgnoreExample.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.reducing; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class IgnoreExample { + + public void exampleIgnoreElements() { + Observable values = Observable.range(0, 10); + + values + .ignoreElements() + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // Completed + } + + + // + // Tests + // + + @Test + public void testIgnoreElements() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.range(0, 10); + + values + .ignoreElements() + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList()); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter2/reducing/TakeSkipExample.java b/tests/java/itrx/chapter2/reducing/TakeSkipExample.java new file mode 100644 index 0000000..5b82f15 --- /dev/null +++ b/tests/java/itrx/chapter2/reducing/TakeSkipExample.java @@ -0,0 +1,395 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.reducing; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscription; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class TakeSkipExample { + + public void exampleTake() { + Observable values = Observable.range(0, 5); + + values + .take(2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // 0 + // 1 + // Completed + } + + public void exampleSkip() { + Observable values = Observable.range(0, 5); + + values + .skip(2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // 2 + // 3 + // 4 + // Completed + } + + public void exampleTakeTime() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + Subscription subscription = values + .take(250, TimeUnit.MILLISECONDS) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + subscription.unsubscribe(); + + // 0 + // 1 + // Completed + } + + public void exampleSkipTime() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + Subscription subscription = values + .skip(250, TimeUnit.MILLISECONDS) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + subscription.unsubscribe(); + + // 2 + // 3 + // 4 + // Completed + } + + public void exampleTakeWhile() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + Subscription subscription = values + .takeWhile(v -> v < 2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + subscription.unsubscribe(); + + // 0 + // 1 + // Completed + } + + public void exampleSkipWhile() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + Subscription subscription = values + .skipWhile(v -> v < 2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + subscription.unsubscribe(); + + // 2 + // 3 + // 4 + // ... + } + + public void exampleSkipLast() { + Observable values = Observable.range(0,5); + + values + .skipLast(2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // 0 + // 1 + // 2 + // Completed + } + + public void exampleTakeLast() { + Observable values = Observable.range(0,5); + + values + .takeLast(2) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + // 3 + // 4 + // ... + } + + public void exampleTakeUntil() { + Observable values = Observable.interval(100,TimeUnit.MILLISECONDS); + Observable cutoff = Observable.timer(250, TimeUnit.MILLISECONDS); + + Subscription subscription = values + .takeUntil(cutoff) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + subscription.unsubscribe(); + + // 0 + // 1 + // Completed + } + + public void exampleSkipUntil() { + Observable values = Observable.interval(100,TimeUnit.MILLISECONDS); + Observable cutoff = Observable.timer(250, TimeUnit.MILLISECONDS); + + Subscription subscription = values + .skipUntil(cutoff) + .subscribe( + v -> System.out.println(v), + e -> System.out.println("Error: " + e), + () -> System.out.println("Completed") + ); + + subscription.unsubscribe(); + + // 2 + // 3 + // 4 + // ... + } + + + // + // Tests + // + + @Test + public void testTake() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.range(0, 5); + + values + .take(2) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0,1)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testSkip() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.range(0, 5); + + values + .skip(2) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(2,3,4)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testTakeTime() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS ,scheduler); + + Subscription subscription = values + .take(250, TimeUnit.MILLISECONDS, scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + subscription.unsubscribe(); + + tester.assertReceivedOnNext(Arrays.asList(0L,1L)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testSkipTime() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + + Subscription subscription = values + .skip(250, TimeUnit.MILLISECONDS, scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(550, TimeUnit.MILLISECONDS); + subscription.unsubscribe(); + + tester.assertReceivedOnNext(Arrays.asList(2L, 3L, 4L)); + tester.assertNoErrors(); + } + + @Test + public void testTakeWhile() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + + Subscription subscription = values + .takeWhile(v -> v < 2) + .subscribe(tester); + + scheduler.advanceTimeBy(550, TimeUnit.MILLISECONDS); + subscription.unsubscribe(); + + tester.assertReceivedOnNext(Arrays.asList(0L, 1L)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testSkipWhile() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + + Subscription subscription = values + .skipWhile(v -> v < 2) + .subscribe(tester); + + scheduler.advanceTimeBy(550, TimeUnit.MILLISECONDS); + subscription.unsubscribe(); + + tester.assertReceivedOnNext(Arrays.asList(2L, 3L, 4L)); + tester.assertNoErrors(); + } + + @Test + public void testerSkipLast() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.range(0,5); + + values + .skipLast(2) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0,1,2)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testTakeLast() { + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.range(0,5); + + values + .takeLast(2) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(3,4)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testTakeUntil() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.interval(100,TimeUnit.MILLISECONDS, scheduler); + Observable cutoff = Observable.timer(250, TimeUnit.MILLISECONDS, scheduler); + + Subscription subscription = values + .takeUntil(cutoff) + .subscribe(tester); + + scheduler.advanceTimeBy(550, TimeUnit.MILLISECONDS); + subscription.unsubscribe(); + + tester.assertReceivedOnNext(Arrays.asList(0L,1L)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testSkipUntil() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber(); + + Observable values = Observable.interval(100,TimeUnit.MILLISECONDS, scheduler); + Observable cutoff = Observable.timer(250, TimeUnit.MILLISECONDS, scheduler); + + Subscription subscription = values + .skipUntil(cutoff) + .subscribe(tester); + + scheduler.advanceTimeBy(550, TimeUnit.MILLISECONDS); + subscription.unsubscribe(); + + tester.assertReceivedOnNext(Arrays.asList(2L,3L,4L)); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter2/transforming/CastTypeOfExample.java b/tests/java/itrx/chapter2/transforming/CastTypeOfExample.java new file mode 100644 index 0000000..0b3a337 --- /dev/null +++ b/tests/java/itrx/chapter2/transforming/CastTypeOfExample.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.transforming; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; + +public class CastTypeOfExample { + + private static class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } + } + + + public void exampleCast() { + Observable values = Observable.just(0, 1, 2, 3); + + values + .cast(Integer.class) + .subscribe(new PrintSubscriber("Map")); + + // Map: 0 + // Map: 1 + // Map: 2 + // Map: 3 + // Map: Completed + } + + public void exampleCastFail() { + Observable values = Observable.just(0, 1, 2, "3"); + + values + .cast(Integer.class) + .subscribe(new PrintSubscriber("Map")); + + // Map: 0 + // Map: 1 + // Map: 2 + // Map: Error: java.lang.ClassCastException: Cannot cast java.lang.String to java.lang.Integer + } + + public void exampleTypeOf() { + Observable values = Observable.just(0, 1, "2", 3); + + values + .ofType(Integer.class) + .subscribe(new PrintSubscriber("Map")); + + // Map: 0 + // Map: 1 + // Map: 3 + // Map: Completed + } + + + // + // Tests + // + + @Test + public void testCast() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.just(0, 1, 2, 3); + + values + .cast(Integer.class) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0,1,2,3)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testCastFail() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.just(0, 1, 2, "3"); + + values + .cast(Integer.class) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0,1,2)); + tester.assertTerminalEvent(); + assertEquals(tester.getOnErrorEvents().size(), 1); // received 1 error + } + + @Test + public void testTypeOf() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.just(0, 1, "2", 3); + + values + .ofType(Integer.class) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0,1,3)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } +} diff --git a/tests/java/itrx/chapter2/transforming/ConcatMapExample.java b/tests/java/itrx/chapter2/transforming/ConcatMapExample.java new file mode 100644 index 0000000..630caa2 --- /dev/null +++ b/tests/java/itrx/chapter2/transforming/ConcatMapExample.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.transforming; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class ConcatMapExample { + + public void exampleConcatMap() { + Observable.just(100, 150) + .concatMap(i -> + Observable.interval(i, TimeUnit.MILLISECONDS) + .map(v -> i) + .take(3)) + .subscribe( + System.out::println, + System.out::println, + () -> System.out.println("Completed")); + + // 100 + // 100 + // 100 + // 150 + // 150 + // 150 + // Completed + } + + + // + // Test + // + + @Test + public void testConcatMap() { + TestSubscriber tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.just(100, 150) + .concatMap(i -> + Observable.interval(i, TimeUnit.MILLISECONDS, scheduler) + .map(v -> i) + .take(3) + ) + .subscribe(tester); + + scheduler.advanceTimeBy(750, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(100, 100, 100, 150, 150, 150)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter2/transforming/FlatMapExample.java b/tests/java/itrx/chapter2/transforming/FlatMapExample.java new file mode 100644 index 0000000..bf7ed57 --- /dev/null +++ b/tests/java/itrx/chapter2/transforming/FlatMapExample.java @@ -0,0 +1,238 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.transforming; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class FlatMapExample { + + private static class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } + } + + public void exampleFlatMap() { + Observable values = Observable.just(2); + + values + .flatMap(i -> Observable.range(0,i)) + .subscribe(new PrintSubscriber("flatMap")); + + // flatMap: 0 + // flatMap: 1 + // flatMap: Completed + } + + public void exampleFlatMapMultipleValues() { + Observable values = Observable.range(1,3); + + values + .flatMap(i -> Observable.range(0,i)) + .subscribe(new PrintSubscriber("flatMap")); + + // flatMap: 0 + // flatMap: 0 + // flatMap: 1 + // flatMap: 0 + // flatMap: 1 + // flatMap: 2 + // flatMap: Completed + } + + public void exampleFlatMapNewType() { + Observable values = Observable.just(1); + + values + .flatMap(i -> + Observable.just( + Character.valueOf((char)(i+64)) + )) + .subscribe(new PrintSubscriber("flatMap")); + + // flatMap: A + // flatMap: Completed + } + + public void exampleFlatMapFilter() { + Observable values = Observable.range(0,30); + + values + .flatMap(i -> { + if (0 < i && i <= 26) + return Observable.just(Character.valueOf((char)(i+64))); + else + return Observable.empty(); + }) + .subscribe(new PrintSubscriber("flatMap")); + + // flatMap: A + // flatMap: B + // flatMap: C + // ... + // flatMap: X + // flatMap: Y + // flatMap: Z + // flatMap: Completed + } + + public void exampleFlatMapAsynchronous() { + Observable.just(100, 150) + .flatMap(i -> + Observable.interval(i, TimeUnit.MILLISECONDS) + .map(v -> i) + ) + .take(10) + .subscribe(new PrintSubscriber("flatMap")); + + // flatMap: 100 + // flatMap: 150 + // flatMap: 100 + // flatMap: 100 + // flatMap: 150 + // flatMap: 100 + // flatMap: 150 + // flatMap: 100 + // flatMap: 100 + // flatMap: 150 + // flatMap: Completed + } + + + // + // Tests + // + + @Test + public void testFlatMap() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.just(2); + + values + .flatMap(i -> Observable.range(0,i)) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0,1)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testFlatMapMultipleValues() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.range(1,3); + + values + .flatMap(i -> Observable.range(0,i)) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0,0,1,0,1,2)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + + } + + @Test + public void testFlatMapNewType() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.just(1); + + values + .flatMap(i -> + Observable.just( + Character.valueOf((char)(i+64)) + )) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList('A')); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testFlatMapFilter() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.range(0,30); + + values + .flatMap(i -> { + if (0 < i && i <= 26) + return Observable.just(Character.valueOf((char)(i+64))); + else + return Observable.empty(); + }) + .subscribe(tester); + + assertEquals(tester.getOnNextEvents().size(), 26); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testFlatMapAsynchronous() { + TestSubscriber tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.just(100, 150) + .flatMap(i -> + Observable.interval(i, TimeUnit.MILLISECONDS, scheduler) + .map(v -> i) + ) + .take(10) + .distinctUntilChanged() + .subscribe(tester); + + scheduler.advanceTimeBy(2, TimeUnit.SECONDS); + + assertTrue(tester.getOnNextEvents().size() > 2); // 100 and 150 succeeded each other more than once + tester.assertNoErrors(); + } +} diff --git a/tests/java/itrx/chapter2/transforming/FlatMapIterableExample.java b/tests/java/itrx/chapter2/transforming/FlatMapIterableExample.java new file mode 100644 index 0000000..0619ef8 --- /dev/null +++ b/tests/java/itrx/chapter2/transforming/FlatMapIterableExample.java @@ -0,0 +1,173 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.transforming; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class FlatMapIterableExample { + + public static class Range implements Iterable { + + private static class RangeIterator implements Iterator { + private int next; + private final int end; + + RangeIterator(int start, int count) { + this.next = start; + this.end = start + count; + } + + @Override + public boolean hasNext() { + return next < end; + } + + @Override + public Integer next() { + return next++; + } + + } + + private final int start; + private final int count; + + public Range(int start, int count) { + this.start = start; + this.count = count; + } + + @Override + public Iterator iterator() { + return new RangeIterator(start, count); + } + } + + public static Iterable range(int start, int count) { + List list = new ArrayList<>(); + for (int i=start ; i range(1, i)) + .subscribe(System.out::println); + + // 1 + // 1 + // 2 + // 1 + // 2 + // 3 + } + + public void exampleFlatMapIterableWithSelector() { + Observable.range(1, 3) + .flatMapIterable( + i -> range(1, i), + (ori, rv) -> ori * rv) + .subscribe(System.out::println); + + // 1 + // 2 + // 4 + // 3 + // 6 + // 9 + } + + public void exampleFlatMapLazyIterable() { + Observable.range(1, 3) + .flatMapIterable( + i -> new Range(1, i), + (ori, rv) -> ori * rv) + .subscribe(System.out::println); + + // 1 + // 2 + // 4 + // 3 + // 6 + // 9 + } + + + // + // Test + // + + @Test + public void testFlatMapIterable() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable.range(1, 3) + .flatMapIterable(i -> range(1, i)) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(1,1,2,1,2,3)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testFlatMapIterableWithSelector() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable.range(1, 3) + .flatMapIterable( + i -> range(1, i), + (ori, rv) -> ori * rv) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(1,2,4,3,6,9)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testFlatMapLazyIterable() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable.range(1, 3) + .flatMapIterable( + i -> new Range(1, i), + (ori, rv) -> ori * rv) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(1,2,4,3,6,9)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter2/transforming/MapExample.java b/tests/java/itrx/chapter2/transforming/MapExample.java new file mode 100644 index 0000000..9ca4ae4 --- /dev/null +++ b/tests/java/itrx/chapter2/transforming/MapExample.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.transforming; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; + +public class MapExample { + + private static class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } + } + + public void exampleMap() { + Observable values = Observable.range(0,4); + + values + .map(i -> i + 3) + .subscribe(new PrintSubscriber("Map")); + + // Map: 3 + // Map: 4 + // Map: 5 + // Map: 6 + // Map: Completed + } + + public void exampleMap2() { + Observable values = + Observable.just("0", "1", "2", "3") + .map(Integer::parseInt); + + values.subscribe(new PrintSubscriber("Map")); + + // Map: 0 + // Map: 1 + // Map: 2 + // Map: 3 + // Map: Completed + } + + + // + // Tests + // + + @Test + public void testMap() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.range(0,4); + + values + .map(i -> i + 3) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(3,4,5,6)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testMap2() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = + Observable.just("0", "1", "2", "3") + .map(Integer::parseInt); + + values.subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0,1,2,3)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter2/transforming/MaterializeExample.java b/tests/java/itrx/chapter2/transforming/MaterializeExample.java new file mode 100644 index 0000000..1bd47de --- /dev/null +++ b/tests/java/itrx/chapter2/transforming/MaterializeExample.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.transforming; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Notification; +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; + +public class MaterializeExample { + + private static class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } + } + + + public void exampleMaterialize() { + Observable values = Observable.range(0,3); + + values.take(3) + .materialize() + .subscribe(new PrintSubscriber("Materialize")); + + // Materialize: [rx.Notification@a4c802e9 OnNext 0] + // Materialize: [rx.Notification@a4c802ea OnNext 1] + // Materialize: [rx.Notification@a4c802eb OnNext 2] + // Materialize: [rx.Notification@18d48ace OnCompleted] + // Materialize: Completed + } + + + // + // Tests + // + + @Test + public void testMaterialize() { + TestSubscriber> tester = new TestSubscriber<>(); + + Observable values = Observable.range(0,3); + + values.take(3) + .materialize() + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList( + Notification.createOnNext(0), + Notification.createOnNext(1), + Notification.createOnNext(2), + Notification.createOnCompleted() + )); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter2/transforming/TimestampTimeIntervalExample.java b/tests/java/itrx/chapter2/transforming/TimestampTimeIntervalExample.java new file mode 100644 index 0000000..02272fa --- /dev/null +++ b/tests/java/itrx/chapter2/transforming/TimestampTimeIntervalExample.java @@ -0,0 +1,131 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter2.transforming; + +import static org.junit.Assert.assertEquals; + +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; +import rx.schedulers.TimeInterval; +import rx.schedulers.Timestamped; + +public class TimestampTimeIntervalExample { + + private static class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } + } + + public void exampleTimestamp() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + values.take(3) + .timestamp() + .subscribe(new PrintSubscriber("Timestamp")); + + // Timestamp: Timestamped(timestampMillis = 1428611094943, value = 0) + // Timestamp: Timestamped(timestampMillis = 1428611095037, value = 1) + // Timestamp: Timestamped(timestampMillis = 1428611095136, value = 2) + // Timestamp: Completed + } + + public void exampleTimeInteval() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + values.take(3) + .timeInterval() + .subscribe(new PrintSubscriber("TimeInterval")); + + // TimeInterval: TimeInterval [intervalInMilliseconds=131, value=0] + // TimeInterval: TimeInterval [intervalInMilliseconds=75, value=1] + // TimeInterval: TimeInterval [intervalInMilliseconds=100, value=2] + // TimeInterval: Completed + } + + + // + // Tests + // + + @Test + public void testTimestamp() { + TestSubscriber> tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + + values.take(3) + .timestamp(scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + assertEquals(tester.getOnNextEvents().get(0).getTimestampMillis(), 100); + assertEquals(tester.getOnNextEvents().get(1).getTimestampMillis(), 200); + assertEquals(tester.getOnNextEvents().get(2).getTimestampMillis(), 300); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testTimeInteval() { + TestSubscriber> tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + + values.take(3) + .timeInterval(scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + + assertEquals(tester.getOnNextEvents().get(0).getIntervalInMilliseconds(), 100); + assertEquals(tester.getOnNextEvents().get(1).getIntervalInMilliseconds(), 100); + assertEquals(tester.getOnNextEvents().get(2).getIntervalInMilliseconds(), 100); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter3/combining/AmbExample.java b/tests/java/itrx/chapter3/combining/AmbExample.java new file mode 100644 index 0000000..ec22051 --- /dev/null +++ b/tests/java/itrx/chapter3/combining/AmbExample.java @@ -0,0 +1,92 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.combining; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class AmbExample { + + public void exampleAmb() { + Observable.amb( + Observable.timer(100, TimeUnit.MILLISECONDS).map(i -> "First"), + Observable.timer(50, TimeUnit.MILLISECONDS).map(i -> "Second")) + .subscribe(System.out::println); + + // Second + } + + public void exampleAmbWith() { + Observable.timer(100, TimeUnit.MILLISECONDS).map(i -> "First") + .ambWith(Observable.timer(50, TimeUnit.MILLISECONDS).map(i -> "Second")) + .ambWith(Observable.timer(70, TimeUnit.MILLISECONDS).map(i -> "Third")) + .subscribe(System.out::println); + + // Second + } + + + // + // Test + // + + @Test + public void testAmb() { + TestSubscriber tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.amb( + Observable.timer(100, TimeUnit.MILLISECONDS, scheduler).map(i -> "First"), + Observable.timer(50, TimeUnit.MILLISECONDS, scheduler).map(i -> "Second")) + .subscribe(tester); + + scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList("Second")); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testAmbWith() { + TestSubscriber tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.timer(100, TimeUnit.MILLISECONDS, scheduler).map(i -> "First") + .ambWith(Observable.timer(50, TimeUnit.MILLISECONDS, scheduler).map(i -> "Second")) + .ambWith(Observable.timer(70, TimeUnit.MILLISECONDS, scheduler).map(i -> "Third")) + .subscribe(tester); + + scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList("Second")); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter3/combining/CombineLatestExample.java b/tests/java/itrx/chapter3/combining/CombineLatestExample.java new file mode 100644 index 0000000..26cfd1f --- /dev/null +++ b/tests/java/itrx/chapter3/combining/CombineLatestExample.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.combining; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class CombineLatestExample { + + public void example() { + Observable.combineLatest( + Observable.interval(100, TimeUnit.MILLISECONDS) + .doOnNext(i -> System.out.println("Left emits")), + Observable.interval(150, TimeUnit.MILLISECONDS) + .doOnNext(i -> System.out.println("Right emits")), + (i1,i2) -> i1 + " - " + i2 + ) + .take(6) + .subscribe(System.out::println); + + // Left emits + // Right emits + // 0 - 0 + // Left emits + // 1 - 0 + // Left emits + // 2 - 0 + // Right emits + // 2 - 1 + // Left emits + // 3 - 1 + // Right emits + // 3 - 2 + } + + + // + // Test + // + + @Test + public void test() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Observable.combineLatest( + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler), + Observable.interval(150, TimeUnit.MILLISECONDS, scheduler), + (i1,i2) -> i1 + " - " + i2 + ) + .subscribe(tester); + + scheduler.advanceTimeTo(100, TimeUnit.MILLISECONDS); + scheduler.advanceTimeTo(150, TimeUnit.MILLISECONDS); + scheduler.advanceTimeTo(200, TimeUnit.MILLISECONDS); + scheduler.advanceTimeTo(300, TimeUnit.MILLISECONDS); + + tester.assertReceivedOnNext(Arrays.asList( + "0 - 0", + "1 - 0", + "1 - 1", + "2 - 1" + )); + } + +} diff --git a/tests/java/itrx/chapter3/combining/ConcatExample.java b/tests/java/itrx/chapter3/combining/ConcatExample.java new file mode 100644 index 0000000..23e054a --- /dev/null +++ b/tests/java/itrx/chapter3/combining/ConcatExample.java @@ -0,0 +1,151 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.combining; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class ConcatExample { + + public void exampleConcat() { + Observable seq1 = Observable.range(0, 3); + Observable seq2 = Observable.range(10, 3); + + Observable.concat(seq1, seq2) + .subscribe(System.out::println); + + // 0 + // 1 + // 2 + // 10 + // 11 + // 12 + } + + public void exampleConcatDynamic() { + Observable words = Observable.just( + "First", + "Second", + "Third", + "Fourth", + "Fifth", + "Sixth" + ); + + Observable.concat(words.groupBy(v -> v.charAt(0))) + .subscribe(System.out::println); + + // First + // Fourth + // Fifth + // Second + // Sixth + // Third + } + + public void exampleConcatWith() { + Observable seq1 = Observable.range(0, 3); + Observable seq2 = Observable.range(10, 3); + Observable seq3 = Observable.just(20); + + seq1.concatWith(seq2) + .concatWith(seq3) + .subscribe(System.out::println); + + // 0 + // 1 + // 2 + // 10 + // 11 + // 12 + // 20 + } + + + // + // Tests + // + + @Test + public void testConcat() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable seq1 = Observable.range(0, 3); + Observable seq2 = Observable.range(10, 3); + + Observable.concat(seq1, seq2) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0,1,2,10,11,12)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testConcatDynamic() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable words = Observable.just( + "First", + "Second", + "Third", + "Fourth", + "Fifth", + "Sixth" + ); + + Observable.concat(words.groupBy(v -> v.charAt(0))) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList( + "First", + "Fourth", + "Fifth", + "Second", + "Sixth", + "Third")); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testConcatWith() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable seq1 = Observable.range(0, 3); + Observable seq2 = Observable.range(10, 3); + Observable seq3 = Observable.just(20); + + seq1.concatWith(seq2) + .concatWith(seq3) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0,1,2,10,11,12,20)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } +} diff --git a/tests/java/itrx/chapter3/combining/MergeDelayErrorExample.java b/tests/java/itrx/chapter3/combining/MergeDelayErrorExample.java new file mode 100644 index 0000000..8f93572 --- /dev/null +++ b/tests/java/itrx/chapter3/combining/MergeDelayErrorExample.java @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.combining; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.exceptions.CompositeException; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class MergeDelayErrorExample { + + public void example1Error() { + Observable failAt200 = + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(2), + Observable.error(new Exception("Failed"))); + Observable completeAt400 = + Observable.interval(100, TimeUnit.MILLISECONDS) + .take(4); + + Observable.mergeDelayError(failAt200, completeAt400) + .subscribe( + System.out::println, + System.out::println); + + // 0 + // 0 + // 1 + // 1 + // 2 + // 3 + // java.lang.Exception: Failed + } + + public void example2Errors() { + Observable failAt200 = + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(2), + Observable.error(new Exception("Failed"))); + Observable failAt300 = + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(3), + Observable.error(new Exception("Failed"))); + Observable completeAt400 = + Observable.interval(100, TimeUnit.MILLISECONDS) + .take(4); + + Observable.mergeDelayError(failAt200, failAt300, completeAt400) + .subscribe( + System.out::println, + System.out::println); + + // 0 + // 0 + // 0 + // 1 + // 1 + // 1 + // 2 + // 2 + // 3 + // rx.exceptions.CompositeException: 2 exceptions occurred. + } + + + // + // Tests + // + + @Test + public void test1Error() { + TestSubscriber tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable failAt200 = + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(2), + Observable.error(new Exception("Failed"))); + Observable completeAt400 = + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .take(4); + + Observable.mergeDelayError(failAt200, completeAt400) + .subscribe(tester); + + scheduler.advanceTimeBy(400, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(0L, 0L, 1L, 1L, 2L, 3L)); + assertThat(tester.getOnErrorEvents().get(0), instanceOf(Exception.class)); + } + + @Test + public void test2Errors() { + TestSubscriber tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable failAt200 = + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(2), + Observable.error(new Exception("Failed"))); + Observable failAt300 = + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(3), + Observable.error(new Exception("Failed"))); + Observable completeAt400 = + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .take(4); + + Observable.mergeDelayError(failAt200, failAt300, completeAt400) + .subscribe(tester); + + scheduler.advanceTimeBy(400, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(0L, 0L, 0L, 1L, 1L, 1L, 2L, 2L, 3L)); + assertThat(tester.getOnErrorEvents().get(0), instanceOf(CompositeException.class)); + } + +} diff --git a/tests/java/itrx/chapter3/combining/MergeExample.java b/tests/java/itrx/chapter3/combining/MergeExample.java new file mode 100644 index 0000000..95f6709 --- /dev/null +++ b/tests/java/itrx/chapter3/combining/MergeExample.java @@ -0,0 +1,121 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.combining; + +import static org.junit.Assert.assertTrue; + +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscription; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class MergeExample { + + public void example() { + Observable.merge( + Observable.interval(250, TimeUnit.MILLISECONDS).map(i -> "First"), + Observable.interval(150, TimeUnit.MILLISECONDS).map(i -> "Second")) + .take(10) + .subscribe(System.out::println); + + // Second + // First + // Second + // Second + // First + // Second + // Second + // First + // Second + // First + } + + public void exampleMergeWith() { + Observable.interval(250, TimeUnit.MILLISECONDS).map(i -> "First") + .mergeWith(Observable.interval(150, TimeUnit.MILLISECONDS).map(i -> "Second")) + .take(10) + .subscribe(System.out::println); + + // Second + // First + // Second + // Second + // First + // Second + // First + // Second + // Second + // First + } + + + // + // Test + // + + @Test + public void test() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Subscription subscription = Observable.merge( + Observable.interval(250, TimeUnit.MILLISECONDS, scheduler).map(i -> "First"), + Observable.interval(150, TimeUnit.MILLISECONDS, scheduler).map(i -> "Second")) + .take(10) + .distinctUntilChanged() + .subscribe(tester); + + // Each time that merge switches between the two sources, + // distinctUntilChanged allows one more value through. + // If more that 2 values comes through, merge is going back and forth + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + assertTrue(tester.getOnNextEvents().size() > 2); + + subscription.unsubscribe(); + } + + @Test + public void testMergeWith() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Subscription subscription = Observable.interval(250, TimeUnit.MILLISECONDS, scheduler).map(i -> "First") + .mergeWith(Observable.interval(150, TimeUnit.MILLISECONDS, scheduler).map(i -> "Second")) + .distinctUntilChanged() + .subscribe(tester); + + // Each time that merge switches between the two sources, + // distinctUntilChanged allows one more value through. + // If more that 2 values comes through, merge is going back and forth + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + assertTrue(tester.getOnNextEvents().size() > 2); + + subscription.unsubscribe(); + } + +} diff --git a/tests/java/itrx/chapter3/combining/RepeatExample.java b/tests/java/itrx/chapter3/combining/RepeatExample.java new file mode 100644 index 0000000..fafe6b3 --- /dev/null +++ b/tests/java/itrx/chapter3/combining/RepeatExample.java @@ -0,0 +1,196 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.combining; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class RepeatExample { + + private static class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } + } + + public void exampleRepeat() { + Observable words = Observable.range(0,2); + + words.repeat() + .take(4) + .subscribe(System.out::println); + + // 0 + // 1 + // 0 + // 1 + } + + public void exampleRepeat2() { + Observable words = Observable.range(0,2); + + words.repeat(2) + .subscribe(System.out::println); + + // 0 + // 1 + // 0 + // 1 + } + + public void exampleRepeatWhen2() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + values + .take(2) + .repeatWhen(ob -> { + return ob.take(2); + }) + .subscribe(new PrintSubscriber("repeatWhen")); + + // repeatWhen: 0 + // repeatWhen: 1 + // repeatWhen: 0 + // repeatWhen: 1 + // repeatWhen: Completed + } + + public void exampleRepeatWithInterval() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + values + .take(5) // Numbers 0 to 4 + .repeatWhen((ob)-> { + ob.subscribe(); + return Observable.interval(2, TimeUnit.SECONDS); + }) // Repeat 0 to 4 every 2s, forever + .take(2) // Stop after second repetition + .subscribe(new PrintSubscriber("repeatWhen")); + + // repeatWhen: 0 + // repeatWhen: 1 + // repeatWhen: 2 + // repeatWhen: 3 + // repeatWhen: 4 + // repeatWhen: 0 + // repeatWhen: 1 + // repeatWhen: 2 + // repeatWhen: 3 + // repeatWhen: 4 + } + + + // + // Tests + // + + @Test + public void testRepeat() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable words = Observable.range(0,2); + + words.repeat() + .take(4) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0,1,0,1)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testRepeat2() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable words = Observable.range(0,2); + + words.repeat(2) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0,1,0,1)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + + } + + @Test + public void testRepeatWhen2() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.range(0, 2); + + values + .repeatWhen(ob -> { + return ob.take(2); + }) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0,1,0,1)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testRepeatWithInterval() { + TestSubscriber tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + + values + .take(5) // Numbers 0 to 4 + .repeatWhen((ob)-> { + ob.subscribe(); + return Observable.interval(2, TimeUnit.SECONDS, scheduler); + }) // Repeat 0 to 4 every 2s, forever + .subscribe(tester); + + scheduler.advanceTimeBy(4, TimeUnit.SECONDS); + + tester.assertReceivedOnNext(Arrays.asList(0L,1L,2L,3L,4L,0L,1L,2L,3L,4L)); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter3/combining/StartWithExample.java b/tests/java/itrx/chapter3/combining/StartWithExample.java new file mode 100644 index 0000000..f442da0 --- /dev/null +++ b/tests/java/itrx/chapter3/combining/StartWithExample.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.combining; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class StartWithExample { + + public void example() { + Observable values = Observable.range(0, 3); + + values.startWith(-1,-2) + .subscribe(System.out::println); + + // -1 + // -2 + // 0 + // 1 + // 2 + } + + + // + // Test + // + + @Test + public void test() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.range(0, 3); + + values.startWith(-1,-2) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(-1,-2,0,1,2)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter3/combining/SwitchMapExample.java b/tests/java/itrx/chapter3/combining/SwitchMapExample.java new file mode 100644 index 0000000..2257b86 --- /dev/null +++ b/tests/java/itrx/chapter3/combining/SwitchMapExample.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.combining; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class SwitchMapExample { + + public void example() { + Observable.interval(100, TimeUnit.MILLISECONDS) + .switchMap(i -> + Observable.interval(30, TimeUnit.MILLISECONDS) + .map(l -> i)) + .take(9) + .subscribe(System.out::println); + + // 0 + // 0 + // 0 + // 1 + // 1 + // 1 + // 2 + // 2 + // 2 + } + + + // + // Test + // + + @Test + public void test() { + TestSubscriber tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .switchMap(i -> + Observable.interval(30, TimeUnit.MILLISECONDS, scheduler) + .map(l -> i)) + .take(9) + .distinctUntilChanged() + .subscribe(tester); + + scheduler.advanceTimeBy(400, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter3/combining/SwitchOnNextExample.java b/tests/java/itrx/chapter3/combining/SwitchOnNextExample.java new file mode 100644 index 0000000..41afcda --- /dev/null +++ b/tests/java/itrx/chapter3/combining/SwitchOnNextExample.java @@ -0,0 +1,87 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.combining; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class SwitchOnNextExample { + + public void example() { + Observable.switchOnNext( + Observable.interval(100, TimeUnit.MILLISECONDS) + .map(i -> + Observable.interval(30, TimeUnit.MILLISECONDS) + .map(i2 -> i) + ) + ) + .take(9) + .subscribe(System.out::println); + + // 0 + // 0 + // 0 + // 1 + // 1 + // 1 + // 2 + // 2 + // 2 + } + + + // + // Test + // + + @Test + public void test() { + TestSubscriber tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.switchOnNext( + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .map(i -> + Observable.interval(30, TimeUnit.MILLISECONDS, scheduler) + .map(i2 -> i) + ) + ) + .distinctUntilChanged() + .subscribe(tester); + + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(0L,1L,2L,3L)); + tester.assertNoErrors(); + assertEquals(tester.getOnCompletedEvents().size(), 0); + } + +} diff --git a/tests/java/itrx/chapter3/combining/ZipExample.java b/tests/java/itrx/chapter3/combining/ZipExample.java new file mode 100644 index 0000000..05d9047 --- /dev/null +++ b/tests/java/itrx/chapter3/combining/ZipExample.java @@ -0,0 +1,246 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.combining; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class ZipExample { + + public void example() { + Observable.zip( + Observable.interval(100, TimeUnit.MILLISECONDS) + .doOnNext(i -> System.out.println("Left emits " + i)), + Observable.interval(150, TimeUnit.MILLISECONDS) + .doOnNext(i -> System.out.println("Right emits " + i)), + (i1,i2) -> i1 + " - " + i2 + ) + .take(6) + .subscribe(System.out::println); + + // Left emits + // Right emits + // 0 - 0 + // Left emits + // Right emits + // Left emits + // 1 - 1 + // Left emits + // Right emits + // 2 - 2 + // Left emits + // Left emits + // Right emits + // 3 - 3 + // Left emits + // Right emits + // 4 - 4 + // Left emits + // Right emits + // Left emits + // 5 - 5 + } + + public void exampleZipMultiple() { + Observable.zip( + Observable.interval(100, TimeUnit.MILLISECONDS), + Observable.interval(150, TimeUnit.MILLISECONDS), + Observable.interval(050, TimeUnit.MILLISECONDS), + (i1,i2,i3) -> i1 + " - " + i2 + " - " + i3) + .take(6) + .subscribe(System.out::println); + + // 0 - 0 - 0 + // 1 - 1 - 1 + // 2 - 2 - 2 + // 3 - 3 - 3 + // 4 - 4 - 4 + // 5 - 5 - 5 + } + + public void exampleZipUneven() { + Observable.zip( + Observable.range(0, 5), + Observable.range(0, 3), + Observable.range(0, 8), + (i1,i2,i3) -> i1 + " - " + i2 + " - " + i3) + .count() + .subscribe(System.out::println); + + // 3 + } + + public void exampleZipWith() { + Observable.interval(100, TimeUnit.MILLISECONDS) + .zipWith( + Observable.interval(150, TimeUnit.MILLISECONDS), + (i1,i2) -> i1 + " - " + i2) + .take(6) + .subscribe(System.out::println); + + // 0 - 0 + // 1 - 1 + // 2 - 2 + // 3 - 3 + // 4 - 4 + // 5 - 5 + } + + public void exampleZipWithIterable() { + Observable.range(0, 5) + .zipWith( + Arrays.asList(0,2,4,6,8), + (i1,i2) -> i1 + " - " + i2) + .subscribe(System.out::println); + + // 0 - 0 + // 1 - 2 + // 2 - 4 + // 3 - 6 + // 4 - 8 + } + + + // + // Test + // + + @Test + public void test() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Observable.zip( + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler), + Observable.interval(150, TimeUnit.MILLISECONDS, scheduler), + (i1,i2) -> i1 + " - " + i2 + ) + .subscribe(tester); + + scheduler.advanceTimeBy(600, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList( + "0 - 0", + "1 - 1", + "2 - 2", + "3 - 3" + )); + tester.assertNoErrors(); + + tester.unsubscribe(); + } + + @Test + public void testZipMultiple() { + TestSubscriber tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.zip( + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler), + Observable.interval(150, TimeUnit.MILLISECONDS, scheduler), + Observable.interval(050, TimeUnit.MILLISECONDS, scheduler), + (i1,i2,i3) -> i1 + " - " + i2 + " - " + i3) + .subscribe(tester); + + scheduler.advanceTimeBy(600, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList( + "0 - 0 - 0", + "1 - 1 - 1", + "2 - 2 - 2", + "3 - 3 - 3" + )); + tester.assertNoErrors(); + + tester.unsubscribe(); + } + + @Test + public void testZipUneven() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable.zip( + Observable.range(0, 5), + Observable.range(0, 3), + Observable.range(0, 8), + (i1,i2,i3) -> i1 + " - " + i2 + " - " + i3) + .count() + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(3)); + + tester.unsubscribe(); + } + + @Test + public void testZipWith() { + TestSubscriber tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .zipWith( + Observable.interval(150, TimeUnit.MILLISECONDS, scheduler), + (i1,i2) -> i1 + " - " + i2) + .subscribe(tester); + + scheduler.advanceTimeBy(600, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList( + "0 - 0", + "1 - 1", + "2 - 2", + "3 - 3" + )); + tester.assertNoErrors(); + + tester.unsubscribe(); + } + + @Test + public void testZipWithIterable() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable.range(0, 5) + .zipWith( + Arrays.asList(0,2,4,6,8), + (i1,i2) -> i1 + " - " + i2) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList( + "0 - 0", + "1 - 2", + "2 - 4", + "3 - 6", + "4 - 8" + )); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + + tester.unsubscribe(); + } + +} diff --git a/tests/java/itrx/chapter3/custom/ComposeExample.java b/tests/java/itrx/chapter3/custom/ComposeExample.java new file mode 100644 index 0000000..aaaccec --- /dev/null +++ b/tests/java/itrx/chapter3/custom/ComposeExample.java @@ -0,0 +1,126 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.custom; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class ComposeExample { + + /** + * A custom operator for calculating a running average + * + * @author Chris + * + */ + public static class RunningAverage implements Observable.Transformer { + private static class AverageAcc { + public final int sum; + public final int count; + public AverageAcc(int sum, int count) { + this.sum = sum; + this.count = count; + } + } + + final int threshold; + + public RunningAverage() { + this.threshold = Integer.MAX_VALUE; + } + + public RunningAverage(int threshold) { + this.threshold = threshold; + } + + @Override + public Observable call(Observable source) { + return source + .filter(i -> i< this.threshold) + .scan( + new AverageAcc(0,0), + (acc, v) -> new AverageAcc(acc.sum + v, acc.count + 1)) + .filter(acc -> acc.count > 0) + .map(acc -> acc.sum/(double)acc.count); + } + } + + public void exampleComposeFromClass() { + Observable.just(2, 3, 10, 12, 4) + .compose(new RunningAverage()) + .subscribe(System.out::println); + + // 2.0 + // 2.5 + // 5.0 + // 6.75 + // 6.2 + } + + public void exampleComposeParameterised() { + Observable.just(2, 3, 10, 12, 4) + .compose(new RunningAverage(5)) + .subscribe(System.out::println); + + // 2.0 + // 2.5 + // 3.0 + } + + + // + // Test + // + + @Test + public void testComposeFromClass() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable.just(2, 3, 10, 12, 4) + .compose(new RunningAverage()) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(2.0, 2.5, 5.0, 6.75, 6.2)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testComposeParameterised() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable.just(2, 3, 10, 12, 4) + .compose(new RunningAverage(5)) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(2.0, 2.5, 3.0)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + +} diff --git a/tests/java/itrx/chapter3/custom/LiftExample.java b/tests/java/itrx/chapter3/custom/LiftExample.java new file mode 100644 index 0000000..35d79a0 --- /dev/null +++ b/tests/java/itrx/chapter3/custom/LiftExample.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.custom; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.functions.Func1; +import rx.observers.TestSubscriber; + +public class LiftExample { + + public static class MyMap implements Observable.Operator { + private Func1 transformer; + + public MyMap(Func1 transformer) { + this.transformer = transformer; + } + + @Override + public Subscriber call(Subscriber subscriber) { + return new Subscriber() { + + @Override + public void onCompleted() { + if (!subscriber.isUnsubscribed()) + subscriber.onCompleted(); + } + + @Override + public void onError(Throwable e) { + if (!subscriber.isUnsubscribed()) + subscriber.onError(e); + } + + @Override + public void onNext(T t) { + if (!subscriber.isUnsubscribed()) + subscriber.onNext(transformer.call(t)); + } + + }; + } + + public static MyMap create(Func1 transformer) { + return new MyMap(transformer); + } + } + + public void exampleLift() { + Observable.range(0, 5) + .lift(MyMap.create(i -> i + "!")) + .subscribe(System.out::println); + + // 0! + // 1! + // 2! + // 3! + // 4! + } + + + // + // Tests + // + + @Test + public void testLift() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable.range(0, 5) + .lift(MyMap.create(i -> i + "!")) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList("0!", "1!", "2!", "3!", "4!")); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } +} diff --git a/tests/java/itrx/chapter3/custom/SerializeExample.java b/tests/java/itrx/chapter3/custom/SerializeExample.java new file mode 100644 index 0000000..3c39d51 --- /dev/null +++ b/tests/java/itrx/chapter3/custom/SerializeExample.java @@ -0,0 +1,197 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.custom; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; + +public class SerializeExample { + + public void exampleSafeSubscribe() { + Observable source = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onCompleted(); + o.onNext(3); + o.onCompleted(); + }); + + source.doOnUnsubscribe(() -> System.out.println("Unsubscribed")) + .subscribe( + System.out::println, + System.out::println, + () -> System.out.println("Completed")); + + // 1 + // 2 + // Completed + // Unsubscribed + } + + public void exampleUnsafeSubscribe() { + Observable source = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onCompleted(); + o.onNext(3); + o.onCompleted(); + }); + + source.doOnUnsubscribe(() -> System.out.println("Unsubscribed")) + .unsafeSubscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("Completed"); + } + + @Override + public void onError(Throwable e) { + System.out.println(e); + } + + @Override + public void onNext(Integer t) { + System.out.println(t); + } + }); + + // 1 + // 2 + // Completed + // 3 + // Completed + } + + public void exampleSerialize() { + Observable source = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onCompleted(); + o.onNext(3); + o.onCompleted(); + }) + .cast(Integer.class) + .serialize();; + + + source.doOnUnsubscribe(() -> System.out.println("Unsubscribed")) + .unsafeSubscribe(new Subscriber() { + @Override + public void onCompleted() { + System.out.println("Completed"); + } + + @Override + public void onError(Throwable e) { + System.out.println(e); + } + + @Override + public void onNext(Integer t) { + System.out.println(t); + } + }); + +// 1 +// 2 +// Completed + } + + + // + // Tests + // + + @Test + public void testSafeSubscribe() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable source = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onCompleted(); + o.onNext(3); + o.onCompleted(); + }); + + source.subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(1, 2)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + tester.assertUnsubscribed(); + } + + @Test + public void testUnsafeSubscribe() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable source = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onCompleted(); + o.onNext(3); + o.onCompleted(); + }); + + source.doOnUnsubscribe(() -> System.out.println("Unsubscribed")) + .unsafeSubscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(1, 2, 3)); + assertEquals(2, tester.getOnCompletedEvents().size()); + tester.assertNoErrors(); + assertFalse(tester.isUnsubscribed()); + } + + @Test + public void testSerialize() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable source = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onCompleted(); + o.onNext(3); + o.onCompleted(); + }) + .cast(Integer.class) + .serialize();; + + + source.doOnUnsubscribe(() -> System.out.println("Unsubscribed")) + .unsafeSubscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(1, 2)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + assertFalse(tester.isUnsubscribed()); + } + +} diff --git a/tests/java/itrx/chapter3/error/ResumeExample.java b/tests/java/itrx/chapter3/error/ResumeExample.java new file mode 100644 index 0000000..da4fa15 --- /dev/null +++ b/tests/java/itrx/chapter3/error/ResumeExample.java @@ -0,0 +1,243 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.error; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.observers.TestSubscriber; + +public class ResumeExample { + + private static class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } + } + + public void exampleOnErrorReturn() { + Observable values = Observable.create(o -> { + o.onNext("Rx"); + o.onNext("is"); + o.onError(new Exception("adjective unknown")); + }); + + values + .onErrorReturn(e -> "Error: " + e.getMessage()) + .subscribe(v -> System.out.println(v)); + + // Rx + // is + // Error: adjective unknown + } + + public void exampleOnErrorResumeNext() { + Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onError(new Exception("Oops")); + }); + + values + .onErrorResumeNext(Observable.just(Integer.MAX_VALUE)) + .subscribe(new PrintSubscriber("with onError: ")); + + // with onError: 1 + // with onError: 2 + // with onError: 2147483647 + // with onError: Completed + } + + public void exampleOnErrorResumeNextRethrow() { + Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onError(new Exception("Oops")); + }); + + values + .onErrorResumeNext(e -> Observable.error(new UnsupportedOperationException(e))) + .subscribe(new PrintSubscriber("with onError: ")); + + // with onError: : 1 + // with onError: : 2 + // with onError: : Error: java.lang.UnsupportedOperationException: java.lang.Exception: Oops + } + + public void exampleOnExceptionResumeNext() { + Observable values = Observable.create(o -> { + o.onNext("Rx"); + o.onNext("is"); + o.onError(new Exception()); // this will be caught + }); + + values + .onExceptionResumeNext(Observable.just("hard")) + .subscribe(v -> System.out.println(v)); + + // Rx + // is + // hard + } + + @SuppressWarnings("serial") + public void exampleOnExceptionResumeNextNoException() { + Observable values = Observable.create(o -> { + o.onNext("Rx"); + o.onNext("is"); + o.onError(new Throwable() {}); // this won't be caught + }); + + values + .onExceptionResumeNext(Observable.just("hard")) + .subscribe(v -> System.out.println(v)); + + // Rx + // is + // uncaught exception + } + + + // + // Tests + // + + @Test + public void testOnErrorReturn() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.create(o -> { + o.onNext("Rx"); + o.onNext("is"); + o.onError(new Exception("adjective unknown")); + }); + + values + .onErrorReturn(e -> "Error: " + e.getMessage()) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList( + "Rx", + "is", + "Error: adjective unknown")); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testerOnErrorResumeNext() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onError(new Exception("Oops")); + }); + + values + .onErrorResumeNext(Observable.just(Integer.MAX_VALUE)) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(1,2,Integer.MAX_VALUE)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testOnErrorResumeNextRethrow() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onError(new Exception("Oops")); + }); + + values + .onErrorResumeNext(e -> Observable.error(new UnsupportedOperationException(e))) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(1,2)); + tester.assertTerminalEvent(); + assertThat(tester.getOnErrorEvents().get(0), + org.hamcrest.CoreMatchers.instanceOf(UnsupportedOperationException.class)); + } + + @Test + public void testOnExceptionResumeNext() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.create(o -> { + o.onNext("Rx"); + o.onNext("is"); + o.onError(new Exception()); // this will be caught + }); + + values + .onExceptionResumeNext(Observable.just("hard")) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList("Rx","is","hard")); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @SuppressWarnings("serial") + @Test + public void testOnExceptionResumeNextNoException() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.create(o -> { + o.onNext("Rx"); + o.onNext("is"); + o.onError(new Throwable() {}); // this won't be caught + }); + + values + .onExceptionResumeNext(Observable.just("hard")) + .subscribe(tester); + + tester.assertTerminalEvent(); + assertEquals(tester.getOnErrorEvents().size(), 1); + } + +} diff --git a/tests/java/itrx/chapter3/error/RetryExample.java b/tests/java/itrx/chapter3/error/RetryExample.java new file mode 100644 index 0000000..03c3cc5 --- /dev/null +++ b/tests/java/itrx/chapter3/error/RetryExample.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.error; + +import static org.junit.Assert.*; + +import java.util.Random; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class RetryExample { + + public void exampleRetry() { + Random random = new Random(); + Observable values = Observable.create(o -> { + o.onNext(random.nextInt() % 20); + o.onNext(random.nextInt() % 20); + o.onError(new Exception()); + }); + + values + .retry(1) + .subscribe(v -> System.out.println(v)); + + // 0 + // 13 + // 9 + // 15 + // java.lang.Exception + } + + + // + // Test + // + + @Test + public void testRetry() { + TestSubscriber tester = new TestSubscriber<>(); + Random random = new Random(); + Observable values = Observable.create(o -> { + o.onNext(random.nextInt() % 20); + o.onNext(random.nextInt() % 20); + o.onError(new Exception()); + }); + + values + .retry(1) + .subscribe(tester); + + assertEquals(tester.getOnNextEvents().size(), 4); + tester.assertTerminalEvent(); + assertEquals(tester.getOnErrorEvents().size(), 1); + } + +} diff --git a/tests/java/itrx/chapter3/error/RetryWhenExample.java b/tests/java/itrx/chapter3/error/RetryWhenExample.java new file mode 100644 index 0000000..6d1becc --- /dev/null +++ b/tests/java/itrx/chapter3/error/RetryWhenExample.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.error; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class RetryWhenExample { + + public void example() { + Observable source = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onError(new Exception("Failed")); + }); + + source.retryWhen((o) -> o + .take(2) + .delay(100, TimeUnit.MILLISECONDS)) + .timeInterval() + .subscribe( + System.out::println, + System.out::println); + + // TimeInterval [intervalInMilliseconds=17, value=1] + // TimeInterval [intervalInMilliseconds=0, value=2] + // TimeInterval [intervalInMilliseconds=102, value=1] + // TimeInterval [intervalInMilliseconds=0, value=2] + // TimeInterval [intervalInMilliseconds=102, value=1] + // TimeInterval [intervalInMilliseconds=0, value=2] + } + + + // + // Test + // + + @Test + public void test() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber intervals = new TestSubscriber<>(); + + Observable source = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onError(new Exception("Failed")); + }); + source.retryWhen((o) -> o + .take(2) + .delay(100, TimeUnit.MILLISECONDS, scheduler) + , scheduler) + .timeInterval(scheduler) + .map(i -> i.getIntervalInMilliseconds()) + .subscribe(intervals); + + scheduler.advanceTimeBy(200, TimeUnit.MILLISECONDS); + intervals.assertReceivedOnNext(Arrays.asList(0L, 0L, 100L, 0L, 100L, 0L)); + intervals.assertTerminalEvent(); + intervals.assertNoErrors(); + } +} diff --git a/tests/java/itrx/chapter3/error/UsingExample.java b/tests/java/itrx/chapter3/error/UsingExample.java new file mode 100644 index 0000000..52e36ec --- /dev/null +++ b/tests/java/itrx/chapter3/error/UsingExample.java @@ -0,0 +1,105 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.error; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class UsingExample { + + public void exampleUsing() { + Observable values = Observable.using( + () -> { + String resource = "MyResource"; + System.out.println("Leased: " + resource); + return resource; + }, + (resource) -> { + return Observable.create(o -> { + for (Character c : resource.toCharArray()) + o.onNext(c); + o.onCompleted(); + }); + }, + (resource) -> System.out.println("Disposed: " + resource)); + + values + .subscribe( + v -> System.out.println(v), + e -> System.out.println(e)); + + // Leased: MyResource + // M + // y + // R + // e + // s + // o + // u + // r + // c + // e + // Disposed: MyResource + } + + + // + // Test + // + + @Test + public void testUsing() { + TestSubscriber tester = new TestSubscriber<>(); + String[] leaseRelease = {"", ""}; + + Observable values = Observable.using( + () -> { + String resource = "MyResource"; + leaseRelease[0] = resource; + return resource; + }, + (resource) -> { + return Observable.create(o -> { + for (Character c : resource.toCharArray()) + o.onNext(c); + o.onCompleted(); + }); + }, + (resource) -> leaseRelease[1] = resource); + + values + .subscribe(tester); + + assertEquals(leaseRelease[0], leaseRelease[1]); + tester.assertReceivedOnNext(Arrays.asList('M','y','R','e','s','o','u','r','c','e')); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter3/hotandcold/CacheExample.java b/tests/java/itrx/chapter3/hotandcold/CacheExample.java new file mode 100644 index 0000000..f993348 --- /dev/null +++ b/tests/java/itrx/chapter3/hotandcold/CacheExample.java @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.hotandcold; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscription; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class CacheExample { + + public void exampleCache() throws InterruptedException { + Observable obs = Observable.interval(100, TimeUnit.MILLISECONDS) + .take(5) + .cache(); + + Thread.sleep(500); + obs.subscribe(i -> System.out.println("First: " + i)); + Thread.sleep(300); + obs.subscribe(i -> System.out.println("Second: " + i)); + + // First: 0 + // First: 1 + // First: 2 + // Second: 0 + // Second: 1 + // Second: 2 + // First: 3 + // Second: 3 + // First: 4 + // Second: 4 + } + + public void exampleCacheUnsubscribe() throws InterruptedException { + Observable obs = Observable.interval(100, TimeUnit.MILLISECONDS) + .take(5) + .doOnNext(System.out::println) + .cache() + .doOnSubscribe(() -> System.out.println("Subscribed")) + .doOnUnsubscribe(() -> System.out.println("Unsubscribed")); + + Subscription subscription = obs.subscribe(); + Thread.sleep(150); + subscription.unsubscribe(); + + // Subscribed + // 0 + // Unsubscribed + // 1 + // 2 + // 3 + // 4 + } + + + // + // Tests + // + + @Test + public void testCache() throws InterruptedException { + TestSubscriber tester1 = new TestSubscriber(); + TestSubscriber tester2 = new TestSubscriber(); + TestScheduler scheduler = Schedulers.test(); + + Observable obs = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .take(5) + .cache(); + + tester1.assertReceivedOnNext(Arrays.asList()); + tester2.assertReceivedOnNext(Arrays.asList()); + + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + obs.subscribe(tester1); + tester1.assertReceivedOnNext(Arrays.asList()); + tester2.assertReceivedOnNext(Arrays.asList()); + + scheduler.advanceTimeBy(300, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L)); + tester2.assertReceivedOnNext(Arrays.asList()); + + obs.subscribe(tester2); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L)); + tester2.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L)); + + scheduler.advanceTimeBy(200, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L)); + tester2.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L)); + } + + @Test + public void testCacheUnsubscribe() throws InterruptedException { + TestSubscriber tester = new TestSubscriber(); + TestScheduler scheduler = Schedulers.test(); + + Observable obs = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .take(5) + .doOnEach(tester) + .cache(); + + Subscription subscription = obs.subscribe(); + scheduler.advanceTimeBy(150, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(0L)); + + subscription.unsubscribe(); + scheduler.advanceTimeBy(350, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L)); + } + +} diff --git a/tests/java/itrx/chapter3/hotandcold/ColdExample.java b/tests/java/itrx/chapter3/hotandcold/ColdExample.java new file mode 100644 index 0000000..372612b --- /dev/null +++ b/tests/java/itrx/chapter3/hotandcold/ColdExample.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.hotandcold; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class ColdExample { + + public void example() throws InterruptedException { + Observable cold = + Observable + .interval(200, TimeUnit.MILLISECONDS) + .take(5); + + cold.subscribe(i -> System.out.println("First: " + i)); + Thread.sleep(500); + cold.subscribe(i -> System.out.println("Second: " + i)); + + // First: 0 + // First: 1 + // First: 2 + // Second: 0 + // First: 3 + // Second: 1 + // First: 4 + // Second: 2 + // Second: 3 + // Second: 4 + } + + + // + // Test + // + + @Test + public void test() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester1 = new TestSubscriber<>(); + TestSubscriber tester2 = new TestSubscriber<>(); + + Observable cold = + Observable + .interval(200, TimeUnit.MILLISECONDS, scheduler) + .take(5); + + cold.subscribe(tester1); + tester1.assertReceivedOnNext(Arrays.asList()); + tester2.assertReceivedOnNext(Arrays.asList()); + + scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); + cold.subscribe(tester2); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L)); + tester2.assertReceivedOnNext(Arrays.asList()); + + scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L)); + tester2.assertReceivedOnNext(Arrays.asList(0L, 1L)); + + scheduler.advanceTimeTo(1500, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L)); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L)); + } + +} diff --git a/tests/java/itrx/chapter3/hotandcold/ConnectableObservableExample.java b/tests/java/itrx/chapter3/hotandcold/ConnectableObservableExample.java new file mode 100644 index 0000000..37212b4 --- /dev/null +++ b/tests/java/itrx/chapter3/hotandcold/ConnectableObservableExample.java @@ -0,0 +1,273 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.hotandcold; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscription; +import rx.observables.ConnectableObservable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class ConnectableObservableExample { + + public void exampleConnect() throws InterruptedException { + ConnectableObservable cold = Observable.interval(200, TimeUnit.MILLISECONDS).publish(); + cold.connect(); + + cold.subscribe(i -> System.out.println("First: " + i)); + Thread.sleep(500); + cold.subscribe(i -> System.out.println("Second: " + i)); + + // First: 0 + // First: 1 + // First: 2 + // Second: 2 + // First: 3 + // Second: 3 + // First: 4 + // Second: 4 + // First: 5 + // Second: 5 + } + + public void exampleDisconnect() throws InterruptedException { + ConnectableObservable connectable = Observable.interval(200, TimeUnit.MILLISECONDS).publish(); + Subscription s = connectable.connect(); + + connectable.subscribe(i -> System.out.println(i)); + + Thread.sleep(1000); + System.out.println("Closing connection"); + s.unsubscribe(); + + Thread.sleep(1000); + System.out.println("Reconnecting"); + s = connectable.connect(); + + // 0 + // 1 + // 2 + // 3 + // 4 + // Closing connection + // Reconnecting + // 0 + // 1 + // 2 + } + + public void exampleUnsubscribe() throws InterruptedException { + ConnectableObservable connectable = Observable.interval(200, TimeUnit.MILLISECONDS).publish(); + connectable.connect(); + + connectable.subscribe(i -> System.out.println("First: " + i)); + Thread.sleep(500); + Subscription s2 = connectable.subscribe(i -> System.out.println("Seconds: " + i)); + + Thread.sleep(500); + System.out.println("Unsubscribing second"); + s2.unsubscribe(); + + // First: 0 + // First: 1 + // First: 2 + // Seconds: 2 + // First: 3 + // Seconds: 3 + // First: 4 + // Seconds: 4 + // Unsubscribing second + // First: 5 + // First: 6 + } + + public void exampleRefcount() throws InterruptedException { + Observable cold = Observable.interval(200, TimeUnit.MILLISECONDS).publish().refCount(); + + Subscription s1 = cold.subscribe(i -> System.out.println("First: " + i)); + Thread.sleep(500); + Subscription s2 = cold.subscribe(i -> System.out.println("Second: " + i)); + Thread.sleep(500); + System.out.println("Unsubscribe first"); + s2.unsubscribe(); + Thread.sleep(500); + System.out.println("Unsubscribe first"); + s1.unsubscribe(); + + System.out.println("First connection again"); + Thread.sleep(500); + s1 = cold.subscribe(i -> System.out.println("First: " + i)); + + // First: 0 + // First: 1 + // First: 2 + // Second: 2 + // First: 3 + // Second: 3 + // Unsubscribe first + // First: 4 + // First: 5 + // First: 6 + // Unsubscribe first + // First connection again + // First: 0 + // First: 1 + // First: 2 + // First: 3 + // First: 4 + } + + + // + // Test + // + + @Test + public void testConnect() throws InterruptedException { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester1 = new TestSubscriber(); + TestSubscriber tester2 = new TestSubscriber(); + + ConnectableObservable cold = + Observable + .interval(200, TimeUnit.MILLISECONDS, scheduler) + .publish(); + Subscription connection = cold.connect(); + + cold.subscribe(tester1); + scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L)); + tester2.assertReceivedOnNext(Arrays.asList()); + + cold.subscribe(tester2); + scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L)); + tester2.assertReceivedOnNext(Arrays.asList(2L, 3L, 4L)); + + connection.unsubscribe(); + } + + @Test + public void testDisconnect() throws InterruptedException { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber(); + + ConnectableObservable connectable = + Observable + .interval(200, TimeUnit.MILLISECONDS, scheduler) + .publish(); + Subscription connection = connectable.connect(); + connectable.subscribe(tester); + + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L)); + + connection.unsubscribe(); + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L)); + + connection = connectable.connect(); + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L, 0L, 1L, 2L, 3L, 4L)); + + connection.unsubscribe(); + } + + @Test + public void testUnsubscribe() throws InterruptedException { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester1 = new TestSubscriber(); + TestSubscriber tester2 = new TestSubscriber(); + + ConnectableObservable connectable = + Observable + .interval(200, TimeUnit.MILLISECONDS, scheduler) + .publish(); + Subscription conSubscription = connectable.connect(); + + connectable.subscribe(tester1); + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L)); + tester2.assertReceivedOnNext(Arrays.asList()); + + Subscription s2 = connectable.subscribe(tester2); + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L)); + tester2.assertReceivedOnNext(Arrays.asList(2L, 3L, 4L)); + + s2.unsubscribe(); + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L, 5L, 6L)); + tester2.assertReceivedOnNext(Arrays.asList(2L, 3L, 4L)); + + conSubscription.unsubscribe(); + } + + @Test + public void testRefcount() throws InterruptedException { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester1 = new TestSubscriber(); + TestSubscriber tester2 = new TestSubscriber(); + TestSubscriber tester3 = new TestSubscriber(); + + Observable cold = + Observable + .interval(200, TimeUnit.MILLISECONDS, scheduler) + .publish() + .refCount(); + + Subscription s1 = cold.subscribe(tester1); + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L)); + tester2.assertReceivedOnNext(Arrays.asList()); + tester3.assertReceivedOnNext(Arrays.asList()); + + Subscription s2 = cold.subscribe(tester2); + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L)); + tester2.assertReceivedOnNext(Arrays.asList(2L, 3L, 4L)); + tester3.assertReceivedOnNext(Arrays.asList()); + + s2.unsubscribe(); + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L, 5L, 6L)); + tester2.assertReceivedOnNext(Arrays.asList(2L, 3L, 4L)); + tester3.assertReceivedOnNext(Arrays.asList()); + + s1.unsubscribe(); + Subscription s3 = cold.subscribe(tester3); + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L, 5L, 6L)); + tester2.assertReceivedOnNext(Arrays.asList(2L, 3L, 4L)); + tester3.assertReceivedOnNext(Arrays.asList(0L, 1L)); + + s3.unsubscribe(); + } + +} diff --git a/tests/java/itrx/chapter3/hotandcold/MulticastExample.java b/tests/java/itrx/chapter3/hotandcold/MulticastExample.java new file mode 100644 index 0000000..eca36ef --- /dev/null +++ b/tests/java/itrx/chapter3/hotandcold/MulticastExample.java @@ -0,0 +1,123 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.hotandcold; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscription; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class MulticastExample { + + public void exampleMutlicast() throws InterruptedException { + Observable cold = + Observable + .interval(200, TimeUnit.MILLISECONDS) + .publish() + .refCount(); + + Subscription s1 = cold.subscribe(i -> System.out.println("First: " + i)); + Thread.sleep(500); + Subscription s2 = cold.subscribe(i -> System.out.println("Second: " + i)); + Thread.sleep(500); + System.out.println("Unsubscribe first"); + s2.unsubscribe(); + Thread.sleep(500); + System.out.println("Unsubscribe first"); + s1.unsubscribe(); + + System.out.println("First connection again"); + Thread.sleep(500); + s1 = cold.subscribe(i -> System.out.println("First: " + i)); + + // First: 0 + // First: 1 + // First: 2 + // Second: 2 + // First: 3 + // Second: 3 + // Unsubscribe first + // First: 4 + // First: 5 + // First: 6 + // Unsubscribe first + // First connection again + // First: 0 + // First: 1 + // First: 2 + // First: 3 + // First: 4 + } + + + // + // Test + // + + @Test + public void testRefcount() throws InterruptedException { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester1 = new TestSubscriber(); + TestSubscriber tester2 = new TestSubscriber(); + TestSubscriber tester3 = new TestSubscriber(); + + Observable cold = + Observable + .interval(200, TimeUnit.MILLISECONDS, scheduler) + .share(); + + Subscription s1 = cold.subscribe(tester1); + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L)); + tester2.assertReceivedOnNext(Arrays.asList()); + tester3.assertReceivedOnNext(Arrays.asList()); + + Subscription s2 = cold.subscribe(tester2); + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L)); + tester2.assertReceivedOnNext(Arrays.asList(2L, 3L, 4L)); + tester3.assertReceivedOnNext(Arrays.asList()); + + s2.unsubscribe(); + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L, 5L, 6L)); + tester2.assertReceivedOnNext(Arrays.asList(2L, 3L, 4L)); + tester3.assertReceivedOnNext(Arrays.asList()); + + s1.unsubscribe(); + Subscription s3 = cold.subscribe(tester3); + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L, 5L, 6L)); + tester2.assertReceivedOnNext(Arrays.asList(2L, 3L, 4L)); + tester3.assertReceivedOnNext(Arrays.asList(0L, 1L)); + + s3.unsubscribe(); + } + +} diff --git a/tests/java/itrx/chapter3/hotandcold/ReplayExample.java b/tests/java/itrx/chapter3/hotandcold/ReplayExample.java new file mode 100644 index 0000000..69aecc6 --- /dev/null +++ b/tests/java/itrx/chapter3/hotandcold/ReplayExample.java @@ -0,0 +1,161 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.hotandcold; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscription; +import rx.observables.ConnectableObservable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class ReplayExample { + + public void exampleReplay() throws InterruptedException { + ConnectableObservable cold = Observable.interval(200, TimeUnit.MILLISECONDS).replay(); + cold.connect(); + + System.out.println("Subscribe first"); + cold.subscribe(i -> System.out.println("First: " + i)); + Thread.sleep(700); + System.out.println("Subscribe second"); + cold.subscribe(i -> System.out.println("Second: " + i)); + Thread.sleep(500); + + // Subscribe first + // First: 0 + // First: 1 + // First: 2 + // Subscribe second + // Second: 0 + // Second: 1 + // Second: 2 + // First: 3 + // Second: 3 + } + + public void exampleReplayWithBufferSize() throws InterruptedException { + ConnectableObservable source = Observable.interval(1000, TimeUnit.MILLISECONDS) + .take(5) + .replay(2); + + source.connect(); + Thread.sleep(4500); + source.subscribe(System.out::println); + + // 2 + // 3 + // 4 + } + + public void exampleReplayWithTime() throws InterruptedException { + ConnectableObservable source = Observable.interval(1000, TimeUnit.MILLISECONDS) + .take(5) + .replay(2000, TimeUnit.MILLISECONDS); + + source.connect(); + Thread.sleep(4500); + source.subscribe(System.out::println); + + // 2 + // 3 + // 4 + } + + + // + // Test + // + + @Test + public void testReplay() throws InterruptedException { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester1 = new TestSubscriber<>(); + TestSubscriber tester2 = new TestSubscriber<>(); + + ConnectableObservable cold = + Observable + .interval(200, TimeUnit.MILLISECONDS, scheduler) + .replay(); + Subscription connection = cold.connect(); + + cold.subscribe(tester1); + scheduler.advanceTimeBy(700, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L)); + tester2.assertReceivedOnNext(Arrays.asList()); + + cold.subscribe(tester2); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L)); + tester2.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L)); + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester1.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L, 5L)); + tester2.assertReceivedOnNext(Arrays.asList(0L, 1L, 2L, 3L, 4L, 5L)); + + connection.unsubscribe(); + } + + @Test + public void testReplayWithBufferSize() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + ConnectableObservable source = Observable.interval(1000, TimeUnit.MILLISECONDS, scheduler) + .take(5) + .replay(2, scheduler); + + source.connect(); + scheduler.advanceTimeBy(4500, TimeUnit.MILLISECONDS); + source.subscribe(tester); + scheduler.triggerActions(); + tester.assertReceivedOnNext(Arrays.asList(2L, 3L)); + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(2L, 3L, 4L)); + } + + @Test + public void testReplayWithTime() throws InterruptedException { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + ConnectableObservable source = Observable.interval(1000, TimeUnit.MILLISECONDS, scheduler) + .take(5) + .replay(2000, TimeUnit.MILLISECONDS, scheduler); + + source.connect(); + scheduler.advanceTimeBy(4500, TimeUnit.MILLISECONDS); + source.subscribe(tester); + tester.assertReceivedOnNext(Arrays.asList(2L, 3L)); + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(2L, 3L, 4L)); + +// 2 +// 3 +// 4 + } + +} diff --git a/tests/java/itrx/chapter3/leaving/FirstLastSingleExample.java b/tests/java/itrx/chapter3/leaving/FirstLastSingleExample.java new file mode 100644 index 0000000..1fe7f0a --- /dev/null +++ b/tests/java/itrx/chapter3/leaving/FirstLastSingleExample.java @@ -0,0 +1,98 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.leaving; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; + +public class FirstLastSingleExample { + + public void exampleFirst() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + long value = values + .take(5) + .toBlocking() + .first(i -> i>2); + System.out.println(value); + + // 3 + } + + public void exampleSingleError() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + try { + long value = values + .take(5) + .toBlocking() + .single(i -> i>2); + System.out.println(value); + } + catch (Exception e) { + System.out.println("Caught: " + e); + } + + // Caught: java.lang.IllegalArgumentException: Sequence contains too many elements + } + + + // + // Tests + // + + @Test + public void testFirst() throws InterruptedException { + List received = new ArrayList<>(); + + Observable values = Observable.range(0,5); + + int value = values + .take(5) + .toBlocking() + .first(i -> i>2); + received.add(value); + + assertEquals(received, Arrays.asList(3)); + } + + @Test(expected = IllegalArgumentException.class) + public void testSingleError() { + Observable values = Observable.range(0, 5); + + long value = values + .take(5) + .toBlocking() + .single(i -> i>2); + System.out.println(value); + } + +} diff --git a/tests/java/itrx/chapter3/leaving/ForEachExample.java b/tests/java/itrx/chapter3/leaving/ForEachExample.java new file mode 100644 index 0000000..d13574e --- /dev/null +++ b/tests/java/itrx/chapter3/leaving/ForEachExample.java @@ -0,0 +1,158 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.leaving; + +import static org.junit.Assert.*; + +import java.lang.Thread.State; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class ForEachExample { + + public void exampleObservableForEach() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + values + .take(5) + .forEach( + v -> System.out.println(v)); + System.out.println("Subscribed"); + + // Subscribed + // 0 + // 1 + // 2 + // 3 + // 4 + } + + public void exampleBlockingForEach() { + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS); + + values + .take(5) + .toBlocking() + .forEach( + v -> System.out.println(v)); + System.out.println("Subscribed"); + + // 0 + // 1 + // 2 + // 3 + // 4 + // Subscribed + } + + public void exampleBlockingForEachError() { + Observable values = Observable.error(new Exception("Oops")); + + try { + values + .take(5) + .toBlocking() + .forEach( + v -> System.out.println(v)); + } + catch (Exception e) { + System.out.println("Caught: " + e.getMessage()); + } + System.out.println("Subscribed"); + + // Caught: java.lang.Exception: Oops + // Subscribed + } + + + // + // Tests + // + + @Test + public void testObservableForEach() { + List received = new ArrayList<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + + values + .take(5) + .forEach( + i -> received.add(i)); + received.add(-1L); // Mark that forEach statement returned + + assertEquals(received, Arrays.asList(-1L)); + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + assertEquals(received, Arrays.asList(-1L, 0L, 1L, 2L, 3L, 4L)); + } + + @Test + public void testBlockingForEach() throws InterruptedException { + List received = new ArrayList<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable values = Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + + // Blocking call on new thread + Thread thread = new Thread(() -> { + values + .take(5) + .toBlocking() + .forEach( + i -> received.add(i)); + received.add(-1L); // Mark that forEach statement returned + + }); + thread.start(); + + assertEquals(received, Arrays.asList()); + // Wait for blocking call to block before producing values + while (thread.getState() != State.WAITING) + Thread.sleep(1); + scheduler.advanceTimeBy(1, TimeUnit.SECONDS); + // Wait for processing to complete + thread.join(50); + assertEquals(received, Arrays.asList(0L, 1L, 2L, 3L, 4L, -1L)); + } + + @Test(expected = Exception.class) + public void testBlockingForEachError() { + Observable values = Observable.error(new Exception("Oops")); + + values + .take(5) + .toBlocking() + .forEach( + v -> {}); + } + +} diff --git a/tests/java/itrx/chapter3/leaving/FutureExample.java b/tests/java/itrx/chapter3/leaving/FutureExample.java new file mode 100644 index 0000000..12f5118 --- /dev/null +++ b/tests/java/itrx/chapter3/leaving/FutureExample.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.leaving; + + +import static org.junit.Assert.*; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; + +public class FutureExample { + + public void exampleFuture() { + Observable values = Observable.timer(500, TimeUnit.MILLISECONDS); + + values.subscribe(v -> System.out.println("Emitted: " + v)); + + Future future = values.toBlocking().toFuture(); + try { + System.out.println(future.get()); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + + // Emitted: 0 + // 0 + } + + + // + // + + @Test + public void testFuture() throws InterruptedException, ExecutionException { + Observable sequence = Observable.just(0); + Future future = sequence.toBlocking().toFuture(); + int value = future.get(); + assertEquals(0, value); + } + +} diff --git a/tests/java/itrx/chapter3/leaving/IterablesExample.java b/tests/java/itrx/chapter3/leaving/IterablesExample.java new file mode 100644 index 0000000..bc9b142 --- /dev/null +++ b/tests/java/itrx/chapter3/leaving/IterablesExample.java @@ -0,0 +1,345 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.leaving; + +import static org.junit.Assert.*; + +import java.lang.Thread.State; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; +import rx.subjects.PublishSubject; +import rx.subjects.Subject; + +public class IterablesExample { + + public void exampleToIterable() { + Observable values = Observable.interval(500, TimeUnit.MILLISECONDS); + + Iterable iterable = values.take(5).toBlocking().toIterable(); + for (long l : iterable) { + System.out.println(l); + } + + // 0 + // 1 + // 2 + // 3 + // 4 + } + + public void exampleNext() throws InterruptedException { + Observable values = Observable.interval(500, TimeUnit.MILLISECONDS); + + values.take(5) + .subscribe(v -> System.out.println("Emitted: " + v)); + + Iterable iterable = values.take(5).toBlocking().next(); + for (long l : iterable) { + System.out.println(l); + Thread.sleep(750); + } + + // Emitted: 0 + // 0 + // Emitted: 1 + // Emitted: 2 + // 2 + // Emitted: 3 + // Emitted: 4 + // 4 + } + + public void exampleLatest() throws InterruptedException { + Observable values = Observable.interval(500, TimeUnit.MILLISECONDS); + + values.take(5) + .subscribe(v -> System.out.println("Emitted: " + v)); + + Iterable iterable = values.take(5).toBlocking().latest(); + for (long l : iterable) { + System.out.println(l); + Thread.sleep(750); + } + + // Emitted: 0 + // 0 + // Emitted: 1 + // 1 + // Emitted: 2 + // Emitted: 3 + // 3 + // Emitted: 4 + } + + public void exampleMostRecent() throws InterruptedException { + Observable values = Observable.interval(500, TimeUnit.MILLISECONDS); + + values.take(5) + .subscribe(v -> System.out.println("Emitted: " + v)); + + Iterable iterable = values.take(5).toBlocking().mostRecent(-1L); + for (long l : iterable) { + System.out.println(l); + Thread.sleep(400); + } + + // -1 + // -1 + // Emitted: 0 + // 0 + // Emitted: 1 + // 1 + // Emitted: 2 + // 2 + // Emitted: 3 + // 3 + // 3 + // Emitted: 4 + } + + + // + // Tests + // + + @Test + public void testToIterable() throws InterruptedException { + TestScheduler scheduler = Schedulers.test(); + List received = new ArrayList<>(); + + Observable values = Observable.interval(500, TimeUnit.MILLISECONDS, scheduler); + + Thread thread = new Thread(() -> { + Iterable iterable = + values + .take(5) + .toBlocking() + .toIterable(); + for (long l : iterable) { + received.add(l); + } + }); + thread.start(); + + assertEquals(received, Arrays.asList()); + // Wait for blocking call to block before producing values + while (thread.getState() != State.WAITING) + Thread.sleep(1); + scheduler.advanceTimeBy(3, TimeUnit.SECONDS); + thread.join(50); + assertEquals(received, Arrays.asList(0L, 1L, 2L, 3L, 4L)); + } + + @Test + public void testNext() throws InterruptedException { + Subject subject = PublishSubject.create(); + List received = new ArrayList<>(); + Semaphore produce = new Semaphore(0); + Semaphore consume = new Semaphore(0); + + Thread thread = new Thread(() -> { + Iterable iterable = subject.toBlocking().next(); + Iterator iterator = iterable.iterator(); + try { + while (true) { + consume.acquire(); + produce.release(); + if (!iterator.hasNext()) { + produce.release(); + break; + } + received.add(iterator.next()); + produce.release(); + } + } catch (InterruptedException e) { + } + }); + thread.start(); + + consume.release(); + produce.acquire(); + while (thread.getState() != State.WAITING) Thread.sleep(1); + subject.onNext(0); + produce.acquire(); + assertEquals(Arrays.asList(0), received); + + subject.onNext(1); + consume.release(); + produce.acquire(); + while (thread.getState() != State.WAITING) Thread.sleep(1); + subject.onNext(2); + produce.acquire(); + assertEquals(Arrays.asList(0, 2), received); + + subject.onNext(3); + consume.release(); + produce.acquire(); + while (thread.getState() != State.WAITING) Thread.sleep(1); + subject.onNext(4); + produce.acquire(); + assertEquals(Arrays.asList(0, 2, 4), received); + + consume.release(); + subject.onCompleted(); + + thread.join(); + } + + @Test + public void testLatest() throws InterruptedException { + Subject subject = PublishSubject.create(); + List received = new ArrayList<>(); + Semaphore produce = new Semaphore(0); + Semaphore consume = new Semaphore(0); + + Thread thread = new Thread(() -> { + Iterable iterable = subject.toBlocking().latest(); + Iterator iterator = iterable.iterator(); + try { + while (true) { + consume.acquire(); + produce.release(); + if (!iterator.hasNext()) { + produce.release(); + break; + } + received.add(iterator.next()); + produce.release(); + } + } catch (InterruptedException e) { + } + }); + thread.start(); + + consume.release(); + produce.acquire(); + while (thread.getState() != State.WAITING) Thread.sleep(1); + assertEquals(Arrays.asList(), received); + + subject.onNext(0); + produce.acquire(); + assertEquals(Arrays.asList(0), received); + + consume.release(); + produce.acquire(); + while (thread.getState() != State.WAITING) Thread.sleep(1); + subject.onNext(1); + produce.acquire(); + assertEquals(Arrays.asList(0, 1), received); + + subject.onNext(2); + subject.onNext(3); + consume.release(); + produce.acquire(); + produce.acquire(); + assertEquals(Arrays.asList(0, 1, 3), received); + + subject.onNext(4); + subject.onCompleted(); + consume.release(); + produce.acquire(); + produce.acquire(); + assertEquals(Arrays.asList(0, 1, 3), received); + + thread.join(); + } + + @Test + public void testMostRecent() throws InterruptedException { + Subject subject = PublishSubject.create(); + List received = new ArrayList<>(); + Semaphore produce = new Semaphore(0); + Semaphore consume = new Semaphore(0); + + Thread thread = new Thread(() -> { + Iterable iterable = subject.toBlocking().mostRecent(-1); + Iterator iterator = iterable.iterator(); + try { + while (true) { + consume.acquire(); + produce.release(); + if (!iterator.hasNext()) { + produce.release(); + break; + } + received.add(iterator.next()); + produce.release(); + } + } catch (InterruptedException e) { + } + }); + thread.start(); + + consume.release(); + produce.acquire(); + produce.acquire(); + assertEquals(Arrays.asList(-1), received); + + consume.release(); + produce.acquire(); + produce.acquire(); + assertEquals(Arrays.asList(-1, -1), received); + + subject.onNext(0); + consume.release(); + produce.acquire(); + produce.acquire(); + assertEquals(Arrays.asList(-1, -1, 0), received); + + subject.onNext(1); + subject.onNext(2); + consume.release(); + produce.acquire(); + produce.acquire(); + assertEquals(Arrays.asList(-1, -1, 0, 2), received); + + subject.onNext(3); + consume.release(); + produce.acquire(); + produce.acquire(); + assertEquals(Arrays.asList(-1, -1, 0, 2, 3), received); + consume.release(); + produce.acquire(); + produce.acquire(); + assertEquals(Arrays.asList(-1, -1, 0, 2, 3, 3), received); + + subject.onNext(4); + subject.onCompleted(); + consume.release(); + produce.acquire(); + produce.acquire(); + assertEquals(Arrays.asList(-1, -1, 0, 2, 3, 3), received); + + thread.join(); + } +} diff --git a/tests/java/itrx/chapter3/sideeffects/AsObservableExample.java b/tests/java/itrx/chapter3/sideeffects/AsObservableExample.java new file mode 100644 index 0000000..76a8fad --- /dev/null +++ b/tests/java/itrx/chapter3/sideeffects/AsObservableExample.java @@ -0,0 +1,198 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.sideeffects; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.subjects.BehaviorSubject; +import rx.subjects.Subject; + +public class AsObservableExample { + + public static class BrakeableService { + public BehaviorSubject items = BehaviorSubject.create("Greet"); + + public void play() { + items.onNext("Hello"); + items.onNext("and"); + items.onNext("goodbye"); + } + } + + public static class BrakeableService2 { + private final BehaviorSubject items = BehaviorSubject.create("Greet"); + + public BehaviorSubject getValuesUnsafe() { + return items; + } + + public Observable getValuesUnsafe2() { + return items; + } + + public void play() { + items.onNext("Hello"); + items.onNext("and"); + items.onNext("goodbye"); + } + } + + public static class SafeService { + private final BehaviorSubject items = BehaviorSubject.create("Greet"); + + public Observable getValues() { + return items.asObservable(); + } + + public void play() { + items.onNext("Hello"); + items.onNext("and"); + items.onNext("goodbye"); + } + } + + public void exampleModifyReference() { + BrakeableService service = new BrakeableService(); + service.items.subscribe((i) -> System.out.println("Before: " + i)); + service.items = BehaviorSubject.create("Later"); + service.items.subscribe((i) -> System.out.println("After: " + i)); + service.play(); + + // Before: Greet + // After: Greet + // After: Hello + // After: and + // After: goodbye + } + + public void examplePush() { + BrakeableService2 service = new BrakeableService2(); + + service.getValuesUnsafe().subscribe(System.out::println); + service.getValuesUnsafe().onNext("GARBAGE"); + service.play(); + + // Greet + // GARBAGE + // Hello + // and + // goodbye + } + + public void examplePush2() { + BrakeableService2 service = new BrakeableService2(); + + service.getValuesUnsafe2().subscribe(System.out::println); + if (service.getValuesUnsafe2() instanceof Subject) { + @SuppressWarnings("unchecked") + Subject subject = (Subject) service.getValuesUnsafe2(); + subject.onNext("GARBAGE"); + } + service.play(); + + // Greet + // GARBAGE + // Hello + // and + // goodbye + } + + public void exampleSafe() { + SafeService service = new SafeService(); + + service.getValues().subscribe(System.out::println); + if (service.getValues() instanceof Subject) { + System.out.println("Not safe!"); + } + service.play(); + + // Greet + // Hello + // and + // goodbye + } + + + // + // Tests + // + + @Test + public void testModifyReference() { + TestSubscriber testerBefore = new TestSubscriber<>(); + TestSubscriber testerAfter = new TestSubscriber<>(); + + BrakeableService service = new BrakeableService(); + service.items.subscribe(testerBefore); + service.items = BehaviorSubject.create("Later"); + service.items.subscribe(testerAfter); + service.play(); + + testerBefore.assertReceivedOnNext(Arrays.asList("Greet")); + testerAfter.assertReceivedOnNext(Arrays.asList( + "Later", + "Hello", + "and", + "goodbye" + )); + } + + @Test + public void testPush() { + TestSubscriber tester = new TestSubscriber<>(); + + BrakeableService2 service = new BrakeableService2(); + service.getValuesUnsafe().subscribe(tester); + service.getValuesUnsafe().onNext("GARBAGE"); + service.play(); + + tester.assertReceivedOnNext(Arrays.asList( + "Greet", + "GARBAGE", + "Hello", + "and", + "goodbye" + )); + } + + @Test + public void testPush2() { + BrakeableService2 service = new BrakeableService2(); + + assertTrue(service.getValuesUnsafe2() instanceof Subject); + } + + @Test + public void testSafe() { + SafeService service = new SafeService(); + + assertFalse(service.getValues() instanceof Subject); + } + +} diff --git a/tests/java/itrx/chapter3/sideeffects/DoOnExample.java b/tests/java/itrx/chapter3/sideeffects/DoOnExample.java new file mode 100644 index 0000000..4d08c43 --- /dev/null +++ b/tests/java/itrx/chapter3/sideeffects/DoOnExample.java @@ -0,0 +1,193 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.sideeffects; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.Subscriber; +import rx.Subscription; +import rx.functions.Func0; +import rx.observers.TestSubscriber; +import rx.subjects.ReplaySubject; + +public class DoOnExample { + + private static class PrintSubscriber extends Subscriber{ + private final String name; + public PrintSubscriber(String name) { + this.name = name; + } + @Override + public void onCompleted() { + System.out.println(name + ": Completed"); + } + @Override + public void onError(Throwable e) { + System.out.println(name + ": Error: " + e); + } + @Override + public void onNext(Object v) { + System.out.println(name + ": " + v); + } + } + + public void exampleDoOnEach() { + Observable values = Observable.just("side", "effects"); + + values + .doOnEach(new PrintSubscriber("Log")) + .map(s -> s.toUpperCase()) + .subscribe(new PrintSubscriber("Process")); + + // Log: side + // Process: SIDE + // Log: effects + // Process: EFFECTS + // Log: Completed + // Process: Completed + } + + public void exampleDoOnEachEncapsulation() { + Func0> service = () -> + Observable + .just("First", "Second", "Third") + .doOnEach(new PrintSubscriber("Log")); + + service.call() + .map(s -> s.toUpperCase()) + .filter(s -> s.length() > 5) + .subscribe(new PrintSubscriber("Process")); + + // Log: First + // Log: Second + // Process: SECOND + // Log: Third + // Log: Completed + // Process: Completed + } + + public void exampleOnSubscriber() { + ReplaySubject subject = ReplaySubject.create(); + Observable values = subject + .doOnSubscribe(() -> System.out.println("New subscription")) + .doOnUnsubscribe(() -> System.out.println("Subscription over")); + + Subscription s1 = values.subscribe(new PrintSubscriber("1st")); + subject.onNext(0); + values.subscribe(new PrintSubscriber("2st")); + subject.onNext(1); + s1.unsubscribe(); + subject.onNext(2); + subject.onNext(3); + subject.onCompleted(); + + // New subscription + // 1st: 0 + // New subscription + // 2st: 0 + // 1st: 1 + // 2st: 1 + // Subscription over + // 2st: 2 + // 2st: 3 + // 2st: Completed + // Subscription over + } + + + // + // Tests + // + + @Test + public void testDoOnEach() { + TestSubscriber testerLog = new TestSubscriber<>(); + TestSubscriber testerFinal = new TestSubscriber<>(); + + Observable values = Observable.just("side", "effects"); + + values + .doOnEach(testerLog) + .map(s -> s.toUpperCase()) + .subscribe(testerFinal); + + testerLog.assertReceivedOnNext(Arrays.asList("side", "effects")); + testerLog.assertTerminalEvent(); + testerLog.assertNoErrors(); + testerFinal.assertReceivedOnNext(Arrays.asList("SIDE", "EFFECTS")); + testerFinal.assertTerminalEvent(); + testerFinal.assertNoErrors(); + } + + @Test + public void testDoOnEachEncapsulation() { + TestSubscriber testerLog = new TestSubscriber<>(); + TestSubscriber testerFinal = new TestSubscriber<>(); + + Func0> service = () -> + Observable + .just("First", "Second", "Third") + .doOnEach(testerLog); + + service.call() + .map(s -> s.toUpperCase()) + .filter(s -> s.length() > 5) + .subscribe(testerFinal); + + testerLog.assertReceivedOnNext(Arrays.asList("First", "Second", "Third")); + testerLog.assertTerminalEvent(); + testerLog.assertNoErrors(); + testerFinal.assertReceivedOnNext(Arrays.asList("SECOND")); + testerFinal.assertTerminalEvent(); + testerFinal.assertNoErrors(); + } + + @Test + public void testOnSubscriber() { + int[] counts = {0, 0}; + + ReplaySubject subject = ReplaySubject.create(); + Observable values = subject + .doOnSubscribe(() -> counts[0]++) + .doOnUnsubscribe(() -> counts[1]++); + + assertArrayEquals(counts, new int[]{0, 0}); + Subscription s1 = values.subscribe(); + assertArrayEquals(counts, new int[]{1, 0}); + subject.onNext(0); + values.subscribe(); + assertArrayEquals(counts, new int[]{2, 0}); + subject.onNext(1); + s1.unsubscribe(); + assertArrayEquals(counts, new int[]{2, 1}); + subject.onNext(2); + subject.onNext(3); + subject.onCompleted(); + assertArrayEquals(counts, new int[]{2, 2}); + } +} diff --git a/tests/java/itrx/chapter3/sideeffects/MutablePipelineExample.java b/tests/java/itrx/chapter3/sideeffects/MutablePipelineExample.java new file mode 100644 index 0000000..a1a185b --- /dev/null +++ b/tests/java/itrx/chapter3/sideeffects/MutablePipelineExample.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.sideeffects; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class MutablePipelineExample { + + private static class Data { + public int id; + public String name; + public Data(int id, String name) { + this.id = id; + this.name = name; + } + } + + public void example() { + Observable data = Observable.just( + new Data(1, "Microsoft"), + new Data(2, "Netflix") + ); + + data.subscribe(d -> d.name = "Garbage"); + data.subscribe(d -> System.out.println(d.id + ": " + d.name)); + + // 1: Garbage + // 2: Garbage + } + + + // + // Test + // + + @Test + public void test() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable data = Observable.just( + new Data(1, "Microsoft"), + new Data(2, "Netflix") + ); + + data.subscribe(d -> d.name = "Garbage"); + data.map(d -> d.name) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList("Garbage", "Garbage")); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } +} diff --git a/tests/java/itrx/chapter3/sideeffects/SideEffectExample.java b/tests/java/itrx/chapter3/sideeffects/SideEffectExample.java new file mode 100644 index 0000000..ff18bd8 --- /dev/null +++ b/tests/java/itrx/chapter3/sideeffects/SideEffectExample.java @@ -0,0 +1,198 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.sideeffects; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; + +public class SideEffectExample { + + private static class Inc { + private int count = 0; + public void inc() { + count++; + } + public int getCount() { + return count; + } + } + + private static class Indexed { + public final int index; + public final T item; + public Indexed(int index, T item) { + this.index = index; + this.item = item; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Indexed) { + Indexed other = (Indexed) obj; + return this.index == other.index && + this.item.equals(other.item); + } + return false; + } + } + + public void exampleBadIndex() { + Observable values = Observable.just("No", "side", "effects", "please"); + + Inc index = new Inc(); + Observable indexed = + values.map(w -> { + index.inc(); + return w; + }); + indexed.subscribe(w -> System.out.println(index.getCount() + ": " + w)); + + // 1: No + // 2: side + // 3: effects + // 4: please + } + + public void exampleBadIndexFail() { + Observable values = Observable.just("No", "side", "effects", "please"); + + Inc index = new Inc(); + Observable indexed = + values.map(w -> { + index.inc(); + return w; + }); + indexed.subscribe(w -> System.out.println("1st observer: " + index.getCount() + ": " + w)); + indexed.subscribe(w -> System.out.println("2nd observer: " + index.getCount() + ": " + w)); + + // 1st observer: 1: No + // 1st observer: 2: side + // 1st observer: 3: effects + // 1st observer: 4: please + // 2nd observer: 5: No + // 2nd observer: 6: side + // 2nd observer: 7: effects + // 2nd observer: 8: please + } + + public void exampleSafeIndex() { + Observable values = Observable.just("No", "side", "effects", "please"); + + Observable> indexed = + values.scan( + new Indexed(0, null), + (prev,v) -> new Indexed(prev.index+1, v)) + .skip(1); + indexed.subscribe(w -> System.out.println("1st observer: " + w.index + ": " + w.item)); + indexed.subscribe(w -> System.out.println("2nd observer: " + w.index + ": " + w.item)); + + // 1st observer: 1: No + // 1st observer: 2: side + // 1st observer: 3: effects + // 1st observer: 4: please + // 2nd observer: 1: No + // 2nd observer: 2: side + // 2nd observer: 3: effects + // 2nd observer: 4: please + } + + + // + // Tests + // + + @Test + public void testBadIndex() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable values = Observable.just("No", "side", "effects", "please"); + + Inc index = new Inc(); + Observable indexed = + values + .map(w -> { + index.inc(); + return w; + }) + .map(w -> index.getCount()); + indexed.subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(1,2,3,4)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testBadIndexFail() { + TestSubscriber tester1 = new TestSubscriber<>(); + TestSubscriber tester2 = new TestSubscriber<>(); + + Observable values = Observable.just("No", "side", "effects", "please"); + + Inc index = new Inc(); + Observable indexed = + values.map(w -> { + index.inc(); + return w; + }) + .map(w -> index.getCount()); + indexed.subscribe(tester1); + indexed.subscribe(tester2); + + tester1.assertReceivedOnNext(Arrays.asList(1,2,3,4)); + tester1.assertTerminalEvent(); + tester1.assertNoErrors(); + tester2.assertReceivedOnNext(Arrays.asList(5,6,7,8)); + tester2.assertTerminalEvent(); + tester2.assertNoErrors(); + } + + @Test + public void testSafeIndex() { + TestSubscriber tester1 = new TestSubscriber<>(); + TestSubscriber tester2 = new TestSubscriber<>(); + + Observable values = Observable.just("No", "side", "effects", "please"); + + Observable indexed = + values.scan( + new Indexed(0, null), + (prev,v) -> new Indexed(prev.index+1, v)) + .skip(1) + .map(i -> i.index); + indexed.subscribe(tester1); + indexed.subscribe(tester2); + + tester1.assertReceivedOnNext(Arrays.asList(1,2,3,4)); + tester1.assertTerminalEvent(); + tester1.assertNoErrors(); + tester2.assertReceivedOnNext(Arrays.asList(1,2,3,4)); + tester2.assertTerminalEvent(); + tester2.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter3/timeshifted/BufferExample.java b/tests/java/itrx/chapter3/timeshifted/BufferExample.java new file mode 100644 index 0000000..1b3d046 --- /dev/null +++ b/tests/java/itrx/chapter3/timeshifted/BufferExample.java @@ -0,0 +1,283 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.timeshifted; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class BufferExample { + + public void exampleByCount() { + Observable.range(0, 10) + .buffer(4) + .subscribe(System.out::println); + + // [0, 1, 2, 3] + // [4, 5, 6, 7] + // [8, 9] + } + + public void exampleByTime() { + Observable.interval(100, TimeUnit.MILLISECONDS).take(10) + .buffer(250, TimeUnit.MILLISECONDS) + .subscribe(System.out::println); + + // [0, 1] + // [2, 3] + // [4, 5, 6] + // [7, 8] + // [9] + } + + public void exampleByCountAndTime() { + Observable.interval(100, TimeUnit.MILLISECONDS) + .take(10) + .buffer(250, TimeUnit.MILLISECONDS, 2) + .subscribe(System.out::println); + + // [0, 1] + // [] + // [2, 3] + // [] + // [4, 5] + // [6] + // [7, 8] + // [] + // [9] + } + + public void exampleWithSignal() { + Observable.interval(100, TimeUnit.MILLISECONDS).take(10) + .buffer(Observable.interval(250, TimeUnit.MILLISECONDS)) + .subscribe(System.out::println); + + // [0, 1] + // [2, 3] + // [4, 5, 6] + // [7, 8] + // [9] + } + + public void exampleOverlappingByCount() { + Observable.range(0,10) + .buffer(4, 3) + .subscribe(System.out::println); + + // [0, 1, 2, 3] + // [3, 4, 5, 6] + // [6, 7, 8, 9] + // [9] + } + + public void exampleOverlappingByTime() { + Observable.interval(100, TimeUnit.MILLISECONDS).take(10) + .buffer(350, 200, TimeUnit.MILLISECONDS) + .subscribe(System.out::println); + + // [0, 1, 2] + // [2, 3, 4] + // [3, 4, 5, 6] + // [5, 6, 7, 8] + // [7, 8, 9] + // [9] + } + + public void exampleOverlappingBySignal() { + Observable.interval(100, TimeUnit.MILLISECONDS).take(10) + .buffer( + Observable.interval(250, TimeUnit.MILLISECONDS), + i -> Observable.timer(200, TimeUnit.MILLISECONDS)) + .subscribe(System.out::println); + + // [2, 3] + // [4, 5] + // [7, 8] + // [9] + } + + + // + // Tests + // + + @Test + public void testByCount() { + TestSubscriber> tester = new TestSubscriber<>(); + + Observable.range(0, 10) + .buffer(4) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList( + Arrays.asList(0, 1, 2, 3), + Arrays.asList(4, 5, 6, 7), + Arrays.asList(8, 9) + )); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testByTime() { + TestSubscriber> tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(10) + .buffer(250, TimeUnit.MILLISECONDS, scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + + tester.assertReceivedOnNext(Arrays.asList( + Arrays.asList(0L, 1L), + Arrays.asList(2L, 3L), + Arrays.asList(4L, 5L, 6L), + Arrays.asList(7L, 8L), + Arrays.asList(9L) + )); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testByCountAndTime() { + TestSubscriber> tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .take(10) + .buffer(250, TimeUnit.MILLISECONDS, 2, scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + + tester.assertReceivedOnNext(Arrays.asList( + Arrays.asList(0L, 1L), + Arrays.asList(), + Arrays.asList(2L, 3L), + Arrays.asList(), + Arrays.asList(4L, 5L), + Arrays.asList(6L), + Arrays.asList(7L, 8L), + Arrays.asList(), + Arrays.asList(9L) + )); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testWithSignal() { + TestSubscriber> tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(10) + .buffer(Observable.interval(250, TimeUnit.MILLISECONDS, scheduler)) + .subscribe(tester); + + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + + tester.assertReceivedOnNext(Arrays.asList( + Arrays.asList(0L, 1L), + Arrays.asList(2L, 3L), + Arrays.asList(4L, 5L, 6L), + Arrays.asList(7L, 8L), + Arrays.asList(9L) + )); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testOverlappingByCount() { + TestSubscriber> tester = new TestSubscriber<>(); + + Observable.range(0,10) + .buffer(4, 3) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList( + Arrays.asList(0, 1, 2, 3), + Arrays.asList(3, 4, 5, 6), + Arrays.asList(6, 7, 8, 9), + Arrays.asList(9) + )); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testOverlappingByTime() { + TestSubscriber> tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(10) + .buffer(350, 200, TimeUnit.MILLISECONDS, scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + + tester.assertReceivedOnNext(Arrays.asList( + Arrays.asList(0L, 1L, 2L), + Arrays.asList(1L, 2L, 3L, 4L), + Arrays.asList(3L, 4L, 5L, 6L), + Arrays.asList(5L, 6L, 7L, 8L), + Arrays.asList(7L, 8L, 9L), + Arrays.asList(9L) + )); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testOverlappingBySignal() { + TestSubscriber> tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(10) + .buffer( + Observable.interval(250, TimeUnit.MILLISECONDS, scheduler), + i -> Observable.timer(200, TimeUnit.MILLISECONDS, scheduler)) + .subscribe(tester); + + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + + tester.assertReceivedOnNext(Arrays.asList( + Arrays.asList(2L, 3L), + Arrays.asList(4L, 5L), + Arrays.asList(7L, 8L), + Arrays.asList(9L) + )); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + +} diff --git a/tests/java/itrx/chapter3/timeshifted/DebounceExample.java b/tests/java/itrx/chapter3/timeshifted/DebounceExample.java new file mode 100644 index 0000000..70d738e --- /dev/null +++ b/tests/java/itrx/chapter3/timeshifted/DebounceExample.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.timeshifted; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class DebounceExample { + + public void exampleDebounce() { + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .debounce(150, TimeUnit.MILLISECONDS) + .subscribe(System.out::println); + + // 3 + // 4 + // 5 + // 9 + } + + public void exampleDebounceDynamic() { + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .debounce(i -> Observable.timer(i * 50, TimeUnit.MILLISECONDS)) + .subscribe(System.out::println); + + // 1 + // 3 + // 4 + // 5 + // 9 + } + + public void exampleThrottleWithTimeout() { + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .throttleWithTimeout(150, TimeUnit.MILLISECONDS) + .subscribe(System.out::println); + + // 3 + // 4 + // 5 + // 9 + } + + + // + // Test + // + + @Test + public void testDebounce() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS, scheduler).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .debounce(150, TimeUnit.MILLISECONDS, scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(2100, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(3, 4, 5, 9)); + } + + @Test + public void testDebounceDynamic() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS, scheduler).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .debounce(i -> Observable.timer(i * 50, TimeUnit.MILLISECONDS, scheduler)) + .subscribe(tester); + + scheduler.advanceTimeBy(2100, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(1, 2, 3, 4, 5, 9)); + } + + @Test + public void testThrottleWithTimeout() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS, scheduler).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .throttleWithTimeout(150, TimeUnit.MILLISECONDS, scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(2100, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(3, 4, 5, 9)); + } +} \ No newline at end of file diff --git a/tests/java/itrx/chapter3/timeshifted/DelayExample.java b/tests/java/itrx/chapter3/timeshifted/DelayExample.java new file mode 100644 index 0000000..bdac209 --- /dev/null +++ b/tests/java/itrx/chapter3/timeshifted/DelayExample.java @@ -0,0 +1,137 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.timeshifted; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class DelayExample { + + public void exampleDelay() { + Observable.interval(100, TimeUnit.MILLISECONDS) + .delay(i -> Observable.timer(i * 100, TimeUnit.MILLISECONDS)) + .timeInterval() + .take(5) + .subscribe(System.out::println); + + // TimeInterval [intervalInMilliseconds=152, value=0] + // TimeInterval [intervalInMilliseconds=173, value=1] + // TimeInterval [intervalInMilliseconds=199, value=2] + // TimeInterval [intervalInMilliseconds=201, value=3] + // TimeInterval [intervalInMilliseconds=199, value=4] + } + + public void exampleDelaySubscription() { + Observable.interval(100, TimeUnit.MILLISECONDS) + .delaySubscription(1000, TimeUnit.MILLISECONDS) + .timeInterval() + .take(5) + .subscribe(System.out::println); + + // TimeInterval [intervalInMilliseconds=1114, value=0] + // TimeInterval [intervalInMilliseconds=92, value=1] + // TimeInterval [intervalInMilliseconds=101, value=2] + // TimeInterval [intervalInMilliseconds=100, value=3] + // TimeInterval [intervalInMilliseconds=99, value=4] + } + + public void exampleDelaySubscriptionWithSignal() { + Observable.interval(100, TimeUnit.MILLISECONDS) + .delaySubscription(() -> Observable.timer(1000, TimeUnit.MILLISECONDS)) + .timeInterval() + .take(5) + .subscribe(System.out::println); + + // TimeInterval [intervalInMilliseconds=1114, value=0] + // TimeInterval [intervalInMilliseconds=92, value=1] + // TimeInterval [intervalInMilliseconds=101, value=2] + // TimeInterval [intervalInMilliseconds=100, value=3] + // TimeInterval [intervalInMilliseconds=99, value=4] + } + + + // + // Tests + // + + @Test + public void testDelay() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .delay(i -> Observable.timer(i * 100, TimeUnit.MILLISECONDS, scheduler)) + .timeInterval(scheduler) + .map(i -> i.getIntervalInMilliseconds()) + .take(5) + .subscribe(tester); + + scheduler.advanceTimeBy(1000, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(100L, 200L, 200L, 200L, 200L)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testDelaySubscription() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .delaySubscription(1000, TimeUnit.MILLISECONDS, scheduler) + .timeInterval(scheduler) + .take(5) + .map(i -> i.getIntervalInMilliseconds()) + .subscribe(tester); + + scheduler.advanceTimeBy(1500, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(1100L, 100L, 100L, 100L, 100L)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testDelaySubscriptionWithSignal() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .delaySubscription(() -> Observable.timer(1000, TimeUnit.MILLISECONDS, scheduler)) + .timeInterval(scheduler) + .take(5) + .map(i -> i.getIntervalInMilliseconds()) + .subscribe(tester); + + scheduler.advanceTimeBy(1500, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(1100L, 100L, 100L, 100L, 100L)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } +} diff --git a/tests/java/itrx/chapter3/timeshifted/SampleExample.java b/tests/java/itrx/chapter3/timeshifted/SampleExample.java new file mode 100644 index 0000000..f4b3a6f --- /dev/null +++ b/tests/java/itrx/chapter3/timeshifted/SampleExample.java @@ -0,0 +1,68 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.timeshifted; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class SampleExample { + + public void exampleSample() { + Observable.interval(150, TimeUnit.MILLISECONDS) + .sample(1, TimeUnit.SECONDS) + .take(3) + .subscribe(System.out::println); + + // 5 + // 12 + // 18 + } + + + // + // Test + // + + @Test + public void testSample() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Observable.interval(150, TimeUnit.MILLISECONDS, scheduler) + .sample(1, TimeUnit.SECONDS, scheduler) + .take(3) + .subscribe(tester); + + scheduler.advanceTimeBy(3, TimeUnit.SECONDS); + tester.assertReceivedOnNext(Arrays.asList(5L, 12L, 18L)); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } +} diff --git a/tests/java/itrx/chapter3/timeshifted/TakeLastBufferExample.java b/tests/java/itrx/chapter3/timeshifted/TakeLastBufferExample.java new file mode 100644 index 0000000..fa97db3 --- /dev/null +++ b/tests/java/itrx/chapter3/timeshifted/TakeLastBufferExample.java @@ -0,0 +1,119 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.timeshifted; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class TakeLastBufferExample { + + public void exampleByCount() { + Observable.range(0, 5) + .takeLastBuffer(2) + .subscribe(System.out::println); + + // [3, 4] + } + + public void exampleByTime() { + Observable.interval(100, TimeUnit.MILLISECONDS) + .take(5) + .takeLastBuffer(200, TimeUnit.MILLISECONDS) + .subscribe(System.out::println); + + // [2, 3, 4] + } + + public void exampleByCountAndTime() { + Observable.interval(100, TimeUnit.MILLISECONDS) + .take(5) + .takeLastBuffer(2, 200, TimeUnit.MILLISECONDS) + .subscribe(System.out::println); + + // [3, 4] + } + + + // + // Tests + // + + @Test + public void testByCount() { + TestSubscriber> tester = new TestSubscriber<>(); + + Observable.range(0, 5) + .takeLastBuffer(2) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList( + Arrays.asList(3, 4) + )); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testByTime() { + TestSubscriber> tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .take(5) + .takeLastBuffer(200, TimeUnit.MILLISECONDS, scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList( + Arrays.asList(2L, 3L, 4L) + )); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } + + @Test + public void testByCountAndTime() { + TestSubscriber> tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .take(5) + .takeLastBuffer(2, 200, TimeUnit.MILLISECONDS, scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList( + Arrays.asList(3L, 4L) + )); + tester.assertTerminalEvent(); + tester.assertNoErrors(); + } +} diff --git a/tests/java/itrx/chapter3/timeshifted/ThrottleExample.java b/tests/java/itrx/chapter3/timeshifted/ThrottleExample.java new file mode 100644 index 0000000..da8012b --- /dev/null +++ b/tests/java/itrx/chapter3/timeshifted/ThrottleExample.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.timeshifted; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class ThrottleExample { + + public void exampleThrottleFirst() { + Observable.interval(150, TimeUnit.MILLISECONDS) + .throttleFirst(1, TimeUnit.SECONDS) + .take(3) + .subscribe(System.out::println); + + // 0 + // 7 + // 14 + } + + public void exampleThrottleLast() { + Observable.interval(150, TimeUnit.MILLISECONDS) + .throttleLast(1, TimeUnit.SECONDS) + .take(3) + .subscribe(System.out::println); + + // 5 + // 12 + // 18 + } + + + // + // Test + // + + @Test + public void testThrottleFirst() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Observable.interval(150, TimeUnit.MILLISECONDS, scheduler) + .throttleFirst(1, TimeUnit.SECONDS, scheduler) + .take(3) + .subscribe(tester); + + scheduler.advanceTimeBy(3, TimeUnit.SECONDS); + tester.assertReceivedOnNext(Arrays.asList(0L, 7L, 14L)); + } + + @Test + public void testThrottleLast() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Observable.interval(150, TimeUnit.MILLISECONDS, scheduler) + .throttleLast(1, TimeUnit.SECONDS, scheduler) + .take(3) + .subscribe(tester); + + scheduler.advanceTimeBy(3, TimeUnit.SECONDS); + tester.assertReceivedOnNext(Arrays.asList(5L, 12L, 18L)); + } +} diff --git a/tests/java/itrx/chapter3/timeshifted/TimeoutExample.java b/tests/java/itrx/chapter3/timeshifted/TimeoutExample.java new file mode 100644 index 0000000..f1feef3 --- /dev/null +++ b/tests/java/itrx/chapter3/timeshifted/TimeoutExample.java @@ -0,0 +1,203 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter3.timeshifted; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.Assert.*; +import org.junit.Test; +import static org.hamcrest.CoreMatchers.*; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class TimeoutExample { + + public void exampleTimeout() { + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .timeout(200, TimeUnit.MILLISECONDS) + .subscribe( + System.out::println, + System.out::println); + + // 0 + // 1 + // 2 + // 3 + // java.util.concurrent.TimeoutException + } + + public void exampleTimeoutWithResume() { + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .timeout(200, TimeUnit.MILLISECONDS, Observable.just(-1)) + .subscribe( + System.out::println, + System.out::println); + + // 0 + // 1 + // 2 + // 3 + // -1 + } + + public void exampleTimeoutPerItem() { + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .timeout(i -> Observable.timer(200, TimeUnit.MILLISECONDS)) + .subscribe( + System.out::println, + System.out::println); + + // 0 + // 1 + // 2 + // 3 + // java.util.concurrent.TimeoutException + } + + public void exampleTimeoutPerItemWithResume() { + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .timeout(i -> Observable.timer(200, TimeUnit.MILLISECONDS), Observable.just(-1)) + .subscribe( + System.out::println, + System.out::println); + + // 0 + // 1 + // 2 + // 3 + // -1 + } + + + // + // Tests + // + + @Test + public void testTimeout() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS, scheduler).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .timeout(200, TimeUnit.MILLISECONDS, scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(2300, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(0, 1, 2, 3)); + assertEquals("Observable emits one error", + 1, tester.getOnErrorEvents().size()); + assertThat("Observable times out", + tester.getOnErrorEvents().get(0), + instanceOf(TimeoutException.class)); + } + + @Test + public void testTimeoutWithResume() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS, scheduler).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .timeout(200, TimeUnit.MILLISECONDS, Observable.just(-1), scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(2300, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(0, 1, 2, 3, -1)); + } + + @Test + public void testTimeoutPerItem() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS, scheduler).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .timeout(i -> Observable.timer(200, TimeUnit.MILLISECONDS, scheduler)) + .subscribe(tester); + + scheduler.advanceTimeBy(2300, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(0, 1, 2, 3)); + assertEquals("Observable emits one error", + 1, tester.getOnErrorEvents().size()); + assertThat("Observable times out", + tester.getOnErrorEvents().get(0), + instanceOf(TimeoutException.class)); + } + + @Test + public void testTimeoutPerItemWithResume() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber<>(); + + Observable.concat( + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(3), + Observable.interval(500, TimeUnit.MILLISECONDS, scheduler).take(3), + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler).take(3) + ) + .scan(0, (acc, v) -> acc+1) + .timeout(i -> Observable.timer(200, TimeUnit.MILLISECONDS, scheduler), Observable.just(-1)) + .subscribe(tester); + + scheduler.advanceTimeBy(2300, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(0, 1, 2, 3, -1)); + } + +} diff --git a/tests/java/itrx/chapter4/backpressure/ConsumerSideExample.java b/tests/java/itrx/chapter4/backpressure/ConsumerSideExample.java new file mode 100644 index 0000000..659b86a --- /dev/null +++ b/tests/java/itrx/chapter4/backpressure/ConsumerSideExample.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.backpressure; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class ConsumerSideExample { + + public void exampleSample() { + Observable.interval(1, TimeUnit.MILLISECONDS) + .observeOn(Schedulers.newThread()) + .sample(100, TimeUnit.MILLISECONDS) + .take(3) + .subscribe( + i -> { + System.out.println(i); + try { + Thread.sleep(100); + } catch (Exception e) { } + }, + System.out::println); + + // 82 + // 182 + // 283 + } + + public void exampleBuffer() { + Observable.interval(10, TimeUnit.MILLISECONDS) + .observeOn(Schedulers.newThread()) + .buffer(100, TimeUnit.MILLISECONDS) + .take(3) + .subscribe( + i -> { + System.out.println(i); + try { + Thread.sleep(100); + } catch (Exception e) { } + }, + System.out::println); + + // [0, 1, 2, 3, 4, 5, 6, 7] + // [8, 9, 10, 11, 12, 13, 14, 15, 16, 17] + // [18, 19, 20, 21, 22, 23, 24, 25, 26, 27] + } + + + // + // Test + // + + @Test + public void testSample() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber() { + @Override + public void onNext(Long t) { + scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); + super.onNext(t); + } + }; + + Observable.interval(1, TimeUnit.MILLISECONDS, scheduler) + .observeOn(scheduler) + .sample(100, TimeUnit.MILLISECONDS, scheduler) + .take(3) + .subscribe(tester); + + scheduler.advanceTimeBy(300, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList(98L, 199L, 299L)); + tester.assertNoErrors(); + } + + @Test + public void testBuffer() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber> tester = new TestSubscriber>() { + @Override + public void onNext(List t) { + scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); + super.onNext(t); + } + }; + + Observable.interval(10, TimeUnit.MILLISECONDS, scheduler) + .observeOn(scheduler) + .buffer(100, TimeUnit.MILLISECONDS, scheduler) + .take(3) + .subscribe(tester); + + scheduler.advanceTimeBy(300, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList( + Arrays.asList( 0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L), + Arrays.asList( 9L, 10L, 11L, 12L, 13L, 14L, 15L, 16L, 17L, 18L, 19L), + Arrays.asList(20L, 21L, 22L, 23L, 24L, 25L, 26L, 27L, 28L, 29L) + )); + tester.assertNoErrors(); + } +} diff --git a/tests/java/itrx/chapter4/backpressure/ControlledPullSubscriber.java b/tests/java/itrx/chapter4/backpressure/ControlledPullSubscriber.java new file mode 100644 index 0000000..7c6db40 --- /dev/null +++ b/tests/java/itrx/chapter4/backpressure/ControlledPullSubscriber.java @@ -0,0 +1,84 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.backpressure; + +import rx.Subscriber; +import rx.functions.Action0; +import rx.functions.Action1; + +/** + * An Rx Subscriber that does not accept any items unless manually requested to. + * + * @author Chris + * + * @param + */ +public class ControlledPullSubscriber extends Subscriber { + + private final Action1 onNextAction; + private final Action1 onErrorAction; + private final Action0 onCompletedAction; + + public ControlledPullSubscriber( + Action1 onNextAction, + Action1 onErrorAction, + Action0 onCompletedAction) { + this.onNextAction = onNextAction; + this.onErrorAction = onErrorAction; + this.onCompletedAction = onCompletedAction; + } + + public ControlledPullSubscriber( + Action1 onNextAction, + Action1 onErrorAction) { + this(onNextAction, onErrorAction, () -> {}); + } + + public ControlledPullSubscriber(Action1 onNextAction) { + this(onNextAction, e -> {}, () -> {}); + } + + @Override + public void onStart() { + request(0); + } + + @Override + public void onCompleted() { + onCompletedAction.call(); + } + + @Override + public void onError(Throwable e) { + onErrorAction.call(e); + } + + @Override + public void onNext(T t) { + onNextAction.call(t); + } + + public void requestMore(int n) { + request(n); + } +} diff --git a/tests/java/itrx/chapter4/backpressure/NoBackpressureExample.java b/tests/java/itrx/chapter4/backpressure/NoBackpressureExample.java new file mode 100644 index 0000000..be2d11c --- /dev/null +++ b/tests/java/itrx/chapter4/backpressure/NoBackpressureExample.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.backpressure; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class NoBackpressureExample { + + public void exampleSynchronous() { + // Produce + Observable producer = Observable.create(o -> { + o.onNext(1); + o.onNext(2); + o.onCompleted(); + }); + // Consume + producer.subscribe(i -> { + try { + Thread.sleep(1000); + System.out.println(i); + } catch (Exception e) { } + }); + + // 1 + // 2 + } + + public void exampleNoBackpressure() { + Observable.interval(1, TimeUnit.MILLISECONDS) + .observeOn(Schedulers.newThread()) + .subscribe( + i -> { + System.out.println(i); + try { + Thread.sleep(100); + } catch (Exception e) { } + }, + System.out::println); + + // 0 + // 1 + // rx.exceptions.MissingBackpressureException + } + + + // + // Tests + // + + @Test + public void testSynchronous() { + List execution = new ArrayList(); + + // Produce + Observable producer = Observable.create(o -> { + execution.add("Producing 1"); + o.onNext(1); + execution.add("Producing 2"); + o.onNext(2); + o.onCompleted(); + }); + // Consume + producer.subscribe(i -> execution.add("Processed " + i)); + + assertEquals( + Arrays.asList( + "Producing 1", + "Processed 1", + "Producing 2", + "Processed 2" + ), + execution); + } + + @Test + public void testNoBackpressure() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber tester = new TestSubscriber() { + @Override + public void onNext(Long t) { + scheduler.advanceTimeBy(100, TimeUnit.MILLISECONDS); + super.onNext(t); + } + }; + + Observable.interval(1, TimeUnit.MILLISECONDS, scheduler) + .observeOn(scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(10, TimeUnit.MILLISECONDS); + assertThat( + tester.getOnErrorEvents().get(0), + instanceOf(rx.exceptions.MissingBackpressureException.class)); + + } + +} diff --git a/tests/java/itrx/chapter4/backpressure/OnBackpressureExample.java b/tests/java/itrx/chapter4/backpressure/OnBackpressureExample.java new file mode 100644 index 0000000..aa74d47 --- /dev/null +++ b/tests/java/itrx/chapter4/backpressure/OnBackpressureExample.java @@ -0,0 +1,153 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.backpressure; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class OnBackpressureExample { + + public void exampleOnBackpressureBuffer() { + Observable.interval(1, TimeUnit.MILLISECONDS) + .onBackpressureBuffer(1000) + .observeOn(Schedulers.newThread()) + .subscribe( + i -> { + System.out.println(i); + try { + Thread.sleep(100); + } catch (Exception e) { } + }, + System.out::println + ); + + // 0 + // 1 + // 2 + // 3 + // 4 + // 5 + // 6 + // 7 + // 8 + // 9 + // 10 + // 11 + // rx.exceptions.MissingBackpressureException: Overflowed buffer of 1000 + } + + public void exampleOnBackpressureDrop() { + Observable.interval(1, TimeUnit.MILLISECONDS) + .onBackpressureDrop() + .observeOn(Schedulers.newThread()) + .subscribe( + i -> { + System.out.println(i); + try { + Thread.sleep(100); + } catch (Exception e) { } + }, + System.out::println); + + // 0 + // 1 + // 2 + // ... + // 126 + // 127 + // 12861 + // 12862 + // ... + } + + + // + // Test + // + + @Test + public void testOnBackpressureBuffer() { + TestScheduler scheduler = Schedulers.test(); + List received = new ArrayList<>(); + List errors = new ArrayList<>(); + ControlledPullSubscriber tester = new ControlledPullSubscriber( + received::add, + errors::add); + + // Subscriber accepts items once every 100ms + scheduler.createWorker().schedulePeriodically( + () -> tester.requestMore(1), + 0, 100, TimeUnit.MILLISECONDS); + + Observable.interval(1, TimeUnit.MILLISECONDS, scheduler) + .onBackpressureBuffer(1000) + .observeOn(scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(2000, TimeUnit.MILLISECONDS); + assertEquals(Arrays.asList(0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L, 11L), received); + assertThat( + errors.get(0), + instanceOf(rx.exceptions.MissingBackpressureException.class)); + } + + @Test + public void testOnBackpressureDrop() { + TestScheduler scheduler = Schedulers.test(); + List received = new ArrayList<>(); + List errors = new ArrayList<>(); + ControlledPullSubscriber tester = new ControlledPullSubscriber( + received::add, + errors::add); + + // Subscriber accepts items once every 100ms + scheduler.createWorker().schedulePeriodically( + () -> tester.requestMore(1), + 0, 100, TimeUnit.MILLISECONDS); + + Observable.interval(1, TimeUnit.MILLISECONDS, scheduler) + .onBackpressureDrop() + .observeOn(scheduler) + .subscribe(tester); + + scheduler.advanceTimeBy(13000, TimeUnit.MILLISECONDS); + assertEquals(129L, received.get(129).longValue()); + assertNotEquals(130L, received.get(130).longValue()); + + + } + +} diff --git a/tests/java/itrx/chapter4/backpressure/OnRequestExample.java b/tests/java/itrx/chapter4/backpressure/OnRequestExample.java new file mode 100644 index 0000000..462e1e8 --- /dev/null +++ b/tests/java/itrx/chapter4/backpressure/OnRequestExample.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.backpressure; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import rx.Observable; + +public class OnRequestExample { + + public void exampleOnRequest() { + Observable.range(0, 3) + .doOnRequest(i -> System.out.println("Requested " + i)) + .subscribe(System.out::println); + + // Requested 9223372036854775807 + // 0 + // 1 + // 2 + } + + public void exampleOnRequestZip() { + Observable.range(0, 300) + .doOnRequest(i -> System.out.println("Requested " + i)) + .zipWith( + Observable.range(10, 300), + (i1, i2) -> i1 + " - " + i2) + .take(300) + .subscribe(); + + // Requested 128 + // Requested 90 + // Requested 90 + // Requested 90 + + } + + public void exampleOnRequestManual() { + ControlledPullSubscriber puller = + new ControlledPullSubscriber(System.out::println); + + Observable.range(0, 3) + .doOnRequest(i -> System.out.println("Requested " + i)) + .subscribe(puller); + + puller.requestMore(2); + puller.requestMore(1); + + // Requested 0 + // Requested 2 + // 0 + // 1 + // Requested 1 + // 2 + } + + + // + // Tests + // + + @Test + public void testOnRequest() { + List requests = new ArrayList(); + + Observable.range(0, 3) + .doOnRequest(requests::add) + .subscribe(); + + assertEquals(Arrays.asList(Long.MAX_VALUE), requests); + } + + @Test + public void testOnRequestZip() { + List requests = new ArrayList(); + + Observable.range(0, 300) + .doOnRequest(requests::add) + .zipWith( + Observable.range(10, 300), + (i1, i2) -> i1 + " - " + i2) + .take(300) + .subscribe(); + + assertTrue("zip makes subsequent requests", + requests.size() > 1); + assertEquals("zip uses a buffer of 128", + requests.get(0), new Long(128)); + } + + @Test + public void testOnRequestManual() { + List received = new ArrayList(); + List requests = new ArrayList(); + + ControlledPullSubscriber puller = + new ControlledPullSubscriber(received::add); + + Observable.range(0, 3) + .doOnRequest(requests::add) + .subscribe(puller); + + assertEquals(Arrays.asList(0L), requests); + assertEquals(Arrays.asList(), received); + puller.requestMore(2); + assertEquals(Arrays.asList(0L, 2L), requests); + assertEquals(Arrays.asList(0, 1), received); + puller.requestMore(1); + assertEquals(Arrays.asList(0L, 2L, 1L), requests); + assertEquals(Arrays.asList(0, 1, 2), received); + } + +} diff --git a/tests/java/itrx/chapter4/backpressure/ReactivePullExample.java b/tests/java/itrx/chapter4/backpressure/ReactivePullExample.java new file mode 100644 index 0000000..ce7d1c1 --- /dev/null +++ b/tests/java/itrx/chapter4/backpressure/ReactivePullExample.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.backpressure; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import rx.Observable; + +public class ReactivePullExample { + + public void example() { + ControlledPullSubscriber tester = new ControlledPullSubscriber( + i -> System.out.println("Consumed " + i)); + + Observable.range(0, 100) + .subscribe(tester); + + System.out.println("Requesting 2 more"); + tester.requestMore(2); + System.out.println("Requesting 3 more"); + tester.requestMore(3); + + // Requesting 2 more + // Consumed 0 + // Consumed 1 + // Requesting 3 more + // Consumed 2 + // Consumed 3 + // Consumed 4 + } + + + // + // Test + // + + @Test + public void test() { + List received = new ArrayList<>(); + ControlledPullSubscriber tester = new ControlledPullSubscriber(received::add); + + Observable.range(0, 100) + .subscribe(tester); + + assertEquals(Arrays.asList(), received); + tester.requestMore(2); + assertEquals(Arrays.asList(0, 1), received); + tester.requestMore(3); + assertEquals(Arrays.asList(0, 1, 2, 3, 4), received); + } +} diff --git a/tests/java/itrx/chapter4/coincidence/GroupJoinExample.java b/tests/java/itrx/chapter4/coincidence/GroupJoinExample.java new file mode 100644 index 0000000..c591286 --- /dev/null +++ b/tests/java/itrx/chapter4/coincidence/GroupJoinExample.java @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.coincidence; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class GroupJoinExample { + + private static class Tuple { + public final T1 item1; + public final T2 item2; + + public Tuple(T1 item1, T2 item2) { + this.item1 = item1; + this.item2 = item2; + } + + public static Tuple create(T1 item1, T2 item2) { + return new Tuple(item1, item2); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Tuple) { + Tuple other = (Tuple) obj; + return this.item1.equals(other.item1) && + this.item2.equals(other.item2); + } + return false; + } + + @Override + public String toString() { + return "(" + item1 + ", " + item2 + ")"; + } + } + + public void example() { + Observable left = + Observable.interval(100, TimeUnit.MILLISECONDS) + .map(i -> "L" + i) + .take(6); + Observable right = + Observable.interval(200, TimeUnit.MILLISECONDS) + .map(i -> "R" + i) + .take(3); + + left + .groupJoin( + right, + i -> Observable.never(), + i -> Observable.timer(0, TimeUnit.MILLISECONDS), + (l, rs) -> rs.toList().subscribe(list -> System.out.println(l + ": " + list)) + ) + .subscribe(); + + // L0: [R0, R1, R2] + // L1: [R0, R1, R2] + // L2: [R1, R2] + // L3: [R1, R2] + // L4: [R2] + // L5: [R2] + } + + + // + // Test + // + + @Test + public void test() { + TestSubscriber tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable left = + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .take(6); + Observable right = + Observable.interval(200, TimeUnit.MILLISECONDS, scheduler) + .take(3); + + left + .groupJoin( + right, + i -> Observable.never(), + i -> Observable.timer(0, TimeUnit.MILLISECONDS, scheduler), + (l, rs) -> rs.toList().map(rl -> Tuple.create(l, rl)) + ) + .flatMap(i -> i) + .subscribe(tester); + + scheduler.advanceTimeTo(600, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList( + Tuple.create(0L, Arrays.asList(0L, 1L, 2L)), + Tuple.create(1L, Arrays.asList(0L, 1L, 2L)), + Tuple.create(2L, Arrays.asList(1L, 2L)), + Tuple.create(3L, Arrays.asList(1L, 2L)), + Tuple.create(4L, Arrays.asList(2L)), + Tuple.create(5L, Arrays.asList(2L)) + )); + } + + @Test + public void exampleEquivalence() { + TestScheduler scheduler = Schedulers.test(); + TestSubscriber testerJoin = new TestSubscriber<>(); + TestSubscriber testerGroupJoin = new TestSubscriber<>(); + + Observable left = + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + Observable right = + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + + left.join( + right, + i -> Observable.timer(150, TimeUnit.MILLISECONDS, scheduler), + i -> Observable.timer(0, TimeUnit.MILLISECONDS, scheduler), + (l,r) -> Tuple.create(l, r) + ) + .take(10) + .subscribe(testerJoin); + + left.groupJoin( + right, + i -> Observable.timer(150, TimeUnit.MILLISECONDS, scheduler), + i -> Observable.timer(0, TimeUnit.MILLISECONDS, scheduler), + (l, rs) -> rs.map(r -> Tuple.create(l, r)) + ) + .flatMap(i -> i) + .take(10) + .subscribe(testerGroupJoin); + + scheduler.advanceTimeTo(600, TimeUnit.MILLISECONDS); + testerJoin.assertReceivedOnNext(testerGroupJoin.getOnNextEvents()); + } +} diff --git a/tests/java/itrx/chapter4/coincidence/JoinExample.java b/tests/java/itrx/chapter4/coincidence/JoinExample.java new file mode 100644 index 0000000..a283e9f --- /dev/null +++ b/tests/java/itrx/chapter4/coincidence/JoinExample.java @@ -0,0 +1,195 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.coincidence; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class JoinExample { + + private static class Tuple { + public final T1 item1; + public final T2 item2; + + public Tuple(T1 item1, T2 item2) { + this.item1 = item1; + this.item2 = item2; + } + + public static Tuple create(T1 item1, T2 item2) { + return new Tuple(item1, item2); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Tuple) { + Tuple other = (Tuple) obj; + return this.item1.equals(other.item1) && + this.item2.equals(other.item2); + } + return false; + } + } + + public void exampleJoinSimple() { + Observable left = + Observable.interval(100, TimeUnit.MILLISECONDS) + .map(i -> "L" + i); + Observable right = + Observable.interval(200, TimeUnit.MILLISECONDS) + .map(i -> "R" + i); + + left + .join( + right, + i -> Observable.never(), + i -> Observable.timer(0, TimeUnit.MILLISECONDS), + (l,r) -> l + " - " + r + ) + .take(10) + .subscribe(System.out::println); + + // L0 - R0 + // L1 - R0 + // L0 - R1 + // L1 - R1 + // L2 - R1 + // L3 - R1 + // L0 - R2 + // L1 - R2 + // L2 - R2 + // L3 - R2 + } + + public void exampleJoin2Way() { + Observable left = + Observable.interval(100, TimeUnit.MILLISECONDS) + .map(i -> "L" + i); + Observable right = + Observable.interval(100, TimeUnit.MILLISECONDS) + .map(i -> "R" + i); + + left + .join( + right, + i -> Observable.timer(150, TimeUnit.MILLISECONDS), + i -> Observable.timer(0, TimeUnit.MILLISECONDS), + (l,r) -> l + " - " + r + ) + .take(10) + .subscribe(System.out::println); + + // L0 - R0 + // L0 - R1 + // L1 - R1 + // L1 - R2 + // L2 - R2 + // L2 - R3 + // L3 - R3 + // L3 - R4 + // L4 - R4 + // L4 - R5 + } + + + // + // Tests + // + + @Test + public void testJoinSimple() { + TestSubscriber> tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable left = + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + Observable right = + Observable.interval(200, TimeUnit.MILLISECONDS, scheduler); + + left + .join( + right, + i -> Observable.never(), + i -> Observable.timer(0, TimeUnit.MILLISECONDS, scheduler), + (l,r) -> Tuple.create(l, r) + ) + .take(10) + .subscribe(tester); + + scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList( + Tuple.create(0L, 0L), + Tuple.create(1L, 0L), + Tuple.create(0L, 1L), + Tuple.create(1L, 1L), + Tuple.create(2L, 1L), + Tuple.create(3L, 1L), + Tuple.create(0L, 2L), + Tuple.create(1L, 2L), + Tuple.create(2L, 2L), + Tuple.create(3L, 2L) + )); + } + + @Test + public void testJoin2Way() { + TestSubscriber> tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable left = + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + Observable right = + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler); + + left + .join( + right, + i -> Observable.timer(150, TimeUnit.MILLISECONDS, scheduler), + i -> Observable.timer(0, TimeUnit.MILLISECONDS, scheduler), + (l,r) -> Tuple.create(l, r) + ) + .take(10) + .subscribe(tester); + + scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList( + Tuple.create(0L, 0L), + Tuple.create(0L, 1L), + Tuple.create(1L, 1L), + Tuple.create(1L, 2L), + Tuple.create(2L, 2L), + Tuple.create(2L, 3L), + Tuple.create(3L, 3L), + Tuple.create(3L, 4L), + Tuple.create(4L, 4L), + Tuple.create(4L, 5L) + )); + } +} diff --git a/tests/java/itrx/chapter4/coincidence/WindowExample.java b/tests/java/itrx/chapter4/coincidence/WindowExample.java new file mode 100644 index 0000000..a227fe1 --- /dev/null +++ b/tests/java/itrx/chapter4/coincidence/WindowExample.java @@ -0,0 +1,184 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.coincidence; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class WindowExample { + + public void exampleParallel() { + Observable + .merge( + Observable.range(0, 5) + .window(3,1)) + .subscribe(System.out::println); + + // 0 + // 1 + // 1 + // 2 + // 2 + // 2 + // 3 + // 3 + // 3 + // 4 + // 4 + // 4 + } + + public void exampleByCount() { + Observable.range(0, 5) + .window(3, 1) + .flatMap(o -> o.toList()) + .subscribe(System.out::println); + + // [0, 1, 2] + // [1, 2, 3] + // [2, 3, 4] + // [3, 4] + // [4] + + } + + public void exampleByTime() { + Observable.interval(100, TimeUnit.MILLISECONDS) + .take(5) + .window(250, 100, TimeUnit.MILLISECONDS) + .flatMap(o -> o.toList()) + .subscribe(System.out::println); + + // [0, 1] + // [0, 1, 2] + // [1, 2, 3] + // [2, 3, 4] + // [3, 4] + // [4] + } + + public void exampleBySignal() { + Observable.interval(100, TimeUnit.MILLISECONDS) + .take(5) + .window( + Observable.interval(100, TimeUnit.MILLISECONDS), + o -> Observable.timer(250, TimeUnit.MILLISECONDS)) + .flatMap(o -> o.toList()) + .subscribe(System.out::println); + + // [1, 2] + // [2, 3] + // [3, 4] + // [4] + // [] + } + + + // + // Tests + // + + @Test + public void testParallel() { + TestSubscriber tester = new TestSubscriber<>(); + + Observable + .merge( + Observable.range(0, 5) + .window(3,1)) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList(0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4)); + } + + @Test + public void testByCount() { + TestSubscriber> tester = new TestSubscriber<>(); + + Observable.range(0, 5) + .window(3, 1) + .flatMap(o -> o.toList()) + .subscribe(tester); + + tester.assertReceivedOnNext(Arrays.asList( + Arrays.asList(0, 1, 2), + Arrays.asList(1, 2, 3), + Arrays.asList(2, 3, 4), + Arrays.asList(3, 4), + Arrays.asList(4) + )); + } + + @Test + public void testByTime() { + TestSubscriber> tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .take(5) + .window(250, 100, TimeUnit.MILLISECONDS, scheduler) + .flatMap(o -> o.toList()) + .subscribe(tester); + + scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList( + Arrays.asList(0L, 1L), + Arrays.asList(0L, 1L, 2L), + Arrays.asList(1L, 2L, 3L), + Arrays.asList(2L, 3L, 4L), + Arrays.asList(3L, 4L), + Arrays.asList(4L) + )); + } + + @Test + public void testBySignal() { + TestSubscriber> tester = new TestSubscriber<>(); + TestScheduler scheduler = Schedulers.test(); + + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler) + .take(5) + .window( + Observable.interval(100, TimeUnit.MILLISECONDS, scheduler), + o -> Observable.timer(250, TimeUnit.MILLISECONDS, scheduler)) + .flatMap(o -> o.toList()) + .subscribe(tester); + + scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); + tester.assertReceivedOnNext(Arrays.asList( + Arrays.asList(0L, 1L, 2L), + Arrays.asList(1L, 2L, 3L), + Arrays.asList(2L, 3L, 4L), + Arrays.asList(3L, 4L), + Arrays.asList(4L) + )); + } +} diff --git a/tests/java/itrx/chapter4/scheduling/ObserveOnExample.java b/tests/java/itrx/chapter4/scheduling/ObserveOnExample.java new file mode 100644 index 0000000..d0c0fd7 --- /dev/null +++ b/tests/java/itrx/chapter4/scheduling/ObserveOnExample.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.scheduling; + +import static org.junit.Assert.*; + +import org.junit.Assert; +import org.junit.Test; + +import rx.Observable; +import rx.schedulers.Schedulers; + +public class ObserveOnExample { + + public void exampleObserveOn() { + Observable.create(o -> { + System.out.println("Created on " + Thread.currentThread().getId()); + o.onNext(1); + o.onNext(2); + o.onCompleted(); + }) + .observeOn(Schedulers.newThread()) + .subscribe(i -> + System.out.println("Received " + i + " on " + Thread.currentThread().getId())); + + // Created on 1 + // Received 1 on 13 + // Received 2 on 13 + } + + public void exampleObserveOnBeforeAfter() { + Observable.create(o -> { + System.out.println("Created on " + Thread.currentThread().getId()); + o.onNext(1); + o.onNext(2); + o.onCompleted(); + }) + .doOnNext(i -> + System.out.println("Before " + i + " on " + Thread.currentThread().getId())) + .observeOn(Schedulers.newThread()) + .doOnNext(i -> + System.out.println("After " + i + " on " + Thread.currentThread().getId())) + .subscribe(); + + // Created on 1 + // Before 1 on 1 + // Before 2 on 1 + // After 1 on 13 + // After 2 on 13 + } + + + // + // Test + // + + @Test + public void testObserveOn() { + long[] threads = {0, 0}; + + Observable.create(o -> { + threads[0] = Thread.currentThread().getId(); + o.onNext(1); + o.onNext(2); + o.onCompleted(); + }) + .observeOn(Schedulers.newThread()) + .subscribe(i -> threads[1] = Thread.currentThread().getId()); + + Assert.assertNotEquals("Create and receive on different threads", threads[0], threads[1]); + } + + @Test + public void testObserveOnBeforeAfter() { + long[] threads = {0, 0, 0, 0, 0}; + + threads[0] = Thread.currentThread().getId(); + + Observable.create(o -> { + threads[1] = Thread.currentThread().getId(); + o.onNext(1); + o.onNext(2); + o.onCompleted(); + }) + .doOnNext(i -> threads[2] = Thread.currentThread().getId()) + .observeOn(Schedulers.newThread()) + .doOnNext(i -> threads[3] = Thread.currentThread().getId()) + .subscribe(i -> threads[4] = Thread.currentThread().getId()); + + assertEquals("Create on main thread", threads[0], threads[1]); + assertEquals("Synchronous before observeOn", threads[1], threads[2]); + assertEquals("Synchronous after observeOn", threads[3], threads[4]); + assertNotEquals("Before and after observeOn on different threads", threads[2], threads[3]); + } + +} diff --git a/tests/java/itrx/chapter4/scheduling/SchedulerExample.java b/tests/java/itrx/chapter4/scheduling/SchedulerExample.java new file mode 100644 index 0000000..32cb235 --- /dev/null +++ b/tests/java/itrx/chapter4/scheduling/SchedulerExample.java @@ -0,0 +1,177 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.scheduling; + +import static org.junit.Assert.*; + +import java.lang.Thread.State; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Scheduler; +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class SchedulerExample { + + public void exampleSchedule() { + Scheduler scheduler = Schedulers.immediate(); + + Scheduler.Worker worker = scheduler.createWorker(); + worker.schedule( + () -> System.out.println("Action")); + } + + public void exampleScheduleFuture() { + Scheduler scheduler = Schedulers.newThread(); + long start = System.currentTimeMillis(); + Scheduler.Worker worker = scheduler.createWorker(); + worker.schedule( + () -> System.out.println(System.currentTimeMillis()-start), + 5, TimeUnit.SECONDS); + worker.schedule( + () -> System.out.println(System.currentTimeMillis()-start), + 5, TimeUnit.SECONDS); + + // 5033 + // 5035 + } + + public void exampleCancelWork() { + Scheduler scheduler = Schedulers.newThread(); + long start = System.currentTimeMillis(); + Scheduler.Worker worker = scheduler.createWorker(); + worker.schedule( + () -> { + System.out.println(System.currentTimeMillis()-start); + worker.unsubscribe(); + }, + 5, TimeUnit.SECONDS); + worker.schedule( + () -> System.out.println(System.currentTimeMillis()-start), + 5, TimeUnit.SECONDS); + + // 5032 + } + + public void exampleCancelWithInterrupt() throws InterruptedException { + Scheduler scheduler = Schedulers.newThread(); + Scheduler.Worker worker = scheduler.createWorker(); + worker.schedule(() -> { + try { + Thread.sleep(2000); + System.out.println("Action completed"); + } catch (InterruptedException e) { + System.out.println("Action interrupted"); + } + }); + Thread.sleep(500); + worker.unsubscribe(); + + // Action interrupted + } + + + // + // Test + // + + @Test + public void testSchedule() { + List executed = new ArrayList<>(); + + Scheduler scheduler = Schedulers.immediate(); + Scheduler.Worker worker = scheduler.createWorker(); + worker.schedule( + () -> executed.add(true)); + + assertEquals(Arrays.asList(new Boolean(true)), executed); + } + + @Test + public void testScheduleFuture() { + long[] executionTimes = {0, 0}; + + TestScheduler scheduler = Schedulers.test(); + Scheduler.Worker worker = scheduler.createWorker(); + worker.schedule( + () -> executionTimes[0] = scheduler.now(), + 5, TimeUnit.SECONDS); + worker.schedule( + () -> executionTimes[1] = scheduler.now(), + 5, TimeUnit.SECONDS); + + scheduler.advanceTimeTo(5000, TimeUnit.MILLISECONDS); + assertEquals("First task executed on time", 5000, executionTimes[0]); + assertEquals("Second task executed on time", 5000, executionTimes[1]); + } + + @Test + public void testCancelWork() { + long[] executionTimes = {0, 0}; + + TestScheduler scheduler = Schedulers.test(); + Scheduler.Worker worker = scheduler.createWorker(); + worker.schedule( + () -> { + executionTimes[0] = scheduler.now(); + worker.unsubscribe(); + }, + 5, TimeUnit.SECONDS); + worker.schedule( + () -> executionTimes[1] = scheduler.now(), + 5, TimeUnit.SECONDS); + + scheduler.advanceTimeTo(5000, TimeUnit.MILLISECONDS); + assertEquals("First task executed on time", 5000, executionTimes[0]); + assertEquals("Second task never executed", 0, executionTimes[1]); + } + + @Test + public void testCancelWithInterrupt() throws InterruptedException { + Scheduler scheduler = Schedulers.newThread(); + Scheduler.Worker worker = scheduler.createWorker(); + Thread[] workerThread = {null}; + Boolean[] interrupted = {false}; + worker.schedule(() -> { + try { + workerThread[0] = Thread.currentThread(); + Thread.sleep(100); + } catch (InterruptedException e) { + interrupted[0] = true; + } + }); + + while (workerThread[0] == null || + workerThread[0].getState() != State.TIMED_WAITING) + Thread.sleep(1); // Wait for task to sleep + worker.unsubscribe(); + workerThread[0].join(); + assertTrue("Task must be interrupted before completing", interrupted[0]); + } + +} diff --git a/tests/java/itrx/chapter4/scheduling/SchedulersExample.java b/tests/java/itrx/chapter4/scheduling/SchedulersExample.java new file mode 100644 index 0000000..67222cc --- /dev/null +++ b/tests/java/itrx/chapter4/scheduling/SchedulersExample.java @@ -0,0 +1,158 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.scheduling; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Semaphore; + +import org.junit.Test; + +import rx.Scheduler; +import rx.schedulers.Schedulers; + +public class SchedulersExample { + + public static void printThread(String message) { + System.out.println(message + " on " + Thread.currentThread().getId()); + } + + public void exampleImmediate() { + Scheduler scheduler = Schedulers.immediate(); + Scheduler.Worker worker = scheduler.createWorker(); + worker.schedule(() -> { + System.out.println("Start"); + worker.schedule(() -> System.out.println("Inner")); + System.out.println("End"); + }); + + // Start + // Inner + // End + } + + public void exampleTrampoline() { + Scheduler scheduler = Schedulers.trampoline(); + Scheduler.Worker worker = scheduler.createWorker(); + worker.schedule(() -> { + System.out.println("Start"); + worker.schedule(() -> System.out.println("Inner")); + System.out.println("End"); + }); + + // Start + // End + // Inner + } + + public void exampleNewThread() throws InterruptedException { + printThread("Main"); + Scheduler scheduler = Schedulers.newThread(); + Scheduler.Worker worker = scheduler.createWorker(); + worker.schedule(() -> { + printThread("Start"); + worker.schedule(() -> printThread("Inner")); + printThread("End"); + }); + Thread.sleep(500); + worker.schedule(() -> printThread("Again")); + + // Main on 1 + // Start on 11 + // End on 11 + // Inner on 11 + // Again on 11 + } + + + // + // Test + // + + @Test + public void testImmediate() { + List execution = new ArrayList<>(); + + Scheduler scheduler = Schedulers.immediate(); + Scheduler.Worker worker = scheduler.createWorker(); + worker.schedule(() -> { + execution.add("Start"); + worker.schedule(() -> execution.add("Inner")); + execution.add("End"); + }); + + assertEquals(Arrays.asList("Start", "Inner", "End"), execution); + } + + @Test + public void testTrampoline() { + List execution = new ArrayList<>(); + + Scheduler scheduler = Schedulers.trampoline(); + Scheduler.Worker worker = scheduler.createWorker(); + worker.schedule(() -> { + execution.add("Start"); + worker.schedule(() -> execution.add("Inner")); + execution.add("End"); + }); + + assertEquals(Arrays.asList("Start", "End", "Inner"), execution); + } + + @Test + public void testNewThread() throws InterruptedException { + List execution = new ArrayList<>(); + List threads = new ArrayList<>(); + Semaphore workfinished = new Semaphore(-2); + + Scheduler scheduler = Schedulers.newThread(); + Scheduler.Worker worker = scheduler.createWorker(); + worker.schedule(() -> { + threads.add(Thread.currentThread()); + execution.add("Start"); + worker.schedule(() -> { + execution.add("Inner"); + workfinished.release(); + }); + execution.add("End"); + workfinished.release(); + }); + worker.schedule(() -> { + threads.add(Thread.currentThread()); + workfinished.release(); + }); + + workfinished.acquire(); + + assertEquals("Same worker schedules on the same thread", + threads.get(0), + threads.get(1)); + assertEquals("New thread used as trampoline", + Arrays.asList("Start", "End", "Inner"), + execution); + } + +} diff --git a/tests/java/itrx/chapter4/scheduling/SingleThreadedExample.java b/tests/java/itrx/chapter4/scheduling/SingleThreadedExample.java new file mode 100644 index 0000000..c4ce155 --- /dev/null +++ b/tests/java/itrx/chapter4/scheduling/SingleThreadedExample.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.scheduling; + +import org.junit.Assert; +import org.junit.Test; + +import rx.subjects.BehaviorSubject; + +public class SingleThreadedExample { + + public void example() { + final BehaviorSubject subject = BehaviorSubject.create(); + subject.subscribe(i -> { + System.out.println("Received " + i + " on " + Thread.currentThread().getId()); + }); + + int[] i = {1}; // naughty side-effects for examples only ;) + Runnable r = () -> { + synchronized(i) { + System.out.println("onNext(" + i[0] + ") on " + Thread.currentThread().getId()); + subject.onNext(i[0]++); + } + }; + + r.run(); // Execute on main thread + new Thread(r).start(); + new Thread(r).start(); + + // onNext(1) on 1 + // Received 1 on 1 + // onNext(2) on 11 + // Received 2 on 11 + // onNext(3) on 12 + // Received 3 on 12 + } + + + // + // Test + // + + @Test + public void test() throws InterruptedException { + long[] emitted = {0, 0, 0}; + long[] received = {0, 0, 0}; + + final BehaviorSubject subject = BehaviorSubject.create(); + subject.subscribe(i -> { + received[i] = Thread.currentThread().getId(); + }); + + int[] i = {0}; // naughty side-effects for examples only ;) + Runnable r = () -> { + synchronized(i) { + int value = i[0]; + emitted[value] = Thread.currentThread().getId(); + subject.onNext(i[0]++); + } + }; + + r.run(); // Execute on main thread + Thread t1 = new Thread(r); + Thread t2 = new Thread(r); + t1.start(); + t2.start(); + t1.join(); + t2.join(); + + Assert.assertArrayEquals("onNext and handler executed on the same thread", + emitted, received); + } +} diff --git a/tests/java/itrx/chapter4/scheduling/SubscribeOnExample.java b/tests/java/itrx/chapter4/scheduling/SubscribeOnExample.java new file mode 100644 index 0000000..ebae61d --- /dev/null +++ b/tests/java/itrx/chapter4/scheduling/SubscribeOnExample.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.scheduling; + +import static org.junit.Assert.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.schedulers.Schedulers; + +public class SubscribeOnExample { + + public void exampleBlocking() { + System.out.println("Main: " + Thread.currentThread().getId()); + + Observable.create(o -> { + System.out.println("Created on " + Thread.currentThread().getId()); + o.onNext(1); + o.onNext(2); + o.onCompleted(); + }) + .subscribe(i -> { + System.out.println("Received " + i + " on " + Thread.currentThread().getId()); + }); + + System.out.println("Finished main: " + Thread.currentThread().getId()); + + // Main: 1 + // Created on 1 + // Received 1 on 1 + // Received 2 on 1 + // Finished main: 1 + } + + public void exampleSubscribeOn() { + System.out.println("Main: " + Thread.currentThread().getId()); + + Observable.create(o -> { + System.out.println("Created on " + Thread.currentThread().getId()); + o.onNext(1); + o.onNext(2); + o.onCompleted(); + }) + .subscribeOn(Schedulers.newThread()) + .subscribe(i -> { + System.out.println("Received " + i + " on " + Thread.currentThread().getId()); + }); + + System.out.println("Finished main: " + Thread.currentThread().getId()); + + // Main: 1 + // Created on 1 + // Received 1 on 11 + // Received 2 on 11 + // Finished main: 11 + } + + public void exampleIntervalThread() { + System.out.println("Main: " + Thread.currentThread().getId()); + + Observable.interval(100, TimeUnit.MILLISECONDS) + .subscribe(i -> { + System.out.println("Received " + i + " on " + Thread.currentThread().getId()); + }); + + System.out.println("Finished main: " + Thread.currentThread().getId()); + + // Main: 1 + // Finished main: 1 + // Received 0 on 11 + // Received 1 on 11 + // Received 2 on 11 + } + + + // + // Test + // + + @Test + public void testBlocking() { + Map threads = new HashMap<>(); + + threads.put("main", Thread.currentThread().getId()); + + Observable.create(o -> { + threads.put("create", Thread.currentThread().getId()); + o.onNext(1); + o.onNext(2); + o.onCompleted(); + }) + .subscribe(i -> { + threads.put("receive", Thread.currentThread().getId()); + }); + + assertEquals(Thread.currentThread().getId(), threads.get("main").longValue()); + assertEquals(Thread.currentThread().getId(), threads.get("create").longValue()); + assertEquals(Thread.currentThread().getId(), threads.get("receive").longValue()); + } + + @Test + public void testSubscribeOn() { + Map threads = new HashMap<>(); + + threads.put("main", Thread.currentThread().getId()); + + Observable.create(o -> { + threads.put("create", Thread.currentThread().getId()); + o.onNext(1); + o.onNext(2); + o.onCompleted(); + }) + .subscribeOn(Schedulers.newThread()) + .subscribe(i -> { + threads.put("receive", Thread.currentThread().getId()); + }); + + assertEquals("Emitting and receiving on the same thread", + threads.get("receive"), + threads.get("create")); + assertNotEquals("Emitting and receiving not on the main thread", + threads.get("main"), + threads.get("receive")); + } + + @Test + public void testIntervalThread() { + long[] threads = {0, 0}; + + threads[0] = Thread.currentThread().getId(); + + Observable.interval(100, TimeUnit.MILLISECONDS) + .subscribe(i -> { + threads[1] = Thread.currentThread().getId(); + }); + + assertNotEquals("interval not executing on main thread", threads[0], threads[1]); + } + +} diff --git a/tests/java/itrx/chapter4/scheduling/UnsubscribeOnExample.java b/tests/java/itrx/chapter4/scheduling/UnsubscribeOnExample.java new file mode 100644 index 0000000..4357c15 --- /dev/null +++ b/tests/java/itrx/chapter4/scheduling/UnsubscribeOnExample.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.scheduling; + +import static org.junit.Assert.*; + +import java.util.Arrays; + +import org.junit.Test; + +import rx.Observable; +import rx.schedulers.Schedulers; + +public class UnsubscribeOnExample { + + public static void example() { + Observable source = Observable.using( + () -> { + System.out.println("Subscribed on " + Thread.currentThread().getId()); + return Arrays.asList(1,2); + }, + (ints) -> { + System.out.println("Producing on " + Thread.currentThread().getId()); + return Observable.from(ints); + }, + (ints) -> { + System.out.println("Unubscribed on " + Thread.currentThread().getId()); + } + ); + + source + .unsubscribeOn(Schedulers.newThread()) + .subscribe(System.out::println); + + // Subscribed on 1 + // Producing on 1 + // 1 + // 2 + // Unubscribed on 11 + } + + + // + // Test + // + + @Test + public void test() { + long[] threads = {0, 0, 0}; + + Observable source = Observable.using( + () -> { + threads[0] = Thread.currentThread().getId(); + return Arrays.asList(1,2); + }, + (ints) -> { + threads[1] = Thread.currentThread().getId(); + return Observable.from(ints); + }, + (ints) -> { + threads[2] = Thread.currentThread().getId(); + } + ); + + source + .unsubscribeOn(Schedulers.newThread()) + .subscribe(); + + assertEquals(threads[0], threads[1]); + assertNotEquals(threads[0], threads[2]); + } + +} diff --git a/tests/java/itrx/chapter4/testing/ExampleExample.java b/tests/java/itrx/chapter4/testing/ExampleExample.java new file mode 100644 index 0000000..803942f --- /dev/null +++ b/tests/java/itrx/chapter4/testing/ExampleExample.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.testing; + +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.schedulers.TestScheduler; + +public class ExampleExample { + + @Test + public void test() { + TestScheduler scheduler = new TestScheduler(); + List expected = Arrays.asList(0L, 1L, 2L, 3L, 4L); + List result = new ArrayList<>(); + Observable + .interval(1, TimeUnit.SECONDS, scheduler) + .take(5) + .subscribe(i -> result.add(i)); + assertTrue(result.isEmpty()); + scheduler.advanceTimeBy(5, TimeUnit.SECONDS); + assertTrue(result.equals(expected)); + } + +} diff --git a/tests/java/itrx/chapter4/testing/TestSchedulerExample.java b/tests/java/itrx/chapter4/testing/TestSchedulerExample.java new file mode 100644 index 0000000..0c5fc16 --- /dev/null +++ b/tests/java/itrx/chapter4/testing/TestSchedulerExample.java @@ -0,0 +1,248 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.testing; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.schedulers.Schedulers; +import rx.schedulers.TestScheduler; + +public class TestSchedulerExample { + + public void exampleAdvanceTo() { + TestScheduler s = Schedulers.test(); + + s.createWorker().schedule( + () -> System.out.println("Immediate")); + s.createWorker().schedule( + () -> System.out.println("20s"), + 20, TimeUnit.SECONDS); + s.createWorker().schedule( + () -> System.out.println("40s"), + 40, TimeUnit.SECONDS); + + System.out.println("Advancing to 1ms"); + s.advanceTimeTo(1, TimeUnit.MILLISECONDS); + System.out.println("Virtual time: " + s.now()); + + System.out.println("Advancing to 10s"); + s.advanceTimeTo(10, TimeUnit.SECONDS); + System.out.println("Virtual time: " + s.now()); + + System.out.println("Advancing to 40s"); + s.advanceTimeTo(40, TimeUnit.SECONDS); + System.out.println("Virtual time: " + s.now()); + + // Advancing to 1ms + // Immediate + // Virtual time: 1 + // Advancing to 10s + // Virtual time: 10000 + // Advancing to 40s + // 20s + // 40s + // Virtual time: 40000 + } + + public void exampleTimeBy() { + TestScheduler s = Schedulers.test(); + + s.createWorker().schedule( + () -> System.out.println("Immediate")); + s.createWorker().schedule( + () -> System.out.println("20s"), + 20, TimeUnit.SECONDS); + s.createWorker().schedule( + () -> System.out.println("40s"), + 40, TimeUnit.SECONDS); + + System.out.println("Advancing by 1ms"); + s.advanceTimeBy(1, TimeUnit.MILLISECONDS); + System.out.println("Virtual time: " + s.now()); + + System.out.println("Advancing by 10s"); + s.advanceTimeBy(10, TimeUnit.SECONDS); + System.out.println("Virtual time: " + s.now()); + + System.out.println("Advancing by 40s"); + s.advanceTimeBy(40, TimeUnit.SECONDS); + System.out.println("Virtual time: " + s.now()); + + // Advancing by 1ms + // Immediate + // Virtual time: 1 + // Advancing by 10s + // Virtual time: 10001 + // Advancing by 40s + // 20s + // 40s + // Virtual time: 50001 + } + + public void exampleTriggerActions() { + TestScheduler s = Schedulers.test(); + + s.createWorker().schedule( + () -> System.out.println("Immediate")); + s.createWorker().schedule( + () -> System.out.println("20s"), + 20, TimeUnit.SECONDS); + + s.triggerActions(); + System.out.println("Virtual time: " + s.now()); + + // Immediate + // Virtual time: 0 + } + + public void exampleCollision() { + TestScheduler s = Schedulers.test(); + + s.createWorker().schedule( + () -> System.out.println("First"), + 20, TimeUnit.SECONDS); + s.createWorker().schedule( + () -> System.out.println("Second"), + 20, TimeUnit.SECONDS); + s.createWorker().schedule( + () -> System.out.println("Third"), + 20, TimeUnit.SECONDS); + + s.advanceTimeTo(20, TimeUnit.SECONDS); + + // First + // Second + // Third + } + + + // + // Test + // + + @Test + public void testAdvanceTo() { + List execution = new ArrayList<>(); + TestScheduler scheduler = Schedulers.test(); + + scheduler.createWorker().schedule( + () -> execution.add("Immediate")); + scheduler.createWorker().schedule( + () -> execution.add("20s"), + 20, TimeUnit.SECONDS); + scheduler.createWorker().schedule( + () -> execution.add("40s"), + 40, TimeUnit.SECONDS); + + + assertEquals(0, scheduler.now()); + assertEquals(Arrays.asList(), execution); + + scheduler.advanceTimeTo(1, TimeUnit.MILLISECONDS); + assertEquals(1, scheduler.now()); + assertEquals(Arrays.asList("Immediate"), execution); + + scheduler.advanceTimeTo(10, TimeUnit.SECONDS); + assertEquals(10000, scheduler.now()); + assertEquals(Arrays.asList("Immediate"), execution); + + scheduler.advanceTimeTo(40, TimeUnit.SECONDS); + assertEquals(40000, scheduler.now()); + assertEquals(Arrays.asList("Immediate", "20s", "40s"), execution); + } + + @Test + public void testTimeBy() { + List execution = new ArrayList<>(); + TestScheduler scheduler = Schedulers.test(); + + scheduler.createWorker().schedule( + () -> execution.add("Immediate")); + scheduler.createWorker().schedule( + () -> execution.add("20s"), + 20, TimeUnit.SECONDS); + scheduler.createWorker().schedule( + () -> execution.add("40s"), + 40, TimeUnit.SECONDS); + + assertEquals(0, scheduler.now()); + assertEquals(Arrays.asList(), execution); + + scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS); + assertEquals(1, scheduler.now()); + assertEquals(Arrays.asList("Immediate"), execution); + + scheduler.advanceTimeBy(10, TimeUnit.SECONDS); + assertEquals(10001, scheduler.now()); + assertEquals(Arrays.asList("Immediate"), execution); + + scheduler.advanceTimeBy(40, TimeUnit.SECONDS); + assertEquals(50001, scheduler.now()); + assertEquals(Arrays.asList("Immediate", "20s", "40s"), execution); + } + + @Test + public void testTriggerActions() { + List execution = new ArrayList<>(); + TestScheduler scheduler = Schedulers.test(); + + scheduler.createWorker().schedule( + () -> execution.add("Immediate")); + scheduler.createWorker().schedule( + () -> execution.add("20s"), + 20, TimeUnit.SECONDS); + + assertEquals(0, scheduler.now()); + assertEquals(Arrays.asList(), execution); + scheduler.triggerActions(); + assertEquals(0, scheduler.now()); + assertEquals(Arrays.asList("Immediate"), execution); + } + + @Test + public void testCollision() { + List execution = new ArrayList<>(); + TestScheduler scheduler = Schedulers.test(); + + scheduler.createWorker().schedule( + () -> execution.add("First"), + 20, TimeUnit.SECONDS); + scheduler.createWorker().schedule( + () -> execution.add("Second"), + 20, TimeUnit.SECONDS); + scheduler.createWorker().schedule( + () -> execution.add("Third"), + 20, TimeUnit.SECONDS); + + assertEquals(Arrays.asList(), execution); + scheduler.advanceTimeTo(20, TimeUnit.SECONDS); + assertEquals(Arrays.asList("First", "Second", "Third"), execution); + } +} diff --git a/tests/java/itrx/chapter4/testing/TestSubscriberExample.java b/tests/java/itrx/chapter4/testing/TestSubscriberExample.java new file mode 100644 index 0000000..3cda32c --- /dev/null +++ b/tests/java/itrx/chapter4/testing/TestSubscriberExample.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2015 Christos Froussios + * + * 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. + * + *******************************************************************************/ +package itrx.chapter4.testing; + +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import rx.Observable; +import rx.observers.TestSubscriber; +import rx.schedulers.TestScheduler; + +public class TestSubscriberExample { + + @Test + public void test() { + TestScheduler scheduler = new TestScheduler(); + TestSubscriber subscriber = new TestSubscriber<>(); + List expected = Arrays.asList(0L, 1L, 2L, 3L, 4L); + Observable + .interval(1, TimeUnit.SECONDS, scheduler) + .take(5) + .subscribe(subscriber); + assertTrue(subscriber.getOnNextEvents().isEmpty()); + scheduler.advanceTimeBy(5, TimeUnit.SECONDS); + subscriber.assertReceivedOnNext(expected); + subscriber.assertTerminalEvent(); + subscriber.assertNoErrors(); + subscriber.assertUnsubscribed(); + } +} From 5c2fa405b7f5fa780ae5d48359b2ae70963c45e9 Mon Sep 17 00:00:00 2001 From: Piotr Bobinski Date: Tue, 12 Sep 2017 15:39:40 +0200 Subject: [PATCH 2/7] modidy readme to be more informative --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index eadd1b8..113bdc8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +#Intro to RxJava exercises and code kata + +This is my fork for the 'Intro to RxJava repo, which can be found under this link: [[CLICK]](/https://github.com/Froussios/Intro-To-RxJava)' + +It will be a place where I will try various stuff from the tutorial. + + + # Intro to RxJava This guide aims to introduce a beginner reactive programmer to the complete power of the [RxJava](https://github.com/ReactiveX/RxJava) implementation of reactive programming for the JVM. It is based on the [IntroToRx](http://www.introtorx.com) guide for Rx.NET. From 89e2dcaba236837bd0cc08e7411b41b8de658f8d Mon Sep 17 00:00:00 2001 From: Piotr Bobinski Date: Tue, 12 Sep 2017 15:41:11 +0200 Subject: [PATCH 3/7] modify readme to be more informative --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 113bdc8..6174307 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#Intro to RxJava exercises and code kata +# Intro to RxJava exercises and code kata This is my fork for the 'Intro to RxJava repo, which can be found under this link: [[CLICK]](/https://github.com/Froussios/Intro-To-RxJava)' From 7ba5e3a5a80bc40d15b7ec5a675eb0724da6ad90 Mon Sep 17 00:00:00 2001 From: Piotr Bobinski Date: Wed, 13 Sep 2017 22:19:45 +0200 Subject: [PATCH 4/7] First 3 classes --- exercises/exercises.iml | 2 +- exercises/pom.xml | 3 +- .../test/java/main/AsyncSubjectTest.groovy | 39 ++++++++++++ .../test/java/main/BehaviorSubjectTest.groovy | 57 ++++++++++++++++++ .../test/java/main/ReplaySubjectTest.groovy | 53 ++++++++++++++++ ...ectTest$__spock_feature_0_0_closure1.class | Bin 0 -> 2394 bytes ...ectTest$__spock_feature_0_1_closure2.class | Bin 0 -> 2394 bytes .../test-classes/main/AsyncSubjectTest.class | Bin 0 -> 6970 bytes ...ectTest$__spock_feature_0_0_closure1.class | Bin 0 -> 2406 bytes ...ectTest$__spock_feature_0_1_closure2.class | Bin 0 -> 2286 bytes ...ectTest$__spock_feature_0_1_closure3.class | Bin 0 -> 2365 bytes ...ectTest$__spock_feature_0_1_closure4.class | Bin 0 -> 2369 bytes ...ectTest$__spock_feature_0_2_closure5.class | Bin 0 -> 2286 bytes ...ectTest$__spock_feature_0_3_closure6.class | Bin 0 -> 2286 bytes .../main/BehaviorSubjectTest.class | Bin 0 -> 9072 bytes ...ectTest$__spock_feature_0_0_closure1.class | Bin 0 -> 2399 bytes ...ectTest$__spock_feature_0_0_closure2.class | Bin 0 -> 2398 bytes ...ectTest$__spock_feature_0_1_closure3.class | Bin 0 -> 2398 bytes ...ectTest$__spock_feature_0_2_closure4.class | Bin 0 -> 2398 bytes .../test-classes/main/ReplaySubjectTest.class | Bin 0 -> 7825 bytes 20 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 exercises/src/test/java/main/AsyncSubjectTest.groovy create mode 100644 exercises/src/test/java/main/BehaviorSubjectTest.groovy create mode 100644 exercises/src/test/java/main/ReplaySubjectTest.groovy create mode 100644 exercises/target/test-classes/main/AsyncSubjectTest$__spock_feature_0_0_closure1.class create mode 100644 exercises/target/test-classes/main/AsyncSubjectTest$__spock_feature_0_1_closure2.class create mode 100644 exercises/target/test-classes/main/AsyncSubjectTest.class create mode 100644 exercises/target/test-classes/main/BehaviorSubjectTest$__spock_feature_0_0_closure1.class create mode 100644 exercises/target/test-classes/main/BehaviorSubjectTest$__spock_feature_0_1_closure2.class create mode 100644 exercises/target/test-classes/main/BehaviorSubjectTest$__spock_feature_0_1_closure3.class create mode 100644 exercises/target/test-classes/main/BehaviorSubjectTest$__spock_feature_0_1_closure4.class create mode 100644 exercises/target/test-classes/main/BehaviorSubjectTest$__spock_feature_0_2_closure5.class create mode 100644 exercises/target/test-classes/main/BehaviorSubjectTest$__spock_feature_0_3_closure6.class create mode 100644 exercises/target/test-classes/main/BehaviorSubjectTest.class create mode 100644 exercises/target/test-classes/main/ReplaySubjectTest$__spock_feature_0_0_closure1.class create mode 100644 exercises/target/test-classes/main/ReplaySubjectTest$__spock_feature_0_0_closure2.class create mode 100644 exercises/target/test-classes/main/ReplaySubjectTest$__spock_feature_0_1_closure3.class create mode 100644 exercises/target/test-classes/main/ReplaySubjectTest$__spock_feature_0_2_closure4.class create mode 100644 exercises/target/test-classes/main/ReplaySubjectTest.class diff --git a/exercises/exercises.iml b/exercises/exercises.iml index 8bb97d8..5ae027b 100644 --- a/exercises/exercises.iml +++ b/exercises/exercises.iml @@ -11,7 +11,7 @@ - + diff --git a/exercises/pom.xml b/exercises/pom.xml index 4534c81..2f68bea 100644 --- a/exercises/pom.xml +++ b/exercises/pom.xml @@ -10,8 +10,9 @@ - io.reactivex.rxjava2 + io.reactivex rxjava + 1.1.6 org.codehaus.groovy diff --git a/exercises/src/test/java/main/AsyncSubjectTest.groovy b/exercises/src/test/java/main/AsyncSubjectTest.groovy new file mode 100644 index 0000000..2051888 --- /dev/null +++ b/exercises/src/test/java/main/AsyncSubjectTest.groovy @@ -0,0 +1,39 @@ +package main + +import rx.subjects.AsyncSubject +import spock.lang.Specification + + +class AsyncSubjectTest extends Specification { + + // AsyncSubject cached last value. It doesnt emit anything before sequence finishes. + // It is to be used to emit a single value and immediately terminate + def "Standard AsyncSubject test"() { + when: + AsyncSubject s = AsyncSubject.create() + s.subscribe { v -> println("Emit: " + v) } + + s.onNext(0) + s.onNext(1) + + s.onCompleted() + + then: + s.hasValue() && s.hasCompleted() + } + + def "AsyncSubject has no values because it did not complete"() { + when: + AsyncSubject s = AsyncSubject.create() + s.subscribe { v -> println("Emit: " + v) } + + s.onNext(0) + s.onNext(1) + + //s.onCompleted() <- events didnt complete + + then: + !s.hasValue() || !s.hasCompleted() + } + +} \ No newline at end of file diff --git a/exercises/src/test/java/main/BehaviorSubjectTest.groovy b/exercises/src/test/java/main/BehaviorSubjectTest.groovy new file mode 100644 index 0000000..eb9d073 --- /dev/null +++ b/exercises/src/test/java/main/BehaviorSubjectTest.groovy @@ -0,0 +1,57 @@ +package main + +import rx.subjects.BehaviorSubject +import spock.lang.Specification + + +class BehaviorSubjectTest extends Specification { + + // BehavioSubject only remembers last value. Similar to REPLAYSubject with buffer of size 1 + def "Standard BehaviorSubject test"() { + when: + BehaviorSubject s = BehaviorSubject.create() + s.onNext(0) + s.onNext(1) + s.onNext(2) + s.subscribe { v -> println "Late: " + v } + s.onNext(3) + then: + s.getValues().size() == 1 + } + + def "BehaviorSubject that is empty because events finished before subscription"() { + when: + BehaviorSubject s = BehaviorSubject.create(); + s.onNext(0) + s.onNext(1) + s.onNext(2) + s.onCompleted(); + s.subscribe( + { v -> println v }, + { println "Error" }, + { println "Completed" } + ) + then: + s.getValues().size() == 0 + } + + def "BehaviorSubject handles first event if it was placed before someone subscribed"() { + when: + BehaviorSubject s = BehaviorSubject.create(0); + s.subscribe({ v -> println v }) + s.onNext(1) + then: + s.getValues().size() == 1 + } + + def "If events are completed BehaviourSubject empties"() { + when: + BehaviorSubject s = BehaviorSubject.create(0); + s.subscribe({ v -> println v }) + s.onNext(1) + s.onCompleted() + then: + s.getValues().size() == 0 + } + +} \ No newline at end of file diff --git a/exercises/src/test/java/main/ReplaySubjectTest.groovy b/exercises/src/test/java/main/ReplaySubjectTest.groovy new file mode 100644 index 0000000..61bbf62 --- /dev/null +++ b/exercises/src/test/java/main/ReplaySubjectTest.groovy @@ -0,0 +1,53 @@ +package main + +import rx.schedulers.Schedulers +import rx.subjects.ReplaySubject +import spock.lang.Specification +import spock.lang.Unroll + +import java.util.concurrent.TimeUnit + + +class ReplaySubjectTest extends Specification { + + @Unroll + def "Standard ReplaySubject"() { + when: + ReplaySubject s = ReplaySubject.create(); + s.subscribe { v -> println "Early: " + v } + s.onNext(0) + s.onNext(1) + s.subscribe { v -> println "Late: " + v } + s.onNext(2) + then: + s != null + } + + def "ReplaySubject with size 2"() { + when: + ReplaySubject s = ReplaySubject.createWithSize(2) + s.onNext(0) + s.onNext(1) + s.onNext(2) + s.onNext(3) + s.subscribe { v -> println "Late: " + v } + s.onNext(4) + then: + s != null + } + + def "ReplaySubject with createWithTime"() { + when: + ReplaySubject s = ReplaySubject.createWithTime(550, TimeUnit.MILLISECONDS, Schedulers.immediate()) + s.onNext(0) + Thread.sleep(200) + s.onNext(1) + Thread.sleep(200) + s.onNext(2) + s.subscribe { v -> println "Late: " + v } + s.onNext(3) + then: + s != null + } + +} \ No newline at end of file diff --git a/exercises/target/test-classes/main/AsyncSubjectTest$__spock_feature_0_0_closure1.class b/exercises/target/test-classes/main/AsyncSubjectTest$__spock_feature_0_0_closure1.class new file mode 100644 index 0000000000000000000000000000000000000000..5def0a34468cf3a5eb5d26d7a47685d8eb5bfb78 GIT binary patch literal 2394 zcmb7FZBr9h6n<_J*sv@RR>4xFg*KJEpaG=T8bL84r39>oQjyy2vfPBlu$%5~nBgb% zhx9wu4q}~l`l-(NqdGlzvtS?#Gfrmi-rRfMo^zga?z{i~{2RaoJch{~-L!JE?w)0o z>KohK@K(6%B{j{h*~W9N%yqBsaBZA^M%8v{KOuoJT&OsQ*IJXxvDMGK|?y zC1=>1d`qvpxj@RPTb{YYbMxHdj_&czV~sY3OQ*eGYiKd_g$5@f&Y&h+0%VA$(yIz& zbTTB8uBUsZQK;&!yI_@VhD**`=-!MKAfpTjHMX6N+IVl|JF;_=;+g zI87%B6AW>|9l=I2Ny_hK{6iwebxg^a#Fq>c=OwgcIA+bex94%uk+Yk6jp{DpD~2vX z=;J^bvLV?$D{2K_rOL3qw^ z`Mej8idmrk5rfhu60Pcvsc%&IidYzWil)Vv>pL6V2~OmaU3U!r!1S-@Eb(#@M~IeN zHbGJrEP4k0+;SJggfj-3l87c`(N93)SM(r3UsfKKFH2rC{=-)IA));*|57EDz z$&S6kN59iw2AkGlf9lE@z{m6t^coK0GTn*Y5bYwg{{)}XDoi~>n=d~^rk2Lk;mORH zIx?w#o_&pU6t8hLihZ_Ef)U?%KYc+4kBkrEns2;Dry|A}x)U+vFdmq{PVz!yG;@F( zYUDS>Gl#gn{{l0`>|6AR(?i@}E55``_5kz3kpN9B4h#%XbRzbyj5@3?l``sx`miLx z3Na!{AU5J>5a?4M|Am!Ba4xBg*KJEpaG=T8bL84r39>oQjyy2vfPBlu$%5~nBgb% zhx9wu4q}~l`l-(NqdGlzvtS?#Gk&>y_ug}!^Y)x`-~IRJ-vB1yF--30rj?s@_bj7S z-`M7cx58a7scCM_HlAx`u6uQdYvbC4W>jsL_LCAA!^MhY+q-+Ys%}+sg~pA9D8rcT zRC0#B$+z^nn+v3zy5*TWJU7oR?&u!hJl1GqxP03BwT2c$Z>Vz;;tXoC#X*K>D!r;e zMkhle>3X_n8ilIvx(im>X2=vnUE}2{MQz#^g%QZ+86-_3grx!ld&F5;4m z2znLtK$Q{0WrmoyWx5Pg#n1$uTRn>Hf@aq}?krf8BFo^E%+?SXIyGUW5hG<_(-uB4 zTnWiQx>Y|)1cO+JEA4YTrgv*Nj?WmbhSuP$@FgTESdkTmn<0ij*`g(!N-C*BhGbe~WR{i*Z`JI7a4 zi^ORNTpngs&L7 z1fh=|VaSGL_bjg!e2rT&Zh{=?!i5fE=QKkq)RBcaq)mypA@xk>c*j_ygIy`E7@i5F)wrChQX;ceM!Ti@y7-4 zkJU7)PG4%hNwwrTrd7#{o|kI0^{Q4JMWSu3T6g1EX1Ejv#BhTu8=AATy1}0!dC^pn z6U1qFAfcq-TP%uU@jcC&4~LfU!>I|^s6=SAVjQi>c!H-2zQKYZ!H*0B@2T37QVBoN zh^F#TMoy4A=r{#GLzkgpL&0}=B;welH=vmrfuxx7Jtzy=3PYRTn6Bd=K3h#}cTfn= z83xaL@u-*u>K`#EZ6eXC?wI;Um9L0}p}S~We7U}}!JXhlF4=X*;15jydd?CrCvk*m zxn)yG%7R7Dpr2drVwmELfu+apS}ppUrokGcZ|5QU zmNVJ0SNQ06`em?b9rmZLjDCDff1uZJ0E2WVc0;s_(EbyAN~?f+gf?G(h@h6n)Zxj@ zm^w14ex7}ebQG^~HHv+m{CL{y^17Ae~>xZUhn>`o_=!1upGmLKQ*o0RID5{*C(p literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/AsyncSubjectTest.class b/exercises/target/test-classes/main/AsyncSubjectTest.class new file mode 100644 index 0000000000000000000000000000000000000000..9d52a0b76c5dc5ff4d8b03a19d3e23556ec42ca6 GIT binary patch literal 6970 zcmd5>d3+pI9sj;$lih5#Z91iCQwjtKZLLDmhjkTw)Zn+7DIv8@^>yOU(-W+%){ zLQ|^KZmu zX1|%zUDL`&QOwl=+p#gsiNP*LN!6erb|(H*Bf+iwXZ z74H<84C*6JlP{rd^k=9S1#imJl5MoNmmD^?35ab1WkWnrEmQc8pl(tUwZJ{AUR?Nd zO(T=eOJDo~|4?pE{HSH8&8(i08bDy-bPZ|*O4piM(>+U|xVCOf0!vX9M+D0x@qppB z>6uKI=^E>7TOSi>sjb@{uCi~>6zLt(H{~|ACa@gG#8HG}DU~~DI#S(G3{c%g^2{z} z<-8RHs%q?o1~^Q6@RGS=4yPVppZolWWl8qgR=Jx)#_fq7*#&MJZ0P(ebV z6h(_bBB!AT#504(drH=YJ1T@BeY6@ciQ^QU>M_pimB8sZQ7)W8uhpm+>XnL{ddA#k zDCE(^M@=!YuY3Y0$gM>$luEu-K=YaO8@fAU8@;P~sd>v`fZU@mz?^t47g$ED zWYfBxt`3HOwabh`WcOv5PAK1D1krj)n&(cW?l7}yfrb~COzqr6YQi_tbN+@-ffCo) z>GIr2w|bpFtjoMZ^3VS6Np8qZDt@A;JenGaZy>@9#qy} zQg9iKV+5DU?Im(3C6;Z&F}SILF+K-ur`VXRUW46n=yX{= zF4rc_i=)k>qtV`pcg1lXu4k)JK)kma#d}oE8MSs8OjCnant8K52oWz=js^$_aX|&UQ21W4ePIcBYJBPxkBoPh{^N zw5`kZzKqc-yTec5lT!RoF)9lP&67m&8JRU=0w;wyF5lM6gk!TQb)wkEwnBD%!DgQh zl=YLHS$N7(eA>gdFof>FopIcbyU2HxJqjCo!!M>rS~=WFU|hDiow$dX^t%ybO3@Xl zp9>7raKrY;=fVREbtZQ=IGmcq&Oxv(Sk^IHc_BfWn{x!k4qz*9eA3Kpu9~Gkssv7* z(jGC+s}7}cg*oi@{4aH~^(c9y%#x=P@8ZzASrktTYz)b10X|1^SOQPt8|C;qz9~@7khsvch7H>tn^rz% zPvO%P_0^gwO=$Li2H%lqf0t*|(1o+7G$FIAL!+PZ-oa2bHJ7YxC(}_3zol`kY#yR9{GQD$yYW$@D@9+$@JDuB z^xS6FC7Hu)*~Hs1{8?s*5GfgzeuA6r_9<;KE9JeJmm7`#UQ8Md#fZ0K5sq@WvbY_U+_FS% ziA%qbY#zz@1&5m!@ogpFiuf!`-h-nja9qdjd=cU(jtMSB__vx8pdupg>i(uvxJ7PS znH)!*R&*<3$q6*=y93P~^$%fzoSndFTRZMVbNx8h>?0{EiqML+K7(d%mk?bfPbB$S zn;Vk7cjN4aeL@l!&L@h!^C`b2e$O`k-%2%4^D_0C4l8ZFwuBrf6XfSMgx6RC=Den(4HWIu+sCTWY|N!Z2U zq{B%v-LIQ-rj`v4o*zq-3M{Q1a4HnZ`y;6WEU;akVWCmXHhs!yE%+SG_n^` zr*5_QanZ9EC#@-IXbh3Ps$>G67CqQYckM-NO=(r>fqJdHrCz(Og?_tRY3`{{l7?|i zDB}5RqTcVS=z-e_p=-oOu@RqD&tW=lqc8=ZSS_vK?Tu}kbCUgh&9I(nDb zBcH_U0r3c*LA*Yd$LmBMuQ%uM>iBrwJq53o#NuikO}s8AUXLYSk0V}drepOB602X7 zSnWL$tg;v8Cs{w$o=9{Hio7Iw7tY>#2$6j~$*s(|%(imI%gq8`!B_na8aK$~UYe{Y zjVuSUxqcE~^Vh2w&oXz_yQbHrKJFiwZGqo8#b}r{c}H_+o~0@ zRJ`CRAD91$;ghfS<*mI#h(lP&aa@!hV1&ML3%t2HpwDSJ|hko9E literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BehaviorSubjectTest$__spock_feature_0_0_closure1.class b/exercises/target/test-classes/main/BehaviorSubjectTest$__spock_feature_0_0_closure1.class new file mode 100644 index 0000000000000000000000000000000000000000..b7e8854f0eb6ffa4703b100dfd07e7e3bfb1337b GIT binary patch literal 2406 zcmb7FZBr9h6n<_J*sv@TR-q!&LYqon&;Y*H2o<7IO2BF;6{+1W%S~7eyXo$R8Gb^4 zNWW9vdxn|sfB&f9a&{qNnMe*>6=$8dc|H?7`?&bn1r*3)X4$sYVi#xi_1AEHQdB?O&Z-!wk zRovEh^+xay{jB9%PtvOleaQ;<3VO9#GCe-)IQkyLR4Tn5?nIC!gXCS3&mAe|6`aL6 z84>g==z}UFhEEt`-j?Yy+$^@s(Wzym@Xly<-Q&)JMOm^8PWfz&f}vXzb{b()5;kq& z7Q>~GB&1vQqeM1{g-Fv`q}tyeiQ{vI%b{gxmjMY$idQ6t;Z}&@R=XjtD7b{NZhVOp z1tqWwb;scrHNxFgv6;{FKDXN!w$++~46*`xoT#!(?IG%3^0~LfJ>BON!(b|X!p`v( z)gtjBqa;i+#07l>AqkR{-^=)i#EYxACSwZM875ClXvuKQnm4!SanYKyn|h7vF5xCa zkKpvNBMjM)?6&h-!B@B~;}*z~9-QeSf=)A}LLCWdFbQ8XC`S=C+H<$xwE(P~7Aub*e>yx7PO$|BRlvjtBb0xd%J?3Rj+%PzmW*})eGzYoh z{jr)x)fq^wH>s99$FwSW(e_e}wqDhWqe!%^RqJjX%M9njfEaF2WkYk;t{eP8k{3-C zIYFF;2NFsOzQv-L7vIyk`FLmvKYTFc8kGo*R!pQd8Bg$3!8ceCB>0hG=si_iQYzsm zn$lDr%E$>)2OX#2XXr9CY$*5+k3<}s4E@c_2qeXz??YM0Rv0?;#(W-;zRQZZn7DmG$D(A0usNX7YX{xBDGZSPP(TDfL4qCP19hF(YN~$ z1IwB0_$!?Mo&GY|w2t^wSH>VN&_B?FIE0IICw9ZMi_rd4d`7E)dV~&Newd(^#?_Ij z%(yx_rGAlpjdT>RaXE^8woif)AAEqmAiyK=AzbmnYji4nj3JSTA%}?o{wm1}jj_xD zZm5yp5YHUq&i)I`6ti#9D^3q_Z>{(eGuZ>o3qvAkVsQX4NZtwGdot>Xx>U-jqw2$w z2v&#@NdmD^KZ8KO`uH!bERvDcH+Zt9Ml!FkeuPSIX&`j8{^NMZ3U r)I;#aH_+Bdb?YUbW&S|bMSYA%?;@WMQ|xoZesZE2ihxDXM*jX;s%T(=Mv^fIK<9bq!WGubr_ zD*73cX-^o!GD{7^^A_xy!;mXS2FPm-%H48oN+y&oGAO!E7M4m3>@h>nJfI7naFzT@97;{s<+fmWkSVuQZr-O~ckDX@(eN?S{rCu9}**!B=W0y*!V1nVaq+KfzhI}OQ?dY_G$^8^=s?_ecG@QnnKH|_FhD>B2 z5j~*bQwHrg!=Qtv+ua9AW~2EWVUPm)97PqM;a*_46{?2&$jXZ^h?r?v`?j9B+=i>m zYlVta6OVX}6E{pwr5Q<^E{!UF+%bNu>63Jk*=SR(2-mXfMcJ9kE^S4_?!`lfvr*~{ zcc`9`NOj|_@3tr@Pb0F8E`eVKUu!7gOWDX}ngj1|TEX%=(syWxpi zahk5l=PdvFp)KDwVg`|M%c&AE7i@Z{d~CUgQOYwxGpq;kq$;}zNdAfeBpH&iyV)ff}u&bP0N&&f@}ovSpuOW;Z}fef;t&6^}I zHO6v>D5PS)p*MGg+XqkSDapUYfIL0I^m_Rj?&c5iKsu6{5_LlZLllw>SjnYEQ*#xW zn-m~UgaTK6z<}Y@!rxe2B=buzu(F*ihRp5U;`EoM= literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BehaviorSubjectTest$__spock_feature_0_1_closure3.class b/exercises/target/test-classes/main/BehaviorSubjectTest$__spock_feature_0_1_closure3.class new file mode 100644 index 0000000000000000000000000000000000000000..02e4f9ae4db2c5a76e509267608a600fc6179b4b GIT binary patch literal 2365 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 literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BehaviorSubjectTest$__spock_feature_0_2_closure5.class b/exercises/target/test-classes/main/BehaviorSubjectTest$__spock_feature_0_2_closure5.class new file mode 100644 index 0000000000000000000000000000000000000000..dade4506d2e74bd83d3e8054df540267c63e3c22 GIT binary patch literal 2286 zcmb7FZBrXn6n<`!vaoDR2nGA9RAb2tZGe_mO=%0UAQC_uL@DaJY;MBRu*>Y;FvCyq zhxi>^hoT)FKiL_7l;gR(OPV$tX8Pss-FwgLbDneVzyJO57l27TWtiSIEW0qrw~c+v zaVyQO9d3#>?uoRndwY)gRIhPEG+nMw=u^7ca6H;iD_{&mb=PtB4+;&#t`|zdje*`1-%TZbVryB@l1AI zgNlBJWZDyku*_1!@VrI4<}l>Skpc2rgL1bVo017-iwugclZE9H1AD^IbI-CZai3u< zQ{FN5jUf3|AGKoVNp_uKFkR=OWHcHTOYm9OH4Yf2GTF`OAVQQ(lJ`iyaH3e$Fog3e zVi?vih?I&1-eX9JZOdc0TkeL@sb!?}&go85aCgxrlx&j|o}F1R^y|`3kS2jpb!1o! z*CRm4cIwB8Y+$$%iF&t$61XuFyqASQT^vViL&X7q`el6o4Qt8HVLq#4p875By zp<=q$o|rojTz1cF)!3ubD41Y4D{0pXgdrb^d^XNQ0m+{+fFylY2`$yT zhwf=I)5_^@2655=`j0WPlFN_3z=dBWDNv(zlrBN<(?wjOuiRb66}pqVtF(*J{(XEv zt28xF(o#M}rk2N3qf@#3@3hgm~U}}z7$K#ViIG%#X6nJz!JzN6UZPN+Pp>b zQe!N4ghDFz3wm?Mm_B?)Pf7k22IT26W;V*taW{X2htiSEl&BjT7^0A5z)CJPnwqc3 z+@t_;A{4mh0|pGI7XQT35}99qiPeo%EcXJBPwdf~7K+~v?PZ=}?H>%%dW0u`lfu#~ rnfUihFR{Kss`_)R<$i`kZq@U};6_56 zVcd4=dBd)f(WaXZrJSbanY%o{$Sv;Z9wFc#G+7=}f$`%+TO(P2{MF#emq35n?nch8y zF}1X#@9RPGYd&g)&XY`qVK80iUQur}%BIKX97jK3m{K#F(LsbL86@wLeEvkSpkN5+ zWyCP7U=S%834Fki@U~5t;ZCU=MyHmM(mSWwO^-WE7NKMrobc?-f}vj%eu6X!gsLsV zVz?dwLZ(wcPGkeajY!nHB`o0@`4u=6n~uXRkKut@YNyG55Ur1CNXDnXBr1R2m5r3}*%HT7fX+B9U)LrzK48r!Xy3yWdi97U%kiLo*C& zWFQeeAmK9x^2T5k4`5a-80{Q|48K2{BV7L{kf_un_i~B^(w5WYs&s=T8 z)#0^#*{*qyc#RV`3{IsPNgEE0Dt^*2eyeGdbfIpxsg^y*wCV-XnbIC@y@u6`hYaVV z)EQ=|o{>m(O0dXz&|oaF)F^1v4(FI z%wb-XzRYm>4M97Al<*ymPpUQ{@)niDK2z{LevnbYhJra(-Xt1^Md}(c!EB;VMR!bntHIaB!Z1)WExy{^-QrGo zBA4x^WAFvjzkXnL4<-lvPWL|?JHj4N~}c2{W^qy2~Y zh*n{0fux0eicGDHr$(o;xj%6|jze}xr!n8+2z@D*mc=B-e2aBD6@eu%l}tcICbT(C z@)AhG_!dZKoBYNg!K7~jeNeDNJDIGC^s2!B%e8V_ literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BehaviorSubjectTest.class b/exercises/target/test-classes/main/BehaviorSubjectTest.class new file mode 100644 index 0000000000000000000000000000000000000000..f1890bcac2238830fa2ddafeddebd040a323295d GIT binary patch literal 9072 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 rLOlducm-{ZRJWevN$yWr0Rrj#N_JzA;L$Q*= literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReplaySubjectTest$__spock_feature_0_0_closure2.class b/exercises/target/test-classes/main/ReplaySubjectTest$__spock_feature_0_0_closure2.class new file mode 100644 index 0000000000000000000000000000000000000000..9b1e2018cf2a92ba5e901fa61b79c285cce767e2 GIT binary patch literal 2398 zcmb7FYg5}s6g_JjWP}Jt0UAnZp>=~FB<7K%X$UmLO(?~X22&bBQ&oty5fPFr$;|K* z`a}AiCLKbWX{Vo>nf|Cw@5+LhSTK|E%xb-RU+3O)@BaJWAAbRufX^_stDAOVh1V>7 zzg*wk;fBA;JwL5!Ud=I{X%(*fb(d@7^fN5Sqy3}=#&EIfI?mpH!P4z&q1d>Q5N8;3 z+-kvaw)nPQ_X?quTep34mlx)_&0XE+TPGTA43}EmUu&o_^hbv$A<3YoTS8=rXR>Pw zWOOp5(w?vTrctzX&s(r74nwXK862-zgxhp%0ustjGf0|77M6+(>XubDeLm;9`aZ*CCc6IgfAJCV}y<7oN0F70GW+$l?a0r z#vJa+xC`o6VfX}>g8LX37Y~T_X;J&8b{uNTtII2evQzO_c!d);3{Iu#Pa7^xKrVQH zqNY)G`ZF6%s%76b?dr5>dZ|WR-?Eb^5p8Q$-AiJbp*PA9!%eDeWX{g&hG2|Li>8X5 zB2FU$31tP}U{Oqq?`YV(ziA2Izcb<*m54+u=Fz&0$9SUPYb*#7{J=2ymZ~i&mGC3Y zXetk7>6ZUo$g8NipVoP!Y0KhBm!1UdP{mxSH7MAPCPG zE}!@4Q8A0uKVnciM4~m_HT6x4uZo4CyJXsYxxTx}-S9*%J9XFK56s~DzAYY4;t0`l z+aXBGf=w@BkX!Cym~bXQQxfr%EcyvZ{eo_!=qroVQoY;hp2k0|HvOBS!J43N=Mnms zbNR8Cxbz$SWpHR64yLY*0endRK+oYIF4LXZ4bd(}`;YK3t-{nJv<31*WNK+l9iGgM zsUwr>r}!NoF3t;_0kK><_|G190}0G;?TeVMJHnK%BjQZQaPuNs1M5m ztPmrTgkmE>27x|x6<_Y6nM0g$*KUk3;YlMPLHMl literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReplaySubjectTest$__spock_feature_0_1_closure3.class b/exercises/target/test-classes/main/ReplaySubjectTest$__spock_feature_0_1_closure3.class new file mode 100644 index 0000000000000000000000000000000000000000..21b888597ee9e8d4dfabab3431c07b70bfe9e586 GIT binary patch literal 2398 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 BYKi~= literal 0 HcmV?d00001 From aca8d031a426169d83e68407aaf05e0012a91524 Mon Sep 17 00:00:00 2001 From: Piotr Bobinski Date: Thu, 14 Sep 2017 12:50:26 +0200 Subject: [PATCH 5/7] More exercises --- .../test/java/main/BasicObservableTest.groovy | 53 ++++++++++++++++++ .../test/java/main/ErrorHandlingTest.groovy | 39 +++++++++++++ .../test/java/main/PublishSubjectTest.groovy | 22 ++++++++ .../test/java/main/UnsubscribingTest.groovy | 53 ++++++++++++++++++ ...bleTest$__spock_feature_0_0_closure1.class | Bin 0 -> 2408 bytes ...bleTest$__spock_feature_0_0_closure2.class | Bin 0 -> 2409 bytes ...bleTest$__spock_feature_0_0_closure3.class | Bin 0 -> 2369 bytes ...bleTest$__spock_feature_0_1_closure4.class | Bin 0 -> 2408 bytes ...bleTest$__spock_feature_0_1_closure5.class | Bin 0 -> 2409 bytes ...bleTest$__spock_feature_0_1_closure6.class | Bin 0 -> 2369 bytes ...bleTest$__spock_feature_0_2_closure7.class | Bin 0 -> 2406 bytes ...bleTest$__spock_feature_0_2_closure8.class | Bin 0 -> 2406 bytes ...leTest$__spock_feature_0_3_closure10.class | Bin 0 -> 2409 bytes ...leTest$__spock_feature_0_3_closure11.class | Bin 0 -> 2409 bytes ...bleTest$__spock_feature_0_3_closure9.class | Bin 0 -> 2431 bytes .../main/BasicObservableTest.class | Bin 0 -> 8850 bytes ...ingTest$__spock_feature_0_0_closure1.class | Bin 0 -> 2400 bytes ...ingTest$__spock_feature_0_0_closure2.class | Bin 0 -> 2401 bytes ...ingTest$__spock_feature_0_1_closure3.class | Bin 0 -> 2400 bytes .../test-classes/main/ErrorHandlingTest.class | Bin 0 -> 7006 bytes ...ectTest$__spock_feature_0_0_closure1.class | Bin 0 -> 2402 bytes .../main/PublishSubjectTest.class | Bin 0 -> 5598 bytes ...ingTest$__spock_feature_0_0_closure1.class | Bin 0 -> 2278 bytes ...ingTest$__spock_feature_0_0_closure2.class | Bin 0 -> 2278 bytes ...ingTest$__spock_feature_0_0_closure3.class | Bin 0 -> 2356 bytes ...ingTest$__spock_feature_0_1_closure4.class | Bin 0 -> 2398 bytes ...ingTest$__spock_feature_0_1_closure5.class | Bin 0 -> 2399 bytes ...ingTest$__spock_feature_0_2_closure6.class | Bin 0 -> 2357 bytes .../test-classes/main/UnsubscribingTest.class | Bin 0 -> 9066 bytes 29 files changed, 167 insertions(+) create mode 100644 exercises/src/test/java/main/BasicObservableTest.groovy create mode 100644 exercises/src/test/java/main/ErrorHandlingTest.groovy create mode 100644 exercises/src/test/java/main/PublishSubjectTest.groovy create mode 100644 exercises/src/test/java/main/UnsubscribingTest.groovy create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_0_closure1.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_0_closure2.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_0_closure3.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_1_closure4.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_1_closure5.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_1_closure6.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_2_closure7.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_2_closure8.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_3_closure10.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_3_closure11.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_3_closure9.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest.class create mode 100644 exercises/target/test-classes/main/ErrorHandlingTest$__spock_feature_0_0_closure1.class create mode 100644 exercises/target/test-classes/main/ErrorHandlingTest$__spock_feature_0_0_closure2.class create mode 100644 exercises/target/test-classes/main/ErrorHandlingTest$__spock_feature_0_1_closure3.class create mode 100644 exercises/target/test-classes/main/ErrorHandlingTest.class create mode 100644 exercises/target/test-classes/main/PublishSubjectTest$__spock_feature_0_0_closure1.class create mode 100644 exercises/target/test-classes/main/PublishSubjectTest.class create mode 100644 exercises/target/test-classes/main/UnsubscribingTest$__spock_feature_0_0_closure1.class create mode 100644 exercises/target/test-classes/main/UnsubscribingTest$__spock_feature_0_0_closure2.class create mode 100644 exercises/target/test-classes/main/UnsubscribingTest$__spock_feature_0_0_closure3.class create mode 100644 exercises/target/test-classes/main/UnsubscribingTest$__spock_feature_0_1_closure4.class create mode 100644 exercises/target/test-classes/main/UnsubscribingTest$__spock_feature_0_1_closure5.class create mode 100644 exercises/target/test-classes/main/UnsubscribingTest$__spock_feature_0_2_closure6.class create mode 100644 exercises/target/test-classes/main/UnsubscribingTest.class diff --git a/exercises/src/test/java/main/BasicObservableTest.groovy b/exercises/src/test/java/main/BasicObservableTest.groovy new file mode 100644 index 0000000..d038595 --- /dev/null +++ b/exercises/src/test/java/main/BasicObservableTest.groovy @@ -0,0 +1,53 @@ +package main + +import rx.Observable +import spock.lang.Specification + + +class BasicObservableTest extends Specification { + + def "Emit a series of values then terminate"() { + when: + Observable s = Observable.just("one", "two", "three") + s.subscribe( + { v -> println "onNext: " + v }, + { e -> println "onError: " + e }, + { println "Completed" } + ) + then: + s != null + } + + def "Observable.empty emits single onCompleted and nothing else"() { + when: + Observable s = Observable.empty() + s.subscribe( + { v -> println "onNext: " + v }, + { e -> println "onError: " + e }, + { println "Completed" } + ) + then: + s != null + } + + def "just() caches one value, which might not be preferred solution"() { + when: + Observable time = Observable.just(System.currentTimeMillis()) + time.subscribe { v -> println "Emit: " + v } + Thread.sleep(1000) + time.subscribe { v -> println "Emit: " + v } + then: + time != null + } + + def "using defer() might solve this problem"() { + when: + Observable defer = Observable.defer { Observable.just(System.currentTimeMillis()) } + defer.subscribe { v -> println "Emit: " + v } + Thread.sleep(1000) + defer.subscribe { v -> println "Emit: " + v } + + then: + defer != null + } +} diff --git a/exercises/src/test/java/main/ErrorHandlingTest.groovy b/exercises/src/test/java/main/ErrorHandlingTest.groovy new file mode 100644 index 0000000..8110d91 --- /dev/null +++ b/exercises/src/test/java/main/ErrorHandlingTest.groovy @@ -0,0 +1,39 @@ +package main + +import rx.exceptions.OnErrorNotImplementedException +import rx.subjects.ReplaySubject +import spock.lang.Specification + + +class ErrorHandlingTest extends Specification { + + def "onError action appeared in the event stream"() { + when: + ReplaySubject s = ReplaySubject.create() + s.subscribe( + { v -> println "onNext: " + v }, + { e -> println "onError: " + e } + ) + s.onNext(0) + s.onError(new Exception("Oops")) + + then: + s != null + } + + def "onError action appeared in the event stream but no onError action was provided to the subscribe"() { + when: + ReplaySubject s = ReplaySubject.create() + s.subscribe( + { v -> println "onNext: " + v }, +// { e -> println "onError: " + e } + ) + s.onNext(0) + s.onError(new Exception("Oops")) + + then: + s != null + thrown OnErrorNotImplementedException + } + +} \ No newline at end of file diff --git a/exercises/src/test/java/main/PublishSubjectTest.groovy b/exercises/src/test/java/main/PublishSubjectTest.groovy new file mode 100644 index 0000000..989b0f3 --- /dev/null +++ b/exercises/src/test/java/main/PublishSubjectTest.groovy @@ -0,0 +1,22 @@ +package main + +import rx.subjects.PublishSubject +import spock.lang.Specification + + +class PublishSubjectTest extends Specification { + + def "When value is pushed to PublishSubject it pushes it down to subscribers at the given moment"() { + when: + PublishSubject s = PublishSubject.create() + s.onNext(0) + s.subscribe{ v -> println "Event:" + v } + s.onNext(1) + s.onNext(2) + + then: + s != null + } + + +} \ No newline at end of file diff --git a/exercises/src/test/java/main/UnsubscribingTest.groovy b/exercises/src/test/java/main/UnsubscribingTest.groovy new file mode 100644 index 0000000..b9e513e --- /dev/null +++ b/exercises/src/test/java/main/UnsubscribingTest.groovy @@ -0,0 +1,53 @@ +package main + +import rx.Subscription +import rx.functions.Action0 +import rx.subjects.ReplaySubject +import rx.subjects.Subject +import rx.subscriptions.Subscriptions +import spock.lang.Specification + + +class UnsubscribingTest extends Specification { + + def "Unsubscribe will cause stop receiving values"() { + when: + Subject values = ReplaySubject.create() + def subscription = values.subscribe( + { v -> println v }, + { e -> println e }, + { println "Done" } + ) + values.onNext(0) + values.onNext(1) + subscription.unsubscribe() + values.onNext(2) + then: + values != null + } + + def "Unsubscribing one obs does not interfere with obs on the same observable"() { + when: + Subject s = ReplaySubject.create() + def subscription1 = s.subscribe { v -> println "First:" + v } + def subscription2 = s.subscribe { v -> println "Second:" + v } + + s.onNext(0) + subscription2.unsubscribe() + s.onNext(1) + + then: + s != null + !subscription1.isUnsubscribed() + subscription2.isUnsubscribed() + } + + def "Create takes an Action that will be performed upon unsubscription "() { + when: + Subscription s = Subscriptions.create { println "Clean" } + s.unsubscribe() + then: + s.isUnsubscribed() + } + +} \ No newline at end of file diff --git a/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_0_closure1.class b/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_0_closure1.class new file mode 100644 index 0000000000000000000000000000000000000000..d0b39e774867397e832509d0480cc29a5088745a GIT binary patch literal 2408 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 literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_1_closure4.class b/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_1_closure4.class new file mode 100644 index 0000000000000000000000000000000000000000..e4d554f698c78a51a542d01aa9c300df60688090 GIT binary patch literal 2408 zcmb7FZBr9h6n<_J+^{SXR-q!&LYqon&;a5~MW_&!QUX*%sYva1v$+Y2VK?2~Fy$xo zhx9vD2eD2&{iHMfQJtQ<2^h?V8Nb}Ud+#~Vd3(;e|Ni&q-vB1yFyIx$=+^TIn(@I?TY7W=NwF%9r*e>m-Brt|^WyiL6_c9gTDrd6w8wp{C zG21C;477Hq1E4u40S|ytyl?!x`mnz)wOxvPh8nV+2lBN;Da+ZNTVK{Tgv`lZ7 zVKkB3)_3)K@T-Mw(wuHRNv<>W#>?Ex>Xk~~^!S|P=z9#4iR5Om6OAkxB=3@Z=16f` z!8x3l5kjAWUZ^r6_=F+iZJ92^&0M=2omxf;@2qCmJnk%7lqJjHl+V^E7`ilJryeFH zp=b-Y7%m4SA=#=QCbB^+M4G5=t?*yH+rv?O&Tu8L5bbgxAx;sC)G*u%Fx_f7z!?RX zG1`SMk)W^yR<`Ci+@e;vo5(dYdckLR`_i^rQ;3E&F1St zCWG500Fd06!@P{Upq>@L$8jmRk8yGFfOsDlwQp+4p{Be#yp+k?CGQb0apH!-sWkm@ z!=XvY1@n*9G^$R2VzWs#?>VMbo)(QSRcY&0tSE9s+iIoeMzO+hJ_v~6230mNXYIPd zA0^YGtwJY=Q};kZUct9m5)3=IVZ-{GN%qsY+L%#4Pl81=m<3E473hh87C;~zg@ zO>B2k2+tTUo%Z-qF|*V^VpQ5hqIKOd{YO(Q3_Us1;w!bC0(aKxCvx7dIR<}V`q%d@ z@rV*fh?ZM6g`_N6^dkDX)NKLN4d(1RF#WszE{_ZhmUCxBLq{$^;j zM(Ephi2jvSdh8W0{6W7AHm$?{+?6qai}VM25(jaK?!<10b|Kn-iqB{jP!G}J%MTIM z@|Ze2nHp0^Ce<&}uaOMnHLirQ&-O_$t<8 literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_1_closure5.class b/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_1_closure5.class new file mode 100644 index 0000000000000000000000000000000000000000..736b36895f8540a9126880f1e2d77427f54707a1 GIT binary patch literal 2409 zcmb7FZBr9h6n<_J+^{SX7NG*tLYqon&;W{BBUDI{QUX*%sYva1S#H8&*iCmg%2Fz4x5wygldKcmMtM4}b}H3{!i$X=Ubg*ECkQ zT<+}aTV=k+T`#U_ZpAj9X+^GkRflWi+Jt75ZI|{_5*R~I$+7MIgG^bsN|{{kMnafj z%yvo{!`|jQdezO;rJSnenR`65z%B0R9^XFIXk!=%aNv4Pk>PSH7bQd))OeGd4Bw^ny?C*0pW6rXYp1fF2{ZP%e8!=?gynmbs_=Rup-nZKYgwqgY|M6a>U@iz*wKvsT^U z&yuX@tI!$Z)I5+-Q1Bg=#K`!8CeFu0OZf4FIajMhV6};EVpiHjqIKOd{Z~^g3|)EC;w#m?E$*z)NKLN4d(1jR%WszE{_XWDAH-J`){-$ZR zM(EpdjJ}mrdh8`G|3SYDHm$?{;FZyj0r~^IiG#R8cVah0yAbU^#b>k%sE26t<%bAr zc}yLiOpU1{lj;}gS4f8O3fICoWQQae^1=J)3j#a=AH;PZyh5kK#|Q>u5o9o4hrdDc zLSr;_gj;IpcSKXixOey*Gx_vebcxere6x{%ftmCX7K9-YG_kl2&`;h8-#b(4u)17G zsUzxYK?Ezrh$MBf5kG@Kulne3tSym|_1Ad3p@vc~v3Y_@&uLx!W*s&098ce%o7N*3 w|B}MeTd2q2ORu4Akm}A0JWc(HvX4MIzmnY$BslbqpdAWa+R20}g4_fC2NQ0SyZ`_I literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_1_closure6.class b/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_1_closure6.class new file mode 100644 index 0000000000000000000000000000000000000000..45af67caa953c450713ab6da7fb8c45e8b680713 GIT binary patch literal 2369 zcmb7FZBrXn6n<`!vSHbluoUd8QjI0qPy)22Do_itMI?YWh(^?P+1!MsVVBw6FvCyq zhxi>^hoT)FKiQf7sE+6EE*P5)GySr;x%Zssp7WgNocrqaAAbRugu`%m*Rbs3g5g=_ z+Lp)NePgT1*SY6sb=})@%qM!C8-B~>`h-5In@z`~^|S=WFxYS%XaAtsH0(yP6rM;( zGK@KHqi8xczHPL;Vr1pEY~R}D#YJv&*YJ7m)Z-k(m6!m=Lq~>7-9nU*W>B*oVKO9h z`3(g!`WP}<&o_L_EHw?!Te9nvvsR7`kk^~s^exAxWFp%c21(ZmVWq^tzGLW_wQS43 z&oG)R?-=_=nEcw-4tdUW?&LQZ2C@zAmyBk!V)=aDb&Ugtsa$?DK8T1Vlk7dRFP=Eg zC>X>=83_z27=S7xg?AWI{Xzl0Ac3_(k&<+|MV86M=y?Fw8B z)Y!cgopCAn5I6gf#VA9uP250lKtH$V&^@$o4h z$heRB5co7N1tpA&gU?9GS)q=$CcEBNahKPN6{qe$=5!P5hvs zj1>{E&M?$o_^3I>Y#x9iY_}NB8R1Nvc>9T11Mj6+>kQY8-{BI zublWW^p`E0ueNr#xVs+i$Q7sMn*5O!96z+ht49(b)onX9Qs9zJPgp=A_b@_vrf9nL zAeoUxy8xM=(T@!M%7Q~GEIpTEX4BWAe{)EZ2hewn;Z?0L_8gaf71hTMeMjgJ^m1Ls z75WvctGGsIVs)KX30l)UqOX8jBx_+mNKh+d>d2I)zW+ONNgT36+D-(J!}JTf{{(UX z`2g|>?e-8*imni#S_G<*z3@;7w5+isjD=`nfC^ci2AE0V7RDpL+mYX>c7!`>;uoZ~ zV|;S>6tm^ROZ1D~V=Pq5&oEm!!b1_DpoqjB0Sr%+woAV$^g@A6E16IQE!)7q>zRE~ literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_2_closure7.class b/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_2_closure7.class new file mode 100644 index 0000000000000000000000000000000000000000..47a12b38520361d72d9a84ba88fd36ff9b42131c GIT binary patch literal 2406 zcmb7F?NZxD6g_JjWP}Jt2{eSzLhA;9kQm4ZX$Umn5=t@9#FU26RF&nm5iyc0$;|Kw zeUSdANr#YT+Uc)mrVrKWUD*&5%gp4@YPEasxnJkr{rA5={sJ%wkKy*7Zd#cI-8GH% zg3FzKy-?vB-1Xv`=2mUvxmM!3S97>Fp-pK<#dc{wErBsyEIYQne~_u@RymWc-$)2E zjN48*W7tK$qu1O_L&~XHp1H>}OWfj)?(yQOMhC-{00(Z=6&WtKb5TN+L5;V#$q-H? zHxXDW9!TFm!9ePCZOY zLeUm(F$xsE(MF25El=L^>I=AriL7D%B#amnY>-{9`h0>ZWx?OGY~f% znuA>M{!~q)>I@{dn^g0jV_M}I(e_f6wqC`GB1g2XR%&h(YYdlyfEeyjWdn28t{eP8 zG9#KQbcQ%}4G^MFLl#w%}ZgiZ2pPuGR@*CZXQO^1=k1zf@Q0>7E_{S}ppUrNJ7Z zZ}%|<)>7&5SGfEe{W93Jj`&ko#vrcHALv0G!d1ExyJ6aeX#X)jp;bUVLWeIuOi-)i z>c~`TTpgWKKTE$xGK|-_9>yU%B*BmmK0sd(;0gE;ZusC;Iu$-f5RXNW!9)Z8Cdmtp zvD6XnsG(mGO&#O@;S0>=(r?i#PLJ``R_-O{(nnYlhD6ZB;s(GVc_)1DNvR|1YCffo zs_S_XtPmrTG{i>z3<;K`O6O1;AN2`as%4e?tI)Wi!s`v-lr9>MsV u6jt6sJqBNS18s{`cV6OI>UUIp1k(AL?1mu0p>G6TP~g%|CR7pR9`HX3fRcs) literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_2_closure8.class b/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_2_closure8.class new file mode 100644 index 0000000000000000000000000000000000000000..af29af5c70f255924811b142edc2f7de02cd47b7 GIT binary patch literal 2406 zcmb7F?NZxD6g_JjWP}Jt2{eSzLhFJ*NDSnQ#(@T0LMaBCn9>lMszR)dh>=`LW`;-T zgY-X5I)pUSPJbmcd8khB%7&O&W+s1DtKECg{W|yVyZ`?B2fze8hFg2OX=Ubg*ECkQ zT<+}aTV=k+T`#U_ZpAj9X+^GkRflWi+N5TbZI|{_5*Wk9l4INZ2br>Nl`^^7jf616 znC+A@hP};q^s1YwOF31`GxvCAfm__sJ-&Ua(Z+Bkz=7*EMTX0*T$B)HP~%N*GK3S! zbp>G3m37x$w2C%EDj(<|FP6FCnYKm2)Mc{_lBN;Da*lyLVQ9Z+TBbL{ zFq+8k>ic>v_|>gl(#$rWB-a^w;wA3o^m4gidVJP#^aF;;L~=9OiF%d{l6OfybE244 za1obegwU&?2daz+K4FM>JEqHUC*LYZ=a!MeyP(-sk2{MNWyvx)<+C{ohE7e`sf9^N z*tUgR3?l(aNH*(-iEI!Hk*0lq&-CsNMe#YqwZJm8%7BD8#VZoSFco09*=~R<3Pv#6 zi7%0$pafQ~>Nwn@M!28IH}ZMG=XUGDHd|AWLRvtN5mol7Jw)9LKKGWnr~8~@=uafi z*g3tTTEsqNl!OU}sGyG^Bteq$dl~v zCEQ`?5}ZDDgdrV}-Bw;J_zHJrOo1Hf!i5eZ=rltj(2;-!lkhczauQ*!J*OMZ*Mdw2 zH%kB@xiO158TUaQD}YbqQZSEkaq)my9~ZT6Xvo2aygIy?DcD8t5ifG$hQX;ceR0E~ zImiX?Pt`Q4PG4fPLABsHrd7&{wwEfj^~zQhd7^ElTy>*ZVYn0o#BiG`8(D-O~d=t3`j)G*~0_ z?L0=`N-90}5|{s=Uk01jA%E)1=*JcM13icXxJq|oH%Pk>?LWn5vhVcs5!Z>7yBpC9+`{)Y-JOLlTbsxM!r^3ewMq&|UFkXkhLGnUl zG_#89&^jma`(_?(Ik$-`i^br<>ArUmOxDL=y-U;8kQtFVpTu7S{p*E5wK-b+KVTgFvtP=x?kok&*S+c)X#8QZKQ2f=X{`UHoPpHSruz-=K%qBN+dZ u!qQu)$KXq^p>2@r&I>$E{fV-VKsvvY-4G-=^o^hc3S8RBgerpE1O5jeDUyo- literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_3_closure10.class b/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_3_closure10.class new file mode 100644 index 0000000000000000000000000000000000000000..76d2e1de214fbcecd0301d9900fb223410e4c9e1 GIT binary patch literal 2409 zcmb7FZBrXn6n<_J*sv@mYzvkm8Z?%?mGDw)Erk{$MI?Z25KFPH%W@NzhTXWkVTPaJ z5Ai#;4yAT<{A6eRQI6+s0u9ZEnSQx@_ug}!^Y)x`|NZZezW_|YW4N`in^taKcTHn$ z+vU!IzFpz#-1QQg=2mUvnO5SuS97>Fu1#r1#dc{wF)o2ITrNAdeQ=no=vFzGuU|VTtmvKS)RGibBo;Kj_&cDGmQ?0YXKgN)fE}8w)0U!ltE3j_{k7X zrZyDF=w^r|Tu=8*BVW;7cgZT*4Cz9kgS=GXhG*Iq1=En7W{@%p&W?~&$o>q%;Zp)XP9US6+Mil)cs97jK7m`tX&f}Ln&$sl=`=x%Qhb**yXJ9c$)Yq_2B(C!hQZLS2}AWb zDGNKc@QdMkKo(N1`eC9Q#6qm;T-Z0gJMkz!VYm@khjuBDkf4A?W+>nQ!>x7$Tv2cx zquuxnNeW6}Z;KU-Zeh!adz56~kaM zb|ki&lx5zN@&q=%&Iqk=yB1Tb31yK z>Mr36h8{ucGe;OQ0oiTmwSq5kN5*ZCBR#m(MGT!`NCr9*&|wnpF({`I*86j&*?c|7 zRB*Qh0FoPXn3r)M)UpEjEG`8L7#9~0i1i6k`=*W@Zpy2}OSz(5@*eRLCvF&=N;8l! z9GZk&@cv9qqv{MKx0+Ooo?}|&Y0>vmm9}2RilRWYtyXGo6srtZf`DicQ)L5l)~*}; zNir>(Ds+xGbq^#I6?}tbF)+TPdGr3z625@f5+8{`G<- zUQr?m@p9Lun3N@pUPM2++(n!Mjey1^!ZBGi6cGCby@=6Q7P+N@chWsQ0km55H$#&( zLf`Ha46LRz*_XKb8~rlaw8s6hD`ODX=nwQH4&g((6T4yBg=qf~KBiSbJw%5uKTJ?7 zSv5YH&Z;Am>Zh4kNQLnVH^Mk#MAJmKypM(ew%K9zDlwA@c^k;`9VxZ5Cc&Hgk+cVMqi`G;RP4l6S)Qp0pZQSBhzM zL|rS2V1*d5q#-uqXAtODAN`5-Wiqny8jm;CQ2HgdPEqMKZHV7&peCQ==|AYB^%%zA vq_F%3>IwMrYiOIKy88l8)4!wQBaqI|WH$r}4t*o&f&!OzGNFnf_ksTbPmYrn literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_3_closure11.class b/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_3_closure11.class new file mode 100644 index 0000000000000000000000000000000000000000..983577e900667e816faea86bdf281fa348028c12 GIT binary patch literal 2409 zcmb7FZBr9h6n<_J*sv@TR-q!Lg*KJEpb22D6+s~)r39#kQjyy2X1NKAVK?2~FvCyi z59xQR9mG2A^i!SjM|FDcCSWidX8dyZ?!D(c=j}P?zWwjdzX43ZW4O7en^tCCcTHoh z=yGRYFIMlSCL}P1i)F{Q_YX1^-705t^(zTs zhH={|XAFCr@8~r*(~xp%mS^tq%p$kAqkDY&RHK98N`ME~>xvAQ+xaLV%Am$u{A36x zk{b$SbTh=_uBUsZk*nygyJVGYhEzV#L0+nG!!vD*f@#Q3Gf0|72rD@T_JpDHj%k_R zEW=nLzpL--_2AcvyQDeYdXn5==!=)Rm(weig6Z)&$I%ZMCKJi6U?&<`GDzMf`OJyp zw1SJcBqM}=1$|IuMDP(q#M?1lhFke|IXb_N6yF8Su6f*9vM5cK!6~7wVK8)S!caX< z%EGoS{9?EokcDKcewgS6u@GxI7xql=_DB?;FkMeFBvAzOK8Dx%&Iqk;BnELbK82A z>Mr3ch8{ucQ%4xm0oiTmwSup4TgEMrBR#m#MGT!`NCY|(&|wnpGAJhz*86j&*?c|7 zWN^0x0FoPXn3r)6)UpEjG%f`T$cl>x#QM0XeN#scH|5pgrA)ytd5?IB6E_S_r5T7D z4oyNXcz>#Auh#a8+*z-m$OXIR82o|hUoTkV z6(y1oFL!K;Nm;V!Mf8))U5rqm5zv@KI3|mR0%E_S7cu(EBDYlVPP(TjfL4qCW@xfT z=-Yjafz?!c{3R~`PQMH`tt0-}l`)7b^apwphww4oiQO>mLbU$`pVBIz9-_mSA10`k zadl)eHLi|Ms-LG{AsNOiTnpon9g<+k2Opp>2=D}a2-kh^DxC@+Bgn=g$ROK*pCWmo zF_t>QO*QlzqN!utIed=UeEJP~#pyBbZ{}ZMHhqLeVMqi`G;RP4l6S)Qo|HPGt`t)0 zsJd1V!3r^ANkeSZ&mhpRKKcvm%VcEZH6CxOq0~!kouJZd+7Q3dKutWy(|^%N>k*89 vNMZR6)MN1F*U&aeb>{`1rv5<1MB5@-maA&nbs0Wlx>tV3FGlaykhi773mts=|UM#M-|l9}=o z`a}9olMW%xw9}_%ra!9Fy^=9zESSlQban6U(eBx^d;k6KkG}v+VVB|dwqe=XdBd~J zm6FHZU87XvtK9Qby6){b<_o>T4ZrSkeNvy%&6?xU`o09laG~lt&hB2eX4utiK0J{S zXBcnW89FjtIxR#AoeXNKElh@Z zIuL2_&D zlI9UH-;#Dntb|((okA=^egt{qpTQ=n;12G}xQ%-ZQ)e+$G+k@QpWpMjXvDd)u|rjs z@ClVcD7YC2!&n4Kr$w#c0cK_7@K8Yy&UaDnpE0B(1BqxS3G)ofQHEhN&bIzOOfnPQ z8WDiR$LDw?Bag=+a5FCjUtmTYERe=iLKrRWH{2oumsheyr{X{374C9s0V>Tv%5-V! z@uTJox>lc}gY-s=YteTtyP6YCp4h5;ekWF_-Oaj7IQ}XPyCthuv%F3`B>_c2GTfmW zN3wUC(1TENqH|&=BoxLXfyUkvmW3$4r7`pV&=P3MofvLmO(OFaljwUH8~8!NA__td zI>XR$Kev%9!Jz3(3Q%AoE2D(6f+AK$z!ir6RxvcDs+i<`*c7%F!#N|Ipp)-EKVP8M zMX_!(TsiA*qhjW%WyBgF4-XOpu@FCm`z+KwyAjkwJ0{CnLUL$+qp%8pQ zV+WWD(ZWy_vTBB!Okf%_4ZoR&->7zg`)cf0bZUqAbpIvh3S;lkD|Qd@WWDeTb7Kei zQUoZ-BAGV;1}Q56-lM4_>Q_Zgy{*Tui3eUCQVFw80;M1& literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BasicObservableTest.class b/exercises/target/test-classes/main/BasicObservableTest.class new file mode 100644 index 0000000000000000000000000000000000000000..2949a4e3586e492d4f5ebfc787e20ac709e2befc GIT binary patch literal 8850 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 KSmfu*C-Z-YF0#=8 literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ErrorHandlingTest$__spock_feature_0_0_closure1.class b/exercises/target/test-classes/main/ErrorHandlingTest$__spock_feature_0_0_closure1.class new file mode 100644 index 0000000000000000000000000000000000000000..cd9843b8b3830394162a89311ba5ed37a528d029 GIT binary patch literal 2400 zcmb7FZBr9h6n<_J+^{SVR-sa)g*KJEpy5TWHA2M{krJ>PN=0h7%jPC5F1zXOhABUx zKcwHOb`a~d(@%A#KdRGnHvxlLm~k?5_vYSnp7ZvcbN~JCkG}v+!e^M?(M>xy@4AkA zU$@JaX;)Xd=O;DIt2xFqt-^J`?s9E{eum|Ew4ao~7|vH+$JyP>S-M@#6&g1Zq737X zTg@3xnQ!TJFV~cE>$Y$1@Z18oxvTrUe5}#IaIww&)rK0wK)8Dn;tXoC%|(W2D!rya zMi)aO>G`^E8U;)DyhXd>Fl36M&hd)H4c~NZ3ZyBUXOJ|F5S9xJ>-f44qj(3a&?U>OOZDZOV~taLQ(T3=CbGu+j*UQc!k; zQw*0wQjl)fj}pls7NSesu~+!d{;iQXK4G{LT87gyAR$Tdio`J73^CnqIm8(Smoe6b z&yb?91XiK$y4-jzm`b0p zb9_bBNW9A^36qpFK_5X#f+Xd)GQlD7Vj9nf_Y4ci+jZTq^NyMLk_p()#a63$*K5{c!d);3{IsPNE$B9 zKrWbntfoswLm0(U})*FV$%4TXq~pqHWEpdvUBVTnGbVxIvW-&Dm+)5Db#M zXsgHx;xs&vP*U(Umc+dHmd4HdLreJXogvq#L};{PBCX4Kj3)~2V^NUcdxoL6RBcPC zgdb>1Q+X&OCrI7wI0ZjKm!V-(!8dp);wUrpw=$zCDF%HXDnho((4jX5?8N(zR}k!VeKO?}hit72j3Et)o8sqbuZw|OF$oVsi9duDJwZ;R)X zI6}1CawsHa(WbXB$SwCULUG1GGZxW=EcyvZ{DNL2=qroVQoYa7Jv{)l+VpRRMr(|| zU56N0$z;c0;^J@gm%*WRB$&H02Js>N13idCxI}kiH%z+-?LWfDvT)=^?&aFTTKR_5cgQkO-Ps+yodT?}YE&8FfTmE@jkF z^+8DlE5wK-O|j7+gFwIf=ufOJk&(66c)YGgGB2@lgi3E|Q~X*JHT4`%|3M$E2QdC7 ug{3!855bpSLt7`+trvKj`5jh(KsrB>-3TPO^o^kt3Ow4$gerpE0saR>SCCx* literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ErrorHandlingTest$__spock_feature_0_0_closure2.class b/exercises/target/test-classes/main/ErrorHandlingTest$__spock_feature_0_0_closure2.class new file mode 100644 index 0000000000000000000000000000000000000000..618bc6beac1e015ab23e50894c6209618b753aa2 GIT binary patch literal 2401 zcmb7FZBr9h6n<_J+^{SX7NJt4g*KJEpy5TW6;Uxoqy(&nQjyy2vbhP1%Wk^6afYAJ zAJXqsJBW4K>8Co=AJyr(n+1bem~k?5_vYSnp7ZvcbN~JCkG}v+!e^M?(M`KB@4AkA zU$-lkY1dY{=chHzt2@Rst;%)3;c{()eum|Ew4ai|82W0i<2>IjSh`&+6q`2^;tb=C zTPqk&g>UH%uMkSP4cj+&cwvFt+|_+vInwB0INxUea#M|AAlf|%Nd`6D<|0EplU-9F zql+Px_I%wpjiRM{-lAP~7;>dZ=XllPhHpAH1ro~6Fi4t42+KtV_JrZgP1830TMT2F z(zgCwZwCHgbDK10+E21;487?Z_lvq^l}(?|xvsv;FqO$}L^}~?$RK%-VF)(y#!b&qpNvd8Qk zT~Rqw?^8;`Btug0M=+8gN%@^ja7e_M#uXV;xXLhjT0+Z)Yu5d{yFM3vIakr^RC)thD@X*5gjJsHiL2)VY5GHTg^9v%tm)h z1R%LFhr2TFfLc}nAH}6$9uwl?9uYq+YTwe4!!3Drd9_e>s{SKh<-`qxQ)vd$hD$?` z3+f-KX;hto%tni9*>`Dn&WOI3>a_JOJBbp}wr(}NBvu&CMFBBfqsm6+?4)i8CdrKG ztJpE(G(C_|R`3m$#K8ED=FNvgOZfi1Dc7t-WVB)=t;=|fCkpOkQIOyVhM{*Zf}fzv(6FiCTRaqTR2ceOnGs5gN#BdAkgYLv=*T&ID-4BA${(KLM#<(1R3xWszE{_ZhmU7l2lq{>{>CP0+XN z00S$z{P-)J|Be1KIJAxggIC5NKBj-57jXy|=uYg0X&0mYC-{_B0re0af&4H*Esv`s zQ@L?uN0b3LA&0^q7X?S3=ax3q1V?y|nJb_?r}# s-aPN=0h7%jPC5F1zXOhABUx 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 literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/UnsubscribingTest$__spock_feature_0_0_closure1.class b/exercises/target/test-classes/main/UnsubscribingTest$__spock_feature_0_0_closure1.class new file mode 100644 index 0000000000000000000000000000000000000000..00eabb40963c70453f0cd6ab3c6b07be0d72e3a2 GIT binary patch literal 2278 zcmb7FZBr9h6n<_JSy)y{f>5p6Vw*~`LBoqLZ6hkCLMZ`SgK3er+s)=CEQVcXcf$-n zp+BVGsdkXospF?Q(;wC8xw}h@W?{z3%-x%N&+BuZbMC+Y{q+*S6rM25Z0n{~cx1Wl znrk>_&9s_p-1Rb==I+?W6Rp8@ukCPcl72?Zc49oydBE3|a0SttcJ65``w|QZKTinq-Uhim}VmR01{#u~MFd7}6gnou}rYA&(cs5s6 zAY+grnQ=YcGmTP9cily+VKb=Z$l!RR#SPE2ElMPmEiy=&Mi!P!4D2yO-)++}y*mu! z+47dYs|Sg%*0xBq*n5(zGNdw1?v?abt73Y5&T;fThUsi>BRYr>A%o;yk}n)778MNR ztc(~&6r_-rk-!HG32)PM8E%xL5jwGel-wE3ZhPEWvhT)Gl2lV5>CsqHx2@)+)A%iUyK@QHUk^1Xp5_!OBze1gxY zmb=7VQJ9KPj%DuYe$g4uXLE1)>0A+9$#+XGL1pL{^%aaElcfAc#y_OOP2j4GJgzZJ zodiOKI&a6jzvpq$H*(A(QJ+|NCAC`qKq$aJ22b{RlyzP#Km1AW=7P$t79&8 z;p*^4p<*|@hrGdw8wRJ+jAjgnh7>>Q7r)W8DZ0pRbg5Q6hh|$*^rf^zTd!sH;{n6j zD0POLRL@AHj^nNGwkRl1!?BAlfnN#VC@A48(Z*$(0`G5H!pb|dC%`{4M=>bAm9dKN z6wKkiD1C+D!W)A204d=S4Ns~zA@UZL!#-2+J${f;#kzt8d@b_WU>NDA~EaeE+HU3-kd>p~i6Wq}PZ>VTrm%OfZ|MQ`H?)ueJD^SQv)Nro~s<+coZl zCvwGZI|iRO{p$yoc-4p*M8-|KPQ+Za=!x>N zBri0^)dLjLvER_I9^%ISQ`{=&Ut>s|9%6RA{0z792e>C3iA;&Qp@CrvNdzpZ>9O>D zMdT&~h!df}WgjqLB)#}ImX^r;@(Zl4r(^1KJUp^TFIp&mJ+zm7inV``qV)ieUy;Jn rYmxYmOD|AeCspkk*3>^>_!dZKll;aY!K7~j15mIAwX}B literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/UnsubscribingTest$__spock_feature_0_0_closure2.class b/exercises/target/test-classes/main/UnsubscribingTest$__spock_feature_0_0_closure2.class new file mode 100644 index 0000000000000000000000000000000000000000..7dfd76cc02711914cd374fb64d164e335bacde11 GIT binary patch literal 2278 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 literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/UnsubscribingTest$__spock_feature_0_1_closure5.class b/exercises/target/test-classes/main/UnsubscribingTest$__spock_feature_0_1_closure5.class new file mode 100644 index 0000000000000000000000000000000000000000..a2d962199f8e5f86fd2e7fb568aa330c3f5239c5 GIT binary patch literal 2399 zcmb7FZBr9h6n<_J+^{SXR-sa)g*KJEpy5TV6+tm7r39=7sYva1v$+Y2VK?2~FvCyi z59xQR9mG2A^i!SjM|FDcX2D<03O)K}nlJYy% zooddo%Y0LBxVb>eX;_}Q&2tOf;*Rd|@{vXd!-Y2USDI=J{o(FOh%>0kHWwM9sq~rx z8C?vCr0eOPY2<6V>n>Uqn;}yOb&gkR-0)1>qCf)K83swy2w^$Tz@9RkxoKLaH^(rV zDs1UHdNc3`r7hB&X+KG?G4v*@+{^2=TG90QyyNJ*43nw!Mz|9}h76K-Nj`U|IHTYk z&dZ3PPeCtK88Ljq5c4)omtndPo}p6G|8Jx1&9s@&{Cag4rq!g5G z;S|HAkQAib^`k^Gh=u4P6}e$sfQ%CK%#^J%Wz}Ny_hK{6nI}6t2pc#5BXiX$dV-U)Q}myB<}b%;(E`oeD4E zYldz?=_5xNvLV?$$!i7Q;D(IrAV<1!wv+fd%a96nB&5G2++t7;BW!l(Y^(WZkm>MV z2>~QG=5a^HZBWAs;G?({+{3uIxKFfCirTmI<4{Xp9bU;5?TYt^S2%IQ;8dFaq~XvA zk z=&Hyu;xs*wP*m_8mc+PtOw;D$p(XtA!HjEGA~aetkk(~9!BYj_Vo{LbM~1=oRBcPC zgr8_gQ+X&O$4DJ?oPwXB%g|6#@I4-iILZutt;`4{#hmX&MaWheI`rmz9sl_0YGSLC zLU_(_@w8WuikYYW5tGs;60PZush4VeRV)lW1=HdyjqMV5f)lxDHynfCH~s5-mUumh zBSgzhn?h0+EqV(5+;SJg6lV-HVi8TqqMv}oujoO7zOqOy)%y(H)BLB^qQ6<1tTFm_ z9iV?DlO2173%}DZgH7wOKXzpd;8Xeoy@!LiNOxj4M7s#>Kf~v=3aE$Z@a2aHYI#f@ zp3ID?Ba`Zv+1E%%@fw$-*kgMn81cdT=?elp1Rul|AG}Vd!p9g!5;5d39>7nLywDiU z?Bkjm`3>>R0dDTSz+55w7Cqwh0C(35FEN+h$AT~j5?w| zD2iZ(7?C6p8}Ty;^r?^j!s-$kS$l&g>uMzP3LA&0^ppnTR|C}43q1Q5y|nJb_=gmh s-ax1p)ZCc#7!v0kTxc?E^Sp=J{u7uF_N4a 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 literal 0 HcmV?d00001 From 9159644b4d633445aa37db7082fd4780fc541851 Mon Sep 17 00:00:00 2001 From: Piotr Bobinski Date: Thu, 14 Sep 2017 15:42:26 +0200 Subject: [PATCH 6/7] work work --- .../test/java/main/BasicObservableTest.groovy | 42 +++++++++ .../java/main/ReducingSequenceTest.groovy | 82 ++++++++++++++++++ .../main/TransitionToObservableTest.groovy | 75 ++++++++++++++++ ...bleTest$__spock_feature_0_0_closure1.class | Bin 2408 -> 2408 bytes ...bleTest$__spock_feature_0_0_closure2.class | Bin 2409 -> 2409 bytes ...bleTest$__spock_feature_0_0_closure3.class | Bin 2369 -> 2369 bytes ...bleTest$__spock_feature_0_1_closure4.class | Bin 2408 -> 2408 bytes ...bleTest$__spock_feature_0_1_closure5.class | Bin 2409 -> 2409 bytes ...bleTest$__spock_feature_0_1_closure6.class | Bin 2369 -> 2369 bytes ...bleTest$__spock_feature_0_2_closure7.class | Bin 2406 -> 2406 bytes ...bleTest$__spock_feature_0_2_closure8.class | Bin 2406 -> 2406 bytes ...leTest$__spock_feature_0_3_closure10.class | Bin 2409 -> 2409 bytes ...leTest$__spock_feature_0_3_closure11.class | Bin 2409 -> 2409 bytes ...bleTest$__spock_feature_0_3_closure9.class | Bin 2431 -> 2431 bytes ...leTest$__spock_feature_0_4_closure12.class | Bin 0 -> 2338 bytes ...leTest$__spock_feature_0_4_closure13.class | Bin 0 -> 2411 bytes ...leTest$__spock_feature_0_4_closure14.class | Bin 0 -> 2412 bytes ...leTest$__spock_feature_0_4_closure15.class | Bin 0 -> 2372 bytes ...leTest$__spock_feature_0_5_closure16.class | Bin 0 -> 2289 bytes ...leTest$__spock_feature_0_6_closure17.class | Bin 0 -> 2402 bytes ...leTest$__spock_feature_0_6_closure18.class | Bin 0 -> 2399 bytes ...leTest$__spock_feature_0_6_closure19.class | Bin 0 -> 2437 bytes .../main/BasicObservableTest.class | Bin 8850 -> 12037 bytes ...nceTest$__spock_feature_0_0_closure1.class | Bin 0 -> 2548 bytes ...nceTest$__spock_feature_0_0_closure2.class | Bin 0 -> 2412 bytes ...nceTest$__spock_feature_0_0_closure3.class | Bin 0 -> 2411 bytes ...nceTest$__spock_feature_0_0_closure4.class | Bin 0 -> 2376 bytes ...nceTest$__spock_feature_0_1_closure5.class | Bin 0 -> 2498 bytes ...nceTest$__spock_feature_0_1_closure6.class | Bin 0 -> 2349 bytes ...nceTest$__spock_feature_0_1_closure7.class | Bin 0 -> 2412 bytes ...nceTest$__spock_feature_0_1_closure8.class | Bin 0 -> 2411 bytes ...nceTest$__spock_feature_0_1_closure9.class | Bin 0 -> 2376 bytes ...ceTest$__spock_feature_0_2_closure10.class | Bin 0 -> 2501 bytes ...ceTest$__spock_feature_0_2_closure11.class | Bin 0 -> 2352 bytes ...ceTest$__spock_feature_0_2_closure12.class | Bin 0 -> 2415 bytes ...ceTest$__spock_feature_0_2_closure13.class | Bin 0 -> 2414 bytes ...ceTest$__spock_feature_0_2_closure14.class | Bin 0 -> 2379 bytes .../main/ReducingSequenceTest.class | Bin 0 -> 8517 bytes ...bleTest$__spock_feature_0_0_closure1.class | Bin 0 -> 2470 bytes ...bleTest$__spock_feature_0_0_closure2.class | Bin 0 -> 2427 bytes ...bleTest$__spock_feature_0_0_closure3.class | Bin 0 -> 2424 bytes ...bleTest$__spock_feature_0_0_closure4.class | Bin 0 -> 2462 bytes ...bleTest$__spock_feature_0_1_closure5.class | Bin 0 -> 2470 bytes ...bleTest$__spock_feature_0_1_closure6.class | Bin 0 -> 2427 bytes ...bleTest$__spock_feature_0_1_closure7.class | Bin 0 -> 2424 bytes ...bleTest$__spock_feature_0_1_closure8.class | Bin 0 -> 2462 bytes ...leTest$__spock_feature_0_2_closure10.class | Bin 0 -> 2427 bytes ...leTest$__spock_feature_0_2_closure11.class | Bin 0 -> 2465 bytes ...bleTest$__spock_feature_0_2_closure9.class | Bin 0 -> 2427 bytes .../main/TransitionToObservableTest.class | Bin 0 -> 8810 bytes 50 files changed, 199 insertions(+) create mode 100644 exercises/src/test/java/main/ReducingSequenceTest.groovy create mode 100644 exercises/src/test/java/main/TransitionToObservableTest.groovy create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_4_closure12.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_4_closure13.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_4_closure14.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_4_closure15.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_5_closure16.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_6_closure17.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_6_closure18.class create mode 100644 exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_6_closure19.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure1.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure2.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure3.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure4.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_1_closure5.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_1_closure6.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_1_closure7.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_1_closure8.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_1_closure9.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure10.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure11.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure12.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure13.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure14.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest.class create mode 100644 exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_0_closure1.class create mode 100644 exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_0_closure2.class create mode 100644 exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_0_closure3.class create mode 100644 exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_0_closure4.class create mode 100644 exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_1_closure5.class create mode 100644 exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_1_closure6.class create mode 100644 exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_1_closure7.class create mode 100644 exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_1_closure8.class create mode 100644 exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_2_closure10.class create mode 100644 exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_2_closure11.class create mode 100644 exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_2_closure9.class create mode 100644 exercises/target/test-classes/main/TransitionToObservableTest.class diff --git a/exercises/src/test/java/main/BasicObservableTest.groovy b/exercises/src/test/java/main/BasicObservableTest.groovy index d038595..8fdb0f1 100644 --- a/exercises/src/test/java/main/BasicObservableTest.groovy +++ b/exercises/src/test/java/main/BasicObservableTest.groovy @@ -1,8 +1,11 @@ package main import rx.Observable +import rx.Subscription import spock.lang.Specification +import java.util.concurrent.TimeUnit + class BasicObservableTest extends Specification { @@ -50,4 +53,43 @@ class BasicObservableTest extends Specification { then: defer != null } + + def "Supply a Subscriber to create"() { + when: + Observable values = Observable.create { o -> + o.onNext("Hello"); + o.onCompleted(); + } + values.subscribe( + { v -> println "onNext: " + v }, + { e -> println "onError: " + e }, + { println "Completed" } + ) + then: + values != null + } + + def "Emit specified range of integers"() { + when: + Observable numbers = Observable.range(1, 15) + numbers.subscribe { v -> println v } + + then: + numbers != null + } + + def "timer() usage example"() { + when: + Observable values = Observable.timer(2, 1, TimeUnit.SECONDS); + Subscription subscription = values.subscribe( + { v -> System.out.println("Received: " + v) }, + { e -> System.out.println("Error: " + e) }, + { System.out.println("Completed") } + ) + System.in.read(); + + then: + values != null + } + } diff --git a/exercises/src/test/java/main/ReducingSequenceTest.groovy b/exercises/src/test/java/main/ReducingSequenceTest.groovy new file mode 100644 index 0000000..2a0680c --- /dev/null +++ b/exercises/src/test/java/main/ReducingSequenceTest.groovy @@ -0,0 +1,82 @@ +package main + +import rx.Observable +import rx.Subscription +import spock.lang.Specification + + +class ReducingSequenceTest extends Specification { + + def "test of the distinct() transformation"() { + when: + Observable obs = Observable.create { o -> + o.onNext(1) + o.onNext(2) + o.onNext(2) + o.onNext(3) + o.onNext(3) + o.onNext(1) + o.onCompleted() + } + + Subscription subscribe = obs + .distinct() + .subscribe( + { v -> println "Emited: " + v }, + { e -> println "Error: " + e }, + { println "onCompleted " } + ) + + then: + obs != null + } + + def "you can supply the distinct key inside the distinct method"() { + when: + Observable obs = Observable.create { o -> + o.onNext("a") + o.onNext("a") + o.onNext("b") + o.onNext("b") + o.onNext("bb") + o.onNext("aa") + o.onCompleted() + } + + Subscription subscribe = obs + .distinct { event -> event.charAt(0) } + .subscribe( + { v -> println "Emited: " + v }, + { e -> println "Error: " + e }, + { println "onCompleted " } + ) + + then: + obs != null + } + + def "you can also use the distinctUntilChanged that filters out only consecutive same events"() { + when: + Observable obs = Observable.create { o -> + o.onNext("a") + o.onNext("b") + o.onNext("b") + o.onNext("aa") + o.onNext("bb") + o.onNext("bb") + o.onCompleted() + } + + Subscription subscribe = obs + .distinctUntilChanged { event -> event.charAt(0) } + .subscribe( + { v -> println "Emited: " + v }, + { e -> println "Error: " + e }, + { println "onCompleted " } + ) + + then: + //expecting a, b, aa, bb + obs != null + } +} \ No newline at end of file diff --git a/exercises/src/test/java/main/TransitionToObservableTest.groovy b/exercises/src/test/java/main/TransitionToObservableTest.groovy new file mode 100644 index 0000000..82e7a91 --- /dev/null +++ b/exercises/src/test/java/main/TransitionToObservableTest.groovy @@ -0,0 +1,75 @@ +package main + +import rx.Observable +import rx.Subscription +import spock.lang.Specification + +import java.util.concurrent.FutureTask +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException + + +class TransitionToObservableTest extends Specification { + +// Observable events = Observable.create(o -> { +// button2.setOnAction(new EventHandler() { +// @Override public void handle(ActionEvent e) { +// o.onNext(e) +// } +// }); +// }) + + def "turn future into observable"() { + when: + FutureTask f = new FutureTask({ + Thread.sleep(2000); + return 21; + }); + new Thread(f).start(); + + Observable values = Observable.from(f); + + Subscription subscription = values.subscribe( + { v -> System.out.println("Received: " + v) }, + { e -> System.out.println("Error: " + e) }, + { System.out.println("Completed") } + ); + + then: + f != null + } + + def "turn future into observable, but await only certain time before an event gets emitted"() { + when: + FutureTask future = new FutureTask({ + Thread.sleep(3000) + return 30 + }) + + Observable obs = Observable.from(future, 2000, TimeUnit.MILLISECONDS) + + Subscription subscription = obs.subscribe( + { v -> System.out.println("Received: " + v) }, + { e -> System.out.println("Error: " + e) }, + { System.out.println("Completed") } + ); + + then: + thrown TimeoutException + } + + def "You can turn any collection into observables as well, elements of the collection will get emitted and then the onComplete event"() { + when: + Integer[] array = [1, 2, 3, 4] + Observable obs = Observable.from(array) + + Subscription subscription = obs.subscribe( + { v -> System.out.println("Received: " + v) }, + { e -> System.out.println("Error: " + e) }, + { System.out.println("Completed") } + ); + then:// TODO implement then + obs != null + } + +} \ No newline at end of file diff --git a/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_0_closure1.class b/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_0_closure1.class index d0b39e774867397e832509d0480cc29a5088745a..38b51668648d22d5ad1fbb97385c6ead9ea99c76 100644 GIT binary patch delta 13 UcmaDM^g?Ju76+riobWmu6AP1w+W+4uJMgSqg1AqVk delta 14 VcmX>obWmu6AO|D=W+4uJMgSqO1APDh diff --git a/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_1_closure4.class b/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_1_closure4.class index e4d554f698c78a51a542d01aa9c300df60688090..572ce8c027b8de7a649b1c0bd4334fd6cf574ce7 100644 GIT binary patch delta 13 UcmaDM^g?Ju76+ruobWmu6AP1w|W+4uJMgSrT1B?Iw delta 14 VcmX>obWmu6AP1xLW+4uJMgSrB1Bn0t diff --git a/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_2_closure7.class b/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_2_closure7.class index 47a12b38520361d72d9a84ba88fd36ff9b42131c..6fc529f9141ce8e08b860dc1f0e251ce559d901e 100644 GIT binary patch delta 13 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 literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_4_closure15.class b/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_4_closure15.class new file mode 100644 index 0000000000000000000000000000000000000000..a260844ffbd21b0a20c9c6b9d56af0f3133be25e GIT binary patch literal 2372 zcmb7FZBrXn6n<`!vaoDR5(@T3lxi%=h7v+)tpaT!wul7K2GNMRZZ~ALV%N?t-z|Fw-xan|sfB?m5qS&bfd8^V=T)im(~(?&+pgSkhh7 z*r>VOInZk@zR6uLqiOEGZ9LT)T=&`z*QT{u&1l&!t&4XgFoxl#W7`LZg_dqL3#H&l zLY!gJcA5pluJawe?G{2Sr)_!W9xp6&i#xi<>t`Mp7_LP`Fcmm5TXBwrJ?z$^hgOXOukpc2XiyNM4Ta-*_JI5et8X>He7}z%qee(ID;CjY8Jr01PJ>}k6Bh+} z5({-(#Kmwkf`weSf1Kn7hJlj3x8LGa`+nSDxD{E2UIa*ZpTZEll-iENEsx=0w%jSf zWnYfHYtbE-f)8+W5E)D`#7siGoQ;TH!8>?YFrO#Qi(*!ML9KC5_l3f6mE1ZbIXfbL zldsD}LV=-QXhNurApdu#ze&X|Vn)U^ZZj0mqp4yz=DxRd=yB0+i*MAt}IU{Fpo40>*%^ZFpkTyzIS01_V$ z@KDBmEC#@5c_}DiN*sJbO3ny%badHRN5vi9C{*l*_k=e%smI`y&}hbRXu9#!9`l=1 zE7C!ByTi5OIi}T|6TK|$)6#2M{rHT;66H=KmFgMER4--w*UpI^p`qF#m*8Fr8wx(f ziV)xzG&$ZJT0-Tuc@^*fHj7Z&ccVyDTZ?jy0EP=T+oA|IQ`}$u?{{!u{IdS&V8?FP?o4a#Bj4oBwM;; z`VXD>FbtJVi?6r$YTVfjcI1lPb`1X5^p77|;^89=knDEsI!SQFqDRbUk-HeDL=!aL z`VddbqF;dIj~GIdeq})-RhAx1F}3My(!T}7$paWX!RWf0pL~HUKa28Xm%ih42ztA& z;u`&m)pfj0XJR!*s~D|mAkkMqEt9pdA10`^$@KV)nttzBWaBtuN3IptRdWQLO{uPGA?g^Hv<>#2sALEe-P*6nb4grQK7XdD*>GAYxMUWy4NKK)^4WDUX zB)$GSK3^sHCoi#CO~=$1`05mo9?H;sHpI(5L-jAD=zEOs|0IXiS4f}Wht-$Zu9Beir}$ literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_5_closure16.class b/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_5_closure16.class new file mode 100644 index 0000000000000000000000000000000000000000..d816d96405e41840bb3ce52aa2a56182ea4383bc GIT binary patch literal 2289 zcmb7FZBr9h6n<_JSy)yHL8vdK#Wt0^paE30ji`_cr3ug)nigrh-E3~cV%TMNH_Y%8 z`a}AiY6oeZI)17%{ZXBsySv0_Hq7|t?%jLO>vNuS?!W*2^%B4go-oYqYKECx)Eq-! zuR7e`*Q!mv!5ufLs?MIJKT+#kb6Yl7r`21k-n1Or&&)|+3?mKOvi1*hP0eiN3jURZ zD8rOxH*&gF6j7+8SpVE`>yJ-B)C|&?`m>{S1j@SDXyd zRJx)-#vnsH>A0F}=!K@{I4fq|V#t(21LXB4*ImOhDVabv&mgHPSy(MFu*VF2cMa2U z?=eiKN;}%V<|n^i-673<_er|KFq~{~x1cqfWy9qQwyhm7%%;*?;Xwo_=_K!veC|Xs zuV4h{WkfKlU>FG*F?_%fbGHqL;ZCU+MyJ=2@;j$mEtlIXCc$LtoB-`kgJDn=j{H0c zg_dtU6m43@l`-W>o(1P zJY+Z@rcM)*>KTeuFW!1?^Mdj;BRlBg`<3vuf&#u2om`?p@cyPHti3aSeEdUm6tm(R z8SD5~!2%XV>B|h4-w?D5ND1H3{G@6VB5zSS=raZ1;|CcPY${mBS0axshS5$w1d?I| z5922x`!mBC%^!W!??3o{fi^%XR2jxkeU)e&7N~8+2(ySg70ouh=S(aNLnXuHYpvZX zw>SI~xoov;oi7>Q^+QuUZ$u4ZKj9Z6Kal4d#jUjP*jQP#dGu+M|;el`@G9~T?21Y0(5wMg= zOeB`dA~zvGqzDAAcz^+;iIu-mERy-v7g*m+L^99u=)@kqYJvF8z+UPpHvYjdtw(tL viWG{kMdCjeU!bx{s_HXrWd4BeSsu>C literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_6_closure17.class b/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_6_closure17.class new file mode 100644 index 0000000000000000000000000000000000000000..19e64f51d77e2fab2566eb66c347c090eeeacd92 GIT binary patch literal 2402 zcmb7GZC4vb6n-XzZdkSjmV$i|0ZmKZ+CW-qZAvY~7LfpzP?S<#m(3(B4ZCr76XhrP zL;Q}dhf;g=_{pB*kMekDH$XH?&*_)h$;^Fy?sM$cnjC?(urhzWiO#*s-NofE6C%nj60Z7a2Dr!h=HFnBtioT=^zQS49Zc4 zLI2IPyAP5~hId1VLBgYe&t%NuL16eeF9q|+iHpyP)p1emwvHQV!`0!HY{{;8tGvP; zPNb*O495+JCLI^NK33DF=pwP%rdskG)2il0UrTk`dNnJCWg0ltL@_)flGSSsH-=S) z^I;Zg;!=r2!8?uY{>aD+PSa>@V^9!^gs&77u_PwL1`U>XH!WfFf8#7DNod+)_IxAb zDZW+k1r`MxzGJw2!r2aNC2SEqL;ynMKkRM|O$B9e83rl}*6>*5vCS~lPLYEg$TNBmx-(k7Cn7FxZK4UB^m|IQgrppqO*YhpU{VX`pN=Z zD){(O~j5trx}=kj;|NV)sxqF zx}io=FQFaTrf0MzezRpe@f`YJ7@+k4)jvsL@eR~Nn2WDrY>;a01@zRfsQVU3r$K%r VkkF)W6g^Pz1MOt8DpLI(_!sZKi!lHI literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_6_closure18.class b/exercises/target/test-classes/main/BasicObservableTest$__spock_feature_0_6_closure18.class new file mode 100644 index 0000000000000000000000000000000000000000..457d4e54a978915996faa00ce83c9bd7ef4ed508 GIT binary patch literal 2399 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 delta 1616 zcmZ`(OHf;76h0@9=H@1WaFaj+c>xK8B#=TV0xilz5E`gdbQoN;O_^&33=fll!^nt^ zPNOyao+?z(WS6l~kH%_BDk}2|ypiNF4P7WtV4vQQXA@FfAIxP+iPfSv5Lnw%eslJKf z$2%s*Q;CUqTug9Sf}j@1CQ~yUI*2hml}L!=sfS0##J-Vud?d-Cix|ncC{A)%%E6xq zX3ua!jHbknXgtn-Eb8)*&^Q{Mj)vmV@mOdel^7Y1X|Pg{1t{>Kz$%2g`70JSW_3xU zu+|u>c?@EW9`n(tCBja2-s*PMAt=eA9_wKbk73-UM-O_X0{0+TeC!iVkp`QjH_gmv zYvJ}_inP{ zs@;aY?4-Swo;U3t%D5d;rrB{61$MczSL&>TIesYz+Cy z(0ge(m0g@pLnga8vjR`3VK7*|2+z{i&i@#3N=DRY>jJ_#7WD<$W9~Atbi}>gQW5l0 z_epS)rAzMa;!T=_LYf3-$SOy8zcht_aFIo;tZaYjEc&)>a>eM&cH3Phb^A(LQ-joP zA*iX_au@^;#K8+sLj^GKVT9hb(SY0OJ>LW8!JLE(VCeyo{p=1Ee|Kl^)49Fd<-OC= z-U{KWG$_M08dMs#vhDxCUMM%?b#}bG4a3`P{bmejZACqjt#>_5j=Q$1;Ss2TQ3$~- z?1Xs;Q|CHwCvQY(B+qmV*s(wr2RGrbY_J}%&cQ9XojV{lP|>#pF!&eYZ+{@Tf+`if zMG2t$tHvEUnw3b>Inv9t71S#4uz1w{Z?QZTNGA@AymTpYw7Jiz8dCOxUnn%8dB86m zltDCMAw>xjS_zJuu$bU=6WR$@Wr|LQH4{21*&#E}%FJ^z^P)`oP==4>njf3cO@hxz zFh}@>3Cjq+l=WZB`fp@?S{7cF;hG7{iFsXS-jH>_$kFez?hi@#Z=ZB`bA6W4Y^64- z$BI?-Mf}U?Ph)UsPEh!lFeFcz2Rk@&g8JNCd~aiI1`6o8hz%R0h@1go85<*M3=ffX z_X5`X-@*H`6+np((k2SfU_arbbS+bK&Rc0cL)b<*OW01hLD&J%On>5Z5h#%%>?FKI d*hTmi;RA$Qgx!Qc5%v)NO1PJP5q6`-{0~RVP#gdN diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure1.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure1.class new file mode 100644 index 0000000000000000000000000000000000000000..a620e6dd2313a2eb811a9db21d830efab5784e4e GIT binary patch literal 2548 zcmb7GZBrXn6n<_JSXdSaOF?VjsK$~PASA7&nxfK_)<^&)NTq1wvbhNhVV7<;So9b8 zL;Q}d1JsTkKiL_7l;gRZB?PlD)5*-)yL-<$_nhZE=iL1B?;o!Lr0_Gt?QP94lS{l* z*A25$;7{w^)cG>6Igw(qwqxmA#WL5Ny3LD|^wXUh*_EVjmH38MuO&S#yKXwhHcvj}Cbu<*mzowG443>Om}posTs$*K30(|oq%BZ} zU^KR(Kt>NkI8t*o$I!D?tyasKWs4!6_YIMkt6X;s%cOKX-3)_NERu)$ECYMY(0SJ| z4d+va(P(~C+tnKBFRpKrWv2a0Y=vPUQsGWkt5yq!!)I(;+ha&aV{86FcsS{#UnBkG zv0+BRATG!VU`W9LR2d<>!w_;dj2gq8{Fxw~TS>~VuV~dBZs$ybiHc8vwx_|+Qxpd^ z@+1^WmWYeth7Ss{cKaX^4ur%BVmZ^{6>dwoP6+R6)jD4+GYm&_t=66rrTzgTGTz6B z4CB6~I}0@lqlA$FlQ8m$wLR3<^S!bvh#}sCC=wzMCPYAppb#M;I)vyHde?;BP2vjC zY)}-w;F52iJDN)>qH{EsZ*86C7S$qr0#M^@33nK}1b+l0$t4+|=(ryVollUF@iFc) zq|QU9pxefdb8pY#qC00w+78uS0+G2_kh&QNL&68Dv!Yh;Ic8*hhI)MYeD%p_nD+zN7P=-wht`<0lW#v|ckr~EQm`2=pLYk+SjkcsiE(KWl~y#{(AKMbSqO&y zykYW%`t~}vy)SaXs@pn$V7S|}rnpOqDMZcRH!8D^frwkSpYpp7+#1c#t!lBZ}iHrOLBxZLAT*Wyhl&5x`fNL zCstQz6`=K1Tq7x59g?=t-y&D@W9mpc5jw(+mlzA;(U>}(PIR=qPMm&CrW2hlyOXD1 zQ>R^}(}|b36~I2*r{e-{sKfLGjAp0?K5|3-k-lDqL^<}-J=jNgK|lS43DfKuAUsq& z!C*Lq+nDl_xJiD5r_uNUrq#f&=!zd9yZ;=s`NSLai?2tRTg|_~Y~lb5A^-s`^@`_U ziZBoeCF1Icx?G5>$d4SEo$YAab)FV{qUZK27rq&CT;=iNjdLWx;bZ&sa U*`sF&-B9ootz_~lAaH>H0PT~d!vFvP literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure2.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure2.class new file mode 100644 index 0000000000000000000000000000000000000000..99d0a1d6b4dc66de22f94205250ac189a06370a0 GIT binary patch literal 2412 zcmb7FZBr9h6n<_J+^{SXR-vUx3vDWSK?7*56+tmYN(opAr6RT4Wpfi2!*1N&FvCyi z59xQR9mG2A^i!SbkLvW?%@TvzFymzA?#;dD?K$T;=l=WOAAbRug2!-UPdBaH8ZXri z)2bBtvpTm7zRq1QscCM_HlAu_u6uQdYm@XdsW1 zVZwGQIm0gT9lh@60x73%dFCF^Epm%Hy2ndRjV^}aun4X;G#Q3E1}P!VpeEY_Wr(KI z8wzCfFeH+$r+cQ6uj;P5WR-1(Od&KxUaoS(Gi{6D1hO*>lBSV`l{^D`%y90uX_?*} z!+5H&tMBU#_z$*sNpq(CB)!4VpR8~%uUD%@)8q4wqaQF#r_x*DK?Hd+NZuv++==3h zf(y7PBZ2`1{ZM7Z@Bu^2+c8~+n}v=^IcE zO16lL;c`e8((U?DA{@j*yooRD5w6m$(KtS3xDr~4PDzlEB*-E+1UbZYyXTN#6kNu5 z4?ahVUpWvNq+wM(423cWxg4nW8Jt9h9^y#<4J>91j5jvGV z<)?W?)kwTcDhX4RGr=IiNis>wZ)N;LqQ*5`mobem7^cojXwh)Yns@KO~@M;!B@B?<0dGOUYzeHj?OZqLIVluF$s4VloN!F4xMdv-vF5o z@0SpR6vjO6$+!z@T4A_}OThvr#l?N%eNxoEr6)&P^6Kz%u4tFNHD2b#4TDo@29t(E z!;lN+H`O$%&R}Y*MYZTTG)reh=Swx(dQ~fq0@1cst-EooGF%KZM8lXW8=A9D-QdrX z8PQgeQ^aXRAfc$>TP%x_@jXqP_ctx!hj->&qY|NL#ZcOm@d%F;L39Z7^Lv;D_BV=l2 zLLHsXOsHei>Sx(kNJsGsSE4v%ha?#BjStcnWbnlJFs}N>Yji4NjA1AdLk^RH`D-LE zG{!SWxS>XVMLcti+lS9FSIE9WpEy0n*PDeGn9CkvQ8*HyiN%3|A&O4K-kVWJ)se`=JUnVCTukmP8jbvV8>%=NOr-AtOz-sC_p8SJ;T907- vO$y6zpdN!SzlOF+syi?6B=b9}z6H{GMt&oZ;LtaQZYXeRCzDkHau4_)NMDsd literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure3.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure3.class new file mode 100644 index 0000000000000000000000000000000000000000..1749b7caba34d16ed2d48bba894a4c00e577e8cf GIT binary patch literal 2411 zcmb7FZBr9h6n<_J+^{SX7NJF?g*KJEpy9<@D?-H-DJ5Vfl#0}Dm(5LB47+i6;|xEc zKcwHOb`a~-@l&1YkLvW?&4R&fm~k?5_vYSnp7ZvcbN~JCuh#%3;W1p_(@iV4#!Gd> zv?@jZq|PmauXEQ+YMNWKjmKJ<>t5aA+64WKs_oK#S^{I}t2nm(bU#(>>G3S9RB2vdT6?rV#2PFITzYnYKmY1hO*>lBN;DN}ho|WH@)rv`lY~ zVJubH)t~B(@b7K!lIBe7NqU2!H(B9cUawY*rpMIq7qp7y5n$*y5V-J(9G#YpWp3E+iFcg23Y|;PGq4>_KC_DefF(zPxo0xd`_iL z**UqQVkF+@l!Qr!xFC?ABteq$I~o6wm@$oOGN$k)!{k{BEgFtl^X}|>T(swWNv~1i zC49xuEm(cx2tzg`yY0MI@HKAAxB+sc8|OQTqO%OCP)9;qOu{z|%5j8^2Ayp--v}}t z-Yy}4N>K`=qFSQ%epv<<;TkT+uFjYrM>f8wRJ+3?vPQ zW+4~EKT*@DIs>V#Ce@s75d3PjslweH5T%5WhJh-NWWHZ*7Ly1^eM zGoq^^r-;+=KtfT$_gEGa<3}1e9}X?yr}xHOqY|OfikY-2;{hHj_zp{g1V7VYe@E4p zluG!8<}{UuGIEO4LB}ch6}k)!+X{ZbeGx~Ap}(0KfutDqy(kOW3PXq97_k!{K44Ak zc2WqB87`gm{82IU)IVZU+C-ua-7)p;Dqj~1Lr=l9_-cJ`n>)dYT(s+s!S9;>^@1gy zQQ`>Ea>u5SlqHMaL_fFO#R$b21C3Zj6SC+hAn_Y|kf5(DQcLwdNB8su&}z}YS(>ad z`gR>*U^SB+e}RwxpuY?@tt0-}l`)8m^bhnT4&f5riQO>mBDDVmpVBIz?xVw(A10`k zadl)WGp>$Ksh?+GA|1s`T#4d<9gtwe2Opp>2=Ewu2v>dZ8l4IsW4M@zA%}?oewyTk z##rVM*VV}Hh-Z#)>);vY3fZ^l5vND^cC+vtbJ;^I3PU1jVsQX4NZtwGyEE#Dx>C%j zqw2k)2v&#@NdmD^KZ8KOy7o8Lm&wS+D?Hd#BbgW2I!2|}G!VZQpr)ST(Ld;=^$^Az uQdoWq^$2|V6|_xK-Fc2jnLknW5lH6=*^NMgL*E!Wp}?h`OsFErJ>Y)`ij?C3 literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure4.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure4.class new file mode 100644 index 0000000000000000000000000000000000000000..3269ffb113ece0df95f5878b62bcf007be3e6393 GIT binary patch literal 2376 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 literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_1_closure7.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_1_closure7.class new file mode 100644 index 0000000000000000000000000000000000000000..f328f80035f780440018f2a3c1aa4aeeae173506 GIT binary patch literal 2412 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 literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_1_closure9.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_1_closure9.class new file mode 100644 index 0000000000000000000000000000000000000000..4ae83a256a72110aba62ebf015fc56a969838714 GIT binary patch literal 2376 zcmb7F?^7F96g@9VyAZY|go4(#N;Q@wP{NN|TY(FiyI^bMwdUML19 z8sY+@j$1F7PE~Fht!5##a$B}%?a0Esw54l!vU==sM&MFZ1Y?1xz{L}T)X*i6&U6GS z5YOh;bR^LukjgYY!?Vm{!)P`a>>AU~mm))ywT3i3%dweGXge*SRVqYSDhi0N1$0f_D$Ls zjvS|T^y5MjF%0PFLpq5B-VjK5TUJxxR_R2NPOT*KJ6~~Ho^%&%(!}DE(9SdjdMfIo zAWyPTbyQpe*CJTRb^6CCI0)#DU37MK8cUll>yd>xi31JqF%m^hvE{nb_5|)_ zOYJ(G_ceKPIXXkr@jkBgAcJ9nm_@`3*@y&myp4Ah`6Cp*s%P2P)ROcJUn^88-P$8L zJ|e}bSCyinAkd{`p@c@r|J~_tvfdMzOkx~U0u!gvR5o2}*So*xN!4_7Rb!Xs)bN48 zIi=O(Kmx(Olx z$;VyXOX3dZ0^sAkbQCeB4nC%sGfEw8jdrE2;;yU}%1+H&ku^#6n34$%W=xk8P9C+H z-=CEU9%MJ#T+5!z0XePOS=;5(YuH`*lwyf;=UinyBbho$+5WZDs!cdq+vF15tKpH3 zPq3f__&JBi>qBcOzcR7{{v&f#^W}>qR>2Ya5c8_^UvWtPC*lqwHE=Y>S#2HPAfLnr zzSU8}k_xyYFwkE3usPLy?t`IhHwDfZ!CV}F{i#@m>}ISrfy<}8Ssax`wuc&T4mGl7 zxR$ZmkgMt=&|9)>x!l^>ly10_EITdNln*Wc_<^n7K1zV9+j6Q@;DXJE%qLMcF~mF* z9B^k5PbF2m0I46*ixhv8ibEEbkEWX2{961si#R=iomuIbuUcKgWuB?k6|Q1ja~|=lpyp|!oLy89BfM6`-Ps;tm1&nTrC~^68=UVp)-*3@E11;Hu9w zFpysU9iJ@{e&q#L*VD25b9{M($EPwhzY*ePpJM$l^znOu@BXC2;!C6t@%`cpY^>AO he1`S>FR1$n^l9+k7>l;U-vqj$!{#bUR7J}c@GtDLfPw%3 literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure10.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure10.class new file mode 100644 index 0000000000000000000000000000000000000000..7731bd1d9d84291783874b880481110fc3cffff6 GIT binary patch literal 2501 zcmb7FZFdt@5Poi2x*=VlT_}pRP&7hbTGAGzU?^`9ur@8)V6oJ?ZjzgHA>A#TO{Kp4 z0Dp*|{DRq8Y+qjSJZno}Dv?|XPojlW ziTmr^pJ_X0bsWT@7$O+bF$g1uUc4pH>#x~$f%ExYL3;gFvcUbO+wi3~>kubiKN0P& zL!jSOCp8Nt3}sg(CU7DIhGe&Y4=D#CXhm^cU_2E5-744cKI>F~2%HN!uKTo*1ay3W z6a9$eq)LVK6~!o0Y8_F6w2fFy?vzq`sT66RRLUMD`jq=I<^CZ($)JPkP749A=A~~1 zd?8a4$$V#5?3jr1z^iQ0@R2}NQA3f9A?f^LPwegQKvWI@7Bz7DiP$+I{7t6RQQ zjd!JNZSY1lTo%};__~vbKsp4$-Llqk6}cF$;A0*8uzxSfcU>S6nn+0VX!t}xZ&%oC zy_wGAn?)wW8zCeh@%R*TG0Y<047^j9js=`ohMVMbTydqN*+x2W^<*Vea4Y_ztVmBy zJ6_GHc{ zKF6Y>0w=}r3&M5*tD(v{%-bg-FHyTSEOpqp8^apz=~%`cRmZx(P$xfHJJl#3gsb*$ z2<)+%BXr`;2dIboUY5X&Mqc~AaoptCMrsneCo+rLs3ctANyBlyxC!8`n_)iBAIrnsqBw(kWxqSSqB9WFW@^ zIUdM~K%Nfd8RN@>0$BwiJ6l1H2R8={87qHcb&e^R&tNSZk;1>5q<%vqh(Mq3cy5H?e9vz$_Cm)GT*VkwL9hw@ E50a9VQ~&?~ literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure11.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure11.class new file mode 100644 index 0000000000000000000000000000000000000000..55f8a7cecd46924cf1b414bab1685d8515268e5c GIT binary patch literal 2352 zcmb7FZBrXn6n<`!vaoDR2nDSbr5a0KXcuVhiz!wbTZ{xyLQ$Z)E}NTVY1pNk4Kw@% ze~90)b!fDs<0m`gk8(VBcN3!7Fw-x)clVxi?s=Z`oOA#F=eIuq%;6cs?S0*})OB8N z8m3h(@n=nL8GM5`+@z*84s7G8R^htabhtLF&1**8ZqRyeP6A^Xt2(xQc%;^KtE%Sx zEeTPE8QZC<~~=KxWyga<>ijYIflz&DO~qG87}qBQo^dsFQ`Kd7sMG%~|TA2?#j50PZXTTg&~sPJ%&_hA|dS~fjUKLBlMeZq5F6r zWIDVRLIM&WpJ7qPeLV03cW^2A9IDuONQzF1f_JssWEZavuc#%v;;!=wcf`D-(o7@` zhXx&QH=Ea`+MLkiPM2%Rb!blJL{kqKHQiZs2eHg>G0dCc7L_xUsD4`YPR)r{q4CKZZnY?98V?wI;+oo|Q-!${Gz_-b>1mpj29xnwsTgFiC8?T41Q8A%x=#+pqT zD+P;gIgec4z!U+E(eN8UG%kxS0^&bn1aW%Hf?F#1fEbQQ(DNyMEg(uBz;Fu_tC{S~ zOT7KFPzCDroT5$8)piN*&|9o7<6YVlt4Uf#X#F1Er>6*2AZuYiPN7z25>xZpXbV?= z#Y_~(?3gwpUeF18gD#kM&@o)|g6`8_ky{K)@ffZ{4Z_|ad*LyiIl;$?$WIu|w2(V~ zj)h|OHAckW7V=xg7g)%i;E^~;fF=0`0mdjRk+YOZOeKmX0Z$l^E&_uq9%qzzV(oW) zy-eXtukd&)5y`y7x9xazBL?O-gLtXu*!~Nn^gO{2f0D!UYb09OU4DfpTja`LU_0{* cOfLfYSafa#5^Q?MFa!k$w2~>RfVT(y3nEQ}{Qv*} literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure12.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure12.class new file mode 100644 index 0000000000000000000000000000000000000000..295cb01eb2e6caa3e6299f733295d63c81ba31dc GIT binary patch literal 2415 zcmb7F?NZxD6g_L3$OsXP5@;!81?Y;e6O}DDKLi0*O zlwrblsyV|h^KHH1<^n0FVR_~*&n<9^JG#fqCmLN0!(lF5X=*YIb@Wm~oIy>tdCCw? zrPme6=wV1CT~GH+qfpabchRcY44Go6i@Z|fhG*Iqg%ik5Gf0|72+IWq_L$+^9n&(s zS%&dcaYx_No8dp$+9A#9_LKBFLw~Z$y@Fn=l}wM%IgY;1kWZyI!<`87WRSc|^0{Nh zX$2Q>QAPv<3i_eSh~WcaLl@QZ{OphLFdYP zohmQk21Bpl^@$@4*^umZ@>;%JXY`x76l1@q|yGC zs%DK~G3@(M5wcZ=F1 zn%L>45S}uOocRJ$H4D^0VpiJ3qIKOd^{pCT6AMFM(X{wVV|R-?!HHb58;-&6oBs8@ zC7x0u3Gs5?@?Bc!kSR9IyitjQHS#^aTMPgAe114_>EJ;bRPoi5PO24B)Sl zywDiW9OAkf`4#cZ5$+s3$80hC27Ti62w!g$Utl(Shy`It1Why!0EWmr;d^gJ9aWc0 z8Ffs3P!hokF=9y|Hs)s#7*JRL#M%-WS$~a38)_u;5}U`U^q>af*85uC4tSpF$WoGiSTJ4_mo!fWL{`=n_e*u_;$8c>=H?7QFno@!;Tdv%9v6WWw!RBe~`lhYCyLtn+Q?dSWss%}+s`Noxm zD8snzRC0!0;yZfX%>`0U-SW&mo?GM=cXW@JPBc0ghQeI9+|XngZ0n_jID?vO@suH& zN^dBT(Z!HRx}NTtM!u@M?vhou88U@X7kRnL4bQYK3MY`AVURS95LWUG>@mZ+Tc%}t za|~mt!mj>YZ-oC~dzUn4T2Im&486$;_wstRS~NXA?>PEC!&EB074AfkCxhf&lFuD0 z&M4@^1sM_aE9iwPBZiL{V&0DFGTbP%P14z=qyW!rcHQI7l0|u<@>51z<6!90gr!EH zl!lTm9Amf`l7@7vev~K&u@G^j1;??SnAhJY;@>sv-w7l z>F|CD0VFr(aaYD2P}2(FleiQtU_xBnBibiL?VEaXxGAp=FXxJO*<0gfPTVj!m1ZDm zI5Z8pApVJ(M%5WeZ8fPDJ%@(rjOcu+Mq96H#Ze&I)~ai4 zTT&|FCmPgL9?HlmQU@KU;AiMEG;AyQ4i7~fC5HZHW(1OA*7u?;WGf6EdSk{;eE5Vl zvD--@JY^U@^8uu4=Ba_isI-Yi8@gla+f}|U7KWaJY4O$i-ZpoF6S-*D9fRL9{p$rw zyre`D;^mG_F)2$Hy^4Nvxr-4BGzOZnh$dvwP(b1r^dLcBS>%=qevaFB-HF{W?IN`Q44=~~pdO;bmmemm zm2q`sDl@K*PN`pJUn3pGYg~%rfE|!v#0MXsF9`4$da&<_g)j=n45_8!@EDA#+XrgfdFi74B-@7yFh`LhD zsH5tGq6k)q5laHGQ9pw~zqAD;K@JerS%ZT v-=who7U~iB@*8NIq`LDGPcpxw>LZZOGqM|j1c$ycbV7kkJDE^LkbA)Y7k`zp literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure14.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure14.class new file mode 100644 index 0000000000000000000000000000000000000000..1a6e4a46b795775dd90004cb91f506f794ce7e7d GIT binary patch literal 2379 zcmb7FVN)Ab6g@9VyAZY|go4(JQjH}ElmNxn3e?irA`++)L=iPEo0qUO?BZtA8GeF4 z#P8TT6z%Bv$o|}13k}1r7mC4= zhPc3(Eq%uv<@GP^~Fq+LpyT-iprN|Ivtsza%a%`p(+Rg}Ql?oA-ivr?nf%CJL zZFzSDMzW|8Q%Eo->_0xpjfQOkH|KqtPf^o}71GV_#q@o7;>IBIL=WeUtWu zW5*dC{kW7w3wJbfu7Yk)dxAsU* zj!1FpWvOT=2y`i3D5Vkde|P$uEchg*l9<2^fyuLIDx0pg=iS@)r0Tl)s*nL9JdMg|hmIU43z@8b-EzME^mK1ebb-31YV zGp5TKCy)Eg zZ_vsl53-wWu4T{Vh@4UVtnG2>HS8{YO0h(_3*2NqBbho)+5WXNs!cdr+vF15tKp%J zPq3&2_&KM?t3zujzcjM~{v&f#1Llh)*1#G101K-0UvWzRC*lqwHE=e@S#2HPAfLo0 zzSU8}vI@8&FwkE3usPL$?t`Ihw*<}^!C;(t^|4rm>}ISrfuS>BEe^{f`$Nq)hay=w zT+7&M$Tjs5=q*{cTxsoYNjKa{mYtSs$_JKzeBV~jA2mSPZ97#;aM9*d=Cdf97-pgg zPPp@kr;@5)fYguZMT);kMItN9XH!jXel7l+L!2H!&k+V!@}pzV@cPfH{Mg}ln1|r| zbs2B)SFNt#O`fUM5LYp-Igt2OPz$tH_Wguf9!n2T<DW)` z${*pwgD03RjlMvy+C9R(jnY%hjvnH^3Q$o*b%y}`%te7~`SfslsjNs*22@jMaMfoT z7)Y=Dj?b0|zxo_&8|hg78NNKm<6{|`PltHfC)oH4ef%EcyFcl$^aAN4e82P@n;UdB hpJF5b3+g@seHy$s#-i==H-T>Gu(?VSRnf8y{0me9fh7O{ literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReducingSequenceTest.class b/exercises/target/test-classes/main/ReducingSequenceTest.class new file mode 100644 index 0000000000000000000000000000000000000000..39600264a9e2064717b19d43dd79efaa4aaf3101 GIT binary patch literal 8517 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 Iy!6fZF9&>=Qvd(} literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_0_closure1.class b/exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_0_closure1.class new file mode 100644 index 0000000000000000000000000000000000000000..a82824800a2a9736fe795c4b46e705a6741a18ee GIT binary patch literal 2470 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 ZG|6uW5?b^OqYnyxq@7GwMXEml{{o4bmW%)Z literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_0_closure3.class b/exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_0_closure3.class new file mode 100644 index 0000000000000000000000000000000000000000..f368ab2f020cbe8f7fb9c3516b17a07ef033d8fe GIT binary patch literal 2424 zcmb7GZBrXn6n<_9-LPy+SPEL(3e>dZtqp`)YXh}3QbYpO22n~iE}NUMH0;K^815{a-M#1aInOyafB*B_9{?usBg53TVcD4#Vc4$a zS&qHpEN{46>=+w0zQSEERw}u5$9!6R6 zHM1ow%8rbV;aUI|iBA14A|m8MOo|?ZSKM_yzOCR2!}Y+roCJx27(<^#YL0skg;VFk z+tqLl!@anQG3hBp^_vyK5qCx+ND^F9n&Eapz@701q@W><>(crSq648cAZuIn`L)PB z!{-gdg?OUS-YG{%1aw>CO&k?WGDIXrB~ldY}PY3tSO2$pE(PzOcukVsaqHQWdu zF`N&w$Z(5F90=Y?O!ucoP7<1CZ5xA{p(yxDLjj9&IIPiZd3)0e*8exrnk5NLTaKS^ zR6N1A8a~H@WW#q17mqpHfvtiKf`-?x7UuGPrcwtw+VoQzOZ7=n$J$4Pp7WrCezC6fB!BHMTdn zSZRJyi%vtB{DI{k&)V|#B(e}cH=QzZb>60{(8rd$7@<7FpgD@Js45!^i2j5=MCqwY zXsO<3=$xiKt%BaBX?%w1*?WMYrDQ7o0`LApuM94&BmQhvaRKkq8|e1Ch)Z-Pcb91w zqW$|Ard66+N4GCOMy3|i`bajdk7o4`ULqbsT2Ey4RO%%%UGSGZwnr)<-^~y`K@;%M z%~g#1Zan%bqYdLqG)#XuvMooGBri3_lKZ%=hkiyRd4Rin&v37hdW}B$^#Bjn3eRyb zwU2q}NTyGGZW$P+IAs(Xda)>zm%>Dvmhh;LC@`q6{*JE~$kpRl zc(SI4k}ps?v`rUjOZ;ZbcKjKPztB(XKB|9`!oq9l2e1}i!CWKN+H)AmUs3lhkWPdA WhM-`Fo?-Mr!w1&5E| z2l0Pw9ZK!!^sjct59N5@ZlI}I=yWpk_GS0p^X@(O-249d_s_opOyC!WnW|ygnRVB& zJxf@Qz3!}Sd)(bKwkv#{dm>RNcs0j-R48#n)LmW}r=MAIJlanwURzT{<<@OsRe5HS+uSt-FP?auV;DX?j_ZLx!{A#2RuE&* z6D>hAv?Ws;8dP*J#1oz{gk@$chUYEWC5IuM3k{c-D%_OBP-+d^X$GZGAcU1H1AD-5 ze#WvbF~=~H%=I2p6eV|27gLVoX9`V~ALS;Zicx>O&05 zI!#c&$kOM1`K@qa__AW?Be&*8I{8F(XyPTHS-~d^F{vP_NrHUiO#dZG<5S#HaTA|0 zOq@kw-gK>+SlAa__Ue4ms8P)o(7Byb)+d25jE0bOTGSe5F{fe%UufvWg?7q)o*@|; zNJx(yPx8{Rgem#q8&Z5i%Au(z2bx6S z@=_-6l*D~r;x4C#Ac}eurb~m7OU<9S6{yO+$*m^Wyl^eMJS{t4snJ$c>=^FSNTLRd z;XbJ*42R(+6*iQz)8y}ul4;pbkvDV`1fbwM4Xap_O5CJ@^X||Jw*EKffRZ)Lz`hMr~xH0GD{oT?JmPP zBbcS*?>^~7hT&TNT`3ouJUPqetM%$Och`eAYTl{4 zCck6(pKsgp?j$Xc{C1ooNpZ=hi_j;Ldl;fTqo65@wzw+$28jQPF2w1nN)D;8bm_`j zPwUV0ZqGjzd|DsY^La^?}Wrxk;{%p1?@| d0mnxmA3^6vsEBoXM$rxpd$dytRgzN!{sSG=m$m=^ literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_1_closure5.class b/exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_1_closure5.class new file mode 100644 index 0000000000000000000000000000000000000000..a41ff01858ca96e4ede8bdbdf2ce027b73d172ac GIT binary patch literal 2470 zcmb7FZBrXn6n<`!c466;5K6R`wxF@(1qe`DtGp=C7b5|*iK&3PE}NUMH0 zKg93YIyBnR@spkDk8(VBcY$aYI{k9@?!D*rInO!w^*_J=31AXUhS^=sF!N2_7Cz6&8+8_ z{2K{ThEdC|=XI;fx3#8|52Wm-=^DE{|A?F1)?8ja(YU}ce0Ch;zCOdvHv}x9iy@Kf z2$~_9&Qui0=wXPb99MG)BvmB~D@zP)gW=+= zVH)l{!$`WgqwQ-x6l+^Mq&d@hlBqECrRv;W(i)AD;qnFB)(#k^(wWWhAOg&ElBX*1 z{ITMUf__|)5y60hJ|tws@D4-F-8LMC`QjU(I=8lz}sy4)_91eU3Dg0(Xlh8|V8 z^Rp!^s+Ne3;Z_J1nNIyE5fN07Q{2kiHrJ{Wh6$z9(}L;pI=3Z!KP_xNVt52dou3g zV}{A|a4G4wvF9!xxLow_LRH%%9!Ma+mju;M0%6F7fOJ;W3g$2`V-}w(xP;5yl=}ih zIy8`wzLP-xrW|MJckNuedq2rccn^gbBs?DAp^W=@vf__hE^1Qui3G4rxcRu=|8H(YwDPsfQ zDkx$_6#jdLYp31a0i%Q;Xkb&V6>MQbh6Y{1W0XX|G>8V;%N}%{818+j3)yXgRP%@G z#M_V59X#!(SZM+bp7;3CoLHjf5L3}2hEz1$@E%REF!UA;ldm>+x42#QPvnx-v~~W_ z@U9=2;t?eR5a+h7D)Fyi(u?SU$Q=w(o-vw>7ZHuiqFI3WPv}LQzOulO3QMoA82YqY z^f!knX#hPfBv-S!(HD647g2re(KcZ6jTq(F?g9vJAS)P!*!fNthnoEryPgHovB__Q UYUt26hHfZuX(y9a0gDOz3&VMwPXGV_ literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_1_closure6.class b/exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_1_closure6.class new file mode 100644 index 0000000000000000000000000000000000000000..14159ca326767b4accdc6508e3b7ffcf678ded17 GIT binary patch literal 2427 zcmb7GZC4vb6n-XzZdkS@ECsD?1vD*rYXd1_Z2(JSi%5V<5T#V(vYCXXVK?q>qWlDZ zh~Kg8q0}BdezNEIqdeZ(4G_&jPruAgX71~ApL-{N|MS})04DGg!}P9Zn3*+OGabV< zEOX6T*>bqu)V6ATjXQ3vRC4N;{-jjln%l5>X}mO1(rcDO`zZ;GVW4VTR&y^?)68ln z@83x1Vn|zdHKSW)zO6N!OiRjcn69zQGxOZ!w&wEku|_w;=;?9X^z|8r-w?2b2!k5y z2%4cQo>*5Pqn9BXb6m|e^n6WooCULDF(eCt;qpq2>jD@`ttFddkV+-8u#{(Dj~LEP z8>ZpTGK|FwJ6coop;+14A#UaQrLhRf${TiavE#uJ;tL9{T_NuH|2 zGe?R!1p~M!BZNT({ZM6u@eV`S-8LMC*}@y3IfaJju;5?H3r3D(YJ7 zou4gXQMN>M4A%p&NObCV5fLF4Vp8-3yyBkY@?8lZFx&{N%W05Eh%xjDq~^K%(6;K_ zcJ~xq$8ayMVN7@mG03Z2=SGv4?~Fu{B*cUQ!|i~WJ3|i0LO~iggoB&Jh$i)fC~wiD z*b;X&k2(yO;)z0gvm9R$)@`}_P)fMP5D_d9yd#sOe1ea6Nag8 zI1#KeTnw_vFhwN}1n)GmdxIk=5`8#G?t-n4|x{|z<2B!Ov* z3G}Uu$M{ac7g!W*_@3eN31>U7m9Rzd5CI60|FF9?Iu(?`W$36VSi@H$k8Os*c8atl z#bEEpj*xxA(5?9cb^Pte=`PG3N`c;kD`!1?RLndzl9-4Vv1whi4e!Ae3yq(GVe;k1 z?iRP#{1droHEf+fFud#grg%V!EX2=kt4v&7FzGe)u;mU$D9 zU8P-!_U~huR$*!r-JbjynOaJ#BiXb%npHo1iFgQUHIY?Qsh7xf!CUs(KBm zXSka>z=Cij(kDK*3=C78B8!2fI-;%=lj^AYC9MgySQN<%VIoaSc+^7_7*yAP$2W`Q z>d`Aa-cUoy7bqRsrnj^uKH0Jze+um{^wWBP>Yt>r_!{aVjKx>bH%PVi3|jIz>YfGC ZX^`I#BsA$8Mh_JHNIRLVid269{sjYSmahN+ literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_1_closure7.class b/exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_1_closure7.class new file mode 100644 index 0000000000000000000000000000000000000000..b7a35fcf76a4a9a2f81843940a1e5094214e1b32 GIT binary patch literal 2424 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 X{6?T)hrUttK*JBTQ^~4K^?Tr7>3fzI literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_2_closure11.class b/exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_2_closure11.class new file mode 100644 index 0000000000000000000000000000000000000000..b01b5d389a486435fe8a725acbf418b9fb1f1dec GIT binary patch literal 2465 zcmb7F@mJea6#ic6mJ-4!*+3By&^p?}R$w@%j7_K;A_cY;QN}ce^tFVwNljky@F)0( z`1{;;7;{IDKe==KqdeZ16xP|m&VG4$N$&mLz2E)regFLX`=0=&@H4|)&9t4|x@S7R zEo|3Wch|Ok?(Lb|Rld%BkunUw?phCxGB-uThZCF*;r~TBF0>*H@;<@hLey(ad zm0UjjrJ#dh!u2XS%PsL8v*G8OR$jvqc8%v2xx+nE@Y1oz8HUl51GyafGYr2WVg(5X zJ=GR9Lq|HZp+Q9#Lo($HQ`lC%YWn_?Q+64$g~)Juxymic45ikzon=rAgAi8o4D3F` z+3U7riyI7M>B6qLXNFX)ZSRujZ2OnY219SE!bRS!R*Saa^PXq!Gfby5ThT!@IkU)~ z%EWU=jLD47VY;x8x98j>U=B_G}os z40&FdE|F1kWq1r1Bgn|K`*)BOAs3QT@)di8 zI`_oBhKm^P!p9h6=*+vddXVUoRv%zk z7HWzbM%KO<=x>D!Gtd>o0J*hx((x~LhT7W)d}6 z0{2KYQ8)}&sj!ihoh1KYmdwh2ioK?rFaQPLYFNdZRN^L0oVSNou=T$=7ZxHiSvi!x zSMdNpXeeMsYT!qP!B<`0#;AgAB8AGXp@c~l7Py9Wd?N#{F!Z%5pgCaWQ168;ZFd>Y znBg#;eEU&aMXi%!br^VLm*QH!&^b~A|uQqDi+*=R7s71Ho zS^Tyg{J!PL>yyMl`rC0!q{StNuEKyt?qh@!jf2K0I+Chv93c4%dXS{2DoLcu(zPq6 zJ*_{{+Z;{LI6b=#(Z8A{c*#2%4BECBDPCkXHed=R+++^4T+2{KL{ zC*dcX@L95#9vZNZZWHm>cyfYNt%(mnr5Q`^FW`z z@dv(JrbF((z=KUamVE}}2$e3}ruj@0HT@Xo-{__F0Oh~PVfiKWL+mWSfVD}k51+uy eK7|_~kdL5qV^qWjJ>%$vhCSM;geu9Y1OEXK)tGGn literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_2_closure9.class b/exercises/target/test-classes/main/TransitionToObservableTest$__spock_feature_0_2_closure9.class new file mode 100644 index 0000000000000000000000000000000000000000..f6a34015d0e697c7f339a30f18142b234b8f5ab8 GIT binary patch literal 2427 zcmb7GZC4vb6n-XzZdkSjmV(x{0-Bb*wSlzOwgD`SEg}IbL6lNmm(3(B4ZCr76XhrP zL;Q}dhf;g=_{pB*kMekDH$XHCJ^eB}nYpjeeeRw7{m*ZI0GPy&3{yM0X=PU(-EvLO zw5?TpdDG=iQ{SxdRqlFmO>^tE@l>mD-D@~po6vHaQL|mz-;ux=2CI&3H+Qo&-Ku5_ z!HtA2hK%i0vxZ&fTYAIIwxpbf<(WG?JI5{V=pHX0X>>D;ogByYK%Zgc4FOAtF{ts5 zpc%Rn$u$KsdKvoTuBUsZQK;#zJ8xBNhEy>$TwbYhLjXgmwPf=QlBSV`#R3C+%y4GP zv`lY?VLVaX)|+|&#q#DhY34gml4}eD@hbNUdaYJ6JwEF=`YuB*k=zImqJ^13@>C_B zJygsq7{qxQ5ez99fGQ)3cNn7HmgzFg6yE^VskNmn&uMnUVg^u7+Td?9}ffB0?<0r2b>@O1rMdcO-nka4obhCqW`1&d?{2n&aLh$F6h7 z+f{HCBfYqSap5V#Ag^$Pn@wK6I~qfZ5EBXvw?ks?3^^nV1sPlu4z3d;n$#1byd|Gv zi`>(F>M&eLB#Z6Ma&$#lx8)u{Dd7e~Ot3`oj!csBF+TnwmH#8$l5rCsGfbWaQ^|15 zx_5uqpSwY>SKp4^?5S$dXf;^^V+{LtlvpCm7Jp6{xcH1%9T&xJ>$}TsxH`O&E!h=s zg;%)4iS$&O;ke<@@Z*BlM{3$6T_iTzR7;*?TGhPhZmCXNuV%%tL}Q1VD27KwvU;uI z#<0S0KFlJ+Eh=#+cqftF9~^nXX&Sd}3<^S#@Rfoh7R7v6r}6UkrX_6rZ>R+&2~Ar} zpl@V6!M6%N$AVzPcMKPgIopA)giV5n2tbJZhuy8wsh|ul!$3vBD!vqXY%vVAQ=}y+ z2KxZEh3r#?Zao;N6K_9GcVP8U3iKXaI_=@3Viu^8#6+}-O>4Si`VXd9X#5mSi!U{H zHo3DJoX91+;TZg(>0dvv!~;rXA%1SzW#a0*MX#ZcEq5_Wc}78_6kYwYXe^-rC-kA8 zzOsOp>V1apY2ef9(64Elpi%nv9$epz|7t=+$*MEqfeY3;Ng1l zIqs$RF)tj6^oh?c10xit$YL<1j;hP0lscw{7Z?9N% ZG{|oR5}Ndlq6Z3opq)%sMXKKe{{m)MmdgMD literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/TransitionToObservableTest.class b/exercises/target/test-classes/main/TransitionToObservableTest.class new file mode 100644 index 0000000000000000000000000000000000000000..87c58ac6984bcee16b1e317ac07b70c2e56fdaa3 GIT binary patch literal 8810 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 Date: Fri, 15 Sep 2017 17:53:10 +0200 Subject: [PATCH 7/7] more exercises --- .../src/test/java/main/InspectionTest.groovy | 78 ++++++++++++++++++ .../java/main/ReducingSequenceTest.groovy | 26 ++++++ ...ionTest$__spock_feature_0_0_closure1.class | Bin 0 -> 2496 bytes ...ionTest$__spock_feature_0_0_closure2.class | Bin 0 -> 2453 bytes ...ionTest$__spock_feature_0_0_closure3.class | Bin 0 -> 2388 bytes ...ionTest$__spock_feature_0_0_closure4.class | Bin 0 -> 2387 bytes ...ionTest$__spock_feature_0_0_closure5.class | Bin 0 -> 2352 bytes ...onTest$__spock_feature_0_1_closure10.class | Bin 0 -> 2355 bytes ...ionTest$__spock_feature_0_1_closure6.class | Bin 0 -> 2466 bytes ...ionTest$__spock_feature_0_1_closure7.class | Bin 0 -> 2453 bytes ...ionTest$__spock_feature_0_1_closure8.class | Bin 0 -> 2388 bytes ...ionTest$__spock_feature_0_1_closure9.class | Bin 0 -> 2387 bytes ...onTest$__spock_feature_0_2_closure11.class | Bin 0 -> 2208 bytes ...onTest$__spock_feature_0_2_closure12.class | Bin 0 -> 2377 bytes ...onTest$__spock_feature_0_2_closure13.class | Bin 0 -> 2384 bytes ...onTest$__spock_feature_0_2_closure14.class | Bin 0 -> 2422 bytes ...onTest$__spock_feature_0_2_closure15.class | Bin 0 -> 2334 bytes ...onTest$__spock_feature_0_2_closure16.class | Bin 0 -> 2379 bytes ...onTest$__spock_feature_0_2_closure17.class | Bin 0 -> 2417 bytes .../test-classes/main/InspectionTest.class | Bin 0 -> 9199 bytes ...nceTest$__spock_feature_0_0_closure1.class | Bin 2548 -> 2548 bytes ...nceTest$__spock_feature_0_0_closure2.class | Bin 2412 -> 2412 bytes ...nceTest$__spock_feature_0_0_closure3.class | Bin 2411 -> 2411 bytes ...nceTest$__spock_feature_0_0_closure4.class | Bin 2376 -> 2376 bytes ...nceTest$__spock_feature_0_1_closure5.class | Bin 2498 -> 2498 bytes ...nceTest$__spock_feature_0_1_closure6.class | Bin 2349 -> 2349 bytes ...nceTest$__spock_feature_0_1_closure7.class | Bin 2412 -> 2412 bytes ...nceTest$__spock_feature_0_1_closure8.class | Bin 2411 -> 2411 bytes ...nceTest$__spock_feature_0_1_closure9.class | Bin 2376 -> 2376 bytes ...ceTest$__spock_feature_0_2_closure10.class | Bin 2501 -> 2501 bytes ...ceTest$__spock_feature_0_2_closure11.class | Bin 2352 -> 2352 bytes ...ceTest$__spock_feature_0_2_closure12.class | Bin 2415 -> 2415 bytes ...ceTest$__spock_feature_0_2_closure13.class | Bin 2414 -> 2414 bytes ...ceTest$__spock_feature_0_2_closure14.class | Bin 2379 -> 2379 bytes ...ceTest$__spock_feature_0_3_closure15.class | Bin 0 -> 2521 bytes ...ceTest$__spock_feature_0_3_closure16.class | Bin 0 -> 2232 bytes ...ceTest$__spock_feature_0_3_closure17.class | Bin 0 -> 2415 bytes ...ceTest$__spock_feature_0_3_closure18.class | Bin 0 -> 2414 bytes ...ceTest$__spock_feature_0_3_closure19.class | Bin 0 -> 2379 bytes .../main/ReducingSequenceTest.class | Bin 8517 -> 9757 bytes 40 files changed, 104 insertions(+) create mode 100644 exercises/src/test/java/main/InspectionTest.groovy create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_0_closure1.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_0_closure2.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_0_closure3.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_0_closure4.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_0_closure5.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_1_closure10.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_1_closure6.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_1_closure7.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_1_closure8.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_1_closure9.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure11.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure12.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure13.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure14.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure15.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure16.class create mode 100644 exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure17.class create mode 100644 exercises/target/test-classes/main/InspectionTest.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_3_closure15.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_3_closure16.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_3_closure17.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_3_closure18.class create mode 100644 exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_3_closure19.class diff --git a/exercises/src/test/java/main/InspectionTest.groovy b/exercises/src/test/java/main/InspectionTest.groovy new file mode 100644 index 0000000..c15b50e --- /dev/null +++ b/exercises/src/test/java/main/InspectionTest.groovy @@ -0,0 +1,78 @@ +package main + +import rx.Observable +import rx.Subscription +import spock.lang.Specification + +import java.util.concurrent.TimeUnit + + +class InspectionTest extends Specification { + + def "all() stablishes that every value emitted by an observable meets given criteria - emits single 'false' because all is not met"() { + when: + Observable obs = Observable.create { + o -> + o.onNext(0); + o.onNext(10); + o.onNext(10); + o.onNext(2); + o.onNext(5); + o.onCompleted(); + } + + Subscription subscription = obs + .all { i -> i % 2 == 0 } + .subscribe( + { v -> println "Emited: " + v }, + { e -> println "Error: " + e }, + { println "onCompleted " } + ) + then: + obs != null + } + + def "now emits single 'true' because all is met"() { + when: + Observable obs = Observable.create { + o -> + o.onNext(0); + o.onNext(10); + o.onNext(10); + o.onNext(2); + o.onCompleted(); + } + + Subscription subscription = obs + .all { i -> i % 2 == 0 } + .subscribe( + { v -> println "Emited: " + v }, + { e -> println "Error: " + e }, + { println "onCompleted " } + ) + then: + obs != null + } + + def "other example that shows how all() fails safely"() { + when: + Observable values = Observable.interval(150, TimeUnit.MILLISECONDS).take(5); + + Subscription subscription = values + .all { i -> i < 3 } // Will fail eventually + .subscribe( + { v -> System.out.println("All: " + v) }, + { e -> System.out.println("All: Error: " + e) }, + { System.out.println("All: Completed") } + ); + Subscription subscription2 = values + .subscribe( + { v -> System.out.println(v) }, + { e -> System.out.println("Error: " + e) }, + { System.out.println("Completed") } + ); + System.in.read() + then: + values != null + } +} \ No newline at end of file diff --git a/exercises/src/test/java/main/ReducingSequenceTest.groovy b/exercises/src/test/java/main/ReducingSequenceTest.groovy index 2a0680c..91e8930 100644 --- a/exercises/src/test/java/main/ReducingSequenceTest.groovy +++ b/exercises/src/test/java/main/ReducingSequenceTest.groovy @@ -4,6 +4,8 @@ import rx.Observable import rx.Subscription import spock.lang.Specification +import java.util.concurrent.TimeUnit + class ReducingSequenceTest extends Specification { @@ -79,4 +81,28 @@ class ReducingSequenceTest extends Specification { //expecting a, b, aa, bb obs != null } + + def "takeWhile tests"() { + when: + Observable obs = Observable + .create { o -> + o.onNext(1); + o.onNext(1); + o.onNext(2); + o.onNext(3); + o.onNext(2); + o.onCompleted(); + } + + Subscription subscription = obs + .takeWhile { v -> v < 2 } + .subscribe( + { v -> println "Emited: " + v }, + { e -> println "Error: " + e }, + { println "onCompleted " } + ) + + then: + obs != null + } } \ No newline at end of file diff --git a/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_0_closure1.class b/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_0_closure1.class new file mode 100644 index 0000000000000000000000000000000000000000..45ecf78dee2925ce1982405a3659277b5870d57a GIT binary patch literal 2496 zcmb7FYg5}s6g_JjWP}ooc(i#nq;Y~DfU!wwT0&ZI9(6I$#FUgkafMhL5hJ;hOhf-c ze@Oe8BppJUX{Vo>nf|Cw@5;uQSY#%nnX8rdzRuZu@BaJWAAbQz<7bBP9nCOPIn$|f z-8C$8i92qzSahnE{-jvqnp?AZahx8#VmY)JmtH>*`D1;gbtwyo_kWMc7E|0EjR=%nwEe(K0@NqatGIdYYF;HV zoQuWttv%A*qGO00b5jp1;ZugNC>l|Q7+wRtI3uuASjUg9>V zejsKCqq3n^eJFT|8G^RxPb)G1ndL9y16nujRVpgouXn23u625?I846XIi&=fl?`9f`HgWHWi za>1(EI=^Rl+jmWIX;P(7ac)_}fs!-n2K2b)4u%M4h-PIcf)QDC6%hFaJ&4d-7SxLK z7PAy8NteE+5hM$s`w)W*iR8#jeE5t$8EPblXcKe=p2K;1i`4~Oq&=~^M5_R;Kf+~_ z!qso2E%evP)%=J$lt~5;as3rWf_OBdj%Jb(x#fH8t?yJO+0n8+o=JvVKGT`xD@+8i z&-N)oz{_rs-oRQjyEWYKvfHJ<=Lu$rVD}N!s|0p}R&w0+dfB*CQp8%%tBg14()s0Nfuiit!_N3pR>N@g8qYlB2Dem)ms%QK3_~YYnPQ zyt=(hnlqgz$#sT-c!fJzwOTFc4xh6ub)R86k=zOn!pBAqeQKFPaU9TLieH8X6}kI}>2&D+(80hLnHF6cI684ah&) zsUId{fiO76DrY#n!Yv6`2-`ii+Tg2YhT%l6UE3F;q~Awe#(VgH;aXtPPQy#WIH4n8 zWVjs=YiFqK{{4a~NFvpT1kwysXSpwEmR@(}_Z=?CK37uf1gwN>42ovf>Z-*To;Fkp z<$uy>uB;f8>0vpr=0%7yGh88>l@7^_p`2>KP=7Fgzv>Qb9w zB$3RwH*M>Rco97=mWP7`I_(iG7JWe`N%>gDJtViYxF_Q-?lYVdJ<_tskPg8AH1`VT zFfS6%DmaJpy+rs0hD0Fv0SznRAz^+Ls@Lwb?e4v3lflgwV2~2a;WHVZV#zby%1gmA z?ud&OYNfbfT3dsUwgF`Ea;9LGokzUPEl!4_*YHxQg6sg$5iLEx(f@A4M zWk!sVaLp|BV2$BIkSfD1DoP+qr*(+yaz->LP4Kpwdp;#>D0qZ|m^fQBVBXxcgm3@D zcMto({KQNv%6N?L6nu^^MA^3)E*`V214IcW0);9~h@9YKt9Q$&$l%yfu#T@p9=r6s zv~$6i6i>wfszSEL(4~4Kdh*Q&w268zrSO#D(pgU?&7~|&2{CU?qRqN$>FRcsuZe}B zKd&2nrBU1FmVY7_%!Z}$Mcut#FvQbKtRN!pn1q3nGw2<4vE??#D9;E@+-`)UvgjZn z`V;yQrLQcYrFwUZiHsPn4*i=&m^6UCCWcp1>4_J3`xik9G-w^8OVC3)f_LaEb{BDp z?!<1Cb|Kooi_5eMQw5S1@|Vff@91sOUr zKZ@I~`8_%nQARKljo@S4@g3YCd7&|$I>aZj(9h^eHSyr!85Z;D*XS3gP2@N7&#{<3 z#Hw&4(k34J1_miU5xta(jm5q!h(v_|5yTg`;-Uu(#lHRnn@eQ=o0oXJ5eubWKs~ZY zFR?Fv)3=v+2JJ5l(0T~{6)7yeMy!b^OE1AUNY!`_E%hrb*8=H$Pkuv?@B@7#=!JrP M+R0>9q-O*F0%s1N=Kufz literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_0_closure3.class b/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_0_closure3.class new file mode 100644 index 0000000000000000000000000000000000000000..5fe8f0544ecf9167d4e8d8f1f003f547f5b6a600 GIT binary patch literal 2388 zcmb7FZBr9h6n<_J+^{SXR-vUx3vDWSK@-4QD?-H-DJ5Vvl#0}Dm(5LBOm@@V4Kw_N z{*ZpB+Ci*S$4_;pKdRGnHwy-{FymzA?#;dD?K$T;=l=WOU#|d6!ehwSb<@f%S#EpZu}E$-+ZFCS}kF$|paeYK@SEdaYJ6JwER^`X0knD!myVM35VU7CA27tcZPO)t#gO!zSvNv|#FXbnz-FCj@_MOGMYgt+eX91@Iz%NXy$=SUGOVXM$| z9BvWqZ>Ngw6khb{e0o(oy(!2bD@;!iPj-o}qUa@`cq`n~eL^t|rP3$-9A8m267SMU z!X)KPa7VC_Op@|j8UK(-F^y|7rtk&BhMagWLLZgyuyha2B*>tCJl#19~aC&R@10D zgQ?9n)sp9!R&_@7ywsqrSF_?M5^Wo`rW?m9!^JQ|4A-f$p*cIP8~iCUBibr*f;g=R zB$O06(B4*=k~^n;<-4xOCQQ zN5w2q|AgZHvLLHk@ zKg+&AI*J##62(5-C&7qse2~5%gGa`Pan(28pi>cJ48w^Sa>xhfr%7IDjAssTU5)&X zc;*nd_Mc*|n0p}?h`OjZTRI`BU*dyDh{ literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_0_closure4.class b/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_0_closure4.class new file mode 100644 index 0000000000000000000000000000000000000000..784249130c8c1d14b6f769451a27b1d9cda429b0 GIT binary patch literal 2387 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 literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_1_closure7.class b/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_1_closure7.class new file mode 100644 index 0000000000000000000000000000000000000000..db3ffbfea864039b939debeb1633d7f0fa5673e5 GIT binary patch literal 2453 zcmb7FZC4vb6n-WNEG*j+LP2Ybr5a0KXalsiwUjDEsF48LAPQ*LWits&lihT8gW^B% zhxi>^4~_Qd@smBrALa4R?gGIq9Q$Q zmbuCuH>s*l!_uFq6|T8Wo2wJ*q^j2}hxT_QFowaZZCSf}xteBHb9w(pLX=_Lva31W zD)Viv>Er?_yJ@;co#!5MliQlh%SRer3_~Y<-|%(FMMz*0dKePPj^G%gsq~rx8GQ`# zq~mI?q33Iw;}pz_#gHk6hQ%v2KWR!Kkez0bRFy0&085RjcW8n*et;Q|AO_X95g;s&L_FNcop75fQ_+ko?n~ z`cWbl2!ms+3Z~1e+?H^au-(;aO}=R zrqadsraihMUc`@!<>MfMPJ0B4MPHCfQa+aP4$18d9>}#Sqy+rtVhEyo{Aq^|xAz^+Ps^9K2?e6_()8Wk*VvrIm;8Pi&V9_^xl$U}f z+!Ghe)JjRgw6+ExX#>dSm0ZcHxQ}>++nhR$&>T+cHceB0*xue;P^s9%sm(Ukl4~1g zby|#(Xx%FJV3pxQm@30U|!#}gm3=C zcOU!E{KQOCWjw~W3O>W7?myTK10iuL5fkKrgL{9MWsCO%<%HY^mu!b*19y|2B zv~wYl6i>wfYC^Wo(53k!dgAp5w1q}5rSO#D@>x$N&80j|2{CUiqRpCS8`@TluZo4C zzi60zxmn-hc5ot>tfsB=1;e|ZH^tLStRNz8TZDm9FzFrju;mU$DbE;9+-^kUvgjZn z{v-Mkr>`uarFwUZiHroTF8$3QN*X|43&YEq?Dz}3`LiGenzWA6CFmi&h_~n~c9(FO z?!<0{b`jdYjVrVYQzeoX@>j^z(s*KYD%;hu=Xgz@OCn+qxLZXEyi!Z^~N!5G~J@X4}&jRUuM}8xa@I8HF=!Jqk M+R0>9q~`$t0{8x)5C8xG literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_1_closure8.class b/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_1_closure8.class new file mode 100644 index 0000000000000000000000000000000000000000..15d6d921c66a343e3d9dc93022eab64e34102e25 GIT binary patch literal 2388 zcmb7FZBr9h6n<_J+^{SXR-vUx3vDWSL6e}hRD_BtQcA#TC>5#QE}NUM7zq6f(y7PBZ2`1 z{ZM7Z@Bu^2+cI6UR|-kbnRO#%=QX?OafcG_>6XDMm7M?>dNkppg^=)9Y!MN|nm3t}Ob#OHSiQRU`n9G@~=39Z3t@FgS(tjG$(REX z$Ke*y{&uR=PT>Wg&Zk$k)0=_}vcmKP@no0iDvDnAiMPx>-6s^oP%3@G&+!#iBk?Ys zBoruTf;)nZWRjHM%J_#wifgzoV-jC56wXR$*>KE;cW2Mzq8sNbdV?x2;Y)^ILFnT^ z7_uSRJuPYlU*V>VDNrE2INwd|oMA|X1`^U;624|oju5tbbEe&W3uHRHQ$h?<7<0HI z<2I;Wh2djd3g(d)7k7#GNm2W@ZX9XLtHZ0gvR(Ba@G2*67@SHom^2(3eOxgASWTnq z45l{PRLh=YTD57>^HPJhUfqhLM6_+xn{FH{3>U);G2EcahUVidmQVBoPh^F#T zMoy4A=r{$xK$oFmQ^EJRFK|@oWoTzcAStGNKdM5u#?Ym=rfdHFXRC?rZi4WH;nG>J z9Tl@k{Ub)DO(a^=9aG<|^Hs4h^p#ADuQYcyxf7hoWxMGZ{I2O=&s*ZvB#sa*w`_u> zEL!vg`nlyUMhRyOG-DA>$fBQs#BbgZ%cJ4247tIphQL*GOJyjAssTLyi27 zc;*nd_Mc+5lzoFfae9bv)=SSYn?1mSa3nwzivt5g6rG5@H=~ZK%jJwZrrs+HutJPT z5{QlY83YE@2Y+LAiJYvx#KUzpl6j7eBdhd~2IAKPtEs1W{15tRJ%I6=6qepVJp^BR m32mKJx1Ql~=1p}?h`OjZTR9pHbyx{MtF literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_1_closure9.class b/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_1_closure9.class new file mode 100644 index 0000000000000000000000000000000000000000..52a037d35ea118d9d4ce31de9d5a8fc243e2a91e GIT binary patch literal 2387 zcmb7FZBr9h6n<_J+^{SX7NMm`3vDWSK?7*5il~?(r39>oQjyy2vfPBlu$$i9IKxlq z59xQR9mG2A^i!SbkLvW?&4R%!%=qQ*-Fwg5bIx3IuI#bwBqBE#a9S`#!u2xX@k6vH43OGO6unBmM#+p+yy3}cznw)xC# zLcYJTO`6l~C)rho-gK4wMYC2b+diKa!rWz;%4FB0g9vkDk-SIpg+s+@4ShJLB8Gkq zz0g%8@Bu@@-?BZjSBgl_sdXb{XAQUEb3qCBO~>Mt%60$@U50egL`e8IT^SL>g$VrF zcKtXJ3vwZrD04!%;>Ji4pE6vGEWt_W6{HEPObWxb2+!?~BVy5T0b^bG92r6-Z510r zaEDlbCsS%=@O;4LlZ)E!O+yZOX?mP!LYeH7H7^IeTjIVM@QGnClRf6==!%MwdY4TK zCK-~FI+BcJl9b=d1c$_l%ebOq3STfxo|e$ECG5I?ch~2#8D}@mIu%~QmkiyK&qskU z_INXv~!7GKbTk#+A3MXz@oJup0wggQ+E{T7nW>9qo zGV3j>Wnb7%by~K(Qm3t7bCM_#ZR@p$m&7u|xhO*nSE;g*IXkHvf*~?3yDD~!IL!zY zlr?;VML8?JqtWpGrWJhu&VXxHA`-2fMr$e_;jxCVu^>tC1H;f;sjU;tBvhWg7B2#{ArIJ z6|+eFBPXRxBw96vZEn=~id-0aO18t78#^0ZgePj*Z3v6svxDn7M?RXw5u)XmOOUh$ zhhD%Sx7@=B;Y@%=EaE9u_7jl$1wBa7SCy%ydY_?t8ve98^fyD3H9_C50}L$Z^5ZY@ z;cxWI;LOK&=+Lz(D)E81;*=iDq~DwDwRM16QTLbBri3_a{IWd$9_dJ zcYvFF&vC1ie}f)*dVsIiN-uCLzmIw8NP;F7hXw{IIvIO+P9M>i$~k>hzh9PMr5KSU z6dMgP2=wa@|HR57Iaz&;M{9a4_Y&)eR_PrL#jk``GtcqlAN10?59@DISbPKh0DSQ^ lj5Si-dVwdo-%$%Jkj~HKHwFcQz6o?fgGW1+tV)nO!2gI8i@^W@ literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure11.class b/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure11.class new file mode 100644 index 0000000000000000000000000000000000000000..39a137e64476c0cd07427e91ddf54ad8f9f64fac GIT binary patch literal 2208 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 QeNgZd?PRhlQvDJ54~VCP2mk;8 literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure14.class b/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure14.class new file mode 100644 index 0000000000000000000000000000000000000000..939de95577de1394fc38a00a6ccbf07f2bfb73d0 GIT binary patch literal 2422 zcmb7FZByJ-6n^fqZI%$qk`@#Z3%Zuwg?4$f)>5jlwumhBWf7%RW60iJLbJ)5WO4Wj z{t&-o+o9Bsj-Tv|Kg#jkB+$hT%=F96O>)k2&w0*s&i&`#-~R+KiJuuJ>$+(dmTa%V z4c~O^HSYNtP4gO#@kFa~-EX>Fo6x2-!*V>@PtHhS3>RyzUOkuMk1)>ib2h^MaR&Q&8;ht z(Z!I?c)sqNM$yteZ^^DY4Ea)QSiEY5SyKv;?JR?&X@sy+WMGdN&fhX^)4$Cynl0_< zyLy=Ny{#ScoNfP-TW9Fa)VN>NEvsz$eBO2SJ%*`lZZkfJh#Q0KJ+dzxInFA$h)XgO z=u^-ORYnr;FeLqL(_@$}#n5wh;V88Wn$z^TON{%vZE)hUJp+a=O`H*?NCZ?I5fa0d z7y@$b{v9MOh=pX5e#2OK&+~a*!iNl3V~cSb4-zsAJ%XM^?%#Kv26z2E1y^vn3m;>Y zlA5=yo5Mw?-mo}nD}@}fB_S9iQ-e-kZ~QKQSY9`W7%-c zhQF}qbJ30S6}>@)mq6!s3qcgYe+@IZSaV`6Q^u3J7_(4Gl zD?$T5G7P?H^)^N&Y!N9`b_Eqo$S}Yatl=9GaE)$&Rs}?pRZR0qSR|c2X;b6YX7{Gh<2DoXit-gRsr>Ztc7%j z2x?_q9iAFjN2b(|ULu=-ntzFF9oT34WRM8J`{@Y+JOUp?AprO2>v@7qVj+N^h~V>N zFFX{2FXh<*rb4tZRE4ZWSOHo(iD}G4e$$cPX#M~<)x@tzMGh7(2kS2vCqkI*$MbC@TTpomYp|2j#pvqL#~oBw2b8A-0!a!Pp?zC(oegpTh|d$j7I1 W6I8?|J(K8!f?e9lgeu5s0RI69{Dxlu literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure15.class b/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure15.class new file mode 100644 index 0000000000000000000000000000000000000000..8faa829199c7ae9e7cf0eaace2122eaf1af609e6 GIT binary patch literal 2334 zcmb7FZBr9h6n<`qE-Z_LRcKME(58|XG`y&_jnrZ)loF5{ibbs3Wpfi2!*05J!wf&6 zKcwHOb`a~d(@%A#KdRGnvkQdD!i-<;-o5ALp7WgNocr&8fBXet0zSjUwrM-LdB?AF zOW3Zn!hMl648QJLFN_K|MZ@F9xG`y1HP@&8#BBwP;bPTu-JRWB&2*}{LLgGm!7%1} z)tu#)`KHL}Us&8T4dZbPOG-^r{9G z-3*DOFHB)ug_`O6^G?NO$P^>P;+0yEHKh>h<{1>jAPJ~|>NZu3famPOQEC?qw;{MkhzrxPIAPhI0YkST?+8*P0LrcmiQ#$# z0O@x74x$$1LNrMnBUak=1>aWiF~g0>Vw{G9f+Rz)glCS6WzVg1PwZ;Aj%(fc6r+TQ zlrh|m2(LZ5h=4Vuk&(W#RPh}mo-ALfNufn9%qBG$22<%`E9s9!1Zzv00cr}y89F5k zB(KON86QK@Jf!kYVoIehyhUAj79u6fv+Lr)uHdq-X3A!rN~qutLyx59Q6LQ22nbG# zTEks@uHrL%p`iyCx`=1@7*dgeMD&RQY6X%L|GF*!C!*GWR8wuHIY;KN&yljuy3El(&D0r-49^cBY zU!kG#{;m~J3MWQYP>9H6F*a)Q(0K-6I1 zCl$|OYIuZYnTJIqzLglErX1IO;8M53aLx?I6VG2^A0^`O>nu7 zVM;U(8kXotsIs4c#4qSYg1)N6mI{8JBEy_`pBgI$s43%$iBf? z2lm(=$zx651M~&?J@kDUH=4d(I+Y>DF`J0vCUT+oDbkk~qnUl&)?>e-Gjo9a-b+ju zv+vL=PY+O7E55>Xb|16yBAGL>ICL;b!O75jGWxJyEM@c&eW@gKm1;ziP;I0MAkeRW z_a{mVbjkN`@nlVpWnSayp;vklL-kvs*VIcq`v-lr?qlO`GFW&A{Q%{Kw|Krrrqx$? gmiZkonjXlePWQ&3;Ai^A(FF}2?NstAldJ*%1DnlxKL7v# literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure16.class b/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure16.class new file mode 100644 index 0000000000000000000000000000000000000000..0a4f781345b88a91039e06b1f24fba49383d87d7 GIT binary patch literal 2379 zcmb7GZC4XV6n-WIH!Q1!MQE+1KpQ1*8VHtJBUVh2QUY2-X%XA)vYCX%u$%5~X!!~K zA^lF(gIG^{`l+7NALa4RZh+Vm@YJZP>2PgA%WFp6c4P{)Z(3MQBDv;63 z5Kp+C?wLlhuDkAnRkaz?rO>c=wH{{T6^CY#((4VMruc+7SWz*xcj-&4~2Ze;_jJqPgk@(2485B05u``}RBRCv!{rbF zQl0u;L@kJgXc9k0ti0=bd|SfD3|B&naS{#^5)6F;o_X%wckBjtyj=yCG17}m7#E%* zRJ1wAv7H;EF{BA2A5&Qw%Y|3&AupNy^9I_=i;RPcbdy8a|`GJPo6=;g}8Y&aTHrr_EOM1{GDp z=L}~BS&sr?$b>*}Qq&3xxGCcXW)z&oxgKKP7YxbJKtei4!YqSwm|@U&Gwtq!BvavC z5Mq$QcQ6+iKFUkMJo4h=E24Bl6uYg{hTCv;cr{nHtKI`%2t~qo3QAZK6Jd=8%DbDEu>QX>7L+73Z839xknspVD)<_Uf(<`0 zTs-D%2euM62p%E;A@UZxTfU>2kG<-^?#h07g8{BD~$Ys0f82qm3 zU(Z?MaU`}7K{xFRk#)hMht3C=yBMWJqo6^GuDC2Z3yA-QKE&xO3v8+2XXu`0JgpA> znxXj_rEl*6hL+Qr>~p;T2mNGlX&v>4tBecyfPR7Ayo>md?!<1Gb`jctgb`YWsdaSw z^5bM`DXWg=v+7u0{p1Cb5oFa=Ud?1)AlC(d*<*X867k&((HArV58YhCgzv_qQxRL2GZ)Ndas;T$JB3VO{wLwNL~mNYg)o%KBB;&y80KsUnEx#U*gf48c9Egc4(WP z&zAVLmhI#d=>MXh)_v6eA%(?PP!C`(zJ#$xs`aPP)6dZGEs#!={6-*QhrUttK*2Ax LlgX+`^=IG>;JJdI literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure17.class b/exercises/target/test-classes/main/InspectionTest$__spock_feature_0_2_closure17.class new file mode 100644 index 0000000000000000000000000000000000000000..e4bcf88e76e0b9afa64d534575bd05e63826052e GIT binary patch literal 2417 zcmb7FZBrXn6n<_Pb&>+bI5s-{!P<-=bJIvB=X zuadLe65lo(ey(ZdH5_5rcy5t9+%p9)9ebQ(=sS7%wa|x-h=@!?AM9_0~;V88WhT9O_BgTd4Se&?Q&w!!JkY|J`5&kdhWHCH}Ps#hE0f%6bwr5dTv^={m7WM>} zjW}O2>r`_EbZ)oQ@^K&xV-X~s7PW>s+*WZ5pKIvGg-*(So*^9>NJL{PpoY?pG7Q^t zuJ!mZ$xL*UL;w;WcW_t5mskvekMq*7gc23L8n; zY4Q)|$E@t9*a_W)0Vw!J!z%7eC2r7^d3$IDoBx|_VId-um4oOz6;JTJh5}Zk27X`| zeADG^j4Ie7QmE`2N|;b#fooX9*D~M=4gXdJG>57jyt+= zsd0T|O8@93(lO}Sm$=q}eYQ^qu>icEo*=*@@ImAPaG$=OC&)PN1@IG1_$=8=4~^gp zS$2S_5G@QD78Z%A5>89Uk_5h#hv0sqL9^#ArXILnVy+)7xdWd@)h38lp zJHWCGP?AMDZvqTZRua5Bs}JiBi&=d{FBT<9(u~yAG#d$+2m18&Kk)4`9rE}Uo^0r` z>1QziMlY=gDE~zc%depyVte@&tPOH~`W$BVH@E=;`3O2UMn!DU RGmcJZ*rlCHsFIvI@E;_2g!TXc literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/InspectionTest.class b/exercises/target/test-classes/main/InspectionTest.class new file mode 100644 index 0000000000000000000000000000000000000000..428071f77679c9ac289b7d0bee7c115bc8abdfa7 GIT binary patch literal 9199 zcmd5?dwd*K6+S1~WHys+o9?!u0Sa4aX|hSWP5Pv1OWP1i+BBuz(9jl*lg%WVcC!;^ zCTWA<1E1gvK|#PbB8m#-VVW8hd=*5risGAx_ySP`MMO~WyLTp=O*2`N{P+XCudIyfJO}>Pg2i(=EE~D8x+j>|QguKc3RkUGW44 zjZPz}2@ZwwG1_IBX8(XYyji|)Z{G(M3KcNT9jkAN%ghvSGOez-RNQH4J^BID+8?+2 z(vH!i$9v2UJte2wraM}P=4f?H!A>Kkr@_h;NEvB8A9kxuB@1l_#w6=gnr%0xJ58pT z)SZNqZmrLbd-hp+XUbhzJi`mMN?RK=Y^!JDJxoPCm{1NgozRrMGGU1vfXvcYH~t($ zPjzIh&wmB~!EV=l#k4w%w3ZSpAg0;JuV5il;aVeYIP016%PV$;XdacR6rd9YbC>Sa zYpGPiaP*Cqr42CEmRIccU$W=VB=~Lc$43O~LUc0CS1FH9fmKeoVPmJ7eD)uUjffc& z(#IJeFqM^8Oz57HeOkZf;$Z{{SW-|JE;b1&Aw0_CdNo96&|;MqQ8`nMz{a@!^A?jx zT1Kz4X~5A1$Hoq=*U>G7VoWoH&$4W+EYF(7wWMw$t3y;p)hboek`RSxMiE@Il&Rd; z5Fa2ZRLc|^S)mKVb=lLqi>&b9CLe{^q2;tfrDe3zRh(H@h*s0-V&F_bbfJu(HnDI+ zOBomI5_fR%oG~u;79XP1glHbE3sDKp7K=QKDeNif)HSEi(%Y7{Vdb@xfVj&)3UZ)1 z#B?dZR$dWBRJNxKyIZ%TPPgVn^?uzNi1ur#K0T`U7>=WNMB4|VS~_aB+q%^cZavzg z>y91mGWzv&6x)oAHMD58x-l9dJ@sCM=@R0u%l_yfw+UvX-^?r z!3C3u(oKRjvjBv`ek0w%RQ2kMmy%n-jR*;}p4-sORN&|b9n2eOkw0hlY9g^=4b33< zUXdf?HCUEu)tf1ip-iB88zOx)LN=LZO4pEXwks&pPD zn5up8J5g3p=zOMGB3aa%Y1^^-ge^>~vvKSp$uF=*?ajcgl`bfzU9_7?O=`B&VzvzQ zB7d*St#Kq)d=&1b3q_E=L8MMpCK;a-6p>v5YCDl6tPYVM-o$i<&$QVd%rd)On4J`} zSkK(V}Oq*Wy0U5l0m4-0kIz; z>Tx}p7AED3M_}qk)GB1DWDBiRwSSpHeJb@&M|8PD2UQxFj_3-7-mKCkLiEB(xo*5O z$A9tN@g91M*zval|JdwAU%M@AqAdNbeaF-HmBS?-CHbT*P!^wte1^jOq8ObOpU1b<{{Zaw6FrG=}Jd^dT{H zCF&}YuI0FxN3!&|0?-MztLSQB%tw$REg2O8kByV`&oy+d5dN6R4`vTCo8D|X4Hxxk zsY#=HPl&Fk8$|Se95Ok)m=N9OPB%#@^hr#K(qVKCASrdA%$aGXE6a0j!`Nh6my31m zj%7I`;aKosU89eKvbemAAzHLg(`Qur6n&QI^&Zg2*`q;!()7$Y=L5rvrgm3*D`)(3jDnh~_c- z%z#}KbrMaUe=JEojea7Cf?Me}mA*oUL_U-$`VP#{kH!aQ1l=p2dWYP9J4APhuDO}M zir&_1ar7}|mZ>rqHjaZ4dMZx~CLFCSBgc)&R3Y>V`A7snbkmrUg6|!fd)#!obt|eL)Ovc+&C1@`?luqDQ9ML5i{7~tc?12f)~TlkgxE~+m?a)1 z;xSu1!c6le2KpQ$g~L)h*=JdL+KIQ|1bsfv0}4GRvNber)}8Y#vsbsAfe=0J2F~x9 zwoJ36W*4YT%hW@E6b1B8!nBBZ%$c+oYe!&HgD&YWDm^uY9T=5GbMrTqo}QL0x|?TI zdUjf}Xm9?h(sS<4&z;7i(d7JFr5C0(IRfoPm0p^bEQ0KRD!uF)UMjZi1l*>Krj^8i z?dUxUqXC{NGu~(e!vVoQPdv)dPPn7!le2S}I1i&|5{E1A)PG|61UCj1yTEIt`_272 z*t<=fz{Z?8yjq-<@hlNRm{^!jYEwi?m>?#&V%Svw4!wRRygob4S+ z7p@QTX~3PUCCIp3D)OM$t_S%HR9R>aGH#MooSXagL=xFA$hab!ja}M?_EK~qmN;%( zL9W6f%hC3OVTs#ai`xi@;}#^et+A=8G0{-Jz4`1!kb&s{Ebj>Na;7{Z9b{b2%<)?d z(@L+sndllz_-xFboV?7d6ETHrzU(-Zov6ZfA&&9_;G5UsdS*%od#PGs-1UsXhI`KT zbvdpad85i3cvFZ^;Zt!_#Ah>|G@2d8?W@A)!0&Kxh|i_9DsSe-5U=Jlg~3}9kUmh8 zH=Ddo@QUjb&ArWBI_0}eK96@4!PcGVy>p$xa}Q&4b4f4DJq$qPl3w9Eoar9`BOmF^ z@T%Oyt8)({9nK%`mW=Wm;M`|?w6Y>FbB@DtImh9-oa1m^&T;rI=Qx~~a~$3)^Ny3d z4BNnoXJZ<-h4ODO(2ED^agB_Qt;s}4?$U7M2#yx<5PdrSqM^C3r(L(aFKWWp))jCSpEnn|-jFBA8zxUtd^-gChdrhHNxDME8> zjLdDMQiz-&MN|=RV4Ddx-1|eQ6TcSYR~~3l>@b})M5i|0jvvfjc!n?(z@I2Appt;N zpIPWG3#)c?<=nkrBs=SwGiLXPnYIoC}R8u)fYYu^xO7f_V)_MwR zATBaeM9c{G_1mgqZC|AgRfkwGkIs*#)SVx;Y=qHy!e~AQ+loN93h~NtX>GVUe~4O+ z(4Kq>HxJPp@1f>xRjrlbw%V|EggWvltjVuhb2Yx|wc)N|GIqzX=F|}~A=D*@yVeBC z0$}K^tqfmOTNW55TQWJ4X_)%uuLHw0AYU#Xrc30@rAO#(1r)waD!FV;L0Lg-RrMgf zJN({~=n#EikUm^e7&)vuv(1by(}sG9MVfhv*YnmVCL9TW_XC zYYMBXeUz3J4$;lrN;ku)H&bwpQl{Kp8NLOp_);yrd;1Z(Gmqp0O7g1)X;3oHToZKP z%Yt{`j*Y&ap243l3d*I}hG)>$2)dkC@L8aF5*F(LMB**pww%mM=r9doKWlj&=J*;+ z*h!DkFnt{wucaI48}v;opnuSOx|{C7DZd+%&(0ftv6Y3cfWFM!q{CM!x5Zk)skL_sQ-3z8^-4 z;obWsMjnt%KlCtiO#b@uFg++=emYDK$(Nr`jFFY@roXlrIbNwhM(VMOU%Z|%vIrPi z0*tIY9wXlt82OICNZS-K^2-s7@Q5N#C5&U_3~#&)*;rKE8L>g08LO;1!jS-tsF}m)9b`K&4@^Q$$Yndi zIE;^QmGOxaDw`i5cTSa~yuf3-7s|vUhGXH<2%nyah1*=BM|cr_^+tFx$VCyZ0J$Z? zl^|b=a5czxUC!^hv-ziw3#D4XQRp366b>BTqE3S&#iFpHHQ(U1TVq4Kewa7x4(G)V zbG_>;Rs1UmkAJ!6gz`JM;V8|(1A(pgQh11)whr^=-4NY=CpX0I;)K+IfB#}& zjyzI$ALte6g7BML@NR>i584m93v>Xq74#C&3qbD!-3|Hx=pN8xprSTD2znvtL!fT} yeG2rApihIgfj$fRCeY_VHP9D8+d*FfO@h7*+JXN?r&$6I1Yuo~jrc;nF8)6uu~04m literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure1.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure1.class index a620e6dd2313a2eb811a9db21d830efab5784e4e..508caf2506f57ee39db11157f2446c7b640fe69d 100644 GIT binary patch delta 38 scmew&{6%=f0S-xi24MyP1|B0HcQoApigX delta 38 tcmew&{6%=f0S-xC24Mz11|z>% diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure4.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_0_closure4.class index 3269ffb113ece0df95f5878b62bcf007be3e6393..40cc6bceea28e8d7f9a492f71e132b0c956f59f6 100644 GIT binary patch delta 14 VcmX>hbV6u@1P7z+W=RfxMgSuf1E&B0 delta 14 VcmX>hbV6u@1P7z^W=RfxMgSuT1El}} diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_1_closure5.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_1_closure5.class index 0a92774872be84990df54b9d6641d0aee2806630..7a1e8c14afc35c123aa13bb514b737eb8307a455 100644 GIT binary patch delta 38 scmX>kd`NghABUtmgCK(jgB*hkd`NghABUtWgCK(%gB*i8gEoT(g9U>ogDZm;LlA@Z=H(pHi~yEJ2FU;b diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_1_closure6.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_1_closure6.class index db04afcb4b4236cb89324fcc51fdde58cc15a2a9..c15792fa29b0d085b292e061b2739ac42fcde05f 100644 GIT binary patch delta 13 UcmZ20v{qtBWHXNM03OiteWHXNM03OE#WB>pF diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_1_closure7.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_1_closure7.class index f328f80035f780440018f2a3c1aa4aeeae173506..e58fc417d81cd6af36809923b99711ce90498fc9 100644 GIT binary patch delta 13 UcmaDO^hRhy9tWfGJUH||9 delta 13 UcmaDO^hRhy9tWf0hbV6u@1P7z(W=RfxMgSwF1HS+O delta 14 VcmX>hbV6u@1P7z>W=RfxMgSw31HAwM diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure10.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure10.class index 7731bd1d9d84291783874b880481110fc3cffff6..f7bb22a1fd2dc68b729a87593a34b1a68678d4f3 100644 GIT binary patch delta 38 tcmX>qd{lVDL=H)N20;b~1~~>t25kl>1`7sf23H0bh9Cyl&8s-183CkT2a5mz delta 38 tcmX>qd{lVDL=H(?20;cp1~~?M25klh1`7s923H0rh9Cy#&8s-183ChW2Ymnl diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure11.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure11.class index 55f8a7cecd46924cf1b414bab1685d8515268e5c..81840e9d6957b5f015796a7b5c3cfdecb161300a 100644 GIT binary patch delta 13 UcmdlWv_WWt1qY+&WJ`|k03WghiU0rr delta 13 UcmdlWv_WWt1qY-1WJ`|k03WCXhyVZp diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure12.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_2_closure12.class index 295cb01eb2e6caa3e6299f733295d63c81ba31dc..5a14181af9ed912810adb1ee23d1e7f00672179b 100644 GIT binary patch delta 13 UcmaDa^j>H~AqS)PH~AqS)9tbXsVGGzX*aW*H8BMgSzL1LFVy delta 14 VcmX>tbXsVGGzX*iW*H8BMgSz91K|Jw diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_3_closure15.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_3_closure15.class new file mode 100644 index 0000000000000000000000000000000000000000..eb9b282c7aa4afaaec20f02810ed4b33cdef1c55 GIT binary patch literal 2521 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 literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_3_closure18.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_3_closure18.class new file mode 100644 index 0000000000000000000000000000000000000000..81dd3e06790b6d456d9c8ec4fe02e8af1558da79 GIT binary patch literal 2414 zcmb7F?NZxD6g_L3$OsXP2(*;aLhA;9keH7&AtBHZmr#l!4W=}NrmB$FM#M;tBxi<4 z=!5h>O*(`$(@uXiGkvH|@5+LhSY{@FR;%56&;2_0?!W*2@fUzecnml8b<@hN^HSX~ ztxAzUt#ixZ8{GAhn&#GQt9&Hla;xM%8v{KRF|TG4xd&+kSSCtLj!Imv3B2 zh%$`ZP90k7Ec+X zsr04-8C?vCr0eOPY2>TA>n>Ymn;}yOb&;2=-0)1>qHqG)Sq4ed2w^qPz#cJNxNTad zH_tGZD(vac^hWsiclJngw)G^v$PLxk5DO7UT67%Sxiu2UXADm7QG(3<{RPZfU#K`!bCeHgqOZefPIoGH}XtZJ|ZOM3uM+&~dvLL~aG}+%$ zwI!tzexgB5<)Mt6A$8Dk3VwzzL&J`O@9;pxQDW$CW=0?>W_>TpLbk%tp*Lpi#QRTJ z6MLN$!V`w!b00veW}X^Ij7pnWw5dC$zEkBJVqxehm=<5F@9%IYIFXBX-7)w*)4yJ{ z#7jyfAztp<6qB-S(W~eum%A9DKx3d8i)ca?4Fx2AK@Sr2l|^o;;1}qg9syb{`kSNC z8l!L5F$UH$+3}b7@HhHpuxTCf=dO%Fd_;esM{x)r)1BB2(=I~$Pw*+N0_p)eeEDI5 zS{+wMrZVH|=#=_-_7&1myuy_z4%s0IMttxA`hoyYz=v?v2d~ko@G%CSh#`lG0DhX} zg~nLs2-nrfuZU-kar^K&<_p<3=n<#K_Hde^U=4(9MQX`p{*gip}_cRc{7NDk{LZZOQ?eU@1c$ycbV7kkJDE^Lko&;@t1y+{ literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_3_closure19.class b/exercises/target/test-classes/main/ReducingSequenceTest$__spock_feature_0_3_closure19.class new file mode 100644 index 0000000000000000000000000000000000000000..5c7b94689d60bf0bb7d7358e2fdeb82fef094c87 GIT binary patch literal 2379 zcmb7F?^7F96g@9VSy;9OLP2Xqsm787O88M*5vZlHMI=xqh$3oSHZNgm*u~AJGyDnu zA%4f!p=d|PPj<#X%JIC-g0Wec>6gvRd-tAq@44sR_xC@){Q+PSuE6a*({^%evfQw2 zr&^RhG^At6by@dQrBZ$0wH}o!()1giEKQWAOIFRTb3HkuK?w9$J=cAFkgJ(aHJ5K5 zX^0Dqxn4DAxn;RyHtM<1%4;~jy(e>v(vhC&%krtm1%azkA&fUY1+JVKrG`#{WU4Jv zfp|KzVL(T>KyRw+m{QTXC4Slr88U6L4-V6w6D`X zcj7o}pdXiY#4uo>4@n&fye5$FckH^rOyNwD&Mzesyi{@8_?5e;7u1C<2Y4?v)au6_FC-3g<*CYin+=*)fHzF%>76}^OWh9E5e8cmk;|ttP z7g~k57>M%hdbEdT;5}ULMhYVWF`I~&(-8?6cnfbU_D89FRnTG}suk&*fmkS0y0u1f zdPItQUzUo7oIt12g;E+J|95Aw$%0Q}O2-7I1t!m9a1ocfm^(8`M+OqnIU43!?~@FheK+5FeKW~SbQeSb zl8=vYSH~SJG=Wd^GLXl(I{27kPAPS?blOl$#XVWc72S%zCM%Nau_O~3Oj#ahoIL5X zpg~KMJVDlIXO|GktKu^JT@F@6}bm0KGq=2fh5&6^X1YpG`Hn`L+3P9&vgA-NzVQ&5n*e!|Ok*@?)3ZVIG3- z*A=|MU$wf5H+iO3LtMqU=0M_CK`qi++4mD_Wh^;7l}*0$E7EZsibHP40?0xBg7=?5 z_8}8MKH_c%ffA?$pxF>KOMB&E5VVjLM;L3OH3KwgbsAuA0yi)o`b~#^BiSR|OvZjf zXZ9E$9zMZbVe|!h)b26vZ5EzlZuAKERe*{jsyhVeXD$j{%O;1D%SAnS#~zn~f*(5J?GV=USpe-r3}0f#G{sEU>y;9u+_fl~kg literal 0 HcmV?d00001 diff --git a/exercises/target/test-classes/main/ReducingSequenceTest.class b/exercises/target/test-classes/main/ReducingSequenceTest.class index 39600264a9e2064717b19d43dd79efaa4aaf3101..b03a7e529fb89e74635a1c5109f63f29c46bc0cf 100644 GIT binary patch delta 2186 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