├── .gitignore ├── LICENSE ├── README.md ├── build-instructions.sh ├── docs ├── assets │ ├── css │ │ ├── asciidoctor.css │ │ ├── bootstrap.css │ │ ├── font-awesome.min.css │ │ ├── github.css │ │ └── hol.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ └── js │ │ ├── bootstrap.js │ │ ├── highlight.pack.js │ │ └── jquery-3.1.1.js ├── build.sh ├── chapters │ ├── 0-introduction.adoc │ ├── 1-thinking_reactive.adoc │ ├── 2-observers_and_subscribers.adoc │ ├── 3-single_completable_maybe.adoc │ ├── 4-operators.adoc │ ├── 5-creating_an_rx_api.adoc │ ├── 6-schedulers.adoc │ ├── 7-testing.adoc │ └── 8-conclusion.adoc ├── docinfo.html └── index.adoc ├── pom.xml └── src ├── main ├── java │ └── me │ │ └── escoffier │ │ ├── lab │ │ ├── chapter1 │ │ │ ├── Code1.java │ │ │ └── Code2.java │ │ ├── chapter2 │ │ │ ├── Code1.java │ │ │ ├── Code1_Solution.java │ │ │ ├── Code2.java │ │ │ ├── Code2_Solution.java │ │ │ ├── Code3.java │ │ │ ├── Code3_Solution.java │ │ │ ├── Code4.java │ │ │ ├── Code5.java │ │ │ ├── Code5_Solution.java │ │ │ ├── Code6.java │ │ │ ├── Code7.java │ │ │ ├── Code8.java │ │ │ ├── Code9.java │ │ │ └── HotStream.java │ │ ├── chapter3 │ │ │ ├── Code1.java │ │ │ ├── Code10.java │ │ │ ├── Code2.java │ │ │ ├── Code2_Solution.java │ │ │ ├── Code3.java │ │ │ ├── Code4.java │ │ │ ├── Code5.java │ │ │ ├── Code5_Solution.java │ │ │ ├── Code6.java │ │ │ ├── Code7.java │ │ │ ├── Code7_Solution.java │ │ │ ├── Code8.java │ │ │ └── Code9.java │ │ ├── chapter4 │ │ │ ├── Code1.java │ │ │ ├── Code10.java │ │ │ ├── Code11.java │ │ │ ├── Code12.java │ │ │ ├── Code12_Solution.java │ │ │ ├── Code13.java │ │ │ ├── Code13_Solution.java │ │ │ ├── Code14.java │ │ │ ├── Code14_Solution.java │ │ │ ├── Code15.java │ │ │ ├── Code15_Solution.java │ │ │ ├── Code16.java │ │ │ ├── Code1_Solution.java │ │ │ ├── Code2.java │ │ │ ├── Code2_Solution.java │ │ │ ├── Code3.java │ │ │ ├── Code4.java │ │ │ ├── Code5.java │ │ │ ├── Code6.java │ │ │ ├── Code6_Solution.java │ │ │ ├── Code7.java │ │ │ ├── Code7_Solution.java │ │ │ ├── Code8.java │ │ │ └── Code9.java │ │ ├── chapter5 │ │ │ ├── AbstractSuperAPI.java │ │ │ ├── Code1.java │ │ │ ├── Code10.java │ │ │ ├── Code11.java │ │ │ ├── Code11_Solution.java │ │ │ ├── Code12.java │ │ │ ├── Code13.java │ │ │ ├── Code14.java │ │ │ ├── Code15.java │ │ │ ├── Code16.java │ │ │ ├── Code17.java │ │ │ ├── Code18.java │ │ │ ├── Code19.java │ │ │ ├── Code19_Solution.java │ │ │ ├── Code2.java │ │ │ ├── Code3.java │ │ │ ├── Code4.java │ │ │ ├── Code4_Solution.java │ │ │ ├── Code5.java │ │ │ ├── Code5_Solution.java │ │ │ ├── Code6.java │ │ │ ├── Code6_Solution.java │ │ │ ├── Code7.java │ │ │ ├── Code8.java │ │ │ ├── Code8_Solution.java │ │ │ ├── Code9.java │ │ │ ├── Code9_Solution.java │ │ │ └── SuperAPI.java │ │ ├── chapter6 │ │ │ ├── Code1.java │ │ │ ├── Code2.java │ │ │ ├── Code3.java │ │ │ ├── Code4.java │ │ │ ├── Code5.java │ │ │ ├── Code6.java │ │ │ ├── Code7.java │ │ │ ├── Code7_Solution.java │ │ │ ├── Code8.java │ │ │ └── Code8_Solution.java │ │ └── chapter7 │ │ │ ├── Code01.java │ │ │ ├── Code02.java │ │ │ ├── Code03.java │ │ │ ├── Code04.java │ │ │ └── Code05.java │ │ └── superheroes │ │ ├── Character.java │ │ ├── Helpers.java │ │ ├── Scraper.java │ │ └── SuperHeroesService.java └── resources │ ├── characters.json │ └── super │ ├── heroes │ ├── Spock │ ├── SuperGirl │ ├── Tigra │ └── Yoda │ └── villains │ ├── Sauron │ ├── Violator │ └── Walrus └── test └── java └── me └── escoffier └── lab └── chapter7 ├── AsyncTest.java ├── AsyncTest_Solution.java └── BadAsyncTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | target/ 4 | pom.xml.tag 5 | pom.xml.releaseBackup 6 | pom.xml.versionsBackup 7 | pom.xml.next 8 | release.properties 9 | dependency-reduced-pom.xml 10 | buildNumber.properties 11 | .mvn/timing.properties 12 | 13 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 14 | !/.mvn/wrapper/maven-wrapper.jar 15 | .settings 16 | .vscode 17 | docs/output/* 18 | /hello.txt 19 | .idea/ 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RX Java 2 Lab 2 | 3 | This repository is a lab about RX Java 2 and how to build RX Java API. 4 | 5 | The instructions are available on http://escoffier.me/rxjava-hol/. 6 | 7 | ## Teasing 8 | 9 | Reactive programming combines functional programming and data streams. 10 | In other words it's a bit fuzzy... 11 | 12 | RX Java 2 is a library implementing the reactive programing paradigm. 13 | In this lab, you learn how to use RX Java 2, the concepts, the operators and the good practices. 14 | It explains how RX Java 2 lets you build concurrent applications easily, how to manage errors...but RX Java 2 is not a silver bullet! 15 | 16 | This lab also covers how to propose an RX Java API. 17 | Indeed, with the reactive movements, lots of blocking and synchronous API are not usable anymore. 18 | 19 | ## Content 20 | 21 | * RX Java 2 22 | * Reactive thinking 23 | * The RX Java type of streams 24 | * The operators 25 | * How to build RX API 26 | * Schedulers and concurrency 27 | * Testing 28 | 29 | ## Want to improve this lab? 30 | 31 | Forks and pull requests are definitely welcome! 32 | 33 | ## Building 34 | 35 | **Code**: 36 | 37 | mvn clean compile 38 | 39 | **Documentation**: 40 | 41 | docker run -it -v $(pwd):/documents asciidoctor/docker-asciidoctor "docs/build.sh" 42 | 43 | 44 | -------------------------------------------------------------------------------- /build-instructions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | BASEDIR=$(cd `dirname "${0}"` && pwd) 3 | echo "Base dir: ${BASEDIR}" 4 | docker run -it -v "${BASEDIR}":/documents asciidoctor/docker-asciidoctor "docs/build.sh" -------------------------------------------------------------------------------- /docs/assets/css/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | } 14 | 15 | .hljs-comment, 16 | .hljs-quote { 17 | color: #998; 18 | font-style: italic; 19 | } 20 | 21 | .hljs-keyword, 22 | .hljs-selector-tag, 23 | .hljs-subst { 24 | color: #333; 25 | font-weight: bold; 26 | } 27 | 28 | .hljs-number, 29 | .hljs-literal, 30 | .hljs-variable, 31 | .hljs-template-variable, 32 | .hljs-tag .hljs-attr { 33 | color: #008080; 34 | } 35 | 36 | .hljs-string, 37 | .hljs-doctag { 38 | color: #d14; 39 | } 40 | 41 | .hljs-title, 42 | .hljs-section, 43 | .hljs-selector-id { 44 | color: #900; 45 | font-weight: bold; 46 | } 47 | 48 | .hljs-subst { 49 | font-weight: normal; 50 | } 51 | 52 | .hljs-type, 53 | .hljs-class .hljs-title { 54 | color: #458; 55 | font-weight: bold; 56 | } 57 | 58 | .hljs-tag, 59 | .hljs-name, 60 | .hljs-attribute { 61 | color: #000080; 62 | font-weight: normal; 63 | } 64 | 65 | .hljs-regexp, 66 | .hljs-link { 67 | color: #009926; 68 | } 69 | 70 | .hljs-symbol, 71 | .hljs-bullet { 72 | color: #990073; 73 | } 74 | 75 | .hljs-built_in, 76 | .hljs-builtin-name { 77 | color: #0086b3; 78 | } 79 | 80 | .hljs-meta { 81 | color: #999; 82 | font-weight: bold; 83 | } 84 | 85 | .hljs-deletion { 86 | background: #fdd; 87 | } 88 | 89 | .hljs-addition { 90 | background: #dfd; 91 | } 92 | 93 | .hljs-emphasis { 94 | font-style: italic; 95 | } 96 | 97 | .hljs-strong { 98 | font-weight: bold; 99 | } 100 | -------------------------------------------------------------------------------- /docs/assets/css/hol.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 10px; 3 | } 4 | 5 | p, 6 | blockquote, 7 | dt, 8 | td.content, 9 | span.alt { 10 | font-size: inherit; 11 | } 12 | 13 | #header, 14 | #content, 15 | #footnotes, 16 | #footer { 17 | max-width: 90.5em; 18 | } 19 | 20 | .quoteblock blockquote, 21 | .quoteblock blockquote, 22 | td.content { 23 | font-size: 1.2em; 24 | } 25 | 26 | .assignment pre code { 27 | background-color: transparent; 28 | } 29 | 30 | .sidebarblock p, 31 | .sidebarblock dt, 32 | .sidebarblock td.content, 33 | p.tableblock { 34 | font-size: 1em; 35 | } 36 | 37 | a[data-toggle='collapse'] { 38 | margin-bottom: 10px; 39 | } 40 | 41 | h1 { 42 | font-size: 46px; 43 | font-weight: bold; 44 | } 45 | 46 | #toctitle, 47 | .h1, 48 | .h2, 49 | .h3, 50 | .h4, 51 | .h5, 52 | .h6, 53 | h1, 54 | h2, 55 | h3, 56 | h4, 57 | h5, 58 | h6 { 59 | font-family: inherit; 60 | line-height: 1.1; 61 | color: inherit; 62 | } 63 | 64 | h1 { 65 | margin: .67em 0; 66 | font-size: 1.5em; 67 | } 68 | 69 | li p { 70 | font-size: 1em; 71 | } 72 | 73 | code { 74 | color: #c7254e; 75 | } 76 | 77 | code, 78 | kbd, 79 | pre, 80 | samp { 81 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace; 82 | } 83 | 84 | .quoteblock blockquote:before { 85 | vertical-align: baseline; 86 | } -------------------------------------------------------------------------------- /docs/assets/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/docs/assets/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /docs/assets/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/docs/assets/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/assets/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/docs/assets/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/assets/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/docs/assets/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/assets/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/docs/assets/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/docs/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /docs/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/docs/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /docs/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/docs/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /docs/assets/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/docs/assets/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /docs/assets/js/highlight.pack.js: -------------------------------------------------------------------------------- 1 | /*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ 2 | !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(//g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset"}function u(e){s+=""}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"
":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="
",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",r:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"|$)",e:">",k:{name:"style"},c:[t],starts:{e:"",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"|$)",e:">",k:{name:"script"},c:[t],starts:{e:"",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}});hljs.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"meta",r:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"comment",v:[{b:/Index: /,e:/$/},{b:/={3,}/,e:/$/},{b:/^\-{3}/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+{3}/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"addition",b:"^\\!",e:"$"}]}});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b://,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,s,a,t]}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}});hljs.registerLanguage("java",function(e){var a="[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",t=a+"(<"+a+"(\\s*,\\s*"+a+")*>)?",r="false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",s="\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",c={cN:"number",b:s,r:0};return{aliases:["jsp"],k:r,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{r:0,c:[{b:/\w+@/,r:0},{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",r:0},{cN:"function",b:"("+t+"\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:r,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},c,{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("shell",function(s){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```w*s*$",e:"^```s*$"},{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}}); -------------------------------------------------------------------------------- /docs/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | ### 5 | # IMPORTANT - this script is intended to be executed from the Docker container providing all the requirements, the code and the chapters. 6 | ## 7 | 8 | # Run using: 9 | # 10 | # ./convert.sh 11 | # 12 | # or 13 | # 14 | # ./convert.sh html,pdf 15 | # 16 | # ...where the first argument is a comma-delimited list of formats 17 | 18 | # Program paths 19 | ASCIIDOCTOR=asciidoctor 20 | FOPUB=fopub 21 | ASCIIDOCTOR_PDF=asciidoctor-pdf 22 | 23 | # File names 24 | MASTER_ADOC=index.adoc 25 | #MASTER_DOCBOOK=${MASTER_ADOC/.adoc/.xml} 26 | 27 | # Command options 28 | 29 | cd docs || exit 30 | mkdir -p output 31 | SHARED_OPTIONS='-a toc=left -a stylesheet! -a numbered -a experimental -a source-highlighter=prettify -r asciidoctor-diagram -a imagesdir=images 32 | --destination-dir=output' 33 | 34 | cp -R assets output 35 | 36 | echo "Converting to HTML ..." 37 | $ASCIIDOCTOR -v "${SHARED_OPTIONS}" "${MASTER_ADOC}" 38 | 39 | if [[ -f "chapters/*.png" ]]; then 40 | mv chapters/*.png images 41 | fi 42 | mv index.html output 43 | rm -Rf ./**/.asciidoctor 44 | 45 | 46 | exit 0 47 | -------------------------------------------------------------------------------- /docs/chapters/0-introduction.adoc: -------------------------------------------------------------------------------- 1 | == Introduction 2 | 3 | In this lab, we are going to see how Reactive eXtension for Java (RX Java) let you use reactive programming and build concurrent and responsive applications. 4 | 5 | This lab offers attendees an intro-level, hands-on session with RX Java, from the first line of code, to make a library exposing RX Java API. It illustrates what reactive programming is, and how to build applications using this paradigm. Yes, because it's a paradigm and it's quite different from _traditional_ Java development. 6 | 7 | This is a BYOL (Bring Your Own Laptop) session, so bring your Windows, OSX, or Linux laptop. You need JDK 8+ on your machine, and Apache Maven (3.5+). 8 | 9 | === Prerequisites 10 | 11 | We will get to the good stuff, coding all the way soon... But before we start, we need to install a couple of software on our machine. 12 | 13 | ==== Hardware 14 | 15 | * Operating System: whatever recent enough 16 | * Memory: At least 4 GB+ 17 | 18 | ==== Java Development Kit 19 | We need a JDK 8+ installed on our machine. Latest JDK can be downloaded from: 20 | 21 | * http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html[Oracle JDK 8] 22 | 23 | You can use either Oracle JDK or OpenJDK. 24 | 25 | ==== Apache Maven 26 | 27 | 1. Download Apache Maven from https://maven.apache.org/download.cgi. 28 | 2. Unzip to a directory of your choice and add it to the `$PATH`. 29 | 30 | ==== IDE 31 | We recommend you to use an IDE. You can use Eclipse IDE, VS Code, IntelliJ or Netbeans. 32 | 33 | _No IDE ?_ 34 | If you don’t have an IDE, here are the step to get started with Eclipse. 35 | 36 | 1. First download Eclipse from https://www.eclipse.org/downloads/[the download page]. 37 | 2. In the Eclipse Package list, select Eclipse IDE for Java Developers. It brings you to a download page with a Download button. 38 | 3. Once downloaded, unzip it. 39 | 4. In the destination directory, you should find an `eclipse` binary that you can execute. 40 | 5. Eclipse asks you to create a workspace. 41 | 6. Once launched, click on the Workbench arrow (top right corner). 42 | 43 | === Let’s start ! 44 | [source, bash] 45 | ---- 46 | git clone https://github.com/cescoffier/rxjava2-lab.git 47 | ---- 48 | 49 | You can import the code into your IDE as a Maven project. You can refer to your IDE documentation to know how to import Maven projects. 50 | 51 | For Eclipse: 52 | 53 | 1. Click on `File - Import …​ - Maven - Existing Maven Projects`` 54 | 2. Select the location where you cloned the sources 55 | 3. Click Finish and wait... 56 | 57 | === How to run the different exercises 58 | 59 | Maven is only used to retrieve a few dependencies. From the location where you cloned the repository, run: 60 | 61 | [source, bash] 62 | ---- 63 | mvn compile 64 | ---- 65 | 66 | It will download the required dependencies. 67 | 68 | The code is in `src/main/java`. All the exercises have a `public static void main(String... args)` method. To run the exercise, just run this `main` method. 69 | 70 | I believe you are ready to start! 71 | -------------------------------------------------------------------------------- /docs/chapters/1-thinking_reactive.adoc: -------------------------------------------------------------------------------- 1 | == The reactive thinking: from OOP to streams 2 | 3 | Forget everything you know about code, and look around. Modeling this world with code is not easy. As developers, we tend to follow counter-intuitive approaches. Since the 80's, object-oriented programming has been seen as a _silver bullet_. Every entity from our world is represented by an object containing fields and exposing methods. Most of the time, interacting with these objects is done using a blocking and synchronous protocol. You invoke a method and wait for a response. 4 | 5 | But... the world in which we are living is asynchronous. The interactions are done using events, messages, and stimuli. To overcome the limitations of the object orientation, many patterns and paradigms emerged. But, more recently, functional programming is making a come-back, not to replace the object-orientation, but to complement it. _Reactive programming_ is a functional event-driven programming approach that is used in combination with the regular object-oriented paradigm. 6 | 7 | A few years ago, Microsoft created a reactive programming framework for .NET called http://reactivex.io/[Reactive eXtensions] (also called ReactiveX or RX). RX is an API for asynchronous programming with **observable streams**. This API has been ported to several languages such as Swift, JavaScript, Python, C++, and Java. 8 | 9 | Let's observe our world for a moment. Observe entities in motion, traffic jams, weather, conversations, financial markets. Things are moving and evolving concurrently. Multiple _things_ happen at the same time, sometimes independently, sometimes in an orchestrated manner. Each object is creating a _stream_ of events. For instance, your mouse cursor position is moving. The sequence of position is a stream. The number of people in the room may be stable, but someone can come in or go out, generating a new value. So we have another stream of values. There is a fundamental mantra behind reactive programming: _events are data and data are events_. 10 | 11 | But don't be mistaken, reactive programming is not a silver bullet. Reactive programming and RX let you express business logic in term of streams of events, helping you with concurrency and error recovery; but don't think it's magic... because it's not. RX gives you superpowers when dealing with asynchronous scenarios, but it does not come without costs. 12 | 13 | === "Enough philosophy, I wanna see code" 14 | 15 | Alright! Let's see the code. Can we start with our beloved XML? In the `pom.xml` file located at the source of the code repository, you can see a few dependencies. One of them is: 16 | 17 | [source,xml] 18 | ---- 19 | 20 | io.reactivex.rxjava2 21 | rxjava 22 | x.y.z 23 | 24 | ---- 25 | 26 | That's the only dependency you need to start using RX Java. 27 | 28 | Now open the `src/main/java/me/escoffier/lab/chapter1/Code1.java`: 29 | 30 | [source,java] 31 | ---- 32 | include::../../src/main/java/me/escoffier/lab/chapter1/Code1.java[] 33 | ---- 34 | 35 | This is your first RX Java application. A couple of points to understand: 36 | 37 | * `import io.reactivex.*;` imports the classes form RX Java 38 | * `Observable`: represents a stream of data (here `String`). Notice the class name, it invites you to _observe_ it. 39 | * `Observable.fromIterable`: creates a stream (`Observable`) from a collection 40 | * `stream.subscribe`: declare an observer consuming the data passing in the streams. The passed lambda is called for each item. 41 | 42 | Run this example and you should see: 43 | 44 | [source, txt] 45 | ---- 46 | Superman 47 | Batman 48 | Aquaman 49 | Asterix 50 | Captain America 51 | ---- 52 | 53 | A gentle note about `subscribe`... If you don't subscribe to a stream, nothing happens. None of the processing stages will be executed until you subscribe to it. This is very important to remember to avoid thousands of hours of debugging! 54 | 55 | === That's all? 56 | 57 | Ok, not really impressive... But the true power of RX Java comes from its set of operators to manipulate the streams. Jump to `src/main/java/me/escoffier/lab/chapter1/Code2.java`: 58 | 59 | [source, java] 60 | ---- 61 | include::../../src/main/java/me/escoffier/lab/chapter1/Code2.java[] 62 | ---- 63 | 64 | This example uses two operators: 65 | 66 | 1. `map`: for each item of the observed stream, apply the function - here transform the name to uppercase 67 | 2. `filter`: for each item of the observed stream (the uppercase names), select only names starting with an `A`. 68 | 69 | Run this example, you should see: 70 | 71 | [source, txt] 72 | ---- 73 | AQUAMAN 74 | ASTERIX 75 | ---- 76 | 77 | There is a very important point to make here: operators consume items from a stream and produce a stream. The first part is simple to understand. Typically, for `filter`, it received `SUPERMAN` then `BATMAN` and so on. The second part is a bit more tricky. Let's take `map` as an example: 78 | 79 | [source] 80 | ---- 81 | Input: Superman Batman Aquaman ... <- input stream 82 | | | | 83 | Result: SUPERMAN BATMAN AQUAMAN ... <- this is also a stream 84 | ---- 85 | 86 | It produces one value per received value, in other words, a sequence of value: it's also a stream. 87 | 88 | === Ready to see more 89 | 90 | In this chapter, you have seen some very basic RX Java 2, there is a lot more... 91 | -------------------------------------------------------------------------------- /docs/chapters/2-observers_and_subscribers.adoc: -------------------------------------------------------------------------------- 1 | == Observables and Subscribers 2 | 3 | === Anatomy of a stream 4 | 5 | What's a _stream_? A stream is a sequence of data, potentially unbounded. This data may be known or unknown when the stream is created. We will see later that some special streams emit a single item and some none at all. Streams are asynchronous constructs. When you observe a stream, you don't know when data is going to be emitted. 6 | 7 | In a (regular) _stream_, 3 types of items can be conveyed, and so an observer can receive 3 types of events, each of them is notified using one of the following methods: 8 | 9 | * `onNext` - this passes each item the observed stream is emitting 10 | * `onComplete` - this communicates the end of the stream. `onNext` won't be called anymore. This notification does not happen on unbounded streams 11 | * `onError` - this communicates that something bad happened up the chain to the observer. Unless there is a `retry` (we will cover this later), the stream won't emit any more items. `onComplete` is not called either in this case. 12 | 13 | Time to go back to code. Open the `me.escoffier.lab.chapter2.Code1` class, and extend the code to display a message (using `System.out.println`) when: 14 | 15 | 1. an item is emitted 16 | 2. the stream is completed 17 | 18 | [.assignment] 19 | **** 20 | [source, java] 21 | ---- 22 | include::../../src/main/java/me/escoffier/lab/chapter2/Code1_Solution.java[] 23 | ---- 24 | **** 25 | 26 | You should get an output like: 27 | 28 | [source] 29 | ---- 30 | Next >> Superman 31 | Next >> Batman 32 | Next >> Aquaman 33 | Next >> Asterix 34 | Next >> Captain America 35 | Completion 36 | ---- 37 | 38 | Now, let's see what happens when an error occurs. In `me.escoffier.lab.chapter2.Code2`, use the `doOnComplete`, `doOnNext` and `doOnError` to print the different events. 39 | 40 | [.assignment] 41 | **** 42 | [source, java] 43 | ---- 44 | include::../../src/main/java/me/escoffier/lab/chapter2/Code2_Solution.java[] 45 | ---- 46 | **** 47 | 48 | Notice the output: 49 | 50 | [source] 51 | ---- 52 | >> SUPERMAN 53 | >> BATMAN 54 | >> AQUAMAN 55 | Oh no! What a terrible failure! 56 | ---- 57 | 58 | Once the error is reached, no more items are sent. In addition, the `doOnComplete` method is not called. 59 | 60 | While the `doOnX` methods are interesting to understand what's going on and also implement side-effects (be aware they are not necessarily a good thing), you can also receive these events in the subscriber directly. The `subscribe` method can: 61 | 62 | * receive the items such as in: `stream.subscribe(i -> {});` 63 | * receive the error such as in: `stream.subscribe(i -> {}, err -> {});` 64 | * receive the completion event such as in: `stream.subscribe(i -> {}, err -> {}, () -> {});` 65 | 66 | 67 | You can also implement the subscriber interface directly. In `me.escoffier.lab.chapter2.Code3`, use the 3 lambdas version of the `subscribe` method to print the different events. 68 | 69 | [.assignment] 70 | **** 71 | [source, java] 72 | ---- 73 | include::../../src/main/java/me/escoffier/lab/chapter2/Code3_Solution.java[] 74 | ---- 75 | **** 76 | 77 | In this example, the stream is created with the method `just` taking the list of items as a parameter. 78 | 79 | === Creating more dynamic streams 80 | 81 | So far we always used a stream with a fixed collection of values. Of course, items emitted by streams may be unknown at creation time. But let's see how streams work first. 82 | 83 | The `create` method takes a method in a parameter called on every subscription (so for every subscriber). The following code shows how this method is used: 84 | 85 | [source, java] 86 | ---- 87 | include::../../src/main/java/me/escoffier/lab/chapter2/Code4.java[] 88 | ---- 89 | 90 | Now open the `me.escoffier.lab.chapter2.Code5` class and extend it to inject a failure between two emissions. 91 | 92 | [.assignment] 93 | **** 94 | [source, java] 95 | ---- 96 | include::../../src/main/java/me/escoffier/lab/chapter2/Code5_Solution.java[] 97 | ---- 98 | **** 99 | 100 | When you run it you can notice the same behavior as before: once an error is injected, the other values are not received by the subscriber (even if the stream continues emitting errors). This is because, once the error is received, the subscription is canceled - meaning that it does not observe the events anymore. 101 | 102 | Let's see another example to exhibit this behavior: 103 | 104 | [source, java] 105 | ---- 106 | include::../../src/main/java/me/escoffier/lab/chapter2/Code6.java[] 107 | ---- 108 | 109 | This application takes user input and builds a stream out of them. So for every line, it emits the value. If the line contains "error", an error is injected. If the line contains "done" the stream is closed. If you run the application and enter: `hello`, `foo`, `error`, `not received anymore` you get the following output: 110 | 111 | [source] 112 | ---- 113 | hello 114 | Received: hello 115 | foo 116 | Received: foo 117 | error 118 | BOOM 119 | ---- 120 | 121 | === Hot vs. Cold streams 122 | 123 | Things are becoming a bit more subtle... This is a key concept to grasp before going further. Streams can be _cold_ or _hot_ (like coffee). 124 | 125 | ==== Cold streams 126 | 127 | A cold stream restarts from the beginning for each subscriber, and every subscriber gets the full set of items. For instance, let's have a look at the following code: 128 | 129 | [source, java] 130 | ---- 131 | include::../../src/main/java/me/escoffier/lab/chapter2/Code7.java[] 132 | ---- 133 | 134 | When running this application (`me.escoffier.lab.chapter2.Code7`) you get: 135 | 136 | [source] 137 | --- 138 | [A] Received: Black Canary 139 | [A] Received: Catwoman 140 | [A] Received: Elektra 141 | [A] Completion 142 | [B] Received: Black Canary 143 | [B] Received: Catwoman 144 | [B] Received: Elektra 145 | [B] Completion 146 | --- 147 | 148 | As you can see, both subscribers get the full set of items. 149 | 150 | ==== Hot streams 151 | 152 | Unlike cold streams, hot streams broadcast the same items to all listening subscribers. However, if a subscriber arrives later, it won't receive the previous items. Logically, hot streams represent _events_ or _facts_ rather than known finite data sets. 153 | 154 | Let's imagine a counter. This counter is incremented every second, and this value is emitted in a stream. When a subscriber starts listening to, it only gets the data emitted after that. This behavior is depicted in the `me.escoffier.lab.chapter2.Code8` class: 155 | 156 | [source, java] 157 | ---- 158 | include::../../src/main/java/me/escoffier/lab/chapter2/Code8.java[] 159 | ---- 160 | 161 | When running this application, you can see an output similar to: 162 | 163 | [source] 164 | ---- 165 | [A] Received: 0 166 | [A] Received: 1 167 | [B] Received: 1 168 | [A] Received: 2 169 | [B] Received: 2 170 | [A] Received: 3 171 | [B] Received: 3 172 | [A] Received: 4 173 | [B] Received: 4 174 | ... 175 | ---- 176 | 177 | The first subscriber (A) received the values 0, 1, 2, 3 and 4. The second subscriber (B) arrived one second later and so it missed the value 0. It just received 1, 2, 3 and 4. 178 | 179 | There are ways to transform cold stream into hot stream named `ConnectableObservable`. We won't cover these in this lab, but we invite you to check the https://github.com/ReactiveX/RxJava/wiki/Connectable-Observable-Operators[documentation]. 180 | 181 | ==== Stopping emissions 182 | 183 | Cleanup is always a good thing. In the previous example, we had 2 subscribers. Let's reuse this example, but this time _cancel the subscription_ after some time. 184 | 185 | The `subscribe` method returns a `io.reactivex.disposables.Disposable` object that allows canceling the subscription using the `dispose` method. The class `me.escoffier.lab.chapter2.Code9` uses this method to cancel the subscription to the hot stream: 186 | 187 | [source, java] 188 | ---- 189 | include::../../src/main/java/me/escoffier/lab/chapter2/Code9.java[] 190 | ---- 191 | 192 | Once canceled, the subscriber does not receive any event anymore. It does not get a completion event either. 193 | 194 | === Streams, streams, streams... 195 | 196 | In this chapter, we have seen how streams are structured, how they behave and how observers/subscribers work. We have only seen streams containing a set of data. In the next chapter, we will see some more special streams. 197 | 198 | -------------------------------------------------------------------------------- /docs/chapters/3-single_completable_maybe.adoc: -------------------------------------------------------------------------------- 1 | == Single, Completable, Maybe and Flowable 2 | 3 | In the previous chapters, we have used streams containing several items. In this chapter, we are going to see some specialized streams: 4 | 5 | * Single - A stream emitting 1 item 6 | * Maybe - A stream emitting 0 or 1 item 7 | * Completable - A stream containing no items 8 | * Flowable - A multi-item stream supporting back-pressure 9 | 10 | === Single 11 | 12 | A `Single` is a specialized stream that only emits one item. It works like the `Observable` streams we have seen previously but is limited to operators that make sense for a single emission. Typically, `doOnNext` and `doOnComplete` are replaced by `doOnSuccess` that accept the produced item. 13 | 14 | In `me.escoffier.lab.chapter3.Code1`, you can see how this new method is used: 15 | 16 | [source, java] 17 | ---- 18 | include::../../src/main/java/me/escoffier/lab/chapter3/Code1.java[] 19 | ---- 20 | 21 | This specialization also affects the `subscribe` method and provides a new form accepting the result and the error in a _biconsumer_. In `me.escoffier.lab.chapter3.Code2`, complete the code to use this new variant. 22 | 23 | [.assignment] 24 | **** 25 | [source, java] 26 | ---- 27 | include::../../src/main/java/me/escoffier/lab/chapter3/Code2_Solution.java[] 28 | ---- 29 | **** 30 | 31 | Singles are often used for asynchronous operations returning a single result, such as an HTTP request. The following example uses the Vert.x Web Client to retrieve a list of superheroes: 32 | 33 | [source, java] 34 | ---- 35 | include::../../src/main/java/me/escoffier/lab/chapter3/Code3.java[] 36 | ---- 37 | 38 | `client().get("/heroes").rxSend()` returns a `Single`. The rest of the processing is called when the response is received from the server. 39 | 40 | _Super Heroes & Villains service_: the previous example is the first use of this service. It runs locally on `http://localhost:8080`. Don't forget to stop the example/exercise once done or the next run won't work (because of the port already used). If you want to see what is provided by the service check: 41 | 42 | * http://localhost:8080/heroes 43 | * http://localhost:8080/villains 44 | * http://localhost:8080/heroes/random 45 | * http://localhost:8080/villains/random 46 | * http://localhost:8080/heroes/:id - replace `:id` by the id 47 | * http://localhost:8080/villains/:id - replace `:id` by the id 48 | 49 | === Maybe 50 | 51 | `Maybe` is a stream that can emit 0 or 1 item. It is useful because `Single` can't emit `null` (`null` is an illegal value). `Maybe` observers are notified: 52 | 53 | * when a value is emitted using the `onSuccess` method, 54 | * when the stream complete, without a value using the `onComplete` method, 55 | * when an error is thrown using the `onError` method 56 | 57 | Notice the subtlety about `onSuccess` and `onComplete`. The first one is called when there is a value. The second one is called when there is not. This behavior is shown in the class `me.escoffier.lab.chapter3.Code4`: 58 | 59 | [source, java] 60 | ---- 61 | include::../../src/main/java/me/escoffier/lab/chapter3/Code4.java[] 62 | ---- 63 | 64 | The output of this code is: 65 | 66 | [source] 67 | ---- 68 | [A] Received Superman 69 | [B] Completed 70 | ---- 71 | 72 | `Maybe` is often used for methods that may return `null`. For example, an asynchronous version of a `findById` method would return a `Maybe`. Let's use `Maybe` to check if there is a superhero named "Yoda" and another one named "Clement". Open the `me.escoffier.lab.chapter3.Code5` class and fill the code. The output should be something like: 73 | 74 | [source] 75 | ---- 76 | Loaded 727 heroes and villains 77 | Yes, Yoda is a superhero 78 | No, clement is not a superhero 79 | ---- 80 | 81 | [.assignment] 82 | **** 83 | [source, java] 84 | ---- 85 | include::../../src/main/java/me/escoffier/lab/chapter3/Code5_Solution.java[] 86 | ---- 87 | **** 88 | 89 | Don't forget to stop the process once done. 90 | 91 | === Completable 92 | 93 | `Completable` represents a stream not emitting a value but simply concerned with an _action_ being executed. As a consequence, it does not provide a `doOnNext` method as there is no _next_. It indicates the successful completion of a (potentially asynchronous) process or its failure: 94 | 95 | [source, java] 96 | ---- 97 | include::../../src/main/java/me/escoffier/lab/chapter3/Code6.java[] 98 | ---- 99 | 100 | In `me.escoffier.lab.chapter3.Code7`, fill the code to write a simple message into a file. For this, use the method named `rxWriteFile` that accepts the path (such as `hello.txt`) and a `Buffer` (such as `Buffer.buffer("hello")`). It returns a `Completable` indicating when the file has been written to (and it the write has been successful). 101 | 102 | [.assignment] 103 | **** 104 | [source, java] 105 | ---- 106 | include::../../src/main/java/me/escoffier/lab/chapter3/Code7_Solution.java[] 107 | ---- 108 | **** 109 | 110 | === Flowable and back pressure 111 | 112 | So far, we have seen examples of streams that were pushing items to subscribers. However, there is an issue with this model. If your consumer cannot keep up with the pace, something bad is going to happen. Putting a buffer in between will only handle small bumps. This is where back-pressure comes into the picture. But first, let's illustrate the example. 113 | 114 | In `me.escoffier.lab.chapter3.Code8`, we create a stream of integers and display them in the `subscribe` method after a small nap: 115 | 116 | [source, java] 117 | ---- 118 | include::../../src/main/java/me/escoffier/lab/chapter3/Code8.java[] 119 | ---- 120 | 121 | It gives the following output: 122 | 123 | [source] 124 | ---- 125 | Constructing item using 1 126 | Received: 1 127 | Constructing item using 2 128 | Received: 2 129 | Constructing item using 3 130 | Received : 3 131 | Constructing item using 4 132 | Received: 4 133 | ---- 134 | 135 | If you run this example, everything is fine. Each emission is processed one by one, and one at a time from the source all the way down to the subscriber. This is because a single thread is involved in the process, making everything synchronous. It creates a serialized processing, no problem so far. 136 | 137 | Now let's introduce a change of thread (`Schedulers` will be covered later - just trust us for now). This code is in `me.escoffier.lab.chapter3.Code9`: 138 | 139 | [source, java] 140 | ---- 141 | include::../../src/main/java/me/escoffier/lab/chapter3/Code9.java[] 142 | ---- 143 | 144 | Running this example produces an output similar to the following one: 145 | 146 | [source, java] 147 | ---- 148 | ... 149 | Constructing item using 707290 150 | Constructing item using 707291 151 | Constructing item using 707292 152 | Constructing item using 707293 153 | Received: 95 154 | Constructing item using 707294 155 | Constructing item using 707295 156 | Constructing item using 707296 157 | Constructing item using 707297 158 | Constructing item using 707298 159 | Constructing item using 707299 160 | ... 161 | ---- 162 | 163 | We have constructed many items, while the subscriber is only processing the item 95! The emissions of the numbers are too fast for the consumer, and because the emissions are being pushed into an unbounded buffer by `observeOn`, this can be the source of many problems such as... running out of memory. 164 | 165 | To mitigate this issue, RX Java 2 provides a stream named `Flowable`. `Flowable` is like `Observable` (it may contain multiple items) but implements a back-pressure protocol. This protocol tells the source stream to emit items at a pace specified by the consumer. `Flowable` uses a protocol named http://www.reactive-streams.org/[Reactive Streams]. This specification has been introduced in Java 9 under the name `java.util.concurrent.Flow` and is becoming widely popular. 166 | 167 | Let's revisit the previous example, but instead of `Observable.range`, let's use `Flowable.range`. This code is in `me.escoffier.lab.chapter3.Code10`: 168 | 169 | [source, java] 170 | ---- 171 | include::../../src/main/java/me/escoffier/lab/chapter3/Code10.java[] 172 | ---- 173 | 174 | Running this example produces something like: 175 | 176 | 177 | [source] 178 | ---- 179 | .... 180 | Constructing item using 123 181 | Constructing item using 124 182 | Constructing item using 125 183 | Constructing item using 126 184 | Constructing item using 127 185 | Constructing item using 128 186 | Received: 1 187 | Received: 2 188 | Received : 3 189 | Received: 4 190 | Received: 5 191 | .... 192 | Constructing item using 221 193 | Constructing item using 222 194 | Constructing item using 223 195 | Constructing item using 224 196 | Received: 97 197 | Received: 98 198 | Received: 99 199 | Received: 100 200 | Received: 101 201 | Received: 102 202 | Received: 103 203 | .... 204 | ---- 205 | 206 | What we can see here is that the source emits a set of items (128) and then, 96 items have been processed by the rest of the flow. During that time, no items have been emitted. So the consumer is telling to the source that it can't handle more at that time, and the source stops emitting. When the consumer can finally handle more items, it requests more to the source. No more risk of OOM! 207 | 208 | Notice that RX Java 2 provides some other back pressure strategies such as using buffers, or dropping data. Check the https://github.com/ReactiveX/RxJava/wiki/Backpressure-(2.0)[documentation] for further details on these strategies. 209 | 210 | === Conclusion 211 | 212 | In this chapter, we have seen a few different specialized streams to handle various situations. The following table summarizes when to use what: 213 | 214 | |=== 215 | |Type |Use case 216 | 217 | |Single | asynchronous operation returning 1 result 218 | |Maybe | asynchronous operation returning either 0 or 1 result 219 | |Completable | asynchronous operation not returning a result. Indicates the completion 220 | |Observable | sequence of data, no back pressure 221 | |Flowable| sequence of data, back-pressured 222 | |=== 223 | 224 | It's great to have all these types, but the true power of RX Java comes from its set of operators. 225 | 226 | -------------------------------------------------------------------------------- /docs/chapters/4-operators.adoc: -------------------------------------------------------------------------------- 1 | == Stream Operators 2 | 3 | We have already seen a few operators, such as `map`, `filter`, and the different `doOnX` method. In this section, we will introduce some more. We can't cover all of them, so we will just explore the most common ones. 4 | 5 | === Filter, First, Skip, Take... 6 | 7 | This first set of operators allows us to select the set of items to re-emit from a source. 8 | 9 | ==== Filter 10 | 11 | The `filter` operator accepts a predicate checking if the received item should be forwarded. In `me.escoffier.lab.chapter4.Code1#main`, fill in the code to generate a stream containing only villains with _Queen_ in their name. 12 | 13 | [.assignment] 14 | **** 15 | [source, java] 16 | ---- 17 | include::../../src/main/java/me/escoffier/lab/chapter4/Code1_Solution.java[] 18 | ---- 19 | **** 20 | 21 | _Note_: On a `Single`, the `filter` method allows you to transform it as a `Maybe`. 22 | 23 | ==== First & Last 24 | 25 | On `Observable` and `Flowable`, there are a set of _first_ operators that select the first items from a stream: 26 | 27 | * `first(T def)` takes the first item, or emits the given default if the source stream is empty. This method returns a `Single`. 28 | * `firstElement()` forwards the first item from the observed stream. This method returns a `Maybe` as the observed stream can be empty. 29 | * `firstOrError()` forwards the first item from the observed stream. If the observed stream is empty, it emits an error 30 | 31 | The `last` / `lastElement` / `lastOrError` operators are the mirror operators forwarding only the last items of the stream. 32 | 33 | ==== Skip and Take 34 | 35 | The `skip` operator creates a stream that skips the first items emitted by the observed stream and emits the remainder. `skip` accepts a number of items, but also provides a time-based variant skipping all items emitted during a given time window. `skipLast` is the mirror operator trimming the end of the stream. 36 | 37 | The `take` operator creates a stream which emits only the first items emitted by the observed stream. As `skip`, `take` provides a time-based variant and a `takeLast` variant. 38 | 39 | Open the `me.escoffier.lab.chapter4.Code2` and edit the code to create a stream containing 10 villains that are from the 21 positions to 31 positions. 40 | 41 | [.assignment] 42 | **** 43 | [source, java] 44 | ---- 45 | include::../../src/main/java/me/escoffier/lab/chapter4/Code2_Solution.java[] 46 | ---- 47 | **** 48 | 49 | The output should look like this: 50 | 51 | [source] 52 | ---- 53 | Loaded 727 heroes and villains 54 | Anti-Monitor 55 | Red Skull 56 | Anti-Spawn 57 | Red Mist 58 | Redeemer II 59 | Apocalypse 60 | Rhino 61 | Redeemer III 62 | Arclight 63 | Rick Flag 64 | ---- 65 | 66 | === Default and Switch 67 | 68 | Sometimes, when you realize that your stream is empty, you want to inject some _sensible_ defaults. That's what the `defaultIfEmpty` and `switchIfEmpty` operators are doing. 69 | 70 | `me.escoffier.lab.chapter4.Code3` shows an example of `switchIfEmpty`. `switchIfEmpty` returns a stream, so potentially several elements as in: 71 | 72 | [source, java] 73 | ---- 74 | include::../../src/main/java/me/escoffier/lab/chapter4/Code3.java[] 75 | ---- 76 | 77 | 78 | `me.escoffier.lab.chapter4.Code4` gives an example of `defaultIfEmpty`. It basically injects a default value if the observed stream is empty. 79 | 80 | [source, java] 81 | ---- 82 | include::../../src/main/java/me/escoffier/lab/chapter4/Code4.java[] 83 | ---- 84 | 85 | === Scan and Reduce 86 | 87 | Scan and reduce are two very close operators. They both take a seed (there are variants without) and an accumulator function. This function is called with the previous result and the item coming from the observed stream. 88 | 89 | The `me.escoffier.lab.chapter4.Code5` illustrates these two operators. 90 | 91 | [source, java] 92 | ---- 93 | include::../../src/main/java/me/escoffier/lab/chapter4/Code5.java[] 94 | ---- 95 | 96 | Run this example and check the difference. `reduce` is emitting a single value when the observed streams are completed. `scan` emits all computed values, including the seed (0 (seed), 0 + 0, 0 + 1, 1 + 2...). 97 | 98 | [source] 99 | ---- 100 | [Scan] Got 0 101 | [Scan] Got 0 102 | [Scan] Got 1 103 | [Scan] Got 3 104 | [Scan] Got 6 105 | [Scan] Got 10 106 | [Scan] Got 15 107 | [Scan] Got 21 108 | [Scan] Got 28 109 | [Scan] Got 36 110 | [Scan] Got 45 111 | [Reduce] Got 45 112 | ---- 113 | 114 | Open the `me.escoffier.lab.chapter4.Code6` and `me.escoffier.lab.chapter4.Code7` and fill the code to use the `scan` and `reduce` methods. You will finally know if superheroes have more superpowers than supervillains. 115 | 116 | [.assignment] 117 | **** 118 | [source, java] 119 | ---- 120 | include::../../src/main/java/me/escoffier/lab/chapter4/Code6_Solution.java[] 121 | ---- 122 | 123 | ---- 124 | include::../../src/main/java/me/escoffier/lab/chapter4/Code7_Solution.java[] 125 | ---- 126 | **** 127 | 128 | === Mapping a stream to a collection and vice-versa 129 | 130 | It's often required to accumulate items from a stream into a collection (only for bounded streams of course) or transforming a collection into a stream. 131 | 132 | RX Java provides a set of methods to transform a stream into a collection such as `toList` (creating a list of items), `toMap` building a map and to `toMultiMap` building a `Map>`. In the class `me.escoffier.lab.chapter4.Code8` you can see an example of the `toList` operator: 133 | 134 | [source, java] 135 | ---- 136 | include::../../src/main/java/me/escoffier/lab/chapter4/Code8.java[] 137 | ---- 138 | 139 | To transform a collection into a stream, use `Observable.fromIterable` or `Flowable.fromIterable`. `fromArray` is also quite convenient. 140 | 141 | === FlatMap 142 | 143 | `FlatMap` is **the** operator to understand. `FlatMap` is very powerful but requires a bit more explanation. `FlatMap` takes each item emitted by the observable stream and _maps_ it to another stream (this is the _map_ part). Then, it merges the emissions from the returned streams into a single stream (it's the _flat_ part). 144 | 145 | The simplest usage of `flatMap` is to produce a stream of items out of a single item. For example, in `me.escoffier.lab.chapter4.Code9` you can see how the `flatMap` operator is used to produce a stream of words: 146 | 147 | [source, java] 148 | ---- 149 | include::../../src/main/java/me/escoffier/lab/chapter4/Code9.java[] 150 | ---- 151 | 152 | You may wonder why the method is named `flatMapPublisher`, it's because by default `flatMap` returns a stream of the same type. So `Single.flatMap` returns a `Single`. Fortunately, the method `flatMapCompletable`, `flatMapMaybe` and `flatMapPublisher` (for `Flowable`) let us transform the type of streams. 153 | 154 | `FlatMap` is also used as a composing operator to express a sequential composition. For example, chaining two HTTP requests as illustrated in `me.escoffier.lab.chapter4.Code10`: 155 | 156 | [source, java] 157 | ---- 158 | include::../../src/main/java/me/escoffier/lab/chapter4/Code10.java[] 159 | ---- 160 | 161 | You can also use `flatMap` on a `Single` to execute a second request once the `Single` has completed as illustrated in `me 162 | .escoffier.lab.chapter4.Code11`: 163 | 164 | [source, java] 165 | ---- 166 | include::../../src/main/java/me/escoffier/lab/chapter4/Code11.java[] 167 | ---- 168 | 169 | === Merge and Zip 170 | 171 | RX Java also provides operators to merge streams or to associate items from different streams. 172 | 173 | The `mergeWith` operator merges the current stream with other ones. The produced stream contains all the items from the merged streams in the order of their emissions. If you prefer not mixing the items use `concatWith` but be aware it does not work for unbounded streams. 174 | 175 | Open the class `me.escoffier.lab.chapter4.Code12` and fill the code to count the number of superpowers. 176 | 177 | [.assignment] 178 | **** 179 | [source, java] 180 | ---- 181 | include::../../src/main/java/me/escoffier/lab/chapter4/Code12_Solution.java[] 182 | ---- 183 | **** 184 | 185 | The `zipWith` operator associates items from different streams. It takes the _next_ items emitted by each stream and calls a function with all of them. For example, we can use this to generate superheroes vs. supervillains battles. Open the `me.escoffier.lab.chapter4.Code13` class and fill the code to generate fights between heroes and villains. 186 | 187 | [.assignment] 188 | **** 189 | [source, java] 190 | ---- 191 | include::../../src/main/java/me/escoffier/lab/chapter4/Code13_Solution.java[] 192 | ---- 193 | **** 194 | 195 | === Error recovery 196 | 197 | Even when using RX, errors and failures happen. I told you it's not a silver bullet. But, I've got a good news for you: there are operators to handle error recovery. 198 | 199 | Let's start with some _simple_ case. Imagine you want to emit a request to our Super Heroes and Villains service, but the service is not started. Something like the `me.escoffier.lab.chapter4.Code14` class: 200 | 201 | [source, java] 202 | ---- 203 | include::../../src/main/java/me/escoffier/lab/chapter4/Code14.java[] 204 | ---- 205 | 206 | When you run this program, you get: 207 | 208 | [source] 209 | ---- 210 | Oh no... something bad happened: io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: localhost/127.0.0.1:8080 211 | ---- 212 | 213 | Let's now use the `onErrorReturnItem` operator that let you recover from an error by injecting a default result. There is also a variant named `onErrorReturn` that let you decide of the value to return based on the error (`Throwable`). Edit the class to use the `onErrorReturnItem` or `onErrorReturn` to avoid recover from the error. 214 | 215 | [.assignment] 216 | **** 217 | [source, java] 218 | ---- 219 | include::../../src/main/java/me/escoffier/lab/chapter4/Code14_Solution.java[] 220 | ---- 221 | **** 222 | 223 | The `onErrorResumeNext` operator is similar to `onErrorReturnItem` but returns a stream. Basically, if an error happens, it replaces the stream with the given one. Open the `me.escoffier.lab.chapter4.Code15` class and use the `onErrorResumeNext` to inject another `Single`. You can use `Single.just` to create a stream with an already known value. 224 | 225 | [.assignment] 226 | **** 227 | [source, java] 228 | ---- 229 | include::../../src/main/java/me/escoffier/lab/chapter4/Code15_Solution.java[] 230 | ---- 231 | **** 232 | 233 | The `onErrorResumeNext` is quite useful when you want to execute another async operation when an initial one failed. It also can substitute a stream in error with a default stream similar to a _switchOnError_ operator (that does not exist). 234 | 235 | The last error recovery mechanism we are going to see is very powerful but can be dangerous too. The `retry` operator re-subscribes to a stream that emitted an error. Its behavior depends on the cold/hot nature of the observed stream. On a cold stream, the resubscription restarts the stream from the beginning. It's very useful to retry a failed operation such as an HTTP call. The retry operator has several variants letting you decide whether or not you want to retry based on the error and the number of attempts. 236 | 237 | === Conclusion 238 | 239 | This chapter introduced a few operators. RX Java 2 is proposing a lot more operators. Check the https://github.com/ReactiveX/RxJava/wiki/Alphabetical-List-of-Observable-Operators[operator list] to see the full list. 240 | 241 | Now you have enough knowledge to start building an RX Java API. 242 | -------------------------------------------------------------------------------- /docs/chapters/7-testing.adoc: -------------------------------------------------------------------------------- 1 | == Testing all the things 2 | 3 | So far we've built pipelines for processing data and events, and we've run them all from a traditional Java `main` method. Of course, you know that unit and integration testing are key ingredients to building sustainable software projects, and it is now time to turn to the test facilities provided by RX Java. 4 | 5 | === Testing streams 6 | 7 | RX Java pipelines provide a special operator called `test()` that returns instances of `io.reactivex.observers.TestObserver`. As the name suggests, a `TestObserver` provides support for checking what an observable does. 8 | 9 | Here is an example (`me.escoffier.lab.chapter7.Code01`): 10 | 11 | [source, java] 12 | ---- 13 | include::../../src/main/java/me/escoffier/lab/chapter7/Code01.java[] 14 | ---- 15 | 16 | The observable produces the even numbers between 1 and 10. Once we have a `TestObserver` we can perform different kinds of assertions, like: 17 | 18 | * that the observer has subscribed, 19 | * that no emitted number is odd, 20 | * that the completion event was sent, 21 | * that there are 5 emitted values, 22 | * that those values are (in order) 2, 4, 6, 8 and 10. 23 | 24 | === Testing back-pressured streams 25 | 26 | The case of `Flowable` is a little bit different, as event emission is driven by subscribers rather than the producer (because of the back-pressure protocol). There is also a `test` method, albeit we can pass the number of items to be emitted initially, and it returns a `TestSubscriber` rather than a `TestObserver`: 27 | 28 | [source, java] 29 | ---- 30 | include::../../src/main/java/me/escoffier/lab/chapter7/Code02.java[] 31 | ---- 32 | 33 | The `requestMore(n)` method allows requesting further elements. 34 | This is interesting, as we can test what the stream emits in a step-by-step fashion: here we request 2 items, check the currently emitted values and that the completion hasn't been done yet, then request 3 further items and check completion. 35 | 36 | We can also see in this example that we can test what happens when a subscription is being canceled at a very precise point in time, here after 2 events. 37 | With non-back-pressured streams, this would be harder to do since a `TestObserver` cannot control when emissions happen. 38 | 39 | === Testing errors 40 | 41 | Of course, things do not always go according to the plan, and stream processing can end with an error. For all types of streams, we can check the error type, and also perform ad-hoc assertions on the resulting throwable object: 42 | 43 | [source, java] 44 | ---- 45 | include::../../src/main/java/me/escoffier/lab/chapter7/Code03.java[] 46 | ---- 47 | 48 | === Taking control of the clock 49 | 50 | Some streams are time-sensitive, as they emit events with delays or an interval-based pace. All of these streams need a scheduler to offload work at a later point in time. You could think of putting threads to sleep and wait for timing events to happen, but this is error-prone (non-determinism) and it slows test execution dramatically. A better solution is to use `io.reactivex.schedulers.TestScheduler`, a scheduler designed for testing. The good thing with `TestScheduler` is that you are the master of time, and you advance it manually. Here is an example where 2 `Single` are being zipped, producing a string value about 1 second from the subscription: 51 | 52 | [source, java] 53 | ---- 54 | include::../../src/main/java/me/escoffier/lab/chapter7/Code04.java[] 55 | ---- 56 | 57 | Here we make a first time advance at 500ms, and we check that no value has been emitted yet. Adding a further 600ms, we check that a value was emitted. 58 | 59 | It is interesting to note that this test actually completes fast as time is not clock time. 60 | 61 | === When you cannot control the clock 62 | 63 | Of course, it is not always possible to control the clock with a `TestScheduler`. A good example is I/O operations: we can't guarantee latency and we often use timeouts. In such cases, we need to let the test run on clock time. The following example produces string values every 500ms: 64 | 65 | [source, java] 66 | ---- 67 | include::../../src/main/java/me/escoffier/lab/chapter7/Code05.java[] 68 | ---- 69 | 70 | Since this is best-effort scheduling going on, we don't have strong guarantees on the exact timing of events, so one way to work is to wait for a bit more than the expected duration. Indeed, emissions are from another thread because of the `ticks` stream. 71 | 72 | The `awaitTerminalEvent` allows waiting for a completion or error to happen. If awaiting exceeds the duration, then it returns false and we know that we have a timeout. It is possible to use other `await...` methods, like waiting only for completion, or for a number of events. 73 | 74 | This is a much better approach than using a `Thread.sleep(t)`... 75 | 76 | === Putting it all together in JUnit 77 | 78 | We are going to test an HTTP request to a third-party service that returns 520 superheroes. This is all encapsulated in the `me.escoffier.superheroes.Helpers` class. A naive way to test is to assume traditional synchronous semantics and _just_ observe what the stream emits: 79 | 80 | [source, java] 81 | ---- 82 | include::../../src/test/java/me/escoffier/lab/chapter7/BadAsyncTest.java[] 83 | ---- 84 | 85 | 1. What happens when you run the test? 86 | 2. Why is un-commenting the `Thread.sleep(5000)` statement a fix, albeit a dirty fix? 87 | 88 | Using your knowledge of `TestSubscriber`, rewrite the test with the RX Java testing goodies in a cleaner fashion: 89 | 90 | [.assignment] 91 | **** 92 | [source, java] 93 | ---- 94 | include::../../src/test/java/me/escoffier/lab/chapter7/AsyncTest_Solution.java[] 95 | ---- 96 | **** 97 | 98 | -------------------------------------------------------------------------------- /docs/chapters/8-conclusion.adoc: -------------------------------------------------------------------------------- 1 | == Conclusion 2 | 3 | Here we are... it will be time to part. But before this, I would like to tell you a few more things. 4 | 5 | With the reactive trend, being able to provide asynchronous and reactive API is becoming more and more important. In this lab, we have seen one way to build such an API with RX Java. It's not the only way but it's a popular way. RX Java gives you superpowers to build asynchronous and concurrent applications. But as a superhero, you know "with great power comes great responsibility". 6 | 7 | Don't believe these agile gurus telling you that documentation is useless. When dealing with asynchronous things, documentation, and mainly _javadoc_, is primordial. Each method must be documented and a few sets of aspects must be described: 8 | 9 | * How errors are propagated 10 | * Back-pressure: If there is back-pressure - how the back pressure protocol behaves on your method 11 | * Scheduler: On which scheduler the method runs, whether it can be configured. 12 | 13 | For example, http://reactivex.io/RxJava/javadoc/io/reactivex/Flowable.html#delay-io.reactivex.functions.Function-[the delay operator] is documented as follows: 14 | 15 | [source, java] 16 | ---- 17 | @CheckReturnValue 18 | @BackpressureSupport(value=FULL) 19 | @SchedulerSupport(value="none") 20 | public final Flowable delay(Function> itemDelayIndicator) 21 | 22 | Returns a Flowable that delays the emissions of the source Publisher via another Publisher on a per-item basis. 23 | 24 | Note: the resulting Publisher will immediately propagate any onError notification from the source Publisher. 25 | 26 | Backpressure: The operator doesn't interfere with the backpressure behavior which is determined by the source Publisher. All of the other Publishers supplied by the function are consumed in an unbounded manner (i.e., no backpressure applied to them). 27 | 28 | Scheduler: This version of delay does not operate by default on a particular Scheduler. 29 | 30 | Type Parameters: 31 | U - the item delay value type (ignored) 32 | 33 | Parameters: 34 | itemDelayIndicator - a function that returns a Publisher for each item emitted by the source Publisher, which is then used to delay the emission of that item by the resulting Publisher until the Publisher returned from itemDelay emits an item 35 | 36 | Returns: a Flowable that delays the emissions of the source Publisher via another Publisher on a per-item basis 37 | 38 | See Also: ReactiveX operators documentation: Delay 39 | ---- 40 | 41 | The RX library proposes lots of operators. You can also implement your own. However, this is a complicated task, so before you do that, you should check the following projects providing some custom operators for RX Java 2: 42 | 43 | * https://github.com/akarnokd/RxJava2Extensions[RxJava 2.x extra sources, operators and components and ports of many 1.x companion libraries.] 44 | * https://github.com/davidmoten/rxjava2-extras[Utilities for use with RxJava 2] 45 | 46 | This time, it's the end. I hope you enjoyed this lab. Of course, comments, feedback and pull requests are more than welcome. 47 | 48 | -------------------------------------------------------------------------------- /docs/docinfo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/index.adoc: -------------------------------------------------------------------------------- 1 | = Rx Java 2 - Reactive Programming and Libraries 2 | Clement Escoffier, Julien Viet, Stéphane Epardaud, Julien Ponge, Ozan Günalp 3 | v0.3, March, 11th, 2019 4 | :toc: left 5 | :toclevels: 4 6 | :imagesdir: images 7 | :docinfo1: 8 | :icons: font 9 | :sectnums: 10 | 11 | include::chapters/0-introduction.adoc[Introduction] 12 | 13 | include::chapters/1-thinking_reactive.adoc[Thinking the reactive way] 14 | 15 | include::chapters/2-observers_and_subscribers.adoc[Streams and Subscribers] 16 | 17 | include::chapters/3-single_completable_maybe.adoc[Streams of 1, 0, 0..1 and n elements] 18 | 19 | include::chapters/4-operators.adoc[Manipulating Streams and Data] 20 | 21 | include::chapters/5-creating_an_rx_api.adoc[Creating an RX API] 22 | 23 | include::chapters/6-schedulers.adoc[Schedulers and concurrency] 24 | 25 | include::chapters/7-testing.adoc[Testing all the things] 26 | 27 | include::chapters/8-conclusion.adoc[Conclusion] 28 | 29 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | me.escoffier 6 | rxjava2-lab 7 | 1.0 8 | jar 9 | RX Java 2 Lab 10 | http://escoffier.me/rxjava2-hol 11 | 12 | UTF-8 13 | 1.8 14 | 15 | 2.2.7 16 | 3.9.4 17 | 18 | 19 | 20 | 21 | io.reactivex.rxjava2 22 | rxjava 23 | ${rxjava2.version} 24 | 25 | 26 | io.vertx 27 | vertx-core 28 | ${vertx.version} 29 | 30 | 31 | io.vertx 32 | vertx-web 33 | ${vertx.version} 34 | 35 | 36 | io.vertx 37 | vertx-web-client 38 | ${vertx.version} 39 | 40 | 41 | io.vertx 42 | vertx-rx-java2 43 | ${vertx.version} 44 | 45 | 46 | io.vertx 47 | vertx-unit 48 | ${vertx.version} 49 | 50 | 51 | 52 | org.jsoup 53 | jsoup 54 | 1.14.2 55 | 56 | 57 | 58 | junit 59 | junit 60 | 4.13.1 61 | test 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-compiler-plugin 70 | 3.8.0 71 | 72 | ${java.version} 73 | ${java.version} 74 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter1/Code1.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter1; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import io.reactivex.*; 7 | 8 | public class Code1 { 9 | 10 | private static List SUPER_HEROES = Arrays.asList( 11 | "Superman", 12 | "Batman", 13 | "Aquaman", 14 | "Asterix", 15 | "Captain America" 16 | ); 17 | 18 | public static void main(String... args) { 19 | Observable stream = Observable.fromIterable(SUPER_HEROES); 20 | stream.subscribe( 21 | name -> System.out.println(name) 22 | ); 23 | } 24 | } -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter1/Code2.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter1; 2 | 3 | import io.reactivex.Observable; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | public class Code2 { 9 | 10 | private static List SUPER_HEROES = Arrays.asList( 11 | "Superman", 12 | "Batman", 13 | "Aquaman", 14 | "Asterix", 15 | "Captain America" 16 | ); 17 | 18 | public static void main(String... args) { 19 | Observable 20 | .fromIterable(SUPER_HEROES) 21 | .map(n -> n.toUpperCase()) 22 | .filter(name -> name.startsWith("A")) 23 | .subscribe( 24 | name -> System.out.println(name) 25 | ); 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter2/Code1.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter2; 2 | 3 | 4 | import io.reactivex.Observable; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public class Code1 { 10 | 11 | private static List SUPER_HEROES = Arrays.asList( 12 | "Superman", 13 | "Batman", 14 | "Aquaman", 15 | "Asterix", 16 | "Captain America" 17 | ); 18 | 19 | public static void main(String... args) { 20 | Observable.fromIterable(SUPER_HEROES) 21 | // Use doOnNext and doOnComplete to print messages 22 | // on each item and when the stream complete 23 | .subscribe(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter2/Code1_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter2; 2 | 3 | 4 | import io.reactivex.Observable; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public class Code1_Solution { 10 | 11 | private static List SUPER_HEROES = Arrays.asList( 12 | "Superman", 13 | "Batman", 14 | "Aquaman", 15 | "Asterix", 16 | "Captain America" 17 | ); 18 | 19 | public static void main(String... args) { 20 | Observable.fromIterable(SUPER_HEROES) 21 | .doOnNext(s -> System.out.println("Next >> " + s)) 22 | .doOnComplete(() -> System.out.println("Completion")) 23 | .subscribe(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter2/Code2.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter2; 2 | 3 | 4 | import io.reactivex.Observable; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public class Code2 { 10 | 11 | private static List SUPER_HEROES = Arrays.asList( 12 | "Superman", 13 | "Batman", 14 | "Aquaman", 15 | "Asterix", 16 | "Captain America" 17 | ); 18 | 19 | public static void main(String... args) { 20 | Observable.fromIterable(SUPER_HEROES) 21 | .map(name -> { 22 | if (name.endsWith("x")) { 23 | throw new RuntimeException("What a terrible failure!"); 24 | } 25 | return name.toUpperCase(); 26 | }) 27 | // Use doOnNext, doOnComplete and doOnError to print messages 28 | // on each item, when the stream complete, and when an error occurs 29 | .subscribe(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter2/Code2_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter2; 2 | 3 | 4 | import io.reactivex.Observable; 5 | 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | public class Code2_Solution { 10 | 11 | private static List SUPER_HEROES = Arrays.asList( 12 | "Superman", 13 | "Batman", 14 | "Aquaman", 15 | "Asterix", 16 | "Captain America" 17 | ); 18 | 19 | public static void main(String... args) { 20 | Observable.fromIterable(SUPER_HEROES) 21 | .map(name -> { 22 | if (name.endsWith("x")) { 23 | throw new RuntimeException("What a terrible failure!"); 24 | } 25 | return name.toUpperCase(); 26 | }) 27 | // Use doOnNext, doOnComplete and doOnError to print messages 28 | // on each item, when the stream complete, and when an error occurs 29 | .doOnNext(s -> System.out.println(">> " + s)) 30 | .doOnComplete(() -> System.out.println("Completion... not called")) 31 | .doOnError(err -> System.out.println("Oh no! " + err.getMessage())) 32 | .subscribe(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter2/Code3.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter2; 2 | 3 | 4 | import io.reactivex.Observable; 5 | 6 | public class Code3 { 7 | 8 | public static void main(String... args) { 9 | Observable.just("Black Canary", "Catwoman", "Elektra") 10 | // Use the `subscribe` method using the 3 parameters 11 | // to receive (and print): 12 | // 1. the item 13 | // 2. the error 14 | // 3. the completion event 15 | ; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter2/Code3_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter2; 2 | 3 | 4 | import io.reactivex.Observable; 5 | 6 | public class Code3_Solution { 7 | 8 | public static void main(String... args) { 9 | Observable.just("Black Canary", "Catwoman", "Elektra") 10 | .subscribe( 11 | name -> System.out.println(">> " + name), 12 | Throwable::printStackTrace, 13 | () -> System.out.println("Completion") 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter2/Code4.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter2; 2 | 3 | 4 | import io.reactivex.Observable; 5 | 6 | public class Code4 { 7 | 8 | public static void main(String... args) { 9 | Observable stream = Observable.create(subscriber -> { 10 | // Emit items 11 | subscriber.onNext("Black Canary"); 12 | subscriber.onNext("Catwoman"); 13 | subscriber.onNext("Elektra"); 14 | // Notify the completion 15 | subscriber.onComplete(); 16 | }); 17 | 18 | stream 19 | .subscribe( 20 | i -> System.out.println("Received: " + i), 21 | err -> System.out.println("BOOM"), 22 | () -> System.out.println("Completion") 23 | ); 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter2/Code5.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter2; 2 | 3 | 4 | import io.reactivex.Observable; 5 | 6 | public class Code5 { 7 | 8 | public static void main(String... args) { 9 | Observable stream = Observable.create(subscriber -> { 10 | // Emit items 11 | subscriber.onNext("Black Canary"); 12 | subscriber.onNext("Catwoman"); 13 | // Inject an error using the onError 14 | // method 15 | subscriber.onNext("Elektra"); 16 | // Notify the completion 17 | subscriber.onComplete(); 18 | }); 19 | 20 | stream 21 | .subscribe( 22 | i -> System.out.println("Received: " + i), 23 | err -> System.out.println("BOOM"), 24 | () -> System.out.println("Completion") 25 | ); 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter2/Code5_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter2; 2 | 3 | 4 | import io.reactivex.Observable; 5 | 6 | public class Code5_Solution { 7 | 8 | public static void main(String... args) { 9 | Observable stream = Observable.create(subscriber -> { 10 | // Emit items 11 | subscriber.onNext("Black Canary"); 12 | subscriber.onNext("Catwoman"); 13 | // Inject an error 14 | subscriber.onError(new Exception("What a terrible failure")); 15 | subscriber.onNext("Elektra"); 16 | // Notify the completion 17 | subscriber.onComplete(); 18 | }); 19 | 20 | stream 21 | .subscribe( 22 | i -> System.out.println("Received: " + i), 23 | err -> System.out.println("BOOM"), 24 | () -> System.out.println("Completion") 25 | ); 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter2/Code6.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter2; 2 | 3 | 4 | import io.reactivex.Observable; 5 | 6 | import java.util.Scanner; 7 | 8 | public class Code6 { 9 | 10 | public static void main(String... args) { 11 | Observable stream = Observable.create(subscriber -> { 12 | boolean done = false; 13 | Scanner scan = new Scanner(System.in); 14 | while(! done) { 15 | String input = scan.next(); 16 | if (input.contains("done")) { 17 | done = true; 18 | subscriber.onComplete(); 19 | } else if (input.contains("error")) { 20 | subscriber.onError(new Exception(input)); 21 | } else { 22 | subscriber.onNext(input); 23 | } 24 | } 25 | }); 26 | 27 | stream 28 | .subscribe( 29 | i -> System.out.println("Received: " + i), 30 | err -> System.out.println("BOOM"), 31 | () -> System.out.println("Completion") 32 | ); 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter2/Code7.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter2; 2 | 3 | 4 | import io.reactivex.Observable; 5 | 6 | import java.util.Scanner; 7 | 8 | public class Code7 { 9 | 10 | public static void main(String... args) { 11 | Observable stream = Observable.just("Black Canary", "Catwoman", "Elektra"); 12 | 13 | stream 14 | .subscribe( 15 | i -> System.out.println("[A] Received: " + i), 16 | err -> System.out.println("[A] BOOM"), 17 | () -> System.out.println("[A] Completion") 18 | ); 19 | 20 | stream 21 | .subscribe( 22 | i -> System.out.println("[B] Received: " + i), 23 | err -> System.out.println("[B] BOOM"), 24 | () -> System.out.println("[B] Completion") 25 | ); 26 | 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter2/Code8.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter2; 2 | 3 | 4 | import io.reactivex.Observable; 5 | 6 | import static me.escoffier.lab.chapter2.HotStream.nap; 7 | 8 | public class Code8 { 9 | 10 | public static void main(String... args) { 11 | Observable stream = HotStream.create(); 12 | 13 | stream 14 | .subscribe( 15 | i -> System.out.println("[A] Received: " + i), 16 | err -> System.out.println("[A] BOOM"), 17 | () -> System.out.println("[A] Completion") 18 | ); 19 | 20 | nap(); 21 | 22 | stream 23 | .subscribe( 24 | i -> System.out.println("[B] Received: " + i), 25 | err -> System.out.println("[B] BOOM"), 26 | () -> System.out.println("[B] Completion") 27 | ); 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter2/Code9.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter2; 2 | 3 | 4 | import io.reactivex.Observable; 5 | import io.reactivex.disposables.Disposable; 6 | import io.reactivex.schedulers.Schedulers; 7 | 8 | import static me.escoffier.lab.chapter2.HotStream.nap; 9 | 10 | public class Code9 { 11 | 12 | public static void main(String... args) { 13 | Observable stream = HotStream.create(); 14 | 15 | Disposable s1 = stream 16 | .subscribe( 17 | i -> System.out.println("[A] Received: " + i), 18 | err -> System.out.println("[A] BOOM"), 19 | () -> System.out.println("[A] Completion") 20 | ); 21 | 22 | // Wait before starting the next subscriber 23 | nap(); 24 | 25 | Disposable s2 = stream 26 | .subscribe( 27 | i -> System.out.println("[B] Received: " + i), 28 | err -> System.out.println("[B] BOOM"), 29 | () -> System.out.println("[B] Completion") 30 | ); 31 | 32 | nap(5); 33 | 34 | // Cancel the subscription for A 35 | s1.dispose(); 36 | 37 | nap(3); 38 | 39 | // Cancel the subscription for B 40 | s2.dispose(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter2/HotStream.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter2; 2 | 3 | 4 | import io.reactivex.Observable; 5 | 6 | import java.util.concurrent.atomic.AtomicBoolean; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | public class HotStream { 10 | 11 | static Observable create() { 12 | AtomicInteger subscribers = new AtomicInteger(); 13 | AtomicInteger counter = new AtomicInteger(); 14 | return Observable.create(subscriber -> 15 | new Thread(() -> { 16 | while (subscribers.get() > 0) { 17 | subscriber.onNext(counter.getAndIncrement()); 18 | nap(); 19 | } 20 | }).start() 21 | ).publish().autoConnect() 22 | .doOnSubscribe(s -> subscribers.getAndIncrement()) 23 | .doOnDispose(() -> { 24 | System.out.println("A subscriber is leaving"); 25 | subscribers.decrementAndGet(); 26 | }); 27 | } 28 | 29 | public static void nap() { 30 | try { 31 | Thread.sleep(1000); 32 | } catch (InterruptedException e) { 33 | // Ignore me. 34 | } 35 | } 36 | 37 | public static void nap(int sec) { 38 | try { 39 | Thread.sleep(sec *1000); 40 | } catch (InterruptedException e) { 41 | // Ignore me. 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter3/Code1.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter3; 2 | 3 | 4 | import io.reactivex.Single; 5 | 6 | public class Code1 { 7 | 8 | 9 | public static void main(String[] args) { 10 | Single.just("Superman") 11 | .doOnSuccess(s -> System.out.println("Hello " + s)) 12 | .subscribe(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter3/Code10.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter3; 2 | 3 | import io.reactivex.Flowable; 4 | import io.reactivex.schedulers.Schedulers; 5 | import me.escoffier.lab.chapter2.HotStream; 6 | 7 | public class Code10 { 8 | 9 | public static void main(String[] args) { 10 | // Create an observable emitting all numbers between 1 and 999_999_999 11 | Flowable.range(1, 999_999_999) 12 | .map(Item::new) 13 | // Emissions are made on the caller thread (main) 14 | // The next processing stages and the terminal subscriber 15 | // is now called on a separate thread (io thread). 16 | .observeOn(Schedulers.io()) 17 | .subscribe( 18 | item -> { 19 | nap(); 20 | System.out.println("Received : " + item.i); 21 | } 22 | ); 23 | 24 | // Wait for 20 seconds. Without this the process will terminate immediately. 25 | HotStream.nap(20); 26 | } 27 | 28 | 29 | private static class Item { 30 | private final int i; 31 | 32 | Item(int number) { 33 | System.out.println("Constructing item using " + number); 34 | this.i = number; 35 | } 36 | } 37 | 38 | private static void nap() { 39 | try { 40 | Thread.sleep(50); 41 | } catch (InterruptedException e) { 42 | // Ignore me. 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter3/Code2.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter3; 2 | 3 | 4 | import io.reactivex.Single; 5 | 6 | public class Code2 { 7 | 8 | 9 | public static void main(String[] args) { 10 | Single.just("Superman") 11 | // use subscribe to retrieve the element and the error if any. 12 | ; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter3/Code2_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter3; 2 | 3 | 4 | import io.reactivex.Single; 5 | 6 | public class Code2_Solution { 7 | 8 | 9 | public static void main(String[] args) { 10 | Single.just("Superman") 11 | .subscribe( 12 | (name, err) -> { 13 | if (err == null) { 14 | System.out.println("Hello " + name); 15 | } else { 16 | err.printStackTrace(); 17 | } 18 | } 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter3/Code3.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter3; 2 | 3 | 4 | import io.vertx.core.json.JsonObject; 5 | import io.vertx.reactivex.ext.web.client.HttpResponse; 6 | import me.escoffier.superheroes.SuperHeroesService; 7 | 8 | import static me.escoffier.superheroes.Helpers.client; 9 | 10 | public class Code3 { 11 | 12 | public static void main(String[] args) { 13 | SuperHeroesService.run(); 14 | 15 | client().get("/heroes").rxSend() 16 | .map(HttpResponse::bodyAsJsonObject) 17 | .map(JsonObject::size) 18 | .subscribe(length -> System.out.println("Number of heroes: " + length)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter3/Code4.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter3; 2 | 3 | 4 | import io.reactivex.Maybe; 5 | 6 | public class Code4 { 7 | 8 | 9 | public static void main(String[] args) { 10 | Maybe.just("Superman") 11 | .subscribe( 12 | name -> System.out.println("[A] Received " + name), 13 | Throwable::printStackTrace, 14 | () -> System.out.println("[A] Completed") 15 | ); 16 | 17 | Maybe.empty() 18 | .subscribe( 19 | name -> System.out.println("[B] Received " + name + " (not called)"), 20 | Throwable::printStackTrace, 21 | () -> System.out.println("[B] Completed") 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter3/Code5.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter3; 2 | 3 | 4 | import io.vertx.core.json.JsonObject; 5 | import io.vertx.reactivex.ext.web.client.HttpResponse; 6 | import me.escoffier.superheroes.SuperHeroesService; 7 | 8 | import static me.escoffier.superheroes.Helpers.client; 9 | 10 | public class Code5 { 11 | 12 | 13 | public static void main(String[] args) { 14 | SuperHeroesService.run(); 15 | String name1 = "Yoda"; 16 | String name2 = "clement"; 17 | 18 | client().get("/heroes").rxSend() 19 | .map(HttpResponse::bodyAsJsonObject) 20 | // Use the filter operator and contains to check if the is a hero named `name1` 21 | 22 | // Don't forget to subscribe 23 | ; 24 | 25 | 26 | client().get("/heroes").rxSend() 27 | .map(HttpResponse::bodyAsJsonObject) 28 | // Use the filter operator and contains to check if the is a hero named `name2` 29 | 30 | // Don't forget to subscribe 31 | ; 32 | } 33 | 34 | private static boolean contains(String name, JsonObject json) { 35 | return json.stream().anyMatch(e -> e.getValue().toString().equalsIgnoreCase(name)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter3/Code5_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter3; 2 | 3 | 4 | import io.vertx.core.json.JsonObject; 5 | import io.vertx.reactivex.ext.web.client.HttpResponse; 6 | import me.escoffier.superheroes.SuperHeroesService; 7 | 8 | import static me.escoffier.superheroes.Helpers.client; 9 | 10 | public class Code5_Solution { 11 | 12 | 13 | public static void main(String[] args) { 14 | SuperHeroesService.run(); 15 | String name1 = "Yoda"; 16 | String name2 = "clement"; 17 | 18 | client().get("/heroes").rxSend() 19 | .map(HttpResponse::bodyAsJsonObject) 20 | .filter(json -> contains(name1, json)) 21 | .subscribe( 22 | x -> System.out.println("Yes, " + name1 + " is a super hero"), 23 | Throwable::printStackTrace, 24 | () -> System.out.println("No, " + name1 + " is not a super hero") 25 | ); 26 | 27 | 28 | client().get("/heroes").rxSend() 29 | .map(HttpResponse::bodyAsJsonObject) 30 | .filter(json -> contains(name2, json)) 31 | .subscribe( 32 | x -> System.out.println("Yes, " + name2 + " is a super hero"), 33 | Throwable::printStackTrace, 34 | () -> System.out.println("No, " + name2 + " is not a super hero") 35 | ); 36 | } 37 | 38 | private static boolean contains(String name, JsonObject json) { 39 | return json.stream().anyMatch(e -> e.getValue().toString().equalsIgnoreCase(name)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter3/Code6.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter3; 2 | 3 | 4 | import io.reactivex.Completable; 5 | 6 | public class Code6 { 7 | 8 | public static void main(String[] args) { 9 | Completable.fromAction(() -> System.out.println("Hello")) 10 | .subscribe( 11 | () -> System.out.println("OK"), 12 | err -> System.out.println("KO") 13 | ); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter3/Code7.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter3; 2 | 3 | import io.vertx.reactivex.core.buffer.Buffer; 4 | 5 | import static me.escoffier.superheroes.Helpers.fs; 6 | 7 | /** 8 | * @author Clement Escoffier 9 | */ 10 | public class Code7 { 11 | 12 | public static void main(String[] args) { 13 | fs() 14 | // Use rxWriteFile to write a message to a file 15 | // This method accept a buffer, create a buffer with Buffer.buffer("message") 16 | 17 | // Don't forget to subscribe 18 | ; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter3/Code7_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter3; 2 | 3 | import io.vertx.reactivex.core.buffer.Buffer; 4 | 5 | import static me.escoffier.superheroes.Helpers.fs; 6 | 7 | /** 8 | * @author Clement Escoffier 9 | */ 10 | public class Code7_Solution { 11 | 12 | public static void main(String[] args) { 13 | fs() 14 | .rxWriteFile( 15 | "hello.txt", Buffer.buffer("hello") 16 | ) 17 | .subscribe( 18 | () -> System.out.println("File written"), 19 | Throwable::printStackTrace 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter3/Code8.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter3; 2 | 3 | import io.reactivex.Observable; 4 | 5 | public class Code8 { 6 | 7 | public static void main(String[] args) { 8 | // Create an observable emitting all numbers between 1 and 10000 9 | Observable.range(1, 10000) 10 | .map(Item::new) 11 | .subscribe( 12 | item -> { 13 | nap(); 14 | System.out.println("Received : " + item.i); 15 | } 16 | ); 17 | } 18 | 19 | 20 | private static class Item { 21 | private final int i; 22 | 23 | Item(int number) { 24 | System.out.println("Constructing item using " + number); 25 | this.i = number; 26 | } 27 | } 28 | 29 | private static void nap() { 30 | try { 31 | Thread.sleep(50); 32 | } catch (InterruptedException e) { 33 | // Ignore me. 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter3/Code9.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter3; 2 | 3 | import io.reactivex.Observable; 4 | import io.reactivex.schedulers.Schedulers; 5 | import me.escoffier.lab.chapter2.HotStream; 6 | 7 | public class Code9 { 8 | 9 | public static void main(String[] args) { 10 | // Create an observable emitting all numbers between 1 and 999_999_999 11 | Observable.range(1, 999_999_999) 12 | .map(Item::new) 13 | // Emissions are made on the caller thread (main) 14 | // The next processing stages and the terminal subscriber 15 | // is now called on a separate thread (io thread). 16 | .observeOn(Schedulers.io()) 17 | .subscribe( 18 | item -> { 19 | nap(); 20 | System.out.println("Received : " + item.i); 21 | } 22 | ); 23 | 24 | // Wait for 20 seconds. Without this the process will terminate immediately. 25 | HotStream.nap(20); 26 | } 27 | 28 | 29 | private static class Item { 30 | private final int i; 31 | 32 | Item(int number) { 33 | System.out.println("Constructing item using " + number); 34 | this.i = number; 35 | } 36 | } 37 | 38 | private static void nap() { 39 | try { 40 | Thread.sleep(50); 41 | } catch (InterruptedException e) { 42 | // Ignore me. 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code1.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | import me.escoffier.superheroes.SuperHeroesService; 4 | 5 | import static me.escoffier.superheroes.Helpers.villains_names; 6 | 7 | public class Code1 { 8 | 9 | public static void main(String[] args) { 10 | SuperHeroesService.run(); 11 | 12 | villains_names() 13 | // Accept only super villains containing the word "Queen" 14 | .subscribe(System.out::println); 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code10.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import io.reactivex.Observable; 5 | import io.reactivex.Single; 6 | import io.vertx.core.json.JsonObject; 7 | import io.vertx.reactivex.ext.web.client.HttpResponse; 8 | import me.escoffier.superheroes.SuperHeroesService; 9 | 10 | import static me.escoffier.superheroes.Helpers.client; 11 | 12 | public class Code10 { 13 | 14 | public static void main(String[] args) { 15 | SuperHeroesService.run(); 16 | 17 | 18 | Single request1 = client() 19 | .get("/heroes") 20 | .rxSend() 21 | .map(HttpResponse::bodyAsJsonObject); 22 | 23 | 24 | request1 25 | // Transform the response to retrieve a stream of ids. 26 | .flatMapObservable(j -> Observable.fromIterable(j.fieldNames())) 27 | // Take the first one 28 | .take(1) 29 | // this is an observable of 1 element 30 | // Second request 31 | .flatMapSingle(Code10::getHero) 32 | 33 | // Print the result 34 | .subscribe(json -> System.out.println(json.encodePrettily())); 35 | 36 | } 37 | 38 | private static Single getHero(String s) { 39 | return client().get("/heroes/" + s).rxSend().map(HttpResponse::bodyAsJsonObject); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code11.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import io.reactivex.Observable; 5 | import io.reactivex.Single; 6 | import io.vertx.core.json.JsonObject; 7 | import io.vertx.reactivex.ext.web.client.HttpResponse; 8 | import me.escoffier.superheroes.SuperHeroesService; 9 | 10 | import static me.escoffier.superheroes.Helpers.client; 11 | 12 | public class Code11 { 13 | 14 | public static void main(String[] args) { 15 | SuperHeroesService.run(); 16 | 17 | 18 | Single request1 = client() 19 | .get("/heroes") 20 | .rxSend() 21 | .map(HttpResponse::bodyAsJsonObject); 22 | 23 | 24 | 25 | request1 26 | // Transform the response to retrieve a stream of ids. 27 | .flatMapObservable(j -> Observable.fromIterable(j.fieldNames())) 28 | // Take the first one, as a Single 29 | .firstOrError() 30 | 31 | // Second request 32 | .flatMap(Code11::getHero) 33 | 34 | // Print the result 35 | .subscribe(json -> System.out.println(json.encodePrettily())); 36 | 37 | } 38 | 39 | private static Single getHero(String s) { 40 | return client().get("/heroes/" + s).rxSend().map(HttpResponse::bodyAsJsonObject); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code12.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import io.reactivex.Flowable; 5 | import me.escoffier.superheroes.Character; 6 | 7 | import static me.escoffier.superheroes.Helpers.*; 8 | 9 | public class Code12 { 10 | 11 | public static void main(String[] args) { 12 | Flowable villains_superpowers = 13 | villains().map(Character::getSuperpowers) 14 | .flatMap(Flowable::fromIterable); 15 | Flowable heroes_superpowers = 16 | heroes().map(Character::getSuperpowers) 17 | .flatMap(Flowable::fromIterable); 18 | 19 | // Merge both stream using the `mergeWith` operator 20 | 21 | // Filter out duplicates using the `distinct` operator 22 | 23 | // Count the number of item using the count operator 24 | 25 | // Subscribe to print the number of unique super powers 26 | 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code12_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import io.reactivex.Flowable; 5 | import me.escoffier.superheroes.Character; 6 | 7 | import static me.escoffier.superheroes.Helpers.heroes; 8 | import static me.escoffier.superheroes.Helpers.villains; 9 | 10 | public class Code12_Solution { 11 | 12 | public static void main(String[] args) { 13 | Flowable villains_superpowers = 14 | villains().map(Character::getSuperpowers) 15 | .flatMap(Flowable::fromIterable); 16 | Flowable heroes_superpowers = 17 | heroes().map(Character::getSuperpowers) 18 | .flatMap(Flowable::fromIterable); 19 | 20 | // Merge both stream using the `mergeWith` operator 21 | 22 | villains_superpowers.mergeWith(heroes_superpowers) 23 | // Filter out duplicates using the `distinct` operator 24 | .distinct() 25 | // Count the number of item using the count operator 26 | .count() 27 | // Subscribe to print the number of unique super powers 28 | .subscribe(number -> System.out.println("Number of super powers: " + number)); 29 | 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code13.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import io.reactivex.Single; 5 | import io.vertx.core.json.JsonObject; 6 | import io.vertx.reactivex.ext.web.client.HttpResponse; 7 | import io.vertx.reactivex.ext.web.codec.BodyCodec; 8 | import me.escoffier.superheroes.SuperHeroesService; 9 | import me.escoffier.superheroes.Character; 10 | 11 | import static me.escoffier.superheroes.Helpers.client; 12 | 13 | public class Code13 { 14 | 15 | public static void main(String[] args) { 16 | SuperHeroesService.run(); 17 | 18 | Single random_heroes = client() 19 | .get("/heroes/random") 20 | .as(BodyCodec.json(Character.class)) 21 | .rxSend() 22 | .map(HttpResponse::body); 23 | 24 | Single random_villains = client() 25 | .get("/villains/random") 26 | .as(BodyCodec.json(Character.class)) 27 | .rxSend() 28 | .map(HttpResponse::body); 29 | 30 | // Associate the items emitted by both single and call the fight method. 31 | // In the subscribe print the returned json object (using encodePrettily). 32 | 33 | 34 | } 35 | 36 | private static JsonObject fight(Character h, Character v) { 37 | String winner = h.getName(); 38 | if (v.getSuperpowers().size() > h.getSuperpowers().size()) { 39 | winner = v.getName(); 40 | } else if (v.getSuperpowers().size() == h.getSuperpowers().size()) { 41 | winner = "none"; 42 | } 43 | return new JsonObject() 44 | .put("hero", h.getName()) 45 | .put("villain", v.getName()) 46 | .put("winner", winner); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code13_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import io.reactivex.Single; 5 | import io.vertx.core.json.JsonObject; 6 | import io.vertx.reactivex.ext.web.client.HttpResponse; 7 | import io.vertx.reactivex.ext.web.codec.BodyCodec; 8 | import me.escoffier.superheroes.SuperHeroesService; 9 | import me.escoffier.superheroes.Character; 10 | 11 | import static me.escoffier.superheroes.Helpers.client; 12 | 13 | public class Code13_Solution { 14 | 15 | public static void main(String[] args) { 16 | SuperHeroesService.run(); 17 | 18 | Single random_heroes = client() 19 | .get("/heroes/random") 20 | .as(BodyCodec.json(Character.class)) 21 | .rxSend() 22 | .map(HttpResponse::body); 23 | 24 | Single random_villains = client() 25 | .get("/villains/random") 26 | .as(BodyCodec.json(Character.class)) 27 | .rxSend() 28 | .map(HttpResponse::body); 29 | 30 | random_heroes.zipWith(random_villains, (h, v) -> fight(h, v)) 31 | .subscribe(j -> System.out.println(j.encodePrettily())); 32 | 33 | } 34 | 35 | private static JsonObject fight(Character h, Character v) { 36 | String winner = h.getName(); 37 | if (v.getSuperpowers().size() > h.getSuperpowers().size()) { 38 | winner = v.getName(); 39 | } else if (v.getSuperpowers().size() == h.getSuperpowers().size()) { 40 | winner = "none"; 41 | } 42 | return new JsonObject() 43 | .put("hero", h.getName()) 44 | .put("villain", v.getName()) 45 | .put("winner", winner); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code14.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import io.vertx.reactivex.ext.web.client.HttpResponse; 5 | import io.vertx.reactivex.ext.web.codec.BodyCodec; 6 | import me.escoffier.superheroes.Character; 7 | 8 | import static me.escoffier.superheroes.Helpers.client; 9 | 10 | public class Code14 { 11 | 12 | public static void main(String[] args) { 13 | client() 14 | .get("/heroes/random") 15 | .as(BodyCodec.json(Character.class)) 16 | .rxSend() 17 | .map(HttpResponse::body) 18 | .map(Character::getName) 19 | .subscribe( 20 | name -> System.out.println("Retrieved: " + name), 21 | err -> System.err.println("Oh no... something bad happened: " + err) 22 | ); 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code14_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import io.vertx.reactivex.ext.web.client.HttpResponse; 5 | import io.vertx.reactivex.ext.web.codec.BodyCodec; 6 | import me.escoffier.superheroes.Character; 7 | 8 | import static me.escoffier.superheroes.Helpers.client; 9 | 10 | public class Code14_Solution { 11 | 12 | public static void main(String[] args) { 13 | client() 14 | .get("/heroes/random") 15 | .as(BodyCodec.json(Character.class)) 16 | .rxSend() 17 | .map(HttpResponse::body) 18 | .map(Character::getName) 19 | .onErrorReturnItem("Clement, even if I'm not a superhero") 20 | .subscribe( 21 | name -> System.out.println("Retrieved: " + name), 22 | err -> System.err.println("Oh no... something bad happened: " + err) 23 | ); 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code15.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import io.vertx.reactivex.ext.web.client.HttpResponse; 5 | import io.vertx.reactivex.ext.web.codec.BodyCodec; 6 | import me.escoffier.superheroes.Character; 7 | 8 | import static me.escoffier.superheroes.Helpers.client; 9 | 10 | public class Code15 { 11 | 12 | public static void main(String[] args) { 13 | client() 14 | .get("/heroes/random") 15 | .as(BodyCodec.json(Character.class)) 16 | .rxSend() 17 | .map(HttpResponse::body) 18 | .map(Character::getName) 19 | .subscribe( 20 | name -> System.out.println("Retrieved: " + name), 21 | err -> System.err.println("Oh no... something bad happened: " + err) 22 | ); 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code15_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import io.reactivex.Single; 5 | import io.vertx.reactivex.ext.web.client.HttpResponse; 6 | import io.vertx.reactivex.ext.web.codec.BodyCodec; 7 | import me.escoffier.superheroes.Character; 8 | 9 | import static me.escoffier.superheroes.Helpers.client; 10 | 11 | public class Code15_Solution { 12 | 13 | public static void main(String[] args) { 14 | client() 15 | .get("/heroes/random") 16 | .as(BodyCodec.json(Character.class)) 17 | .rxSend() 18 | .map(HttpResponse::body) 19 | .map(Character::getName) 20 | .onErrorResumeNext(Single.just("Clement, even if I'm not a superhero")) 21 | .subscribe( 22 | name -> System.out.println("Retrieved: " + name), 23 | err -> System.err.println("Oh no... something bad happened: " + err) 24 | ); 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code16.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import io.vertx.reactivex.ext.web.client.HttpResponse; 5 | import io.vertx.reactivex.ext.web.codec.BodyCodec; 6 | import me.escoffier.superheroes.Character; 7 | 8 | import static me.escoffier.superheroes.Helpers.client; 9 | 10 | public class Code16 { 11 | 12 | public static void main(String[] args) { 13 | client() 14 | .get("/heroes/random") 15 | .as(BodyCodec.json(Character.class)) 16 | .rxSend() 17 | .map(HttpResponse::body) 18 | .map(Character::getName) 19 | .subscribe( 20 | name -> System.out.println("Retrieved: " + name), 21 | err -> System.err.println("Oh no... something bad happened: " + err) 22 | ); 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code1_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | import me.escoffier.superheroes.SuperHeroesService; 4 | 5 | import static me.escoffier.superheroes.Helpers.villains_names; 6 | 7 | public class Code1_Solution { 8 | 9 | public static void main(String[] args) { 10 | SuperHeroesService.run(); 11 | 12 | villains_names() 13 | .filter(name -> name.contains("Queen")) 14 | .subscribe(System.out::println); 15 | 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code2.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | import me.escoffier.superheroes.SuperHeroesService; 4 | 5 | import static me.escoffier.superheroes.Helpers.villains_names; 6 | 7 | public class Code2 { 8 | 9 | public static void main(String[] args) { 10 | SuperHeroesService.run(); 11 | 12 | // Extract 10 villains that are in the position 21 -> 31 in the stream 13 | villains_names() 14 | .subscribe(System.out::println); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code2_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | import me.escoffier.superheroes.SuperHeroesService; 4 | 5 | import static me.escoffier.superheroes.Helpers.villains_names; 6 | 7 | public class Code2_Solution { 8 | 9 | public static void main(String[] args) { 10 | SuperHeroesService.run(); 11 | 12 | villains_names() 13 | .skip(20) 14 | .take(10) 15 | .subscribe(System.out::println); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code3.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | import io.reactivex.Observable; 4 | import me.escoffier.superheroes.SuperHeroesService; 5 | 6 | import static me.escoffier.superheroes.Helpers.heroes_names; 7 | 8 | public class Code3 { 9 | 10 | public static void main(String[] args) { 11 | SuperHeroesService.run(); 12 | 13 | heroes_names() 14 | .filter(s -> s.equals("Asterix")) 15 | .switchIfEmpty(Observable.just("Oh", "no", "...", "Asterix", "is", "not", "a", "super", "hero")) 16 | .subscribe(System.out::println); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code4.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | import me.escoffier.superheroes.SuperHeroesService; 4 | 5 | import static me.escoffier.superheroes.Helpers.heroes_names; 6 | 7 | public class Code4 { 8 | 9 | public static void main(String[] args) { 10 | SuperHeroesService.run(); 11 | 12 | heroes_names() 13 | .filter(s -> s.equals("Asterix")) 14 | .defaultIfEmpty("Oh no... Asterix is not a super hero") 15 | .subscribe(System.out::println); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code5.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import io.reactivex.Flowable; 5 | 6 | import java.util.HashSet; 7 | 8 | import static me.escoffier.superheroes.Helpers.heroes; 9 | 10 | public class Code5 { 11 | 12 | public static void main(String[] args) { 13 | Flowable.range(0, 10) 14 | .scan(0, (last_result, item) -> last_result + item) 15 | .subscribe(i -> System.out.println("[Scan] Got " + i)); 16 | 17 | Flowable.range(0, 10) 18 | .reduce(0, (last_result, item) -> last_result + item) 19 | .subscribe(i -> System.out.println("[Reduce] Got " + i)); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code6.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import static me.escoffier.superheroes.Helpers.heroes; 5 | 6 | public class Code6 { 7 | 8 | public static void main(String[] args) { 9 | heroes() 10 | // Build a set containing all the super power from the heroes 11 | // This exercise uses the `scan` method 12 | 13 | 14 | .doOnNext(System.out::println) 15 | .count() 16 | .subscribe( 17 | number -> System.out.println("Heroes have " + number + " unique super powers") 18 | ); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code6_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import java.util.HashSet; 5 | 6 | import static me.escoffier.superheroes.Helpers.heroes; 7 | 8 | public class Code6_Solution { 9 | 10 | public static void main(String[] args) { 11 | heroes() 12 | .scan(new HashSet<>(), (set, superstuff) -> { 13 | set.addAll(superstuff.getSuperpowers()); 14 | return set; 15 | }) 16 | .doOnNext(System.out::println) 17 | .count() 18 | .subscribe( 19 | number -> System.out.println("Heroes have " + number + " unique super powers") 20 | ); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code7.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import java.util.HashSet; 5 | 6 | import static me.escoffier.superheroes.Helpers.heroes; 7 | import static me.escoffier.superheroes.Helpers.villains; 8 | 9 | public class Code7 { 10 | 11 | public static void main(String[] args) { 12 | villains() 13 | // Build a set containing all the super power from the villain 14 | // This exercise uses the `reduce` method 15 | 16 | ; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code7_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import java.util.HashSet; 5 | 6 | import static me.escoffier.superheroes.Helpers.villains; 7 | 8 | public class Code7_Solution { 9 | 10 | public static void main(String[] args) { 11 | villains() 12 | .reduce(new HashSet<>(), (set, superstuff) -> { 13 | set.addAll(superstuff.getSuperpowers()); 14 | return set; 15 | }) 16 | .doOnSuccess(System.out::println) 17 | .subscribe( 18 | set -> System.out.println("Villains have " + set.size() + " unique super powers") 19 | ); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code8.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import me.escoffier.superheroes.Character; 5 | 6 | import static me.escoffier.superheroes.Helpers.villains; 7 | 8 | public class Code8 { 9 | 10 | public static void main(String[] args) { 11 | villains() 12 | .map(Character::getName) 13 | .toList() 14 | .subscribe(list -> System.out.println("Collected " + list.size() + " names")); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter4/Code9.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter4; 2 | 3 | 4 | import io.reactivex.Flowable; 5 | import io.reactivex.Single; 6 | 7 | public class Code9 { 8 | 9 | public static void main(String[] args) { 10 | String text = "Super heroes and super villains"; 11 | Single.just(text) 12 | .flatMapPublisher(s -> Flowable.fromArray(s.split(" "))) 13 | .subscribe(System.out::println); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/AbstractSuperAPI.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Flowable; 4 | import io.reactivex.Maybe; 5 | import io.reactivex.Single; 6 | import io.vertx.core.json.JsonArray; 7 | import io.vertx.core.json.JsonObject; 8 | import io.vertx.reactivex.core.Vertx; 9 | import io.vertx.reactivex.core.file.FileSystem; 10 | import me.escoffier.superheroes.Character; 11 | 12 | /** 13 | * A bad implementation of the {@code SuperAPI} 14 | */ 15 | public class AbstractSuperAPI implements SuperAPI { 16 | 17 | protected Flowable load() { 18 | Vertx vertx = Vertx.vertx(); 19 | FileSystem fileSystem = vertx.fileSystem(); 20 | return fileSystem.rxReadFile("src/main/resources/characters.json") 21 | .map(buffer -> buffer.toString()) 22 | .map(content -> new JsonArray(content)) 23 | .flatMapPublisher(array -> Flowable.fromIterable(array)) 24 | .cast(JsonObject.class) 25 | .map(json -> json.mapTo(Character.class)); 26 | } 27 | 28 | @Override 29 | public Single hero() { 30 | return Single.error(new UnsupportedOperationException()); 31 | } 32 | 33 | @Override 34 | public Single villain() { 35 | return Single.error(new UnsupportedOperationException()); 36 | } 37 | 38 | @Override 39 | public Flowable heroes() { 40 | return Flowable.empty(); 41 | } 42 | 43 | @Override 44 | public Flowable villains() { 45 | return Flowable.empty(); 46 | } 47 | 48 | @Override 49 | public Maybe findByName(String name) { 50 | return Maybe.empty(); 51 | 52 | } 53 | 54 | @Override 55 | public Single findByNameOrError(String name) { 56 | return Single.error(new Exception("Not found")); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code1.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Single; 4 | import io.vertx.reactivex.core.Vertx; 5 | import io.vertx.reactivex.core.file.FileSystem; 6 | 7 | public class Code1 { 8 | 9 | public static void main(String[] args) { 10 | getFile().subscribe(System.out::println, Throwable::printStackTrace); 11 | } 12 | 13 | static Single getFile() { 14 | Vertx vertx = Vertx.vertx(); 15 | FileSystem fileSystem = vertx.fileSystem(); 16 | return fileSystem.rxReadFile("src/main/resources/characters.json") 17 | .map(buffer -> buffer.toString()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code10.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Observable; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.nio.file.DirectoryStream; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | 11 | public class Code10 { 12 | 13 | private static final Path DIRECTORY = new File("src/main/resources/super/heroes").toPath(); 14 | 15 | public static void main(String[] args) { 16 | Observable files = getHeroesNames(); 17 | files.subscribe(value -> System.out.println("Subscriber 1: " + value), 18 | Throwable::printStackTrace); 19 | files.subscribe(value -> System.out.println("Subscriber 2: " + value), 20 | Throwable::printStackTrace); 21 | } 22 | 23 | private static Observable getHeroesNames() { 24 | DirectoryStream stream; 25 | try { 26 | stream = Files.newDirectoryStream(DIRECTORY); 27 | } catch (IOException e) { 28 | return Observable.error(e); 29 | } 30 | return Observable.fromIterable(stream) 31 | .map(path -> path.toFile().getName()) 32 | .doFinally(stream::close); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code11.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Observable; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.nio.file.DirectoryStream; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | 11 | public class Code11 { 12 | 13 | private static final Path DIRECTORY = new File("src/main/resources/super/heroes").toPath(); 14 | 15 | public static void main(String[] args) { 16 | Observable files = getHeroesNames(); 17 | files.subscribe(value -> System.out.println("Subscriber 1: " + value), 18 | Throwable::printStackTrace); 19 | files.subscribe(value -> System.out.println("Subscriber 2: " + value), 20 | Throwable::printStackTrace); 21 | } 22 | 23 | private static Observable getHeroesNames() { 24 | return Observable.create(emitter -> { 25 | DirectoryStream stream; 26 | // Emit the directory stream here. 27 | }).map(path -> path.toFile().getName()); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code11_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.DirectoryStream; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | 9 | import io.reactivex.Observable; 10 | 11 | public class Code11_Solution { 12 | 13 | private static final Path DIRECTORY = new File("src/main/resources/super/heroes").toPath(); 14 | 15 | public static void main(String[] args) { 16 | Observable files = getHeroesNames(); 17 | files.subscribe(value -> System.out.println("Subscriber 1: " + value), 18 | Throwable::printStackTrace); 19 | files.subscribe(value -> System.out.println("Subscriber 2: " + value), 20 | Throwable::printStackTrace); 21 | } 22 | 23 | private static Observable getHeroesNames() { 24 | return Observable.create(emitter -> { 25 | DirectoryStream stream; 26 | try { 27 | stream = Files.newDirectoryStream(DIRECTORY); 28 | } catch (IOException e) { 29 | emitter.onError(e); 30 | return; 31 | } 32 | for(Path path : stream) { 33 | emitter.onNext(path); 34 | } 35 | stream.close(); 36 | emitter.onComplete(); 37 | }).map(path -> path.toFile().getName()); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code12.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Observable; 4 | import io.reactivex.Observer; 5 | import io.reactivex.disposables.Disposable; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.nio.file.DirectoryStream; 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | 13 | public class Code12 { 14 | private static final Path DIRECTORY = new File("src/main/resources/super/heroes").toPath(); 15 | 16 | 17 | public static void main(String[] args) { 18 | getNamesWithForcedConsumption(new Observer() { 19 | 20 | @Override 21 | public void onSubscribe(Disposable d) { 22 | } 23 | 24 | @Override 25 | public void onNext(String t) { 26 | System.out.println("File: " + t); 27 | } 28 | 29 | @Override 30 | public void onError(Throwable e) { 31 | e.printStackTrace(); 32 | } 33 | 34 | @Override 35 | public void onComplete() { 36 | } 37 | 38 | }); 39 | } 40 | 41 | private static void getNamesWithForcedConsumption(Observer subscriber) { 42 | Observable.create(emitter -> { 43 | DirectoryStream stream; 44 | try { 45 | stream = Files.newDirectoryStream(DIRECTORY); 46 | } catch (IOException e) { 47 | emitter.onError(e); 48 | return; 49 | } 50 | for (Path path : stream) 51 | emitter.onNext(path); 52 | stream.close(); 53 | emitter.onComplete(); 54 | }) 55 | .map(path -> path.toFile().getName()) 56 | .subscribe(subscriber); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code13.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import me.escoffier.superheroes.Character; 4 | 5 | import java.util.Arrays; 6 | 7 | public class Code13 { 8 | 9 | public static void main(String[] args) { 10 | System.out.println("Before operation"); 11 | Character character = getBlockingSuperVillain(); 12 | System.out.println("After operation: " + character); 13 | } 14 | 15 | private static Character getBlockingSuperVillain() { 16 | System.out.println("Operation starting"); 17 | try { 18 | Thread.sleep(1000); 19 | } catch (InterruptedException e) { 20 | // Ignore me. 21 | } 22 | System.out.println("Operation done"); 23 | return new Character("Frog-Man", 24 | Arrays.asList("super strength", "leaping", "mega agility", "French"), 25 | false); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code14.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Single; 4 | import me.escoffier.superheroes.Character; 5 | 6 | import java.util.Arrays; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | public class Code14 { 10 | 11 | public static void main(String[] args) { 12 | System.out.println("Before operation"); 13 | getBlockingSuperVillain() 14 | .subscribe(value -> System.out.println("Operation completed: " + value)); 15 | } 16 | 17 | private static Single getBlockingSuperVillain() { 18 | return Single.just(new Character("Frog-Man", 19 | Arrays.asList("super strength", "leaping", "mega agility", "French"), 20 | false)) 21 | .delay(1, TimeUnit.SECONDS) 22 | .doAfterTerminate(() -> System.out.println("Operation done")); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code15.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Single; 4 | import me.escoffier.superheroes.Character; 5 | 6 | import java.util.Arrays; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | public class Code15 { 10 | 11 | public static void main(String[] args) throws InterruptedException { 12 | System.out.println("Before operation"); 13 | getBlockingSuperVillain() 14 | .subscribe(value -> System.out.println("Operation completed: " + value)); 15 | 16 | Thread.sleep(2000); 17 | } 18 | 19 | private static Single getBlockingSuperVillain() { 20 | return Single.just(new Character("Frog-Man", 21 | Arrays.asList("super strength", "leaping", "mega agility", "French"), 22 | false)) 23 | .delay(1, TimeUnit.SECONDS) 24 | .doAfterTerminate(() -> System.out.println("Operation done")); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code16.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Single; 4 | import me.escoffier.superheroes.Character; 5 | 6 | import java.util.Arrays; 7 | import java.util.concurrent.CountDownLatch; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public class Code16 { 11 | 12 | public static void main(String[] args) throws InterruptedException { 13 | System.out.println("Before operation"); 14 | CountDownLatch latch = new CountDownLatch(1); 15 | getBlockingSuperVillain() 16 | .subscribe(value -> { 17 | System.out.println("Operation completed: " + value); 18 | latch.countDown(); 19 | }); 20 | System.out.println("After operation"); 21 | latch.await(); 22 | } 23 | 24 | private static Single getBlockingSuperVillain() { 25 | return Single.just(new Character("Frog-Man", 26 | Arrays.asList("super strength", "leaping", "mega agility", "French"), 27 | false)) 28 | .delay(1, TimeUnit.SECONDS) 29 | .doAfterTerminate(() -> System.out.println("Operation done")); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code17.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Single; 4 | import me.escoffier.superheroes.Character; 5 | 6 | import java.util.Arrays; 7 | import java.util.concurrent.ExecutionException; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public class Code17 { 11 | 12 | public static void main(String[] args) throws InterruptedException, ExecutionException { 13 | System.out.println("Before operation"); 14 | Character value = getBlockingSuperVillain().toFuture().get(); 15 | System.out.println("Operation completed: " + value); 16 | } 17 | 18 | private static Single getBlockingSuperVillain() { 19 | return Single.just(new Character("Frog-Man", 20 | Arrays.asList("super strength", "leaping", "mega agility", "French"), 21 | false)) 22 | .delay(1, TimeUnit.SECONDS) 23 | .doAfterTerminate(() -> System.out.println("Operation done")); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code18.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Single; 4 | import me.escoffier.superheroes.Character; 5 | 6 | import java.util.Arrays; 7 | import java.util.concurrent.ExecutionException; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public class Code18 { 11 | 12 | public static void main(String[] args) { 13 | System.out.println("Before operation"); 14 | Character value = getBlockingSuperVillain().blockingGet(); 15 | System.out.println("Operation completed: " + value); 16 | } 17 | 18 | private static Single getBlockingSuperVillain() { 19 | return Single.just(new Character("Frog-Man", 20 | Arrays.asList("super strength", "leaping", "mega agility", "French"), 21 | false)) 22 | .delay(1, TimeUnit.SECONDS) 23 | .doAfterTerminate(() -> System.out.println("Operation done")); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code19.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Single; 4 | import me.escoffier.superheroes.Character; 5 | 6 | import java.util.Arrays; 7 | 8 | public class Code19 { 9 | 10 | public static void main(String[] args) { 11 | System.out.println("Before operation"); 12 | Character result = getBlockingSuperVillain().blockingGet(); 13 | System.out.println("After operation: " + result); 14 | } 15 | 16 | private static Single getBlockingSuperVillain() { 17 | return Single.create(emitter -> 18 | new Thread(() -> { 19 | System.out.println("Operation starting"); 20 | // Do the blocking operation 21 | // and emit 22 | }).start() 23 | ); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code19_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Single; 4 | import me.escoffier.superheroes.Character; 5 | 6 | import java.util.Arrays; 7 | 8 | public class Code19_Solution { 9 | 10 | public static void main(String[] args) { 11 | System.out.println("Before operation"); 12 | Character result = getBlockingSuperVillain().blockingGet(); 13 | System.out.println("After operation: " + result); 14 | } 15 | 16 | private static Single getBlockingSuperVillain() { 17 | return Single.create(emitter -> 18 | new Thread(() -> { 19 | System.out.println("Operation starting"); 20 | try { 21 | Thread.sleep(1000); 22 | } catch (InterruptedException e) { 23 | emitter.onError(e); 24 | return; 25 | } 26 | emitter.onSuccess(new Character("Frog-Man", 27 | Arrays.asList("super strength", "leaping", "mega agility", "French"), 28 | false)); 29 | }).start() 30 | ); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code2.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Single; 4 | import io.vertx.core.json.JsonArray; 5 | import io.vertx.reactivex.core.Vertx; 6 | import io.vertx.reactivex.core.file.FileSystem; 7 | 8 | public class Code2 { 9 | 10 | public static void main(String[] args) { 11 | load() 12 | .map(array -> array.encodePrettily()) 13 | .subscribe(System.out::println, Throwable::printStackTrace); 14 | } 15 | 16 | static Single load() { 17 | Vertx vertx = Vertx.vertx(); 18 | FileSystem fileSystem = vertx.fileSystem(); 19 | return fileSystem.rxReadFile("src/main/resources/characters.json") 20 | .map(buffer -> buffer.toString()) 21 | .map(content -> new JsonArray(content)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code3.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Flowable; 4 | import io.reactivex.Observable; 5 | import io.reactivex.Single; 6 | import io.vertx.core.json.JsonArray; 7 | import io.vertx.core.json.JsonObject; 8 | import io.vertx.reactivex.core.Vertx; 9 | import io.vertx.reactivex.core.file.FileSystem; 10 | import me.escoffier.superheroes.Character; 11 | 12 | public class Code3 { 13 | 14 | public static void main(String[] args) { 15 | load() 16 | .subscribe(System.out::println, Throwable::printStackTrace); 17 | } 18 | 19 | static Flowable load() { 20 | Vertx vertx = Vertx.vertx(); 21 | FileSystem fileSystem = vertx.fileSystem(); 22 | return fileSystem.rxReadFile("src/main/resources/characters.json") 23 | .map(buffer -> buffer.toString()) 24 | .map(content -> new JsonArray(content)) 25 | .flatMapPublisher(array -> Flowable.fromIterable(array)) 26 | .cast(JsonObject.class) 27 | .map(json -> json.mapTo(Character.class)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code4.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Flowable; 4 | import me.escoffier.superheroes.Character; 5 | 6 | public class Code4 extends AbstractSuperAPI { 7 | 8 | public static void main(String[] args) { 9 | new Code4().heroes() 10 | .count() 11 | .subscribe(i -> System.out.println(i + " heroes loaded"), Throwable::printStackTrace); 12 | 13 | new Code4().villains() 14 | .count() 15 | .subscribe(i -> System.out.println(i + " villains loaded"), Throwable::printStackTrace); 16 | } 17 | 18 | @Override 19 | public Flowable heroes() { 20 | return load() 21 | // Select only heroes 22 | ; 23 | } 24 | 25 | @Override 26 | public Flowable villains() { 27 | return load() 28 | // Select only villains 29 | ; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code4_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Flowable; 4 | import me.escoffier.superheroes.Character; 5 | 6 | public class Code4_Solution extends AbstractSuperAPI { 7 | 8 | public static void main(String[] args) { 9 | new Code4_Solution().heroes() 10 | .count() 11 | .subscribe(i -> System.out.println(i + "heroes loaded"), Throwable::printStackTrace); 12 | 13 | new Code4_Solution().villains() 14 | .count() 15 | .subscribe(i -> System.out.println(i + "villains loaded"), Throwable::printStackTrace); 16 | } 17 | 18 | @Override 19 | public Flowable heroes() { 20 | return load() 21 | .filter(character -> !character.isVillain()); 22 | } 23 | 24 | @Override 25 | public Flowable villains() { 26 | return load() 27 | .filter(character -> character.isVillain()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code5.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Single; 4 | import me.escoffier.superheroes.Character; 5 | 6 | public class Code5 extends AbstractSuperAPI { 7 | 8 | public static void main(String[] args) { 9 | new Code5().hero() 10 | .subscribe(System.out::println, Throwable::printStackTrace); 11 | 12 | new Code5().villain() 13 | .subscribe(System.out::println, Throwable::printStackTrace); 14 | } 15 | 16 | @Override 17 | public Single hero() { 18 | return load() 19 | // Implement the pipeline to return a random hero. 20 | .firstOrError(); 21 | } 22 | 23 | @Override 24 | public Single villain() { 25 | return load() 26 | // Implement the pipeline to return a random villain. 27 | .firstOrError(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code5_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Observable; 4 | import io.reactivex.Single; 5 | import me.escoffier.superheroes.Character; 6 | 7 | import java.util.Collections; 8 | 9 | public class Code5_Solution extends AbstractSuperAPI { 10 | 11 | public static void main(String[] args) { 12 | new Code5_Solution().hero() 13 | .subscribe(System.out::println, Throwable::printStackTrace); 14 | 15 | new Code5_Solution().villain() 16 | .subscribe(System.out::println, Throwable::printStackTrace); 17 | } 18 | 19 | @Override 20 | public Single hero() { 21 | return load() 22 | .filter(character -> !character.isVillain()) 23 | .toList() 24 | .map(list -> { 25 | Collections.shuffle(list); 26 | return list; 27 | }) 28 | .flatMapObservable(list -> Observable.fromIterable(list)) 29 | .firstOrError(); 30 | } 31 | 32 | @Override 33 | public Single villain() { 34 | return load() 35 | .filter(character -> character.isVillain()) 36 | .toList() 37 | .map(list -> { 38 | if (list.isEmpty()) { 39 | throw new RuntimeException("No villains"); 40 | } 41 | Collections.shuffle(list); 42 | return list.get(0); 43 | }); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code6.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Maybe; 4 | import io.reactivex.Single; 5 | import me.escoffier.superheroes.Character; 6 | 7 | public class Code6 extends AbstractSuperAPI { 8 | 9 | public static void main(String[] args) { 10 | new Code6().findByName("SuperGirl") 11 | .subscribe( 12 | c -> System.out.println(c.getName() + " is a super " + (c.isVillain() ? "villain" : "hero")), 13 | Throwable::printStackTrace, 14 | () -> System.out.println("Nope")); 15 | 16 | new Code6().findByName("Clement") 17 | .subscribe( 18 | c -> System.out.println(c.getName() + " is a super " + (c.isVillain() ? "villain" : "hero")), 19 | Throwable::printStackTrace, 20 | () -> System.out.println("No, Clement is not a " + "super hero (and not a super villain either despite the rumor)")); 21 | 22 | new Code6().findByNameOrError("Yoda") 23 | .subscribe( 24 | c -> System.out.println(c.getName() + " is a super " + (c.isVillain() ? "villain" : "hero")), 25 | Throwable::printStackTrace); 26 | 27 | new Code6().findByNameOrError("Clement") 28 | .subscribe( 29 | c -> System.out.println(c.getName() + " is a super " + (c.isVillain() ? "villain" : "hero")), 30 | t -> System.out.println("The lookup as failed, as expected, Clement is neither a super hero or super villain")); 31 | } 32 | 33 | @Override 34 | public Maybe findByName(String name) { 35 | // To implement 36 | return super.findByName(name); 37 | } 38 | 39 | @Override 40 | public Single findByNameOrError(String name) { 41 | // To implement 42 | return super.findByNameOrError(name); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code6_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Maybe; 4 | import io.reactivex.Single; 5 | import me.escoffier.superheroes.Character; 6 | 7 | public class Code6_Solution extends AbstractSuperAPI { 8 | 9 | public static void main(String[] args) { 10 | new Code6_Solution().findByName("SuperGirl") 11 | .subscribe( 12 | c -> System.out.println(c.getName() + " is a super " + (c.isVillain() ? "villain" : "hero")), 13 | Throwable::printStackTrace, 14 | () -> System.out.println("Nope")); 15 | 16 | new Code6_Solution().findByName("Clement") 17 | .subscribe( 18 | c -> System.out.println(c.getName() + " is a super " + (c.isVillain() ? "villain" : "hero")), 19 | Throwable::printStackTrace, 20 | () -> System.out.println("No, Clement is not a " + "super hero (and not a super villain either despite the rumor)")); 21 | 22 | new Code6_Solution().findByNameOrError("Yoda") 23 | .subscribe( 24 | c -> System.out.println(c.getName() + " is a super " + (c.isVillain() ? "villain" : "hero")), 25 | Throwable::printStackTrace); 26 | 27 | new Code6_Solution().findByNameOrError("Clement") 28 | .subscribe( 29 | c -> System.out.println(c.getName() + " is a super " + (c.isVillain() ? "villain" : "hero")), 30 | t -> System.out.println("The lookup as failed, as expected, Clement is neither a super hero or super villain")); 31 | } 32 | 33 | @Override 34 | public Maybe findByName(String name) { 35 | return load() 36 | .filter(c -> c.getName().equalsIgnoreCase(name)) 37 | .firstElement(); 38 | } 39 | 40 | @Override 41 | public Single findByNameOrError(String name) { 42 | return load() 43 | .filter(c -> c.getName().equalsIgnoreCase(name)) 44 | .firstOrError(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code7.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Flowable; 4 | import io.reactivex.Single; 5 | import io.vertx.core.json.JsonArray; 6 | import io.vertx.core.json.JsonObject; 7 | import me.escoffier.superheroes.Character; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.nio.ByteBuffer; 12 | import java.nio.channels.AsynchronousFileChannel; 13 | import java.nio.channels.CompletionHandler; 14 | import java.nio.charset.StandardCharsets; 15 | 16 | public class Code7 extends AbstractSuperAPI { 17 | 18 | public static void main(String[] args) throws InterruptedException { 19 | new Code7() 20 | .load() 21 | .subscribe(System.out::println, Throwable::printStackTrace); 22 | Thread.sleep(2000); 23 | } 24 | 25 | @Override 26 | protected Flowable load() { 27 | File file = new File("src/main/resources/characters.json"); 28 | return Single.create(emitter -> { 29 | AsynchronousFileChannel channel = AsynchronousFileChannel.open(file.toPath()); 30 | ByteBuffer buffer = ByteBuffer.allocate((int) file.length()); 31 | 32 | channel.read(buffer, 0, null, new CompletionHandler() { 33 | 34 | @Override 35 | public void completed(Integer result, Void attachment) { 36 | try { 37 | channel.close(); 38 | } catch (IOException e) { 39 | emitter.onError(e); 40 | return; 41 | } 42 | emitter.onSuccess(buffer); 43 | } 44 | 45 | @Override 46 | public void failed(Throwable error, Void attachment) { 47 | try { 48 | channel.close(); 49 | } catch (IOException e) { 50 | // ignore 51 | } 52 | emitter.onError(error); 53 | } 54 | }); 55 | }) 56 | .map(buffer -> new String(buffer.array(), StandardCharsets.UTF_8)) 57 | .map(JsonArray::new) 58 | .flatMapPublisher(Flowable::fromIterable) 59 | .cast(JsonObject.class) 60 | .map(json -> json.mapTo(Character.class)); 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code8.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Observable; 4 | 5 | import java.io.File; 6 | import java.nio.file.FileVisitResult; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.SimpleFileVisitor; 10 | import java.nio.file.attribute.BasicFileAttributes; 11 | 12 | public class Code8 { 13 | 14 | private static final Path DIRECTORY = new File("src/main/resources/super").toPath(); 15 | 16 | public static void main(String[] args) { 17 | getFileNames().subscribe(System.out::println, Throwable::printStackTrace); 18 | } 19 | 20 | private static Observable getFileNames() { 21 | return Observable.create(emitter -> { 22 | Files.walkFileTree(DIRECTORY, 23 | new SimpleFileVisitor() { 24 | @Override 25 | public FileVisitResult visitFile(Path path, BasicFileAttributes attr) { 26 | // ... 27 | return FileVisitResult.CONTINUE; 28 | } 29 | }); 30 | // ... 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code8_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Observable; 4 | 5 | import java.io.File; 6 | import java.nio.file.FileVisitResult; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | import java.nio.file.SimpleFileVisitor; 10 | import java.nio.file.attribute.BasicFileAttributes; 11 | 12 | public class Code8_Solution { 13 | 14 | private static final Path DIRECTORY = new File("src/main/resources/super").toPath(); 15 | 16 | public static void main(String[] args) { 17 | getFileNames().subscribe(System.out::println, Throwable::printStackTrace); 18 | } 19 | 20 | private static Observable getFileNames() { 21 | return Observable.create(emitter -> { 22 | Files.walkFileTree(DIRECTORY, 23 | new SimpleFileVisitor() { 24 | @Override 25 | public FileVisitResult visitFile(Path path, BasicFileAttributes attr) { 26 | emitter.onNext(path.toFile().getName()); 27 | return FileVisitResult.CONTINUE; 28 | } 29 | }); 30 | emitter.onComplete(); 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code9.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.DirectoryStream; 6 | import java.nio.file.Files; 7 | import java.nio.file.Path; 8 | 9 | import io.reactivex.Observable; 10 | 11 | public class Code9 { 12 | 13 | private static final Path DIRECTORY = new File("src/main/resources/super/heroes").toPath(); 14 | 15 | public static void main(String[] args) { 16 | getHeroesNames().subscribe(System.out::println, Throwable::printStackTrace); 17 | } 18 | 19 | private static Observable getHeroesNames() { 20 | DirectoryStream stream; 21 | try { 22 | stream = Files.newDirectoryStream(DIRECTORY); 23 | } catch (IOException e) { 24 | return Observable.error(e); 25 | } 26 | return Observable.fromIterable(stream) 27 | .map(path -> path.toFile().getName()) 28 | // ... 29 | ; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/Code9_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.*; 6 | 7 | import io.reactivex.Observable; 8 | 9 | public class Code9_Solution { 10 | 11 | private static final Path DIRECTORY = new File("src/main/resources/super/heroes").toPath(); 12 | 13 | public static void main(String[] args) { 14 | getHeroesNames().subscribe(System.out::println, Throwable::printStackTrace); 15 | } 16 | 17 | private static Observable getHeroesNames() { 18 | DirectoryStream stream; 19 | try { 20 | stream = Files.newDirectoryStream(DIRECTORY); 21 | } catch (IOException e) { 22 | return Observable.error(e); 23 | } 24 | return Observable.fromIterable(stream) 25 | .map(path -> path.toFile().getName()) 26 | .doFinally(() -> stream.close()); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter5/SuperAPI.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter5; 2 | 3 | import io.reactivex.Flowable; 4 | import io.reactivex.Maybe; 5 | import io.reactivex.Single; 6 | import me.escoffier.superheroes.Character; 7 | 8 | /** 9 | * An API to retrieve Super heroes and villains 10 | */ 11 | public interface SuperAPI { 12 | 13 | /** 14 | * @return a random hero 15 | */ 16 | Single hero(); 17 | 18 | /** 19 | * @return a random villain 20 | */ 21 | Single villain(); 22 | 23 | /** 24 | * @return all heroes 25 | */ 26 | Flowable heroes(); 27 | 28 | /** 29 | * @return all villains 30 | */ 31 | Flowable villains(); 32 | 33 | /** 34 | * Looks for a character with the given name. 35 | * 36 | * @param name the name of the character. Must not be {@code null} 37 | * @return a {@code Maybe} completed with the found character, empty otherwise 38 | */ 39 | Maybe findByName(String name); 40 | 41 | /** 42 | * Looks for a character with the given name. 43 | * 44 | * @param name the name of the character. Must not be {@code null} 45 | * @return a {@code Single} completed with the found character, or failed if not found 46 | */ 47 | Single findByNameOrError(String name); 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter6/Code1.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter6; 2 | 3 | import io.reactivex.Observable; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | import static me.escoffier.superheroes.Helpers.log; 9 | 10 | public class Code1 { 11 | 12 | private static List SUPER_HEROES = Arrays.asList( 13 | "Superman", 14 | "Batman", 15 | "Aquaman", 16 | "Asterix", 17 | "Captain America" 18 | ); 19 | 20 | public static void main(String[] args) { 21 | Observable observable = Observable.create(emitter -> { 22 | for (String superHero : SUPER_HEROES) { 23 | log("Emitting: " + superHero); 24 | emitter.onNext(superHero); 25 | } 26 | log("Completing"); 27 | emitter.onComplete(); 28 | }); 29 | 30 | log("---------------- Subscribing"); 31 | observable.subscribe( 32 | item -> log("Received " + item), 33 | error -> log("Error"), 34 | () -> log("Complete")); 35 | log("---------------- Subscribed"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter6/Code2.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter6; 2 | 3 | import io.reactivex.Observable; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | import static me.escoffier.superheroes.Helpers.log; 9 | import static me.escoffier.superheroes.Helpers.sleep; 10 | 11 | public class Code2 { 12 | 13 | private static List SUPER_HEROES = Arrays.asList( 14 | "Superman", 15 | "Batman", 16 | "Aquaman", 17 | "Asterix", 18 | "Captain America" 19 | ); 20 | 21 | public static void main(String[] args) { 22 | Observable observable = Observable.create(emitter -> { 23 | for (String superHero : SUPER_HEROES) { 24 | sleep(30); // Introduce fake latency 25 | log("Emitting: " + superHero); 26 | emitter.onNext(superHero); 27 | } 28 | log("Completing"); 29 | emitter.onComplete(); 30 | }); 31 | 32 | log("---------------- Subscribing"); 33 | observable.subscribe( 34 | item -> log("Received " + item), 35 | error -> log("Error"), 36 | () -> log("Complete")); 37 | log("---------------- Subscribed"); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter6/Code3.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter6; 2 | 3 | import io.reactivex.Observable; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | import static me.escoffier.superheroes.Helpers.log; 9 | 10 | public class Code3 { 11 | 12 | private static List SUPER_HEROES = Arrays.asList( 13 | "Superman", 14 | "Batman", 15 | "Aquaman", 16 | "Asterix", 17 | "Captain America" 18 | ); 19 | 20 | public static void main(String[] args) { 21 | Observable observable = Observable.create(emitter -> { 22 | new Thread(() -> { 23 | for (String superHero : SUPER_HEROES) { 24 | log("Emitting: " + superHero); 25 | emitter.onNext(superHero); 26 | } 27 | log("Completing"); 28 | emitter.onComplete(); 29 | }).start(); 30 | }); 31 | 32 | log("---------------- Subscribing"); 33 | observable.subscribe( 34 | item -> log("Received " + item), 35 | error -> log("Error"), 36 | () -> log("Complete")); 37 | log("---------------- Subscribed"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter6/Code4.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter6; 2 | 3 | import io.reactivex.Observable; 4 | 5 | import java.util.Arrays; 6 | import java.util.List; 7 | 8 | import static me.escoffier.superheroes.Helpers.log; 9 | import static me.escoffier.superheroes.Helpers.sleep; 10 | 11 | public class Code4 { 12 | 13 | private static List SUPER_HEROES = Arrays.asList( 14 | "Superman", 15 | "Batman", 16 | "Aquaman", 17 | "Asterix", 18 | "Captain America" 19 | ); 20 | 21 | public static void main(String[] args) { 22 | Observable observable = Observable.create(emitter -> { 23 | Thread thread = new Thread(() -> { 24 | for (String superHero : SUPER_HEROES) { 25 | log("Emitting: " + superHero); 26 | emitter.onNext(superHero); 27 | } 28 | log("Completing"); 29 | emitter.onComplete(); 30 | }); 31 | thread.start(); 32 | }); 33 | 34 | log("---------------- Subscribing"); 35 | observable 36 | // Blocking the emission thread 37 | .doOnNext(x -> sleep(30)) 38 | .subscribe( 39 | item -> log("Received " + item), 40 | error -> log("Error"), 41 | () -> log("Complete")); 42 | log("---------------- Subscribed"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter6/Code5.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter6; 2 | 3 | import io.reactivex.Observable; 4 | import io.reactivex.Scheduler; 5 | import io.reactivex.schedulers.Schedulers; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.concurrent.CountDownLatch; 10 | 11 | import static java.util.concurrent.Executors.newFixedThreadPool; 12 | import static me.escoffier.superheroes.Helpers.log; 13 | import static me.escoffier.superheroes.Helpers.threadFactory; 14 | 15 | public class Code5 { 16 | 17 | private static List SUPER_HEROES = Arrays.asList( 18 | "Superman", 19 | "Batman", 20 | "Aquaman", 21 | "Asterix", 22 | "Captain America" 23 | ); 24 | 25 | public static void main(String[] args) throws Exception { 26 | 27 | Scheduler scheduler = Schedulers.from(newFixedThreadPool(10, threadFactory)); 28 | 29 | CountDownLatch latch = new CountDownLatch(1); 30 | 31 | // Synchronous emission 32 | Observable observable = Observable.create(emitter -> { 33 | for (String superHero : SUPER_HEROES) { 34 | log("Emitting: " + superHero); 35 | emitter.onNext(superHero); 36 | } 37 | log("Completing"); 38 | emitter.onComplete(); 39 | }); 40 | 41 | log("---------------- Subscribing"); 42 | observable 43 | .subscribeOn(scheduler) 44 | .subscribe( 45 | item -> log("Received " + item), 46 | error -> log("Error"), 47 | () -> { 48 | log("Complete"); 49 | latch.countDown(); 50 | }); 51 | log("---------------- Subscribed"); 52 | 53 | latch.await(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter6/Code6.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter6; 2 | 3 | import io.reactivex.Observable; 4 | import io.reactivex.Scheduler; 5 | import io.reactivex.schedulers.Schedulers; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | import static java.util.concurrent.Executors.newFixedThreadPool; 11 | import static me.escoffier.superheroes.Helpers.log; 12 | import static me.escoffier.superheroes.Helpers.threadFactory; 13 | 14 | public class Code6 { 15 | 16 | private static List SUPER_HEROES = Arrays.asList( 17 | "Superman", 18 | "Batman", 19 | "Aquaman", 20 | "Asterix", 21 | "Captain America" 22 | ); 23 | 24 | public static void main(String[] args) { 25 | 26 | Scheduler scheduler = Schedulers.from(newFixedThreadPool(10, threadFactory)); 27 | 28 | // Synchronous emission 29 | Observable observable = Observable.create(emitter -> { 30 | for (String superHero : SUPER_HEROES) { 31 | log("Emitting: " + superHero); 32 | emitter.onNext(superHero); 33 | } 34 | log("Completing"); 35 | emitter.onComplete(); 36 | }); 37 | 38 | log("---------------- Subscribing"); 39 | observable 40 | .observeOn(scheduler) 41 | .subscribe( 42 | item -> log("Received " + item), 43 | error -> log("Error"), 44 | () -> log("Complete")); 45 | log("---------------- Subscribed"); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter6/Code7.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter6; 2 | 3 | import io.reactivex.Observable; 4 | import io.vertx.core.json.JsonObject; 5 | import me.escoffier.superheroes.SuperHeroesService; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.InputStreamReader; 9 | import java.net.HttpURLConnection; 10 | import java.net.URL; 11 | 12 | import static me.escoffier.superheroes.Helpers.log; 13 | 14 | public class Code7 { 15 | 16 | private static final int[] SUPER_HEROES_BY_ID = {641, 65, 37, 142}; 17 | 18 | public static void main(String[] args) { 19 | 20 | SuperHeroesService.run(false); 21 | 22 | Observable observable = Observable.create(emitter -> { 23 | for (int superHeroId : SUPER_HEROES_BY_ID) { 24 | 25 | // Load a super hero using the blocking URL connection 26 | URL url = new URL("http://localhost:8080/heroes/" + superHeroId); 27 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 28 | conn.setRequestMethod("GET"); 29 | BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); 30 | StringBuilder result = new StringBuilder(); 31 | String line; 32 | while ((line = rd.readLine()) != null) { 33 | result.append(line); 34 | } 35 | rd.close(); 36 | String superHero = new JsonObject(result.toString()).getString("name"); 37 | 38 | log("Emitting: " + superHero); 39 | emitter.onNext(superHero); 40 | } 41 | log("Completing"); 42 | emitter.onComplete(); 43 | }) 44 | // USe the subscrbeOn operator to use the io scheduler. 45 | 46 | ; 47 | 48 | log("---------------- Subscribing"); 49 | observable 50 | .subscribe( 51 | item -> { 52 | log("Received " + item); 53 | }, error -> { 54 | log("Error"); 55 | }, () -> { 56 | log("Complete"); 57 | }); 58 | log("---------------- Subscribed"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter6/Code7_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter6; 2 | 3 | import io.reactivex.Observable; 4 | import io.reactivex.Scheduler; 5 | import io.reactivex.schedulers.Schedulers; 6 | import io.vertx.core.json.JsonObject; 7 | import me.escoffier.superheroes.SuperHeroesService; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.InputStreamReader; 11 | import java.net.HttpURLConnection; 12 | import java.net.URL; 13 | 14 | import static java.util.concurrent.Executors.newFixedThreadPool; 15 | import static me.escoffier.superheroes.Helpers.log; 16 | import static me.escoffier.superheroes.Helpers.threadFactory; 17 | 18 | public class Code7_Solution { 19 | 20 | private static final int[] SUPER_HEROES_BY_ID = {641, 65, 37, 142}; 21 | 22 | public static void main(String[] args) { 23 | 24 | SuperHeroesService.run(false); 25 | 26 | Scheduler scheduler = Schedulers.from(newFixedThreadPool(10, threadFactory)); 27 | 28 | Observable observable = Observable.create(emitter -> { 29 | for (int superHeroId : SUPER_HEROES_BY_ID) { 30 | // Load a super hero using the blocking URL connection 31 | URL url = new URL("http://localhost:8080/heroes/" + superHeroId); 32 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 33 | conn.setRequestMethod("GET"); 34 | BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); 35 | StringBuilder result = new StringBuilder(); 36 | String line; 37 | while ((line = rd.readLine()) != null) { 38 | result.append(line); 39 | } 40 | rd.close(); 41 | String superHero = new JsonObject(result.toString()).getString("name"); 42 | 43 | log("Emitting: " + superHero); 44 | emitter.onNext(superHero); 45 | } 46 | log("Completing"); 47 | emitter.onComplete(); 48 | }) 49 | .subscribeOn(Schedulers.io()); 50 | 51 | log("---------------- Subscribing"); 52 | observable 53 | .subscribe( 54 | item -> log("Received " + item), 55 | error -> log("Error"), 56 | () -> log("Complete")); 57 | log("---------------- Subscribed"); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter6/Code8.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter6; 2 | 3 | import io.reactivex.Observable; 4 | import io.reactivex.Scheduler; 5 | import io.vertx.core.Context; 6 | import io.vertx.core.Vertx; 7 | import io.vertx.core.json.JsonObject; 8 | import io.vertx.reactivex.RxHelper; 9 | import me.escoffier.superheroes.SuperHeroesService; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.InputStreamReader; 13 | import java.net.HttpURLConnection; 14 | import java.net.URL; 15 | 16 | import static me.escoffier.superheroes.Helpers.log; 17 | 18 | public class Code8 { 19 | 20 | private static final int[] SUPER_HEROES_BY_ID = {641, 65, 37, 142}; 21 | 22 | public static void main(String[] args) { 23 | Vertx vertx = Vertx.vertx(); 24 | Context context = vertx.getOrCreateContext(); 25 | SuperHeroesService.run(false); 26 | 27 | Scheduler contextScheduler = RxHelper.scheduler(context); 28 | 29 | Observable observable = Observable.create(emitter -> { 30 | for (int superHeroId : SUPER_HEROES_BY_ID) { 31 | 32 | // Load a super hero using the blocking URL connection 33 | URL url = new URL("http://localhost:8080/heroes/" + superHeroId); 34 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 35 | conn.setRequestMethod("GET"); 36 | BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); 37 | StringBuilder result = new StringBuilder(); 38 | String line; 39 | while ((line = rd.readLine()) != null) { 40 | result.append(line); 41 | } 42 | rd.close(); 43 | String superHero = new JsonObject(result.toString()).getString("name"); 44 | 45 | log("Emitting: " + superHero); 46 | emitter.onNext(superHero); 47 | } 48 | log("Completing"); 49 | emitter.onComplete(); 50 | }) 51 | // execute the emitter on the io scheduler 52 | // execute the subscriber on the context thread 53 | ; 54 | 55 | // Execute a Vert.x event loop task 56 | context.runOnContext(v -> { 57 | log("---------------- Subscribing"); 58 | observable 59 | .subscribe( 60 | item -> log("Received " + item), 61 | error -> log("Error"), 62 | () -> log("Complete")); 63 | log("---------------- Subscribed"); 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter6/Code8_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter6; 2 | 3 | import io.reactivex.Observable; 4 | import io.reactivex.Scheduler; 5 | import io.reactivex.schedulers.Schedulers; 6 | import io.vertx.core.Context; 7 | import io.vertx.core.Vertx; 8 | import io.vertx.core.json.JsonObject; 9 | import io.vertx.reactivex.RxHelper; 10 | import me.escoffier.superheroes.SuperHeroesService; 11 | 12 | import java.io.BufferedReader; 13 | import java.io.InputStreamReader; 14 | import java.net.HttpURLConnection; 15 | import java.net.URL; 16 | 17 | import static me.escoffier.superheroes.Helpers.log; 18 | 19 | public class Code8_Solution { 20 | 21 | private static final int[] SUPER_HEROES_BY_ID = {641, 65, 37, 142}; 22 | 23 | public static void main(String[] args) { 24 | 25 | Vertx vertx = Vertx.vertx(); 26 | Context context = vertx.getOrCreateContext(); 27 | SuperHeroesService.run(false); 28 | 29 | Scheduler contextScheduler = RxHelper.scheduler(context); 30 | 31 | Observable observable = Observable.create(emitter -> { 32 | for (int superHeroId : SUPER_HEROES_BY_ID) { 33 | URL url = new URL("http://localhost:8080/heroes/" + superHeroId); 34 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); 35 | conn.setRequestMethod("GET"); 36 | BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); 37 | StringBuilder result = new StringBuilder(); 38 | String line; 39 | while ((line = rd.readLine()) != null) { 40 | result.append(line); 41 | } 42 | rd.close(); 43 | String superHero = new JsonObject(result.toString()).getString("name"); 44 | 45 | log("Emitting: " + superHero); 46 | emitter.onNext(superHero); 47 | } 48 | log("Completing"); 49 | emitter.onComplete(); 50 | }) 51 | .subscribeOn(Schedulers.io()) 52 | .observeOn(contextScheduler); 53 | 54 | // Execute a Vert.x event loop task 55 | context.runOnContext(v -> { 56 | log("---------------- Subscribing"); 57 | observable 58 | .subscribe( 59 | item -> log("Received " + item), 60 | error -> log("Error"), 61 | () -> log("Complete")); 62 | log("---------------- Subscribed"); 63 | }); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter7/Code01.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter7; 2 | 3 | import io.reactivex.Observable; 4 | import io.reactivex.observers.TestObserver; 5 | 6 | public class Code01 { 7 | 8 | public static void main(String[] args) { 9 | TestObserver testObserver = Observable.range(1, 10) 10 | .filter(n -> n % 2 == 0) 11 | .test(); 12 | 13 | testObserver 14 | .assertSubscribed() 15 | .assertNever(n -> n % 2 == 1) 16 | .assertComplete() 17 | .assertValueCount(5) 18 | .assertValues(2, 4, 6, 8, 10); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter7/Code02.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter7; 2 | 3 | import io.reactivex.Flowable; 4 | import io.reactivex.subscribers.TestSubscriber; 5 | 6 | public class Code02 { 7 | 8 | public static void main(String[] args) { 9 | Flowable flowable = Flowable.range(1, 10) 10 | .filter(n -> n % 2 == 0); 11 | 12 | TestSubscriber testSubscriber = flowable.test(0); 13 | 14 | testSubscriber 15 | .assertNever(n -> n % 2 == 1) 16 | .requestMore(2) 17 | .assertValues(2, 4) 18 | .assertNotComplete() 19 | .requestMore(3) 20 | .assertValues(2, 4, 6, 8, 10) 21 | .assertComplete(); 22 | 23 | testSubscriber = flowable.test(0); 24 | 25 | testSubscriber 26 | .assertNotComplete() 27 | .requestMore(2) 28 | .assertValues(2, 4) 29 | .cancel(); 30 | 31 | testSubscriber 32 | .assertNotComplete(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter7/Code03.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter7; 2 | 3 | import io.reactivex.Single; 4 | 5 | import java.io.IOException; 6 | 7 | public class Code03 { 8 | 9 | public static void main(String[] args) { 10 | Single.error(new IOException("Source closed")) 11 | .test() 12 | .assertNotComplete() 13 | .assertError(IOException.class) 14 | .assertError(t -> t.getMessage().equals("Source closed")); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter7/Code04.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter7; 2 | 3 | import io.reactivex.Single; 4 | import io.reactivex.observers.TestObserver; 5 | import io.reactivex.schedulers.TestScheduler; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | public class Code04 { 10 | 11 | public static void main(String[] args) throws Throwable { 12 | TestScheduler scheduler = new TestScheduler(); 13 | 14 | Single s1 = Single.timer(1, TimeUnit.SECONDS, scheduler); 15 | Single s2 = Single.just("Hello"); 16 | Single r = Single.zip(s1, s2, (t, s) -> t + " -> " + s); 17 | 18 | TestObserver testObserver = r.test(); 19 | 20 | testObserver.assertNoValues(); 21 | 22 | scheduler.advanceTimeBy(500, TimeUnit.MILLISECONDS); 23 | testObserver.assertNoValues(); 24 | 25 | scheduler.advanceTimeBy(600, TimeUnit.MILLISECONDS); 26 | testObserver 27 | .assertNoErrors() 28 | .assertValue("0 -> Hello"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/lab/chapter7/Code05.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter7; 2 | 3 | import io.reactivex.Observable; 4 | import io.reactivex.observers.TestObserver; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | public class Code05 { 9 | 10 | public static void main(String[] args) { 11 | Observable strings = Observable.just("a", "b", "c", "d"); 12 | Observable ticks = Observable.interval(500, TimeUnit.MILLISECONDS); 13 | Observable stream = Observable.zip(ticks, strings, (t, s) -> s); 14 | 15 | TestObserver testObserver = stream.test(); 16 | if (testObserver.awaitTerminalEvent(3, TimeUnit.SECONDS)) { 17 | testObserver 18 | .assertNoErrors() 19 | .assertComplete() 20 | .assertValues("a", "b", "c", "d"); 21 | System.out.println("Cool!"); 22 | } else { 23 | System.out.println("It did not finish on time"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/superheroes/Character.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.superheroes; 2 | 3 | import io.vertx.core.json.JsonArray; 4 | import io.vertx.core.json.JsonObject; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | import java.util.function.Supplier; 10 | import java.util.stream.Collectors; 11 | 12 | public class Character { 13 | 14 | private final static AtomicInteger ID = new AtomicInteger(); 15 | 16 | private String name; 17 | 18 | private List superpowers = new ArrayList<>(); 19 | 20 | private boolean villain; 21 | 22 | private int id; 23 | 24 | public Character(String name, List list, boolean isHeroes) { 25 | this(ID.getAndIncrement(), name, list, isHeroes); 26 | } 27 | 28 | public Character(int id, String name, List list, boolean isHeroes) { 29 | this.id = id; 30 | this.name = name; 31 | this.superpowers.addAll(list); 32 | this.villain = !isHeroes; 33 | } 34 | 35 | public Character() { 36 | // Used by mapper. 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return name + " (" + (villain ? "villain" : "hero") + ") : " + superpowers; 42 | } 43 | 44 | public int getId() { 45 | return id; 46 | } 47 | 48 | public void setId(int id) { 49 | this.id = id; 50 | } 51 | 52 | public String getName() { 53 | return name; 54 | } 55 | 56 | public void setName(String name) { 57 | this.name = name; 58 | } 59 | 60 | public List getSuperpowers() { 61 | return superpowers; 62 | } 63 | 64 | public void setSuperpowers(List superpowers) { 65 | this.superpowers = superpowers; 66 | } 67 | 68 | public boolean isVillain() { 69 | return villain; 70 | } 71 | 72 | public void setVillain(boolean villain) { 73 | this.villain = villain; 74 | } 75 | 76 | public JsonObject toJson() { 77 | return new JsonObject() 78 | .put("id", id) 79 | .put("name", name) 80 | .put("superpowers", superpowers.stream().collect((Supplier) JsonArray::new, JsonArray::add, JsonArray::addAll)) 81 | .put("villain", villain); 82 | } 83 | 84 | public Character(JsonObject json) { 85 | this.id = json.getInteger("id"); 86 | this.name = json.getString("name"); 87 | this.villain = json.getBoolean("villain"); 88 | JsonArray powers = json.getJsonArray("superpowers"); 89 | this.superpowers = powers.stream().map(Object::toString).collect(Collectors.toList()); 90 | 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/superheroes/Helpers.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.superheroes; 2 | 3 | 4 | import io.reactivex.Flowable; 5 | import io.reactivex.Observable; 6 | import io.vertx.core.json.JsonObject; 7 | import io.vertx.ext.web.client.WebClientOptions; 8 | import io.vertx.reactivex.core.Vertx; 9 | import io.vertx.reactivex.core.buffer.Buffer; 10 | import io.vertx.reactivex.core.file.FileSystem; 11 | import io.vertx.reactivex.ext.web.client.HttpResponse; 12 | import io.vertx.reactivex.ext.web.client.WebClient; 13 | 14 | import java.util.Map; 15 | import java.util.concurrent.ThreadFactory; 16 | import java.util.concurrent.atomic.AtomicInteger; 17 | import java.util.concurrent.atomic.AtomicLong; 18 | import java.util.stream.Collectors; 19 | 20 | public class Helpers { 21 | 22 | private final static Vertx vertx = Vertx.vertx(); 23 | 24 | public static FileSystem fs() { 25 | return vertx.fileSystem(); 26 | } 27 | 28 | public static WebClient client() { 29 | return WebClient.create(vertx, 30 | new WebClientOptions().setDefaultPort(8080).setDefaultHost("localhost") 31 | ); 32 | } 33 | 34 | public static Observable villains_names() { 35 | return client() 36 | .get("/villains") 37 | .rxSend() 38 | .map(HttpResponse::bodyAsJsonObject) 39 | .map(json -> json.stream().map(Map.Entry::getValue).collect(Collectors.toList())) 40 | .flatMapObservable(Observable::fromIterable) 41 | .cast(String.class); 42 | } 43 | 44 | public static Observable heroes_names() { 45 | return client() 46 | .get("/heroes") 47 | .rxSend() 48 | .map(HttpResponse::bodyAsJsonObject) 49 | .map(json -> json.stream().map(Map.Entry::getValue).collect(Collectors.toList())) 50 | .flatMapObservable(Observable::fromIterable) 51 | .cast(String.class); 52 | } 53 | 54 | public static Flowable heroes() { 55 | return fs().rxReadFile("src/main/resources/characters.json") 56 | .map(Buffer::toJsonArray) 57 | .flatMapPublisher(Flowable::fromIterable) 58 | .cast(JsonObject.class) 59 | .map(j -> j.mapTo(Character.class)) 60 | .filter(s -> ! s.isVillain()); 61 | } 62 | 63 | public static Flowable villains() { 64 | return fs().rxReadFile("src/main/resources/characters.json") 65 | .map(Buffer::toJsonArray) 66 | .flatMapPublisher(Flowable::fromIterable) 67 | .cast(JsonObject.class) 68 | .map(j -> j.mapTo(Character.class)) 69 | .filter(Character::isVillain); 70 | } 71 | 72 | public static void sleep(int ms) { 73 | try { 74 | Thread.sleep(ms); 75 | } catch (InterruptedException e) { 76 | Thread.currentThread().interrupt(); 77 | } 78 | } 79 | 80 | private static final AtomicLong START_TIME = new AtomicLong(); 81 | 82 | public static void log(String msg) { 83 | long now = System.currentTimeMillis(); 84 | START_TIME.compareAndSet(0, now); 85 | long elapsed = now - START_TIME.get(); 86 | String name = Thread.currentThread().getName(); 87 | System.out.format("%2$-4s %1$-26s %3$s\n", name, elapsed, msg); 88 | } 89 | 90 | private static final AtomicInteger threadCount = new AtomicInteger(); 91 | public static final ThreadFactory threadFactory = r -> { 92 | Thread thread = new Thread(r); 93 | thread.setName("Scheduler-" + threadCount.getAndIncrement()); 94 | return thread; 95 | }; 96 | 97 | 98 | } -------------------------------------------------------------------------------- /src/main/java/me/escoffier/superheroes/Scraper.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.superheroes; 2 | 3 | 4 | import io.reactivex.Flowable; 5 | import io.reactivex.Single; 6 | import io.vertx.core.json.Json; 7 | import io.vertx.reactivex.core.Vertx; 8 | import io.vertx.reactivex.core.buffer.Buffer; 9 | import io.vertx.reactivex.ext.web.client.HttpResponse; 10 | import io.vertx.reactivex.ext.web.client.WebClient; 11 | import org.jsoup.Jsoup; 12 | import org.jsoup.nodes.Document; 13 | import org.jsoup.nodes.Element; 14 | import org.jsoup.select.Elements; 15 | 16 | import java.io.IOException; 17 | import java.util.LinkedHashMap; 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.concurrent.atomic.AtomicInteger; 21 | import java.util.stream.Collectors; 22 | 23 | public class Scraper { 24 | 25 | public static void main(String[] args) throws IOException { 26 | Document doc = Jsoup.connect("https://www.superherodb.com/characters/").get(); 27 | System.out.println("Scraping " + doc.title()); 28 | Elements links = doc.select("a[title]"); 29 | 30 | Map names = new LinkedHashMap<>(); 31 | links.forEach(element -> { 32 | String name = element.attr("title"); 33 | String href = element.attr("href"); 34 | if (name != null && !name.trim().isEmpty() && ! isExcluded(name)) { 35 | names.put(name, href); 36 | } 37 | }); 38 | 39 | System.out.println(names.size() + " superheros and villains found"); 40 | 41 | Vertx vertx = Vertx.vertx(); 42 | WebClient client = WebClient.create(vertx); 43 | 44 | AtomicInteger counter = new AtomicInteger(); 45 | 46 | Flowable.fromIterable(names.entrySet()) 47 | .flatMapSingle(entry -> scrap(client, entry.getKey(), "https://www.superherodb.com" + entry.getValue())) 48 | .doOnNext(superStuff -> System.out.println("Retrieved " + superStuff + " (" + counter.incrementAndGet() + " / " + 49 | names.size() + ")")) 50 | .toList() 51 | .flatMapCompletable(list -> vertx.fileSystem() 52 | .rxWriteFile("src/main/resources/characters.json", new Buffer(Json.encodeToBuffer(list))) 53 | ) 54 | .subscribe( 55 | () -> System.out.println("Written " + names.size() + " super heroes and villains"), 56 | Throwable::printStackTrace 57 | ); 58 | } 59 | 60 | private static boolean isExcluded(String name) { 61 | return name.contains("All comic book") 62 | || name.startsWith("List of") 63 | || name.contains("Superhero species and types"); 64 | } 65 | 66 | private static Single scrap(WebClient client, String name, String url) { 67 | return client.getAbs(url) 68 | .rxSend() 69 | .map(HttpResponse::bodyAsString) 70 | .map(Jsoup::parse) 71 | .map(document -> { 72 | // We need to skip the first one, it's a title. 73 | Elements powers = document.select("ul a[href^='/powers/']"); 74 | List list = powers.stream().skip(1).map(Element::text).collect(Collectors.toList()); 75 | boolean isHero = ! isVillain(document); 76 | return new Character(name, list, isHero); 77 | }); 78 | } 79 | 80 | private static boolean isVillain(Document document) { 81 | Elements elements = document.select(".td-wide"); 82 | return elements.stream().map(Element::text).anyMatch(s -> s.equalsIgnoreCase("bad")); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/me/escoffier/superheroes/SuperHeroesService.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.superheroes; 2 | 3 | import io.reactivex.Completable; 4 | import io.vertx.core.json.JsonObject; 5 | import io.vertx.reactivex.core.Vertx; 6 | import io.vertx.reactivex.ext.web.Router; 7 | import io.vertx.reactivex.ext.web.RoutingContext; 8 | 9 | import java.util.*; 10 | import java.util.stream.Collectors; 11 | 12 | public class SuperHeroesService { 13 | 14 | public static void main(String[] args) { 15 | SuperHeroesService service = new SuperHeroesService(true); 16 | service.start().subscribe(); 17 | } 18 | 19 | public static void run() { 20 | new SuperHeroesService(true).start().blockingAwait(); 21 | } 22 | 23 | public static void run(boolean verbose) { 24 | new SuperHeroesService(verbose).start().blockingAwait(); 25 | } 26 | 27 | private final boolean verbose; 28 | private Random random = new Random(); 29 | private Map villains; 30 | private Map heroes; 31 | 32 | public SuperHeroesService(boolean verbose) { 33 | this.verbose = verbose; 34 | } 35 | 36 | public Completable start() { 37 | Vertx vertx = Vertx.vertx(); 38 | 39 | Router router = Router.router(vertx); 40 | router.get("/heroes").handler(this::getAllHeroes); 41 | router.get("/villains").handler(this::getAllVillains); 42 | router.get("/heroes/random").handler(this::getRandomHero); 43 | router.get("/heroes/:id").handler(this::getHeroById); 44 | router.get("/villains/random").handler(this::getRandomVillain); 45 | router.get("/villains/:id").handler(this::getVillainById); 46 | 47 | return vertx.fileSystem().rxReadFile("src/main/resources/characters.json") 48 | .map(buffer -> buffer.toJsonArray().stream().map(o -> new Character((JsonObject) o)).collect(Collectors.toList())) 49 | .doOnSuccess(list -> { 50 | if (verbose) { 51 | System.out.println("Loaded " + list.size() + " heroes and villains"); 52 | } 53 | }) 54 | .doOnSuccess(list -> { 55 | this.villains = list.stream().filter(Character::isVillain).collect( 56 | HashMap::new, (map, superStuff) -> map.put(superStuff.getId(), superStuff), HashMap::putAll); 57 | this.heroes = list.stream().filter(e -> ! e.isVillain()).collect( 58 | HashMap::new, (map, superStuff) -> map.put(superStuff.getId(), superStuff), HashMap::putAll); 59 | }) 60 | .flatMap(x -> vertx.createHttpServer() 61 | .requestHandler(router::accept) 62 | .rxListen(8080)) 63 | .toCompletable(); 64 | 65 | } 66 | 67 | private void getAllHeroes(RoutingContext rc) { 68 | rc.response().end(heroes.values().stream() 69 | .collect(JsonObject::new, 70 | (json, superStuff) -> json.put(Integer.toString(superStuff.getId()), superStuff.getName()), 71 | JsonObject::mergeIn) 72 | .encodePrettily()); 73 | } 74 | 75 | private void getAllVillains(RoutingContext rc) { 76 | rc.response().end(villains.values().stream() 77 | .collect(JsonObject::new, 78 | (json, superStuff) -> json.put(Integer.toString(superStuff.getId()), superStuff.getName()), 79 | JsonObject::mergeIn) 80 | .encodePrettily()); 81 | } 82 | 83 | private void getHeroById(RoutingContext rc) { 84 | getById(rc, heroes); 85 | } 86 | 87 | private void getById(RoutingContext rc, Map map) { 88 | String id = rc.pathParam("id"); 89 | try { 90 | Integer value = Integer.valueOf(id); 91 | Character character = map.get(value); 92 | if (character == null) { 93 | rc.response().setStatusCode(404).end("Unknown hero " + id); 94 | } else { 95 | rc.response().end(character.toJson().encodePrettily()); 96 | } 97 | } catch (NumberFormatException e) { 98 | rc.response().setStatusCode(404).end("Unknown hero " + id); 99 | } 100 | } 101 | 102 | private void getVillainById(RoutingContext rc) { 103 | getById(rc, villains); 104 | } 105 | 106 | private void getRandomHero(RoutingContext rc) { 107 | List h = new ArrayList<>(heroes.values()); 108 | int index = random.nextInt(h.size()); 109 | Character hero = h.get(index); 110 | if (verbose) { 111 | System.out.println("Selected hero " + hero); 112 | } 113 | rc.response().end(hero.toJson().encodePrettily()); 114 | } 115 | 116 | private void getRandomVillain(RoutingContext rc) { 117 | List h = new ArrayList<>(villains.values()); 118 | int index = random.nextInt(h.size()); 119 | Character villain = h.get(index); 120 | if (verbose) { 121 | System.out.println("Selected villain " + villain); 122 | } 123 | rc.response().end(villain.toJson().encodePrettily()); 124 | } 125 | 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/resources/super/heroes/Spock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/src/main/resources/super/heroes/Spock -------------------------------------------------------------------------------- /src/main/resources/super/heroes/SuperGirl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/src/main/resources/super/heroes/SuperGirl -------------------------------------------------------------------------------- /src/main/resources/super/heroes/Tigra: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/src/main/resources/super/heroes/Tigra -------------------------------------------------------------------------------- /src/main/resources/super/heroes/Yoda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/src/main/resources/super/heroes/Yoda -------------------------------------------------------------------------------- /src/main/resources/super/villains/Sauron: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/src/main/resources/super/villains/Sauron -------------------------------------------------------------------------------- /src/main/resources/super/villains/Violator: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/src/main/resources/super/villains/Violator -------------------------------------------------------------------------------- /src/main/resources/super/villains/Walrus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cescoffier/rxjava2-lab/829f6bf2d0cf8fbb55bcd01a11307d68fde2ef60/src/main/resources/super/villains/Walrus -------------------------------------------------------------------------------- /src/test/java/me/escoffier/lab/chapter7/AsyncTest.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter7; 2 | 3 | import me.escoffier.superheroes.Helpers; 4 | import org.junit.Test; 5 | 6 | public class AsyncTest { 7 | 8 | @Test 9 | public void theRightWayToTest() { 10 | Helpers.heroes() 11 | // complete here 12 | ; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/me/escoffier/lab/chapter7/AsyncTest_Solution.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter7; 2 | 3 | import io.reactivex.subscribers.TestSubscriber; 4 | import me.escoffier.superheroes.Helpers; 5 | import me.escoffier.superheroes.Character; 6 | import org.junit.Test; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | import java.util.concurrent.TimeoutException; 10 | 11 | public class AsyncTest_Solution { 12 | 13 | @Test 14 | public void theRightWayToTest() throws TimeoutException { 15 | TestSubscriber testSubscriber = Helpers.heroes().test(); 16 | if (!testSubscriber.awaitTerminalEvent(5, TimeUnit.SECONDS)) { 17 | throw new TimeoutException("It timed out!"); 18 | } 19 | testSubscriber 20 | .assertComplete() 21 | .assertNoErrors() 22 | .assertValueCount(520); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/me/escoffier/lab/chapter7/BadAsyncTest.java: -------------------------------------------------------------------------------- 1 | package me.escoffier.lab.chapter7; 2 | 3 | import me.escoffier.superheroes.Helpers; 4 | import me.escoffier.superheroes.Character; 5 | import org.junit.Test; 6 | 7 | import java.util.ArrayList; 8 | 9 | import static org.hamcrest.CoreMatchers.equalTo; 10 | import static org.hamcrest.MatcherAssert.assertThat; 11 | 12 | public class BadAsyncTest { 13 | 14 | private static class TBox { 15 | private Throwable t; 16 | 17 | void set(Throwable t) { 18 | this.t = t; 19 | } 20 | } 21 | 22 | @Test 23 | public void theWrongWayToTest() throws Throwable { 24 | TBox box = new TBox(); 25 | ArrayList stuffs = new ArrayList<>(); 26 | 27 | Helpers.heroes() 28 | .subscribe(stuffs::add, box::set, () -> System.out.println("[ done ]")); 29 | 30 | Thread.sleep(5000); 31 | 32 | if (box.t != null) { 33 | throw box.t; 34 | } 35 | assertThat(stuffs.size(), equalTo(520)); 36 | } 37 | } 38 | --------------------------------------------------------------------------------