├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── reactive_cache ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── reactivecache2 │ │ ├── ActionsList.java │ │ ├── ExceptionAdapter.java │ │ ├── Provider.java │ │ ├── ProviderGroup.java │ │ ├── ProviderGroupList.java │ │ ├── ProviderList.java │ │ └── ReactiveCache.java │ └── test │ └── java │ └── io │ └── reactivecache2 │ ├── ActionsListTest.java │ ├── ExceptionAdapterTest.java │ ├── Jolyglot$.java │ ├── Mock.java │ ├── ProviderGroupListTest.java │ ├── ProviderGroupTest.java │ ├── ProviderListTest.java │ ├── ProviderTest.java │ ├── ReactiveCacheBuilderValidationTest.java │ ├── ReactiveCacheTest.java │ ├── UsageTest.java │ ├── common │ └── BaseTestEvictingTask.java │ ├── encript │ ├── EncryptGroupTest.java │ └── EncryptTest.java │ ├── expiration │ ├── EvictExpirableRecordsGroupTest.java │ ├── EvictExpirableRecordsTest.java │ ├── EvictExpiredRecordsGroupTest.java │ └── EvictExpiredRecordsTest.java │ └── migration │ └── MigrationsTest.java ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── victoralbertos │ │ └── reactivecache │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── io │ │ │ └── victoralbertos │ │ │ └── reactivecache │ │ │ └── MainActivity.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ └── content_main.xml │ │ ├── menu │ │ └── menu_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── io │ └── victoralbertos │ └── reactivecache │ └── ExampleUnitTest.java ├── settings.gradle └── table_built_in_functions.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | **/build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | 28 | # Android Studio 29 | .navigation/ 30 | .idea/ 31 | **/*.iml 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # .DS_Store files 37 | **/.DS_Store -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | jdk: oraclejdk8 4 | 5 | script: "./gradlew reactive_cache:test" 6 | 7 | android: 8 | components: 9 | - tools 10 | - platform-tools 11 | - build-tools-24.0.0 12 | - android-24 13 | - extra-android-support 14 | - extra-android-m2repository 15 | - extra-google-m2repository 16 | 17 | # Additional components 18 | #- extra-google-google_play_services 19 | #- addon-google_apis-google-19 20 | 21 | # Specify at least one system image, if you need to run emulator(s) during your tests 22 | #- sys-img-armeabi-v7a-android-19 23 | #- sys-img-x86-android-17 24 | 25 | before_install: 26 | - export JAVA7_HOME=/usr/lib/jvm/java-7-oracle 27 | - export JAVA8_HOME=/usr/lib/jvm/java-8-oracle 28 | - export JAVA_HOME=$JAVA7_HOME -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 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 2016 Víctor Albertos 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 | :warning: This repository is no longer mantained consider using [Room](https://developer.android.com/topic/libraries/architecture/room) as an alternative :warning: 2 | 3 | [![Build Status](https://travis-ci.org/VictorAlbertos/ReactiveCache.svg?branch=master)](https://travis-ci.org/VictorAlbertos/ReactiveCache) 4 | [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-ReactiveCache-brightgreen.svg?style=flat)](http://android-arsenal.com/details/1/4002) 5 | 6 | # ReactiveCache 7 | The act of caching data with **ReactiveCache** is just another transformation in the reactive chain. ReactiveCache's API exposes both `Single`, `SingleTransformer` and `Completable` **reactive types** to gracefully merge the caching actions with the data stream. 8 | 9 | ## Features 10 | * A **dual cache** based on both memory and disk layers. 11 | * **Automatic deserialization-serialization** for custom `Types`, `List`, `Map` and `Array`. 12 | * **Pagination** 13 | * A **lifetime** system to expire data on specific time lapses. 14 | * Data **encryption**. 15 | * Customizable disk **cache size limit**. 16 | * **Migrations** to evict data by `Type` between releases. 17 | * A complete set of [**built-in functions**](#built-in) to perform **write operations easily** using `List`, such as `addFirst`, `evictLast`, `addAll` and so on. 18 | 19 | ## SetUp 20 | Add to top level *gradle.build* file 21 | 22 | ```gradle 23 | allprojects { 24 | repositories { 25 | maven { url "https://jitpack.io" } 26 | } 27 | } 28 | ``` 29 | 30 | Add to app module *gradle.build* file 31 | ```gradle 32 | dependencies { 33 | compile 'com.github.VictorAlbertos:ReactiveCache:1.1.3-2.x' 34 | compile 'com.github.VictorAlbertos.Jolyglot:gson:0.0.4' 35 | compile 'io.reactivex.rxjava2:rxjava:2.0.4' 36 | } 37 | ``` 38 | 39 | ## Usage 40 | 41 | ### ReactiveCache 42 | Create a **single instace** of `ReactiveCache` for your entire application. The builder offers some [additional configurations](#config_reactive_cache). 43 | 44 | ```java 45 | ReactiveCache reactiveCache = new ReactiveCache.Builder() 46 | .using(application.getFilesDir(), new GsonSpeaker()); 47 | ``` 48 | 49 | **`evictAll()`** returns a `Completable` which evicts the cached data for every provider: 50 | 51 | ```java 52 | cacheProvider.evictAll() 53 | ``` 54 | 55 | ### Provider 56 | 57 | Call `reactiveCache#provider()` to create a `Provider` to manage cache operations. The builder offers some [additional configurations](#config_providers). 58 | 59 | ```java 60 | Provider> cacheProvider = 61 | reactiveCache.>provider() 62 | .withKey("models"); 63 | ``` 64 | 65 | **`replace()`** returns a `SingleTransformer` which replaces the `provider` data with the item emitted from the `Single` source. If the source throws an exception, calling `replace()` doesn't evict the `provider` data. 66 | 67 | ```java 68 | api.getModels() 69 | .compose(cacheProvider.replace()) 70 | ``` 71 | 72 | **`read()`** returns an `Single` which emits the `provider` data. If there isn't any data available, throws an exception. 73 | 74 | ```java 75 | cacheProvider.read() 76 | ``` 77 | 78 | **`readWithLoader()`** returns a `SingleTransformer` which emits the `provider` data. If there isn't any data available, it subscribes to the `Single` source to cache and emit its item. 79 | 80 | ```java 81 | api.getModels() 82 | .compose(cacheProvider.readWithLoader()) 83 | ``` 84 | 85 | **`evict()`** returns a `Completable` which evicts the `provider` data: 86 | 87 | ```java 88 | cacheProvider.evict() 89 | ``` 90 | 91 | 92 | ### ProviderGroup 93 | 94 | Call `reactiveCache#providerGroup()` to create a `ProviderGroup` to manage cache operations with pagination support. The builder offers some [additional configurations](#config_providers). 95 | 96 | 97 | ```java 98 | ProviderGroup> cacheProvider = 99 | reactiveCache.>providerGroup() 100 | .withKey("modelsPaginated"); 101 | ``` 102 | 103 | `ProviderGroup` exposes the same methods as `Provider` but requesting a key as an argument. That way the scope of the `provider` data in every operation is constrained to the data associated with the key. 104 | 105 | ```java 106 | api.getModels(group) 107 | .compose(cacheProvider.replace(group)) 108 | 109 | cacheProvider.read(group) 110 | 111 | api.getModels(group) 112 | .compose(cacheProvider.readWithLoader()) 113 | 114 | cacheProvider.evict(group) 115 | ``` 116 | 117 | `evict()` is an overloaded method to evict the `provider` data for the entire collection of groups. 118 | 119 | ```java 120 | cacheProvider.evict() 121 | ``` 122 | 123 | ## Built-in functions for writing operations 124 | 125 | When the data is encoded as type `List`, you may use `ProviderList` and `ProviderGroupList`. Both clases inherit from their base clase (`Provider` and `ProviderGroup` respectively), so -[besides exposing all their base funcionality](#provider)- they offer a supletory api to perform write operations. 126 | 127 | Call `reactiveCache#providerList()` to create a `ProviderList`. 128 | 129 | ```java 130 | ProviderList cacheProvider = 131 | reactiveCache.providerList() 132 | .withKey("models"); 133 | ``` 134 | 135 | Or call `reactiveCache#providerGroupList()` to create a `ProviderGroupList`. 136 | 137 | ```java 138 | ProviderGroupList cacheProviderGroup = 139 | reactiveCache.providerGroupList() 140 | .withKey("modelsPaginated"); 141 | ``` 142 | 143 | Both **`cacheProvider.entries()`** and **`cacheProviderGroup.entries(group)`** return an `ActionsList` instance which allows to easily operate with the cached data thought a whole set of functions. 144 | 145 | ```java 146 | ActionsList actions = cacheProvider.entries(); 147 | ``` 148 | 149 | ```java 150 | ActionsList actions = cacheProviderGroup.entries(group); 151 | ``` 152 | 153 | Every function exposed through `actions` return a `Completable` which must be subscribed to in order to consume the action. Follow some examples: 154 | 155 | ```java 156 | actions.addFirst(new Model()) 157 | 158 | //Add a new element at 5 position 159 | actions.add((position, count) -> position == 5, new Model()) 160 | 161 | //Evict first element if the cache has already 300 records 162 | actions.evictFirst(count -> count > 300) 163 | 164 | //Update the element with id 5 165 | actions.update(model -> model.getId() == 5, model -> { 166 | mock.setActive(); 167 | return mock; 168 | }) 169 | 170 | //Update all inactive modelds 171 | actions.updateIterable(model -> model.isInactive(), model -> { 172 | model.setActive(); 173 | return mock; 174 | }) 175 | ``` 176 | 177 | [This table](https://github.com/VictorAlbertos/ReactiveCache/blob/2.x/table_built_in_functions.md) summarizes the available functions. 178 | 179 | ## Use cases 180 | 181 | Next examples illustrate how to use **ReactiveCache** on the *data layer* for client **Android** applications. They follow the *well-known* [repository pattern](http://fernandocejas.com/2014/09/03/architecting-android-the-clean-way/) in order to deal with data coming from a remote repository *(server)* and a local one *(ReactiveCache)*. 182 | 183 | ### Simple user session. 184 | ```java 185 | class UserRepository { 186 | private final Provider cacheProvider; 187 | private final ApiUser api; 188 | 189 | UserRepository(ApiUser api, ReactiveCache reactiveCache) { 190 | this.api = api; 191 | this.cacheProvider = reactiveCache.provider() 192 | .withKey("user"); 193 | } 194 | 195 | Single login(String email) { 196 | return api.loginUser(email) 197 | .compose(cacheProvider.replace()); 198 | } 199 | 200 | Single isLogged() { 201 | return cacheProvider.read() 202 | .map(user -> true) 203 | .onErrorReturn(observer -> false); 204 | } 205 | 206 | Single profile() { 207 | return cacheProvider.read(); 208 | } 209 | 210 | Completable updateUserName(String name) { 211 | return cacheProvider.read() 212 | .map(user -> { 213 | user.setName(name); 214 | return user; 215 | }) 216 | .compose(cacheProvider.replace()) 217 | .toCompletable(); 218 | } 219 | 220 | Completable logout() { 221 | return api.logout().andThen(cacheProvider.evict()); 222 | } 223 | } 224 | ``` 225 | 226 | ### Adding and removing tasks. 227 | ```java 228 | class TasksRepository { 229 | private final ProviderList cacheProvider; 230 | private final ApiTasks api; 231 | 232 | TasksRepository(ApiTasks api, ReactiveCache reactiveCache) { 233 | this.api = api; 234 | this.cacheProvider = reactiveCache.providerList() 235 | .withKey("tasks"); 236 | } 237 | 238 | Single>> tasks(boolean refresh) { 239 | return refresh ? api.tasks().compose(cacheProvider.replaceAsReply()) 240 | : api.tasks().compose(cacheProvider.readWithLoaderAsReply()); 241 | } 242 | 243 | Completable addTask(String name, String desc) { 244 | return api.addTask(1, name, desc) 245 | .andThen(cacheProvider.entries() 246 | .addFirst(new Task(1, name, desc))); 247 | } 248 | 249 | Completable removeTask(int id) { 250 | return api.removeTask(id) 251 | .andThen(cacheProvider.entries() 252 | .evict((position, count, element) -> element.getId() == id)); 253 | } 254 | } 255 | ``` 256 | 257 | ### Paginated feed of events. 258 | ```java 259 | class EventsRepository { 260 | private final ProviderGroup> cacheProvider; 261 | private final ApiEvents apiEvents; 262 | 263 | EventsRepository(ApiEvents apiEvents, ReactiveCache reactiveCache) { 264 | this.apiEvents = apiEvents; 265 | this.cacheProvider = reactiveCache.>providerGroup() 266 | .withKey("events"); 267 | } 268 | 269 | Single>> events(boolean refresh, int page) { 270 | if (refresh) { 271 | return apiEvents.events(page) 272 | .compose(cacheProvider.replaceAsReply(page)); 273 | } 274 | 275 | return apiEvents.events(page) 276 | .compose(cacheProvider.readWithLoaderAsReply(page)); 277 | } 278 | } 279 | ``` 280 | 281 | ## Configuration 282 | 283 | 284 | ### ReactiveCache 285 | 286 | When building `ReactiveCache` the next global configurations are available thought the builder: 287 | 288 | * **`diskCacheSize(int)`** sets the max memory in megabytes for all the cached data on disk. *Default value is 100*. 289 | 290 | * **`encrypt(String)`** sets the key to be used for encrypting the data on those providers as such configured. 291 | 292 | * **`useExpiredDataWhenNoLoaderAvailable()`** if invoked, ReactiveCache dispatches records already expired instead of throwing. 293 | 294 | * **`migrations(List)`** every `MigrationCache` expects a version number and a `Class[]` to check what cached data matches with these classes to evict it from disk. Use `MigrationCache` for those `Type` which have added new fields between app releases. 295 | 296 | ```java 297 | ReactiveCache reactiveCache = new ReactiveCache.Builder() 298 | .diskCacheSize(100) 299 | .encrypt("myStrongKey1234") 300 | .useExpiredDataWhenNoLoaderAvailable() 301 | .migrations(Arrays.asList( 302 | new MigrationCache(1, new Class[] {Model.class}), 303 | new MigrationCache(1, new Class[] {Model1.class}))) 304 | .using(application.getFilesDir(), new GsonSpeaker()); 305 | ``` 306 | 307 | ### Config provider 308 | 309 | When building `Provider`, `ProviderList`, `ProviderGroup` or `ProviderGroupList` the next configuration is available thought the builder: 310 | 311 | * **`encrypt(boolean)`** when true, the data cached by this `provider` is encrypted using the key specified in `ReactiveCache#encript(key)`. *Default value is false*. 312 | 313 | * **`expirable(boolean)`** when false, the data cached by this `provider` is not eligible to be expired if not enough space remains on disk. *Default value is true*. 314 | 315 | * **`lifeCache(long, TimeUnit)`** sets the amount of time before the data would be expired. *By default the data has no life time*. 316 | 317 | ```java 318 | Provider cacheModel = reactiveCache.provider() 319 | .encrypt(true) 320 | .expirable(false) 321 | .lifeCache(60, TimeUnit.MINUTES) 322 | .withKey("model"); 323 | ``` 324 | 325 | ## Author 326 | 327 | **Víctor Albertos** 328 | 329 | * 330 | * 331 | * 332 | 333 | 334 | ## Another author's libraries using RxJava: 335 | * [Mockery](https://github.com/VictorAlbertos/Mockery): Android and Java library for mocking and testing networking layers with built-in support for Retrofit. 336 | * [RxCache](https://github.com/VictorAlbertos/RxCache): Reactive caching library for Android and Java. (ReactiveCache uses internally the core from RxCache). 337 | * [RxActivityResult](https://github.com/VictorAlbertos/RxActivityResult): A reactive-tiny-badass-vindictive library to break with the OnActivityResult implementation as it breaks the observables chain. 338 | * [RxFcm](https://github.com/VictorAlbertos/RxFcm): RxJava extension for Android Firebase Cloud Messaging (aka fcm). 339 | * [RxSocialConnect](https://github.com/VictorAlbertos/RxSocialConnect-Android): OAuth RxJava extension for Android. 340 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.0' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | maven { url "https://jitpack.io" } 18 | mavenLocal() 19 | jcenter() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } 26 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VictorAlbertos/ReactiveCache/0053adb4dd3526a437aed048f7ccaf8ae6a29b61/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Mar 08 17:44:02 CET 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /reactive_cache/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply plugin: 'me.tatarka.retrolambda' 3 | apply plugin: 'maven' 4 | group='com.github.VictorAlbertos' 5 | version='1.1.1-2.x' 6 | 7 | sourceCompatibility = 1.8 8 | targetCompatibility = 1.8 9 | 10 | buildscript { 11 | repositories { 12 | mavenCentral() 13 | jcenter() 14 | } 15 | 16 | dependencies { 17 | classpath "me.tatarka:gradle-retrolambda:3.5.0" 18 | } 19 | } 20 | 21 | task sourcesJar(type: Jar, dependsOn: classes) { 22 | classifier = 'sources' 23 | from sourceSets.main.allSource 24 | } 25 | 26 | task javadocJar(type: Jar, dependsOn: javadoc) { 27 | classifier = 'javadoc' 28 | from javadoc.destinationDir 29 | } 30 | 31 | artifacts { 32 | archives sourcesJar 33 | archives javadocJar 34 | } 35 | 36 | dependencies { 37 | compile 'com.github.VictorAlbertos.RxCache:core:1.8.3-2.x' 38 | compile 'com.github.VictorAlbertos.Jolyglot:api:0.0.4' 39 | 40 | retrolambdaConfig 'net.orfjackal.retrolambda:retrolambda:2.5.0' 41 | 42 | testCompile 'com.github.VictorAlbertos.Jolyglot:gson:0.0.4' 43 | testCompile 'com.github.VictorAlbertos.Jolyglot:jackson:0.0.4' 44 | testCompile 'com.github.VictorAlbertos.Jolyglot:moshi:0.0.4' 45 | testCompile 'junit:junit:4.12' 46 | } 47 | 48 | retrolambda { 49 | jdk System.getenv("JAVA8_HOME") 50 | oldJdk System.getenv("JAVA7_HOME") 51 | } 52 | -------------------------------------------------------------------------------- /reactive_cache/src/main/java/io/reactivecache2/ActionsList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | import io.reactivex.Completable; 20 | import io.reactivex.Single; 21 | import java.util.Arrays; 22 | import java.util.Collections; 23 | import java.util.List; 24 | 25 | /** 26 | * Provides a set of entries in order to perform write operations on lists with providers in a more 27 | * easy and safely way. 28 | */ 29 | public class ActionsList { 30 | protected Single> cache; 31 | protected final Evict evict; 32 | 33 | ActionsList(Evict evict, Single> cache) { 34 | this.evict = evict; 35 | this.cache = cache; 36 | } 37 | 38 | /** 39 | * Static accessor to create an instance of Actions in order follow the "builder" pattern. 40 | * 41 | * @param evict The implementation of Evict interface which allow to persists the changes. 42 | * @param cache An single which is the result of calling the provider without evicting its data. 43 | * @param the type of the element of the list to be processed. 44 | * @return an instance of Actions. 45 | */ 46 | static ActionsList with(Evict evict, Single> cache) { 47 | return new ActionsList<>(evict, cache); 48 | } 49 | 50 | /** 51 | * Func2 will be called for every iteration until its condition returns true. When true, the 52 | * element is added to the cache at the position of the current iteration. 53 | * 54 | * @param func2 exposes the position of the current iteration and the count of elements in the 55 | * cache. 56 | * @param element the object to addOrUpdate to the cache. 57 | * @return Completable 58 | */ 59 | public Completable add(Func2 func2, T element) { 60 | return addAll(func2, Arrays.asList(element)); 61 | } 62 | 63 | /** 64 | * Add the object at the first position of the cache. 65 | * 66 | * @param element the object to addOrUpdate to the cache. 67 | * @return Completable 68 | */ 69 | public Completable addFirst(T element) { 70 | return addAll((position, count) -> position == 0, Arrays.asList(element)); 71 | } 72 | 73 | /** 74 | * Add the object at the last position of the cache. 75 | * 76 | * @param element the object to addOrUpdate to the cache. 77 | * @return Completable 78 | */ 79 | public Completable addLast(T element) { 80 | return addAll((position, count) -> position == count, Arrays.asList(element)); 81 | } 82 | 83 | /** 84 | * Add the objects at the first position of the cache. 85 | * 86 | * @param elements the objects to addOrUpdate to the cache. 87 | * @return Completable 88 | */ 89 | public Completable addAllFirst(List elements) { 90 | return addAll((position, count) -> position == 0, elements); 91 | } 92 | 93 | /** 94 | * Add the objects at the last position of the cache. 95 | * 96 | * @param elements the objects to addOrUpdate to the cache. 97 | * @return Completable 98 | */ 99 | public Completable addAllLast(List elements) { 100 | return addAll((position, count) -> position == count, elements); 101 | } 102 | 103 | /** 104 | * Func2 will be called for every iteration until its condition returns true. When true, the 105 | * elements are added to the cache at the position of the current iteration. 106 | * 107 | * @param func2 exposes the position of the current iteration and the count of elements in the 108 | * cache. 109 | * @param elements the objects to addOrUpdate to the cache. 110 | * @return Completable 111 | */ 112 | public Completable addAll(final Func2 func2, final List elements) { 113 | return evict.call(cache.map(items -> { 114 | int count = items.size(); 115 | 116 | for (int position = 0; position <= count; position++) { 117 | if (func2.call(position, count)) { 118 | items.addAll(position, elements); 119 | break; 120 | } 121 | } 122 | 123 | return items; 124 | })).toCompletable(); 125 | } 126 | 127 | /** 128 | * Evict object at the first position of the cache 129 | * 130 | * @return Completable 131 | */ 132 | public Completable evictFirst() { 133 | return evict((position, count, element) -> position == 0); 134 | } 135 | 136 | /** 137 | * Evict as much objects as requested by n param starting from the first position. 138 | * 139 | * @param n the amount of elements to evict. 140 | * @return Completable 141 | */ 142 | public Completable evictFirstN(final int n) { 143 | return evictFirstN(count -> true, n); 144 | } 145 | 146 | /** 147 | * Evict object at the last position of the cache. 148 | * 149 | * @return Completable 150 | */ 151 | public Completable evictLast() { 152 | return evict((position, count, element) -> position == count - 1); 153 | } 154 | 155 | /** 156 | * Evict as much objects as requested by n param starting from the last position. 157 | * 158 | * @param n the amount of elements to evict. 159 | * @return Completable 160 | */ 161 | public Completable evictLastN(final int n) { 162 | return evictLastN(count -> true, n); 163 | } 164 | 165 | /** 166 | * Evict object at the first position of the cache. 167 | * 168 | * @param func1Count exposes the count of elements in the cache. 169 | * @return Completable 170 | */ 171 | public Completable evictFirst(final Func1Count func1Count) { 172 | return evict((position, count, element) -> position == 0 && func1Count.call(count)); 173 | } 174 | 175 | /** 176 | * Evict as much objects as requested by n param starting from the first position. 177 | * 178 | * @param func1Count exposes the count of elements in the cache. 179 | * @param n the amount of elements to evict. 180 | * @return Completable 181 | */ 182 | public Completable evictFirstN(final Func1Count func1Count, final int n) { 183 | return evictIterable((position, count, element) -> position < n && func1Count.call(count)); 184 | } 185 | 186 | /** 187 | * Evict object at the last position of the cache. 188 | * 189 | * @param func1Count exposes the count of elements in the cache. 190 | * @return Completable 191 | */ 192 | public Completable evictLast(final Func1Count func1Count) { 193 | return evict((position, count, element) -> position == count - 1 && func1Count.call(count)); 194 | } 195 | 196 | private boolean startToEvict; 197 | 198 | /** 199 | * Evict as much objects as requested by n param starting from the last position. 200 | * 201 | * @param func1Count exposes the count of elements in the cache. 202 | * @param n the amount of elements to evict. 203 | * @return Completable 204 | */ 205 | public Completable evictLastN(final Func1Count func1Count, final int n) { 206 | startToEvict = false; 207 | return evictIterable((position, count, element) -> { 208 | if (!startToEvict) startToEvict = count - position == n; 209 | 210 | if (startToEvict) { 211 | return count - position <= n && func1Count.call(count); 212 | } else { 213 | return false; 214 | } 215 | }); 216 | } 217 | 218 | /** 219 | * Func1Element will be called for every iteration until its condition returns true. When true, 220 | * the element of the current iteration is evicted from the cache. 221 | * 222 | * @param func1 exposes the element of the current iteration. 223 | * @return Completable 224 | */ 225 | public Completable evict(final Func1 func1) { 226 | return evict((position, count, element) -> func1.call(element)); 227 | } 228 | 229 | /** 230 | * Func3 will be called for every iteration until its condition returns true. When true, the 231 | * element of the current iteration is evicted from the cache. 232 | * 233 | * @param func3 exposes the position of the current iteration, the count of elements in the cache 234 | * and the element of the current iteration. 235 | * @return Completable 236 | */ 237 | public Completable evict(final Func3 func3) { 238 | return evict.call(cache.map(elements -> { 239 | int count = elements.size(); 240 | 241 | for (int position = 0; position < count; position++) { 242 | if (func3.call(position, count, elements.get(position))) { 243 | elements.remove(position); 244 | break; 245 | } 246 | } 247 | 248 | return elements; 249 | })).toCompletable(); 250 | } 251 | 252 | /** 253 | * Evict elements from the cache starting from the first position until its count is equal to the 254 | * value specified in n param. 255 | * 256 | * @param n the amount of elements to keep from evict. 257 | * @return Completable 258 | */ 259 | public Completable evictAllKeepingFirstN(final int n) { 260 | return evictIterable((position, count, element) -> { 261 | int positionToStartEvicting = count - (count - n); 262 | return position >= positionToStartEvicting; 263 | }); 264 | } 265 | 266 | /** 267 | * Evict elements from the cache starting from the last position until its count is equal to the 268 | * value specified in n param. 269 | * 270 | * @param n the amount of elements to keep from evict. 271 | * @return Completable 272 | */ 273 | public Completable evictAllKeepingLastN(final int n) { 274 | return evictIterable((position, count, element) -> { 275 | int elementsToEvict = count - n; 276 | return position < elementsToEvict; 277 | }); 278 | } 279 | 280 | /** 281 | * Func3 will be called for every iteration. When true, the element of the current iteration is 282 | * evicted from the cache. 283 | * 284 | * @param func3 exposes the position of the current iteration, the count of elements in the cache 285 | * and the element of the current iteration. 286 | * @return Completable 287 | */ 288 | public Completable evictIterable(final Func3 func3) { 289 | return evict.call(cache.map(elements -> { 290 | int count = elements.size(); 291 | 292 | for (int position = 0; position < count; position++) { 293 | if (func3.call(position, count, elements.get(position))) { 294 | elements.set(position, null); 295 | } 296 | } 297 | 298 | elements.removeAll(Collections.singleton(null)); 299 | return elements; 300 | })).toCompletable(); 301 | } 302 | 303 | /** 304 | * Func1Element will be called for every iteration until its condition returns true. When true, 305 | * the element of the current iteration is updated. 306 | * 307 | * @param func1 exposes the element of the current iteration. 308 | * @param replace exposes the original element and expects back the one modified. 309 | * @return Completable 310 | */ 311 | public Completable update(final Func1 func1, Replace replace) { 312 | return update((position, count, element) -> func1.call(element), replace); 313 | } 314 | 315 | /** 316 | * Func3 will be called for every iteration until its condition returns true. When true, the 317 | * element of the current iteration is updated. 318 | * 319 | * @param func3 exposes the position of the current iteration, the count of elements in the cache 320 | * and the element of the current iteration. 321 | * @param replace exposes the original element and expects back the one modified. 322 | * @return Completable 323 | */ 324 | public Completable update(final Func3 func3, final Replace replace) { 325 | return evict.call(cache.map(elements -> { 326 | int count = elements.size(); 327 | 328 | for (int position = 0; position < count; position++) { 329 | if (func3.call(position, count, elements.get(position))) { 330 | elements.set(position, replace.call(elements.get(position))); 331 | break; 332 | } 333 | } 334 | 335 | return elements; 336 | })).toCompletable(); 337 | } 338 | 339 | /** 340 | * Func1Element will be called for every. When true, the element of the current iteration is 341 | * updated. 342 | * 343 | * @param func1 exposes the element of the current iteration. 344 | * @param replace exposes the original element and expects back the one modified. 345 | * @return Completable 346 | */ 347 | public Completable updateIterable(final Func1 func1, Replace replace) { 348 | return updateIterable((position, count, element) -> func1.call(element), replace); 349 | } 350 | 351 | /** 352 | * Func3 will be called for every iteration. When true, the element of the current iteration is 353 | * updated. 354 | * 355 | * @param func3 exposes the position of the current iteration, the count of elements in the cache 356 | * and the element of the current iteration. 357 | * @param replace exposes the original element and expects back the one modified. 358 | * @return Completable 359 | */ 360 | public Completable updateIterable(final Func3 func3, final Replace replace) { 361 | return evict.call(cache.map(elements -> { 362 | int count = elements.size(); 363 | 364 | for (int position = 0; position < count; position++) { 365 | if (func3.call(position, count, elements.get(position))) { 366 | elements.set(position, replace.call(elements.get(position))); 367 | } 368 | } 369 | 370 | return elements; 371 | })).toCompletable(); 372 | } 373 | 374 | public interface Evict { 375 | Single> call(final Single> elements); 376 | } 377 | 378 | public interface Func1Count { 379 | boolean call(final int count); 380 | } 381 | 382 | public interface Func1 { 383 | boolean call(final T element); 384 | } 385 | 386 | public interface Func2 { 387 | boolean call(final int position, final int count); 388 | } 389 | 390 | public interface Func3 { 391 | boolean call(final int position, final int count, final T element); 392 | } 393 | 394 | public interface Replace { 395 | T call(T element); 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /reactive_cache/src/main/java/io/reactivecache2/ExceptionAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | import io.reactivex.Completable; 20 | import io.reactivex.Observable; 21 | import io.reactivex.Single; 22 | import io.reactivex.exceptions.CompositeException; 23 | import io.rx_cache2.RxCacheException; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | final class ExceptionAdapter { 28 | 29 | Completable completeOnRxCacheLoaderError(Throwable error) { 30 | if (error instanceof CompositeException) { 31 | for (Throwable e: ((CompositeException) error).getExceptions()) { 32 | if (e instanceof RxCacheException) return Completable.complete(); 33 | } 34 | } 35 | 36 | if (error instanceof RxCacheException) return Completable.complete(); 37 | 38 | return Completable.error(error); 39 | } 40 | 41 | Observable placeholderLoader() { 42 | return Observable.error(new PlaceHolderLoader()); 43 | } 44 | 45 | Single stripPlaceholderLoaderException(Throwable error) { 46 | if (!(error instanceof CompositeException)) return Single.error(error); 47 | 48 | return Single.fromObservable(Observable.just(((CompositeException)error).getExceptions()) 49 | .flatMapIterable(errors -> errors) 50 | .filter(e -> !(e instanceof PlaceHolderLoader)) 51 | .toList().toObservable() 52 | .flatMap(curatedErrors -> { 53 | if (curatedErrors.size() == 1) return Observable.error(curatedErrors.get(0)); 54 | else return Observable.error(new CompositeException(curatedErrors)); 55 | })); 56 | } 57 | 58 | 59 | Single> emptyListIfRxCacheException(Throwable error) { 60 | if (error instanceof RxCacheException) return Single.just(new ArrayList()); 61 | else return Single.error(error); 62 | } 63 | 64 | static class PlaceHolderLoader extends Exception { 65 | 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /reactive_cache/src/main/java/io/reactivecache2/Provider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | import io.reactivex.Completable; 20 | import io.reactivex.Observable; 21 | import io.reactivex.Single; 22 | import io.reactivex.SingleTransformer; 23 | import io.rx_cache2.ConfigProvider; 24 | import io.rx_cache2.EvictDynamicKey; 25 | import io.rx_cache2.Reply; 26 | import io.rx_cache2.internal.ProcessorProviders; 27 | import java.util.concurrent.TimeUnit; 28 | 29 | /** 30 | * Entry point to manage cache CRUD operations. 31 | * 32 | * @param The type of the data to persist. 33 | */ 34 | public class Provider { 35 | private final ProviderBuilder builder; 36 | protected final ExceptionAdapter exceptionAdapter; 37 | 38 | Provider(ProviderBuilder builder) { 39 | this.builder = builder; 40 | this.exceptionAdapter = new ExceptionAdapter(); 41 | } 42 | 43 | /** 44 | * Evict all the cached data for this provider. 45 | */ 46 | public final Completable evict() { 47 | return Completable.defer(() -> 48 | Completable.fromObservable(builder.processorProviders 49 | .process(getConfigProvider(Observable.error(new RuntimeException()), 50 | new EvictDynamicKey(true), false, false))) 51 | .onErrorResumeNext(exceptionAdapter::completeOnRxCacheLoaderError) 52 | ); 53 | } 54 | 55 | /** 56 | * Replace the cached data by the element emitted from the loader. 57 | */ 58 | public final SingleTransformer replace() { 59 | return loader -> 60 | loader.flatMap(data -> Single.fromObservable(builder.processorProviders 61 | .process(getConfigProvider(Observable.just(data), 62 | new EvictDynamicKey(true), false, null)))); 63 | } 64 | 65 | /** 66 | * Read from cache and throw if no data is available. 67 | */ 68 | public final Single read() { 69 | return Single.defer(() -> 70 | Single.fromObservable(builder.processorProviders 71 | .process(getConfigProvider(exceptionAdapter.placeholderLoader(), 72 | new EvictDynamicKey(false), false, null)))) 73 | .onErrorResumeNext(exceptionAdapter::stripPlaceholderLoaderException); 74 | } 75 | 76 | /** 77 | * Read from cache but if there is not data available then read from the loader and cache its 78 | * element. 79 | */ 80 | public final SingleTransformer readWithLoader() { 81 | return loader -> 82 | Single.fromObservable(builder.processorProviders 83 | .process(getConfigProvider(loader.toObservable(), 84 | new EvictDynamicKey(false), false, null))); 85 | } 86 | 87 | /** 88 | * Same as {@link Provider#replace()} but wrap the data in a Reply object for debug purposes. 89 | */ 90 | public final SingleTransformer> replaceAsReply() { 91 | return loader -> 92 | loader.flatMap(data -> Single.fromObservable(builder.processorProviders 93 | .process(getConfigProvider(Observable.just(data), new EvictDynamicKey(true), true, 94 | null)))); 95 | } 96 | 97 | /** 98 | * Same as {@link Provider#readWithLoader()} but wrap the data in a Reply object for debug 99 | * purposes. 100 | */ 101 | public final SingleTransformer> readWithLoaderAsReply() { 102 | return loader -> Single.fromObservable(builder.processorProviders 103 | .process(getConfigProvider(loader.toObservable(), new EvictDynamicKey(false), true, null))); 104 | } 105 | 106 | private ConfigProvider getConfigProvider(Observable loader, 107 | EvictDynamicKey evict, boolean detailResponse, Boolean useExpiredDataIfNotLoaderAvailable) { 108 | Long lifeTime = builder.timeUnit != null ? 109 | builder.timeUnit.toMillis(builder.duration) : null; 110 | 111 | return new ConfigProvider(builder.key, useExpiredDataIfNotLoaderAvailable, lifeTime, 112 | detailResponse, 113 | builder.expirable, builder.encrypted, builder.key, 114 | "", loader, evict); 115 | } 116 | 117 | public static class ProviderBuilder { 118 | protected String key; 119 | private boolean encrypted, expirable; 120 | private Long duration; 121 | private TimeUnit timeUnit; 122 | private final ProcessorProviders processorProviders; 123 | 124 | ProviderBuilder(ProcessorProviders processorProviders) { 125 | this.encrypted = false; 126 | this.expirable = true; 127 | this.processorProviders = processorProviders; 128 | } 129 | 130 | /** 131 | * If called, this provider encrypts its data as long as ReactiveCache has been configured with 132 | * an encryption key. 133 | */ 134 | public ProviderBuilder encrypt(boolean encrypt) { 135 | this.encrypted = encrypt; 136 | return this; 137 | } 138 | 139 | /** 140 | * Make the data associated with this provider eligible to be expired if not enough space 141 | * remains on disk. By default is true. 142 | */ 143 | public ProviderBuilder expirable(boolean expirable) { 144 | this.expirable = expirable; 145 | return this; 146 | } 147 | 148 | /** 149 | * Set the amount of time before the data would be evicted. If life cache is not configured, the 150 | * data will be never evicted unless it is required explicitly using {@link Provider#evict()} or 151 | * {@link Provider#replace()} 152 | */ 153 | public ProviderBuilder lifeCache(long duration, TimeUnit timeUnit) { 154 | this.duration = duration; 155 | this.timeUnit = timeUnit; 156 | return this; 157 | } 158 | 159 | /** 160 | * Set the key for the provider. 161 | */ 162 | public > R withKey(Object key) { 163 | this.key = key.toString(); 164 | return (R) new Provider<>(this); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /reactive_cache/src/main/java/io/reactivecache2/ProviderGroup.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | import io.reactivex.Completable; 20 | import io.reactivex.Observable; 21 | import io.reactivex.Single; 22 | import io.reactivex.SingleTransformer; 23 | import io.rx_cache2.ConfigProvider; 24 | import io.rx_cache2.EvictDynamicKey; 25 | import io.rx_cache2.EvictDynamicKeyGroup; 26 | import io.rx_cache2.Reply; 27 | import io.rx_cache2.internal.ProcessorProviders; 28 | import java.util.concurrent.TimeUnit; 29 | 30 | /** 31 | * Entry point to manage cache CRUD operations with groups. 32 | * 33 | * @param The type of the data to persist. 34 | */ 35 | public class ProviderGroup { 36 | private final ProviderBuilder builder; 37 | protected final ExceptionAdapter exceptionAdapter; 38 | 39 | ProviderGroup(ProviderBuilder builder) { 40 | this.builder = builder; 41 | this.exceptionAdapter = new ExceptionAdapter(); 42 | } 43 | 44 | /** 45 | * Evict all the cached data for this provider. 46 | */ 47 | public final Completable evict() { 48 | return Completable.defer(() -> 49 | Completable.fromObservable(builder.processorProviders 50 | .process(getConfigProvider(Observable.error(new RuntimeException()), "", 51 | new EvictDynamicKey(true), false, false))) 52 | .onErrorResumeNext(exceptionAdapter::completeOnRxCacheLoaderError) 53 | ); 54 | } 55 | 56 | /** 57 | * Evict the cached data by group. 58 | */ 59 | public final Completable evict(final Object group) { 60 | return Completable.defer(() -> 61 | Completable.fromObservable(builder.processorProviders 62 | .process(getConfigProvider(Observable.error(new RuntimeException()), group.toString(), 63 | new EvictDynamicKeyGroup(true), false, false))) 64 | .onErrorResumeNext(exceptionAdapter::completeOnRxCacheLoaderError) 65 | ); 66 | } 67 | 68 | /** 69 | * Replace the cached data by group based on the element emitted from the loader. 70 | */ 71 | public final SingleTransformer replace(final Object group) { 72 | return loader -> 73 | loader.flatMap(data -> Single.fromObservable(builder.processorProviders 74 | .process(getConfigProvider(Observable.just(data), group.toString(), 75 | new EvictDynamicKeyGroup(true), false, null)))); 76 | } 77 | 78 | /** 79 | * Read from cache by group and throw if no data is available. 80 | */ 81 | public final Single read(final Object group) { 82 | return Single.defer(() -> 83 | Single.fromObservable(builder.processorProviders 84 | .process(getConfigProvider(exceptionAdapter.placeholderLoader(), group.toString(), 85 | new EvictDynamicKeyGroup(false), false, null)))) 86 | .onErrorResumeNext(exceptionAdapter::stripPlaceholderLoaderException); 87 | } 88 | 89 | /** 90 | * Read from cache by group but if there is not data available then read from the loader and cache 91 | * its element. 92 | */ 93 | public final SingleTransformer readWithLoader(final Object group) { 94 | return loader -> Single.fromObservable(builder.processorProviders 95 | .process(getConfigProvider(loader.toObservable(), group.toString(), 96 | new EvictDynamicKeyGroup(false), false, null))); 97 | } 98 | 99 | /** 100 | * Same as {@link ProviderGroup#replace(Object)} but wrap the data in a Reply object for debug 101 | * purposes. 102 | */ 103 | public final SingleTransformer> replaceAsReply(final Object group) { 104 | return loader -> 105 | loader.flatMap(data -> Single.fromObservable(builder.processorProviders 106 | .process(getConfigProvider(Observable.just(data), group.toString(), 107 | new EvictDynamicKeyGroup(true), true, null)))); 108 | } 109 | 110 | /** 111 | * Same as {@link ProviderGroup#readWithLoader(Object)} but wrap the data in a Reply object for 112 | * debug purposes. 113 | */ 114 | public final SingleTransformer> readWithLoaderAsReply(final Object group) { 115 | return loader -> Single.fromObservable(builder.processorProviders 116 | .process(getConfigProvider(loader.toObservable(), group.toString(), new EvictDynamicKeyGroup(false), 117 | true, null))); 118 | } 119 | 120 | private ConfigProvider getConfigProvider(Observable loader, String group, 121 | EvictDynamicKey evict, boolean detailResponse, Boolean useExpiredDataIfNotLoaderAvailable) { 122 | Long lifeTime = builder.timeUnit != null ? 123 | builder.timeUnit.toMillis(builder.duration) : null; 124 | 125 | return new ConfigProvider(builder.key, useExpiredDataIfNotLoaderAvailable, lifeTime, 126 | detailResponse, 127 | builder.expirable, builder.encrypted, builder.key, 128 | group, loader, evict); 129 | } 130 | 131 | public static class ProviderBuilder { 132 | protected String key; 133 | private boolean encrypted, expirable; 134 | private Long duration; 135 | private TimeUnit timeUnit; 136 | private final ProcessorProviders processorProviders; 137 | 138 | ProviderBuilder(ProcessorProviders processorProviders) { 139 | this.encrypted = false; 140 | this.expirable = true; 141 | this.processorProviders = processorProviders; 142 | } 143 | 144 | /** 145 | * Same as {@link Provider.ProviderBuilder#encrypt(boolean)} 146 | */ 147 | public ProviderBuilder encrypt(boolean encrypt) { 148 | this.encrypted = encrypt; 149 | return this; 150 | } 151 | 152 | /** 153 | * Same as {@link Provider.ProviderBuilder#expirable(boolean)} 154 | */ 155 | public ProviderBuilder expirable(boolean expirable) { 156 | this.expirable = expirable; 157 | return this; 158 | } 159 | 160 | /** 161 | * Same as {@link Provider.ProviderBuilder#lifeCache(long, TimeUnit)} 162 | */ 163 | public ProviderBuilder lifeCache(long duration, TimeUnit timeUnit) { 164 | this.duration = duration; 165 | this.timeUnit = timeUnit; 166 | return this; 167 | } 168 | 169 | /** 170 | * Same as {@link Provider.ProviderBuilder#withKey(Object)} 171 | */ 172 | public > R withKey(Object key) { 173 | this.key = key.toString(); 174 | return (R) new ProviderGroup<>(this); 175 | } 176 | } 177 | } -------------------------------------------------------------------------------- /reactive_cache/src/main/java/io/reactivecache2/ProviderGroupList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | import io.rx_cache2.internal.ProcessorProviders; 20 | import java.util.List; 21 | 22 | public final class ProviderGroupList extends ProviderGroup> { 23 | ProviderGroupList(ProviderBuilder> builder) { 24 | super(builder); 25 | } 26 | 27 | public ActionsList entries(Object group) { 28 | return ActionsList.with(elements -> elements.compose(replace(group)), 29 | read(group).onErrorResumeNext(exceptionAdapter::emptyListIfRxCacheException)); 30 | } 31 | 32 | public static class ProviderBuilderList extends ProviderBuilder> { 33 | ProviderBuilderList(ProcessorProviders processorProviders) { 34 | super(processorProviders); 35 | } 36 | 37 | @Override public >> R withKey(Object key) { 38 | this.key = key.toString(); 39 | return (R) new ProviderGroupList<>(this); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /reactive_cache/src/main/java/io/reactivecache2/ProviderList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | import io.rx_cache2.internal.ProcessorProviders; 20 | import java.util.List; 21 | 22 | public final class ProviderList extends Provider> { 23 | ProviderList(ProviderBuilder> builder) { 24 | super(builder); 25 | } 26 | 27 | public ActionsList entries() { 28 | return ActionsList.with(elements -> elements.compose(replace()), 29 | read().onErrorResumeNext(exceptionAdapter::emptyListIfRxCacheException)); 30 | } 31 | 32 | public static class ProviderBuilderList extends ProviderBuilder> { 33 | ProviderBuilderList(ProcessorProviders processorProviders) { 34 | super(processorProviders); 35 | } 36 | 37 | @Override public >> R withKey(Object key) { 38 | this.key = key.toString(); 39 | return (R) new ProviderList<>(this); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /reactive_cache/src/main/java/io/reactivecache2/ReactiveCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | import io.reactivex.Completable; 20 | import io.rx_cache2.MigrationCache; 21 | import io.rx_cache2.internal.DaggerRxCacheComponent; 22 | import io.rx_cache2.internal.Locale; 23 | import io.rx_cache2.internal.ProcessorProviders; 24 | import io.rx_cache2.internal.RxCacheModule; 25 | import io.victoralbertos.jolyglot.JolyglotGenerics; 26 | import java.io.File; 27 | import java.security.InvalidParameterException; 28 | import java.util.List; 29 | 30 | public final class ReactiveCache { 31 | private final ProcessorProviders processorProviders; 32 | 33 | private ReactiveCache(Builder builder) { 34 | this.processorProviders = DaggerRxCacheComponent.builder() 35 | .rxCacheModule(new RxCacheModule(builder.cacheDirectory, 36 | builder.useExpiredDataIfLoaderNotAvailable, builder.diskCacheSize, 37 | builder.encryptKey, builder.migrationsCache, 38 | builder.jolyglot)) 39 | .build().providers(); 40 | } 41 | 42 | /** 43 | * Return a {@link Provider.ProviderBuilder} to build the provider. 44 | * 45 | * @param the type of data to be cached. 46 | */ 47 | public Provider.ProviderBuilder provider() { 48 | return new Provider.ProviderBuilder<>(processorProviders); 49 | } 50 | 51 | /** 52 | * Return a {@link ProviderList.ProviderBuilderList} to build the provider. 53 | * 54 | * @param the type of data to be cached. 55 | */ 56 | public ProviderList.ProviderBuilderList providerList() { 57 | return new ProviderList.ProviderBuilderList<>(processorProviders); 58 | } 59 | 60 | /** 61 | * Return a {@link ProviderGroup.ProviderBuilder} to build the provider group. 62 | * 63 | * @param the type of data to be cached. 64 | */ 65 | public ProviderGroup.ProviderBuilder providerGroup() { 66 | return new ProviderGroup.ProviderBuilder<>(processorProviders); 67 | } 68 | 69 | /** 70 | * Return a {@link ProviderGroupList.ProviderBuilder} to build the provider. 71 | * 72 | * @param the type of data to be cached. 73 | */ 74 | public ProviderGroupList.ProviderBuilderList providerGroupList() { 75 | return new ProviderGroupList.ProviderBuilderList<>(processorProviders); 76 | } 77 | 78 | /** 79 | * Evict all the cached data. 80 | */ 81 | public Completable evictAll() { 82 | return Completable.fromObservable(processorProviders.evictAll()); 83 | } 84 | 85 | /** 86 | * Builder for building an specific ReactiveCache instance 87 | */ 88 | public static class Builder { 89 | private boolean useExpiredDataIfLoaderNotAvailable; 90 | private Integer diskCacheSize; 91 | private String encryptKey; 92 | private List migrationsCache; 93 | private File cacheDirectory; 94 | private JolyglotGenerics jolyglot; 95 | 96 | /** 97 | * if called ReactiveCache dispatches records already expired instead of throwing an exception. 98 | */ 99 | public Builder useExpiredDataWhenNoLoaderAvailable() { 100 | this.useExpiredDataIfLoaderNotAvailable = true; 101 | return this; 102 | } 103 | 104 | /** 105 | * Sets the max memory in megabytes for all the cached data on disk If not supplied, 100 106 | * megabytes will be the default value. 107 | */ 108 | public Builder diskCacheSize(Integer megabytes) { 109 | this.diskCacheSize = megabytes; 110 | return this; 111 | } 112 | 113 | /** 114 | * Set the key to encrypt data for specifics providers. 115 | */ 116 | public Builder encrypt(String key) { 117 | this.encryptKey = key; 118 | return this; 119 | } 120 | 121 | /** 122 | * Set the migrations to run between releases. 123 | */ 124 | public Builder migrations(List migrationsCache) { 125 | this.migrationsCache = migrationsCache; 126 | return this; 127 | } 128 | 129 | /** 130 | * Sets the File cache system and the implementation of {@link JolyglotGenerics} to serialise 131 | * and deserialize objects 132 | * 133 | * @param cacheDirectory The File system used by the persistence layer 134 | * @param jolyglot A concrete implementation of {@link JolyglotGenerics} 135 | */ 136 | public ReactiveCache using(File cacheDirectory, JolyglotGenerics jolyglot) { 137 | if (cacheDirectory == null) { 138 | throw new InvalidParameterException(Locale.REPOSITORY_DISK_ADAPTER_CAN_NOT_BE_NULL); 139 | } 140 | if (!cacheDirectory.exists()) { 141 | throw new InvalidParameterException(Locale.REPOSITORY_DISK_ADAPTER_DOES_NOT_EXIST); 142 | } 143 | if (!cacheDirectory.canWrite()) { 144 | throw new InvalidParameterException(Locale.REPOSITORY_DISK_ADAPTER_IS_NOT_WRITABLE); 145 | } 146 | 147 | if (jolyglot == null) { 148 | throw new InvalidParameterException(Locale.JSON_CONVERTER_CAN_NOT_BE_NULL); 149 | } 150 | 151 | this.cacheDirectory = cacheDirectory; 152 | this.jolyglot = jolyglot; 153 | 154 | return new ReactiveCache(this); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/ActionsListTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | import io.reactivex.Single; 20 | import io.reactivex.observers.TestObserver; 21 | import java.util.ArrayList; 22 | import java.util.Arrays; 23 | import java.util.List; 24 | import org.junit.Test; 25 | 26 | import static org.hamcrest.MatcherAssert.assertThat; 27 | import static org.hamcrest.core.Is.is; 28 | 29 | public abstract class ActionsListTest { 30 | 31 | @Test public void Add_All() { 32 | checkInitialState(); 33 | addAll(10); 34 | } 35 | 36 | @Test public void Add_All_First() { 37 | checkInitialState(); 38 | addAll(10); 39 | 40 | actions() 41 | .addAllFirst(Arrays.asList(new Mock("11"), new Mock("12"))) 42 | .test() 43 | .awaitTerminalEvent(); 44 | 45 | List mocks = cache().test().values().get(0); 46 | assertThat(mocks.size(), is(12)); 47 | assertThat(mocks.get(0).getMessage(), is("11")); 48 | assertThat(mocks.get(1).getMessage(), is("12")); 49 | } 50 | 51 | @Test public void Add_All_Last() { 52 | checkInitialState(); 53 | addAll(10); 54 | 55 | actions() 56 | .addAllLast(Arrays.asList(new Mock("11"), new Mock("12"))) 57 | .test() 58 | .awaitTerminalEvent(); 59 | 60 | List mocks = cache().test().values().get(0); 61 | assertThat(mocks.size(), is(12)); 62 | assertThat(mocks.get(10).getMessage(), is("11")); 63 | assertThat(mocks.get(11).getMessage(), is("12")); 64 | } 65 | 66 | @Test public void Add_First() { 67 | checkInitialState(); 68 | 69 | actions() 70 | .addFirst(new Mock("1")) 71 | .test() 72 | .awaitTerminalEvent(); 73 | 74 | List mocks = cache().test().values().get(0); 75 | assertThat(mocks.size(), is(1)); 76 | assertThat(mocks.get(0).getMessage(), is("1")); 77 | } 78 | 79 | @Test public void Add_Last() { 80 | checkInitialState(); 81 | addAll(10); 82 | 83 | actions() 84 | .addLast(new Mock("11")) 85 | .test() 86 | .awaitTerminalEvent(); 87 | 88 | List mocks = cache().test().values().get(0); 89 | assertThat(mocks.size(), is(11)); 90 | assertThat(mocks.get(10).getMessage(), is("11")); 91 | } 92 | 93 | @Test public void Add() { 94 | checkInitialState(); 95 | addAll(10); 96 | 97 | actions() 98 | .add((position, count) -> position == 5, new Mock("6_added")) 99 | .test() 100 | .awaitTerminalEvent(); 101 | 102 | List mocks = cache().test().values().get(0); 103 | assertThat(mocks.size(), is(11)); 104 | assertThat(mocks.get(5).getMessage(), is("6_added")); 105 | } 106 | 107 | @Test public void EvictFirst() { 108 | checkInitialState(); 109 | addAll(10); 110 | 111 | actions() 112 | .evictFirst() 113 | .test() 114 | .awaitTerminalEvent(); 115 | 116 | List mocks = cache().test().values().get(0); 117 | assertThat(mocks.size(), is(9)); 118 | assertThat(mocks.get(0).getMessage(), is("1")); 119 | } 120 | 121 | @Test public void EvictFirstN() { 122 | checkInitialState(); 123 | addAll(10); 124 | 125 | actions() 126 | .evictFirstN(4) 127 | .test() 128 | .awaitTerminalEvent(); 129 | 130 | List mocks = cache().test().values().get(0); 131 | assertThat(mocks.size(), is(6)); 132 | assertThat(mocks.get(0).getMessage(), is("4")); 133 | } 134 | 135 | @Test public void EvictFirstExposingCount() { 136 | checkInitialState(); 137 | addAll(10); 138 | 139 | //do not evict 140 | actions() 141 | .evictFirst(count -> count > 10) 142 | .test() 143 | .awaitTerminalEvent(); 144 | 145 | List mocks = cache().test().values().get(0); 146 | assertThat(mocks.size(), is(10)); 147 | 148 | //evict 149 | actions() 150 | .evictFirst(count -> count > 9) 151 | .test() 152 | .awaitTerminalEvent(); 153 | 154 | mocks = cache().test().values().get(0); 155 | assertThat(mocks.size(), is(9)); 156 | assertThat(mocks.get(0).getMessage(), is("1")); 157 | } 158 | 159 | @Test public void EvictFirstNExposingCount() { 160 | checkInitialState(); 161 | addAll(10); 162 | 163 | //do not evict 164 | actions() 165 | .evictFirstN(count -> count > 10, 5) 166 | .test() 167 | .awaitTerminalEvent(); 168 | 169 | List mocks = cache().test().values().get(0); 170 | assertThat(mocks.size(), is(10)); 171 | 172 | //evict 173 | actions() 174 | .evictFirstN(count -> count > 9, 5) 175 | .test() 176 | .awaitTerminalEvent(); 177 | 178 | mocks = cache().test().values().get(0); 179 | assertThat(mocks.size(), is(5)); 180 | assertThat(mocks.get(0).getMessage(), is("5")); 181 | assertThat(mocks.get(1).getMessage(), is("6")); 182 | } 183 | 184 | @Test public void EvictLast() { 185 | checkInitialState(); 186 | addAll(10); 187 | 188 | actions() 189 | .evictLast() 190 | .test() 191 | .awaitTerminalEvent(); 192 | 193 | List mocks = cache().test().values().get(0); 194 | assertThat(mocks.size(), is(9)); 195 | assertThat(mocks.get(8).getMessage(), is("8")); 196 | } 197 | 198 | @Test public void EvictLastN() { 199 | checkInitialState(); 200 | addAll(10); 201 | 202 | actions() 203 | .evictLastN(4) 204 | .test() 205 | .awaitTerminalEvent(); 206 | 207 | List mocks = cache().test().values().get(0); 208 | assertThat(mocks.size(), is(6)); 209 | assertThat(mocks.get(0).getMessage(), is("0")); 210 | } 211 | 212 | @Test public void EvictLastExposingCount() { 213 | checkInitialState(); 214 | addAll(10); 215 | 216 | //do not evict 217 | actions() 218 | .evictLast(count -> count > 10) 219 | .test() 220 | .awaitTerminalEvent(); 221 | 222 | List mocks = cache().test().values().get(0); 223 | assertThat(mocks.size(), is(10)); 224 | 225 | //evict 226 | actions() 227 | .evictLast(count -> count > 9) 228 | .test() 229 | .awaitTerminalEvent(); 230 | 231 | mocks = cache().test().values().get(0); 232 | assertThat(mocks.size(), is(9)); 233 | assertThat(mocks.get(8).getMessage(), is("8")); 234 | } 235 | 236 | @Test public void EvictLastNExposingCount() { 237 | checkInitialState(); 238 | addAll(10); 239 | 240 | //do not evict 241 | actions() 242 | .evictLastN(count -> count > 10, 5) 243 | .test() 244 | .awaitTerminalEvent(); 245 | 246 | List mocks = cache().test().values().get(0); 247 | assertThat(mocks.size(), is(10)); 248 | 249 | //evict 250 | actions() 251 | .evictLastN(count -> count > 9, 5) 252 | .test() 253 | .awaitTerminalEvent(); 254 | 255 | mocks = cache().test().values().get(0); 256 | assertThat(mocks.size(), is(5)); 257 | assertThat(mocks.get(0).getMessage(), is("0")); 258 | assertThat(mocks.get(1).getMessage(), is("1")); 259 | } 260 | 261 | @Test public void EvictExposingElementCurrentIteration() { 262 | checkInitialState(); 263 | addAll(10); 264 | 265 | actions() 266 | .evict(element -> element.getMessage().equals("3")) 267 | .test() 268 | .awaitTerminalEvent(); 269 | 270 | List mocks = cache().test().values().get(0); 271 | assertThat(mocks.size(), is(9)); 272 | assertThat(mocks.get(3).getMessage(), is("4")); 273 | } 274 | 275 | @Test public void EvictExposingCountAndPositionAndElementCurrentIteration() { 276 | checkInitialState(); 277 | addAll(10); 278 | 279 | //do not evict 280 | actions() 281 | .evict((position, count, element) -> count > 10 && element.getMessage().equals("3")) 282 | .test() 283 | .awaitTerminalEvent(); 284 | 285 | List mocks = cache().test().values().get(0); 286 | assertThat(mocks.size(), is(10)); 287 | assertThat(mocks.get(3).getMessage(), is("3")); 288 | 289 | //evict 290 | actions() 291 | .evict((position, count, element) -> count > 9 && element.getMessage().equals("3")) 292 | .test() 293 | .awaitTerminalEvent(); 294 | 295 | mocks = cache().test().values().get(0); 296 | assertThat(mocks.size(), is(9)); 297 | assertThat(mocks.get(3).getMessage(), is("4")); 298 | } 299 | 300 | @Test public void EvictIterable() { 301 | checkInitialState(); 302 | addAll(10); 303 | 304 | actions() 305 | .evictIterable( 306 | (position, count, element) -> element.getMessage().equals("2") || element.getMessage() 307 | .equals("3")) 308 | .test() 309 | .awaitTerminalEvent(); 310 | 311 | List mocks = cache().test().values().get(0); 312 | assertThat(mocks.size(), is(8)); 313 | assertThat(mocks.get(2).getMessage(), is("4")); 314 | assertThat(mocks.get(3).getMessage(), is("5")); 315 | } 316 | 317 | @Test public void EvictAllKeepingFirstN() { 318 | checkInitialState(); 319 | addAll(10); 320 | 321 | actions() 322 | .evictAllKeepingFirstN(3) 323 | .test() 324 | .awaitTerminalEvent(); 325 | 326 | List mocks = cache().test().values().get(0); 327 | assertThat(mocks.size(), is(3)); 328 | assertThat(mocks.get(0).getMessage(), is("0")); 329 | } 330 | 331 | @Test public void EvictAllKeepingLastN() { 332 | checkInitialState(); 333 | addAll(10); 334 | 335 | actions() 336 | .evictAllKeepingLastN(7) 337 | .test() 338 | .awaitTerminalEvent(); 339 | 340 | List mocks = cache().test().values().get(0); 341 | assertThat(mocks.size(), is(7)); 342 | assertThat(mocks.get(0).getMessage(), is("3")); 343 | } 344 | 345 | @Test public void UpdateExposingElementCurrentIteration() { 346 | checkInitialState(); 347 | addAll(10); 348 | 349 | actions() 350 | .update(element -> element.getMessage().equals("5"), element -> { 351 | element.setMessage("5_updated"); 352 | return element; 353 | }) 354 | .test() 355 | .awaitTerminalEvent(); 356 | 357 | List mocks = cache().test().values().get(0); 358 | assertThat(mocks.size(), is(10)); 359 | assertThat(mocks.get(5).getMessage(), is("5_updated")); 360 | } 361 | 362 | @Test public void UpdateExposingCountAndPositionAndElementCurrentIteration() { 363 | checkInitialState(); 364 | addAll(10); 365 | 366 | //do not evict 367 | actions() 368 | .update((position, count, element) -> count > 10 && element.getMessage().equals("5"), 369 | element -> { 370 | element.setMessage("5_updated"); 371 | return element; 372 | }) 373 | .test() 374 | .awaitTerminalEvent(); 375 | 376 | List mocks = cache().test().values().get(0); 377 | assertThat(mocks.size(), is(10)); 378 | assertThat(mocks.get(5).getMessage(), is("5")); 379 | 380 | //evict 381 | actions() 382 | .update((position, count, element) -> count > 9 && element.getMessage().equals("5"), 383 | element -> { 384 | element.setMessage("5_updated"); 385 | return element; 386 | }) 387 | .test() 388 | .awaitTerminalEvent(); 389 | 390 | mocks = cache().test().values().get(0); 391 | assertThat(mocks.size(), is(10)); 392 | assertThat(mocks.get(5).getMessage(), is("5_updated")); 393 | } 394 | 395 | @Test public void UpdateIterableExposingElementCurrentIteration() { 396 | checkInitialState(); 397 | addAll(10); 398 | 399 | actions() 400 | .updateIterable( 401 | element -> element.getMessage().equals("5") || element.getMessage().equals("6"), 402 | element -> { 403 | element.setMessage("5_or_6_updated"); 404 | return element; 405 | }) 406 | .test() 407 | .awaitTerminalEvent(); 408 | 409 | List mocks = cache().test().values().get(0); 410 | assertThat(mocks.size(), is(10)); 411 | assertThat(mocks.get(5).getMessage(), is("5_or_6_updated")); 412 | assertThat(mocks.get(6).getMessage(), is("5_or_6_updated")); 413 | } 414 | 415 | @Test public void UpdateIterableExposingCountAndPositionAndElementCurrentIteration() { 416 | checkInitialState(); 417 | addAll(10); 418 | 419 | //do not evict 420 | actions() 421 | .updateIterable( 422 | (position, count, element) -> count > 10 && (element.getMessage().equals("5") || element.getMessage() 423 | .equals("6")), element -> { 424 | element.setMessage("5_or_6_updated"); 425 | return element; 426 | }) 427 | .test() 428 | .awaitTerminalEvent(); 429 | 430 | List mocks = cache().test().values().get(0); 431 | assertThat(mocks.size(), is(10)); 432 | assertThat(mocks.get(5).getMessage(), is("5")); 433 | assertThat(mocks.get(6).getMessage(), is("6")); 434 | 435 | //evict 436 | actions() 437 | .updateIterable( 438 | (position, count, element) -> count > 9 && (element.getMessage().equals("5") || element.getMessage() 439 | .equals("6")), element -> { 440 | element.setMessage("5_or_6_updated"); 441 | return element; 442 | }) 443 | .test() 444 | .awaitTerminalEvent(); 445 | 446 | mocks = cache().test().values().get(0); 447 | assertThat(mocks.size(), is(10)); 448 | assertThat(mocks.get(5).getMessage(), is("5_or_6_updated")); 449 | assertThat(mocks.get(6).getMessage(), is("5_or_6_updated")); 450 | } 451 | 452 | private void checkInitialState() { 453 | TestObserver> testObserver = cache().test(); 454 | testObserver.awaitTerminalEvent(); 455 | 456 | if (testObserver.values().isEmpty()) return; 457 | 458 | List mocks = testObserver.values().get(0); 459 | assertThat(mocks.size(), is(0)); 460 | } 461 | 462 | private void addAll(int count) { 463 | List mocks = new ArrayList<>(); 464 | 465 | for (int i = 0; i < count; i++) { 466 | mocks.add(new Mock(String.valueOf(i))); 467 | } 468 | 469 | actions() 470 | .addAll((position, count1) -> true, mocks) 471 | .test() 472 | .assertNoErrors() 473 | .awaitTerminalEvent(); 474 | 475 | assertThat(cache().test().values().get(0).size(), 476 | is(count)); 477 | } 478 | 479 | protected abstract ActionsList actions(); 480 | 481 | protected abstract Single> cache(); 482 | } 483 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/ExceptionAdapterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | import io.reactivex.exceptions.CompositeException; 20 | import io.rx_cache2.RxCacheException; 21 | import java.util.List; 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | 25 | import static org.hamcrest.core.Is.is; 26 | import static org.hamcrest.core.IsEqual.equalTo; 27 | import static org.junit.Assert.assertThat; 28 | 29 | public final class ExceptionAdapterTest { 30 | private ExceptionAdapter exceptionAdapter; 31 | 32 | @Before public void setUp() { 33 | exceptionAdapter = new ExceptionAdapter(); 34 | } 35 | 36 | @Test public void When_Exception_Is_Not_RxCache_Exception_Then_Return_Observable_Error() { 37 | exceptionAdapter.completeOnRxCacheLoaderError(new RuntimeException()) 38 | .test() 39 | .assertNoValues() 40 | .assertNotComplete() 41 | .assertError(RuntimeException.class); 42 | } 43 | 44 | @Test 45 | public void When_CompositeException_Is_Not_RxCache_Exception_Then_Return_Observable_Error() { 46 | CompositeException compositeException = new CompositeException(new RuntimeException()); 47 | exceptionAdapter.completeOnRxCacheLoaderError(compositeException) 48 | .test() 49 | .assertNoValues() 50 | .assertNotComplete() 51 | .assertError(RuntimeException.class); 52 | } 53 | 54 | @Test public void When_Exception_Is_RxCache_Exception_Then_Return_Completable() { 55 | exceptionAdapter.completeOnRxCacheLoaderError(new RxCacheException("")) 56 | .test() 57 | .assertNoValues() 58 | .assertNoErrors() 59 | .assertComplete(); 60 | } 61 | 62 | @Test public void When_CompositeException_Is_RxCache_Exception_Then_Return_Completable() { 63 | CompositeException compositeException = new CompositeException(new RuntimeException(), 64 | new RxCacheException(""), new RuntimeException()); 65 | 66 | exceptionAdapter.completeOnRxCacheLoaderError(compositeException) 67 | .test() 68 | .assertNoValues() 69 | .assertNoErrors() 70 | .assertComplete(); 71 | } 72 | 73 | @Test public void When_StripPlaceholderLoaderException_Then_Strip_It() { 74 | exceptionAdapter.stripPlaceholderLoaderException(new RuntimeException()) 75 | .test() 76 | .assertNoValues() 77 | .assertNotComplete() 78 | .assertError(RuntimeException.class); 79 | } 80 | 81 | @Test public void When_StripPlaceholderLoaderException_Composite_Then_Strip_It() { 82 | CompositeException compositeException = new CompositeException(new RuntimeException(), 83 | new ExceptionAdapter.PlaceHolderLoader(), new RuntimeException()); 84 | 85 | List errors = exceptionAdapter.stripPlaceholderLoaderException(compositeException) 86 | .test() 87 | .assertNoValues() 88 | .assertNotComplete() 89 | .errors(); 90 | assertThat(1, is(errors.size())); 91 | 92 | List compositeErrors = ((CompositeException) errors.get(0)).getExceptions(); 93 | assertThat(2, is(compositeErrors.size())); 94 | assertThat(RuntimeException.class, is(equalTo(compositeErrors.get(0).getClass()))); 95 | assertThat(RuntimeException.class, is(equalTo(compositeErrors.get(1).getClass()))); 96 | } 97 | 98 | @Test public void When_emptyListIfRxCacheException_RxCacheException_Then_Empty_List() { 99 | exceptionAdapter.emptyListIfRxCacheException(new RxCacheException("")) 100 | .test() 101 | .assertNoErrors() 102 | .assertValueAt(0, List::isEmpty) 103 | .assertComplete(); 104 | } 105 | 106 | @Test public void When_emptyListIfRxCacheException_Exception_Then_Throw() { 107 | exceptionAdapter.emptyListIfRxCacheException(new RuntimeException("Fuck")) 108 | .test() 109 | .assertNoValues() 110 | .assertNotComplete() 111 | .assertErrorMessage("Fuck"); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/Jolyglot$.java: -------------------------------------------------------------------------------- 1 | package io.reactivecache2; 2 | 3 | import io.victoralbertos.jolyglot.JolyglotGenerics; 4 | import io.victoralbertos.jolyglot.MoshiSpeaker; 5 | 6 | public final class Jolyglot$ { 7 | public static JolyglotGenerics newInstance() { 8 | return new MoshiSpeaker(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/Mock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | public final class Mock { 20 | private String message; 21 | 22 | public Mock(String message) { 23 | this.message = message; 24 | } 25 | 26 | public Mock() { 27 | this.message = null; 28 | } 29 | 30 | public String getMessage() { 31 | return message; 32 | } 33 | 34 | public void setMessage(String message) { 35 | this.message = message; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/ProviderGroupListTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | import io.reactivex.Single; 20 | import io.reactivex.observers.TestObserver; 21 | import io.rx_cache2.RxCacheException; 22 | import java.util.Arrays; 23 | import java.util.List; 24 | import org.junit.Before; 25 | import org.junit.Rule; 26 | import org.junit.Test; 27 | import org.junit.rules.TemporaryFolder; 28 | 29 | public final class ProviderGroupListTest extends ActionsListTest { 30 | private static final String group = "group"; 31 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); 32 | private ReactiveCache reactiveCache; 33 | private ProviderGroupList cacheProvider; 34 | 35 | @Before public void setUp() { 36 | reactiveCache = new ReactiveCache.Builder() 37 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 38 | cacheProvider = reactiveCache.providerGroupList() 39 | .withKey("mock"); 40 | } 41 | 42 | @Test public void Verify_Inherited_Api() { 43 | final String message = "1"; 44 | 45 | //save 46 | Single.just(Arrays.asList(new Mock(message))) 47 | .compose(cacheProvider.replace(group)) 48 | .test() 49 | .awaitTerminalEvent(); 50 | 51 | TestObserver> observer = cacheProvider.read(group) 52 | .test(); 53 | 54 | //read 55 | observer.awaitTerminalEvent(); 56 | observer 57 | .assertValueAt(0, mocks -> mocks.get(0).getMessage().equals("1")) 58 | .assertNoErrors() 59 | .assertComplete(); 60 | 61 | //evict 62 | cacheProvider.evict().test().awaitTerminalEvent(); 63 | 64 | //read throws 65 | observer = cacheProvider.read(group).test(); 66 | observer.awaitTerminalEvent(); 67 | observer.assertNoValues() 68 | .assertError(RxCacheException.class) 69 | .assertNotComplete(); 70 | 71 | //read with loader 72 | observer = Single.just(Arrays.asList(new Mock(message))) 73 | .compose(cacheProvider.readWithLoader(group)).test(); 74 | observer.awaitTerminalEvent(); 75 | observer 76 | .assertValueAt(0, mocks -> mocks.get(0).getMessage().equals("1")) 77 | .assertNoErrors() 78 | .assertComplete(); 79 | } 80 | 81 | @Override protected ActionsList actions() { 82 | return cacheProvider.entries(group); 83 | } 84 | 85 | @Override protected Single> cache() { 86 | return cacheProvider.read(group); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/ProviderGroupTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | import io.reactivex.Single; 20 | import io.reactivex.observers.TestObserver; 21 | import io.rx_cache2.Reply; 22 | import io.rx_cache2.RxCacheException; 23 | import io.rx_cache2.Source; 24 | import java.util.concurrent.TimeUnit; 25 | import org.junit.Before; 26 | import org.junit.Rule; 27 | import org.junit.Test; 28 | import org.junit.rules.TemporaryFolder; 29 | 30 | import static org.hamcrest.core.Is.is; 31 | import static org.junit.Assert.assertThat; 32 | 33 | public final class ProviderGroupTest { 34 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); 35 | private ReactiveCache reactiveCache; 36 | private ProviderGroup cacheProvider; 37 | private static final String MESSAGE_GROUP = "MESSAGE_GROUP"; 38 | 39 | @Before public void setUp() { 40 | reactiveCache = new ReactiveCache.Builder() 41 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 42 | 43 | cacheProvider = reactiveCache.providerGroup() 44 | .withKey("mock"); 45 | } 46 | 47 | @Test public void When_Evict_With_No_Cached_Data_Then_Do_Not_Throw() { 48 | TestObserver observer = cacheProvider.evict().test(); 49 | observer.awaitTerminalEvent(); 50 | 51 | observer.assertNoErrors(); 52 | observer.assertNoValues(); 53 | observer.assertComplete(); 54 | } 55 | 56 | @Test public void When_Evict_By_Group_With_No_Cached_Data_Then_Do_Not_Throw() { 57 | TestObserver observer = cacheProvider.evict(MESSAGE_GROUP).test(); 58 | observer.awaitTerminalEvent(); 59 | 60 | observer.assertNoErrors(); 61 | observer.assertNoValues(); 62 | observer.assertComplete(); 63 | } 64 | 65 | @Test public void When_Evict_With_Cached_Data_Then_Evict_Data() { 66 | saveMock(MESSAGE_GROUP); 67 | verifyMockCached(MESSAGE_GROUP); 68 | 69 | saveMock(MESSAGE_GROUP+2); 70 | verifyMockCached(MESSAGE_GROUP+2); 71 | 72 | TestObserver observer = cacheProvider.evict().test(); 73 | observer.awaitTerminalEvent(); 74 | 75 | observer.assertNoErrors(); 76 | observer.assertNoValues(); 77 | observer.assertComplete(); 78 | 79 | verifyNoMockCached(MESSAGE_GROUP); 80 | verifyNoMockCached(MESSAGE_GROUP+2); 81 | } 82 | 83 | @Test public void When_Evict_By_Group_With_Cached_Data_Then_Evict_Data() { 84 | saveMock(MESSAGE_GROUP); 85 | verifyMockCached(MESSAGE_GROUP); 86 | 87 | saveMock(MESSAGE_GROUP+2); 88 | verifyMockCached(MESSAGE_GROUP+2); 89 | 90 | TestObserver observer = cacheProvider.evict(MESSAGE_GROUP).test(); 91 | observer.awaitTerminalEvent(); 92 | 93 | observer.assertNoErrors(); 94 | observer.assertNoValues(); 95 | observer.assertComplete(); 96 | 97 | verifyNoMockCached(MESSAGE_GROUP); 98 | verifyMockCached(MESSAGE_GROUP+2); 99 | } 100 | 101 | @Test public void When_Evict_With_Cached_And_Use_Expired_Data_Then_Evict_Data() { 102 | reactiveCache = new ReactiveCache.Builder() 103 | .useExpiredDataWhenNoLoaderAvailable() 104 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 105 | 106 | cacheProvider = reactiveCache.providerGroup() 107 | .withKey("mock"); 108 | 109 | saveMock(MESSAGE_GROUP); 110 | verifyMockCached(MESSAGE_GROUP); 111 | 112 | saveMock(MESSAGE_GROUP+2); 113 | verifyMockCached(MESSAGE_GROUP+2); 114 | 115 | TestObserver observer = cacheProvider.evict().test(); 116 | observer.awaitTerminalEvent(); 117 | 118 | observer.assertNoErrors(); 119 | observer.assertNoValues(); 120 | observer.assertComplete(); 121 | 122 | verifyNoMockCached(MESSAGE_GROUP); 123 | verifyNoMockCached(MESSAGE_GROUP+2); 124 | } 125 | 126 | @Test public void When_Evict_By_Group_With_Cached_And_Use_Expired_Data_Then_Evict_Data() { 127 | reactiveCache = new ReactiveCache.Builder() 128 | .useExpiredDataWhenNoLoaderAvailable() 129 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 130 | 131 | cacheProvider = reactiveCache.providerGroup() 132 | .withKey("mock"); 133 | 134 | saveMock(MESSAGE_GROUP+2); 135 | verifyMockCached(MESSAGE_GROUP+2); 136 | 137 | saveMock(MESSAGE_GROUP); 138 | verifyMockCached(MESSAGE_GROUP); 139 | 140 | TestObserver observer = cacheProvider.evict(MESSAGE_GROUP).test(); 141 | observer.awaitTerminalEvent(); 142 | 143 | observer.assertNoErrors(); 144 | observer.assertNoValues(); 145 | observer.assertComplete(); 146 | 147 | verifyNoMockCached(MESSAGE_GROUP); 148 | verifyMockCached(MESSAGE_GROUP+2); 149 | } 150 | 151 | @Test public void When_Replace_But_Loader_Throws_Then_Do_Not_Replace_Cache() { 152 | saveMock(MESSAGE_GROUP); 153 | verifyMockCached(MESSAGE_GROUP); 154 | 155 | TestObserver observer = Single.error(new RuntimeException()) 156 | .compose(cacheProvider.replace(MESSAGE_GROUP)) 157 | .test(); 158 | observer.awaitTerminalEvent(); 159 | 160 | observer.assertError(RuntimeException.class); 161 | observer.assertNoValues(); 162 | 163 | verifyMockCached(MESSAGE_GROUP); 164 | } 165 | 166 | @Test public void When_Replace_Then_Replace_Cache() { 167 | saveMock(MESSAGE_GROUP); 168 | verifyMockCached(MESSAGE_GROUP); 169 | 170 | TestObserver observer = Single.just(new Mock("1")) 171 | .compose(cacheProvider.replace(MESSAGE_GROUP)) 172 | .test(); 173 | observer.awaitTerminalEvent(); 174 | 175 | observer.assertValueCount(1); 176 | observer.assertNoErrors(); 177 | observer.assertComplete(); 178 | 179 | observer = cacheProvider.read(MESSAGE_GROUP).test(); 180 | observer.awaitTerminalEvent(); 181 | 182 | observer.assertValueCount(1); 183 | observer.assertNoErrors(); 184 | observer.assertComplete(); 185 | assertThat(observer.values() 186 | .get(0).getMessage(), is("1")); 187 | } 188 | 189 | @Test public void When_Read_With_Nothing_To_Read_Then_Throw() { 190 | TestObserver observer = cacheProvider.read(MESSAGE_GROUP).test(); 191 | observer.awaitTerminalEvent(); 192 | 193 | observer.assertError(RxCacheException.class); 194 | observer.assertNoValues(); 195 | } 196 | 197 | @Test public void When_Read_Then_Return_Data() { 198 | saveMock(MESSAGE_GROUP); 199 | 200 | TestObserver observer = cacheProvider.read(MESSAGE_GROUP).test(); 201 | observer.awaitTerminalEvent(); 202 | 203 | observer.assertValueCount(1); 204 | observer.assertNoErrors(); 205 | observer.assertComplete(); 206 | assertThat(observer.values() 207 | .get(0).getMessage(), is(MESSAGE_GROUP)); 208 | } 209 | 210 | @Test public void Verify_Read_With_Loader() { 211 | cacheProvider = reactiveCache.providerGroup() 212 | .lifeCache(100, TimeUnit.MILLISECONDS) 213 | .withKey("ephemeralMock"); 214 | 215 | saveMock(MESSAGE_GROUP); 216 | 217 | TestObserver observer = Single.just(new Mock("1")) 218 | .compose(cacheProvider.readWithLoader(MESSAGE_GROUP)) 219 | .test(); 220 | observer.awaitTerminalEvent(); 221 | 222 | observer.assertNoErrors(); 223 | observer.assertValueCount(1); 224 | assertThat(observer.values() 225 | .get(0).getMessage(), is(MESSAGE_GROUP)); 226 | 227 | waitTime(200); 228 | 229 | observer = Single.just(new Mock("1")) 230 | .compose(cacheProvider.readWithLoader(MESSAGE_GROUP)) 231 | .test(); 232 | observer.awaitTerminalEvent(); 233 | 234 | observer.assertNoErrors(); 235 | observer.assertValueCount(1); 236 | assertThat(observer.values() 237 | .get(0).getMessage(), is("1")); 238 | 239 | observer = Single.just(new Mock("3")) 240 | .compose(cacheProvider.readWithLoader(MESSAGE_GROUP)) 241 | .test(); 242 | observer.awaitTerminalEvent(); 243 | 244 | observer.assertNoErrors(); 245 | observer.assertValueCount(1); 246 | assertThat(observer.values() 247 | .get(0).getMessage(), is("1")); 248 | } 249 | 250 | @Test public void Verify_Read_As_Reply_With_Loader() { 251 | cacheProvider = reactiveCache.providerGroup() 252 | .lifeCache(100, TimeUnit.MILLISECONDS) 253 | .withKey("ephemeralMock"); 254 | 255 | saveMock(MESSAGE_GROUP); 256 | 257 | TestObserver> observer = Single.just(new Mock(MESSAGE_GROUP)) 258 | .compose(cacheProvider.readWithLoaderAsReply(MESSAGE_GROUP)) 259 | .test(); 260 | observer.awaitTerminalEvent(); 261 | 262 | observer.assertNoErrors(); 263 | observer.assertValueCount(1); 264 | assertThat(observer.values() 265 | .get(0).getSource(), is(Source.MEMORY)); 266 | 267 | waitTime(200); 268 | 269 | observer = Single.just(new Mock(MESSAGE_GROUP)) 270 | .compose(cacheProvider.readWithLoaderAsReply(MESSAGE_GROUP)) 271 | .test(); 272 | observer.awaitTerminalEvent(); 273 | 274 | observer.assertNoErrors(); 275 | observer.assertValueCount(1); 276 | assertThat(observer.values() 277 | .get(0).getSource(), is(Source.CLOUD)); 278 | } 279 | 280 | @Test public void Verify_Pagination() { 281 | ProviderGroup mockProvider = reactiveCache.providerGroup() 282 | .withKey("mocks"); 283 | 284 | TestObserver observer; 285 | 286 | Mock mockPage1 = new Mock("1"); 287 | 288 | observer = Single.just(mockPage1) 289 | .compose(mockProvider.replace("1")) 290 | .test(); 291 | 292 | observer.awaitTerminalEvent(); 293 | 294 | Mock mockPage2 = new Mock("2"); 295 | 296 | observer = Single.just(mockPage2) 297 | .compose(mockProvider.replace("2")) 298 | .test(); 299 | 300 | observer.awaitTerminalEvent(); 301 | 302 | Mock mockPage3 = new Mock("3"); 303 | 304 | observer = Single.just(mockPage3) 305 | .compose(mockProvider.replace("3")) 306 | .test(); 307 | observer.awaitTerminalEvent(); 308 | 309 | observer = mockProvider.read("1").test(); 310 | observer.awaitTerminalEvent(); 311 | assertThat(observer.values().get(0).getMessage(), is("1")); 312 | 313 | observer = mockProvider.read("2").test(); 314 | observer.awaitTerminalEvent(); 315 | assertThat(observer.values().get(0).getMessage(), is("2")); 316 | 317 | observer = mockProvider.read("3").test(); 318 | observer.awaitTerminalEvent(); 319 | assertThat(observer.values().get(0).getMessage(), is("3")); 320 | } 321 | 322 | private void saveMock(String messageGroup) { 323 | Single.just(new Mock(messageGroup)) 324 | .compose(cacheProvider.replace(messageGroup)) 325 | .test() 326 | .awaitTerminalEvent(); 327 | } 328 | 329 | private void verifyMockCached(String messageGroup) { 330 | TestObserver observer = cacheProvider.read(messageGroup).test(); 331 | observer.awaitTerminalEvent(); 332 | 333 | observer.assertNoErrors(); 334 | observer.assertValueCount(1); 335 | assertThat(observer.values() 336 | .get(0).getMessage(), is(messageGroup)); 337 | } 338 | 339 | private void verifyNoMockCached(String group) { 340 | TestObserver observer = cacheProvider.read(group).test(); 341 | observer.awaitTerminalEvent(); 342 | 343 | observer.assertError(RxCacheException.class); 344 | observer.assertNoValues(); 345 | observer.onComplete(); 346 | } 347 | 348 | private void waitTime(long millis) { 349 | try { 350 | Thread.sleep(millis); 351 | } catch (InterruptedException e) { 352 | e.printStackTrace(); 353 | } 354 | } 355 | 356 | 357 | } 358 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/ProviderListTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2017 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | import io.reactivex.Single; 20 | import io.reactivex.observers.TestObserver; 21 | import io.rx_cache2.RxCacheException; 22 | import java.util.Arrays; 23 | import java.util.List; 24 | import org.junit.Before; 25 | import org.junit.Rule; 26 | import org.junit.Test; 27 | import org.junit.rules.TemporaryFolder; 28 | 29 | public final class ProviderListTest extends ActionsListTest { 30 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); 31 | private ReactiveCache reactiveCache; 32 | private ProviderList cacheProvider; 33 | 34 | @Before public void setUp() { 35 | reactiveCache = new ReactiveCache.Builder() 36 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 37 | cacheProvider = reactiveCache.providerList() 38 | .withKey("mock"); 39 | } 40 | 41 | @Test public void Verify_Inherited_Api() { 42 | final String message = "1"; 43 | 44 | //save 45 | Single.just(Arrays.asList(new Mock(message))) 46 | .compose(cacheProvider.replace()) 47 | .test() 48 | .awaitTerminalEvent(); 49 | 50 | TestObserver> observer = cacheProvider.read() 51 | .test(); 52 | 53 | //read 54 | observer.awaitTerminalEvent(); 55 | observer 56 | .assertValueAt(0, mocks -> mocks.get(0).getMessage().equals("1")) 57 | .assertNoErrors() 58 | .assertComplete(); 59 | 60 | //evict 61 | cacheProvider.evict().test().awaitTerminalEvent(); 62 | 63 | //read throws 64 | observer = cacheProvider.read().test(); 65 | observer.awaitTerminalEvent(); 66 | observer.assertNoValues() 67 | .assertError(RxCacheException.class) 68 | .assertNotComplete(); 69 | 70 | //read with loader 71 | observer = Single.just(Arrays.asList(new Mock(message))) 72 | .compose(cacheProvider.readWithLoader()).test(); 73 | observer.awaitTerminalEvent(); 74 | observer 75 | .assertValueAt(0, mocks -> mocks.get(0).getMessage().equals("1")) 76 | .assertNoErrors() 77 | .assertComplete(); 78 | } 79 | 80 | @Override protected ActionsList actions() { 81 | return cacheProvider.entries(); 82 | } 83 | 84 | @Override protected Single> cache() { 85 | return cacheProvider.read(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/ProviderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | import io.reactivex.Single; 20 | import io.reactivex.observers.TestObserver; 21 | import io.rx_cache2.Reply; 22 | import io.rx_cache2.RxCacheException; 23 | import io.rx_cache2.Source; 24 | import java.util.concurrent.TimeUnit; 25 | import org.junit.Before; 26 | import org.junit.Rule; 27 | import org.junit.Test; 28 | import org.junit.rules.TemporaryFolder; 29 | 30 | import static org.hamcrest.core.Is.is; 31 | import static org.junit.Assert.assertThat; 32 | 33 | public final class ProviderTest { 34 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); 35 | private ReactiveCache reactiveCache; 36 | private Provider cacheProvider; 37 | private static final String MESSAGE = "0"; 38 | 39 | @Before public void setUp() { 40 | reactiveCache = new ReactiveCache.Builder() 41 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 42 | 43 | cacheProvider = reactiveCache.provider() 44 | .withKey("mock"); 45 | } 46 | 47 | @Test public void When_useExpiredDataWhenNoLoaderAvailable_Then_Retrieve_Expired_Data() { 48 | reactiveCache = new ReactiveCache.Builder() 49 | .useExpiredDataWhenNoLoaderAvailable() 50 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 51 | 52 | cacheProvider = reactiveCache.provider() 53 | .lifeCache(100, TimeUnit.MILLISECONDS) 54 | .withKey("mock"); 55 | 56 | saveMock(); 57 | verifyMockCached(); 58 | 59 | waitTime(300); 60 | 61 | verifyMockCached(); 62 | } 63 | 64 | @Test public void When_Not_useExpiredDataWhenNoLoaderAvailable_Then_Throw() { 65 | reactiveCache = new ReactiveCache.Builder() 66 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 67 | 68 | cacheProvider = reactiveCache.provider() 69 | .lifeCache(100, TimeUnit.MILLISECONDS) 70 | .withKey("mock"); 71 | 72 | saveMock(); 73 | verifyMockCached(); 74 | 75 | waitTime(300); 76 | 77 | verifyNoMockCached(); 78 | } 79 | 80 | @Test public void When_Evict_With_No_Cached_Data_Then_Do_Not_Throw() { 81 | TestObserver observer = cacheProvider.evict().test(); 82 | observer.awaitTerminalEvent(); 83 | 84 | observer.assertNoErrors(); 85 | observer.assertNoValues(); 86 | observer.assertComplete(); 87 | } 88 | 89 | @Test public void When_Evict_With_Cached_Data_Then_Evict_Data() { 90 | saveMock(); 91 | verifyMockCached(); 92 | 93 | TestObserver observer = cacheProvider.evict().test(); 94 | observer.awaitTerminalEvent(); 95 | 96 | observer.assertNoErrors(); 97 | observer.assertNoValues(); 98 | observer.assertComplete(); 99 | 100 | verifyNoMockCached(); 101 | } 102 | 103 | @Test public void When_Evict_With_Cached_And_Use_Expired_Data_Then_Evict_Data() { 104 | reactiveCache = new ReactiveCache.Builder() 105 | .useExpiredDataWhenNoLoaderAvailable() 106 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 107 | 108 | cacheProvider = reactiveCache.provider() 109 | .withKey("mock"); 110 | 111 | saveMock(); 112 | verifyMockCached(); 113 | 114 | TestObserver observer = cacheProvider.evict().test(); 115 | observer.awaitTerminalEvent(); 116 | 117 | observer.assertNoErrors(); 118 | observer.assertNoValues(); 119 | observer.assertComplete(); 120 | 121 | verifyNoMockCached(); 122 | } 123 | 124 | @Test public void When_Replace_But_Loader_Throws_Then_Do_Not_Replace_Cache() { 125 | saveMock(); 126 | verifyMockCached(); 127 | 128 | TestObserver observer = Single.error(new RuntimeException()) 129 | .compose(cacheProvider.replace()) 130 | .test(); 131 | observer.awaitTerminalEvent(); 132 | 133 | observer.assertError(RuntimeException.class); 134 | observer.assertNoValues(); 135 | 136 | verifyMockCached(); 137 | } 138 | 139 | @Test public void When_Replace_Then_Replace_Cache() { 140 | saveMock(); 141 | verifyMockCached(); 142 | 143 | TestObserver observer = Single.just(new Mock("1")) 144 | .compose(cacheProvider.replace()) 145 | .test(); 146 | observer.awaitTerminalEvent(); 147 | 148 | observer.assertValueCount(1); 149 | observer.assertNoErrors(); 150 | observer.assertComplete(); 151 | 152 | observer = cacheProvider.read().test(); 153 | observer.awaitTerminalEvent(); 154 | 155 | observer.assertValueCount(1); 156 | observer.assertNoErrors(); 157 | observer.assertComplete(); 158 | assertThat(observer.values() 159 | .get(0).getMessage(), is("1")); 160 | } 161 | 162 | @Test public void When_Read_With_Nothing_To_Read_Then_Throw() { 163 | TestObserver observer = cacheProvider.read().test(); 164 | ; 165 | observer.awaitTerminalEvent(); 166 | 167 | observer.assertError(RxCacheException.class); 168 | observer.assertNoValues(); 169 | } 170 | 171 | @Test public void When_Read_Then_Return_Data() { 172 | saveMock(); 173 | 174 | TestObserver observer = cacheProvider.read().test(); 175 | observer.awaitTerminalEvent(); 176 | 177 | observer.assertValueCount(1); 178 | observer.assertNoErrors(); 179 | observer.assertComplete(); 180 | assertThat(observer.values() 181 | .get(0).getMessage(), is(MESSAGE)); 182 | } 183 | 184 | @Test public void Verify_Read_With_Loader() { 185 | cacheProvider = reactiveCache.provider() 186 | .lifeCache(100, TimeUnit.MILLISECONDS) 187 | .withKey("ephemeralMock"); 188 | 189 | saveMock(); 190 | 191 | TestObserver observer = Single.just(new Mock("1")) 192 | .compose(cacheProvider.readWithLoader()) 193 | .test(); 194 | observer.awaitTerminalEvent(); 195 | 196 | observer.assertNoErrors(); 197 | observer.assertValueCount(1); 198 | assertThat(observer.values() 199 | .get(0).getMessage(), is(MESSAGE)); 200 | 201 | waitTime(200); 202 | 203 | observer = Single.just(new Mock("1")) 204 | .compose(cacheProvider.readWithLoader()) 205 | .test(); 206 | observer.awaitTerminalEvent(); 207 | 208 | observer.assertNoErrors(); 209 | observer.assertValueCount(1); 210 | assertThat(observer.values() 211 | .get(0).getMessage(), is("1")); 212 | 213 | observer = Single.just(new Mock("3")) 214 | .compose(cacheProvider.readWithLoader()) 215 | .test(); 216 | observer.awaitTerminalEvent(); 217 | 218 | observer.assertNoErrors(); 219 | observer.assertValueCount(1); 220 | assertThat(observer.values() 221 | .get(0).getMessage(), is("1")); 222 | } 223 | 224 | @Test public void Verify_Read_As_Reply_With_Loader() { 225 | cacheProvider = reactiveCache.provider() 226 | .lifeCache(100, TimeUnit.MILLISECONDS) 227 | .withKey("ephemeralMock"); 228 | 229 | saveMock(); 230 | 231 | TestObserver> observer = Single.just(new Mock(MESSAGE)) 232 | .compose(cacheProvider.readWithLoaderAsReply()) 233 | .test(); 234 | observer.awaitTerminalEvent(); 235 | 236 | observer.assertNoErrors(); 237 | observer.assertValueCount(1); 238 | assertThat(observer.values() 239 | .get(0).getSource(), is(Source.MEMORY)); 240 | 241 | waitTime(200); 242 | 243 | observer = Single.just(new Mock(MESSAGE)) 244 | .compose(cacheProvider.readWithLoaderAsReply()) 245 | .test(); 246 | observer.awaitTerminalEvent(); 247 | 248 | observer.assertNoErrors(); 249 | observer.assertValueCount(1); 250 | assertThat(observer.values() 251 | .get(0).getSource(), is(Source.CLOUD)); 252 | } 253 | 254 | private void saveMock() { 255 | TestObserver observer = Single.just(new Mock(MESSAGE)) 256 | .compose(cacheProvider.replace()) 257 | .test(); 258 | observer.awaitTerminalEvent(); 259 | } 260 | 261 | private void verifyMockCached() { 262 | TestObserver observer = cacheProvider.read().test(); 263 | observer.awaitTerminalEvent(); 264 | 265 | observer.assertNoErrors(); 266 | observer.assertValueCount(1); 267 | assertThat(observer.values() 268 | .get(0).getMessage(), is(MESSAGE)); 269 | } 270 | 271 | private void verifyNoMockCached() { 272 | TestObserver observer = cacheProvider.read().test(); 273 | observer.awaitTerminalEvent(); 274 | 275 | observer.assertError(RxCacheException.class); 276 | observer.assertNoValues(); 277 | observer.onComplete(); 278 | } 279 | 280 | private void waitTime(long millis) { 281 | try { 282 | Thread.sleep(millis); 283 | } catch (InterruptedException e) { 284 | e.printStackTrace(); 285 | } 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/ReactiveCacheBuilderValidationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | import java.io.File; 20 | import java.security.InvalidParameterException; 21 | import org.junit.Rule; 22 | import org.junit.Test; 23 | import org.junit.rules.TemporaryFolder; 24 | 25 | /** 26 | * Created by daemontus on 02/09/16. 27 | */ 28 | public class ReactiveCacheBuilderValidationTest { 29 | 30 | @Rule 31 | public TemporaryFolder temporaryFolder = new TemporaryFolder(); 32 | 33 | @Test(expected = InvalidParameterException.class) 34 | public void Cache_Directory_Null() { 35 | new ReactiveCache.Builder() 36 | .using(null, Jolyglot$.newInstance()); 37 | 38 | } 39 | 40 | @Test(expected = InvalidParameterException.class) 41 | public void Jolyglot_Null() { 42 | new ReactiveCache.Builder() 43 | .using(temporaryFolder.getRoot(), null); 44 | } 45 | 46 | @Test(expected = InvalidParameterException.class) 47 | public void Cache_Directory_Not_Exist() { 48 | File cacheDir = new File(temporaryFolder.getRoot(), "non_existent_folder"); 49 | new ReactiveCache.Builder() 50 | .using(cacheDir, Jolyglot$.newInstance()); 51 | } 52 | 53 | @Test(expected = InvalidParameterException.class) 54 | public void Cache_Directory_Not_Writable() { 55 | File cacheDir = new File(temporaryFolder.getRoot(), "non_existent_folder"); 56 | if (!cacheDir.mkdirs()) { 57 | throw new IllegalStateException("Cannot create temporary directory"); 58 | } 59 | if (!cacheDir.setWritable(false, false)) { 60 | throw new IllegalStateException("Cannot modify permissions"); 61 | } 62 | new ReactiveCache.Builder() 63 | .using(cacheDir, Jolyglot$.newInstance()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/ReactiveCacheTest.java: -------------------------------------------------------------------------------- 1 | package io.reactivecache2; 2 | 3 | import io.reactivex.Single; 4 | import io.reactivex.observers.TestObserver; 5 | import org.junit.Before; 6 | import org.junit.Rule; 7 | import org.junit.Test; 8 | import org.junit.rules.TemporaryFolder; 9 | 10 | import static org.hamcrest.core.Is.is; 11 | import static org.junit.Assert.assertThat; 12 | 13 | public final class ReactiveCacheTest { 14 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); 15 | private ReactiveCache reactiveCache; 16 | 17 | @Before public void setUp() { 18 | reactiveCache = new ReactiveCache.Builder() 19 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 20 | } 21 | 22 | @Test public void Verify_Evict_All() { 23 | assertThat(temporaryFolder.getRoot().listFiles().length, is(0)); 24 | 25 | Provider provider1 = reactiveCache.provider() 26 | .withKey("1"); 27 | 28 | ProviderGroup provider2 = reactiveCache.providerGroup() 29 | .withKey("2"); 30 | 31 | Single.just(new Mock()) 32 | .compose(provider1.replace()) 33 | .test() 34 | .awaitTerminalEvent(); 35 | 36 | for (int i = 0; i < 50; i++) { 37 | Single.just(new Mock()) 38 | .compose(provider2.replace(i)) 39 | .test() 40 | .awaitTerminalEvent(); 41 | } 42 | 43 | assertThat(temporaryFolder.getRoot().listFiles().length, is(51)); 44 | 45 | TestObserver observer = reactiveCache.evictAll().test(); 46 | observer.awaitTerminalEvent(); 47 | 48 | observer.assertComplete(); 49 | observer.assertNoErrors(); 50 | observer.assertNoValues(); 51 | 52 | assertThat(temporaryFolder.getRoot().listFiles().length, is(0)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/UsageTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2; 18 | 19 | import io.reactivex.Completable; 20 | import io.reactivex.Single; 21 | import io.reactivex.observers.TestObserver; 22 | import io.rx_cache2.Reply; 23 | import io.rx_cache2.Source; 24 | import java.util.ArrayList; 25 | import java.util.Arrays; 26 | import java.util.HashMap; 27 | import java.util.List; 28 | import org.junit.Before; 29 | import org.junit.Rule; 30 | import org.junit.Test; 31 | import org.junit.rules.TemporaryFolder; 32 | 33 | import static org.hamcrest.core.Is.is; 34 | import static org.junit.Assert.assertNotNull; 35 | import static org.junit.Assert.assertThat; 36 | 37 | public final class UsageTest { 38 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); 39 | private ReactiveCache reactiveCache; 40 | 41 | @Before public void setUp() { 42 | reactiveCache = new ReactiveCache.Builder() 43 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 44 | } 45 | 46 | @Test public void Verify_User() { 47 | UserRepository userRepository = new UserRepository(new ApiUser(), reactiveCache); 48 | 49 | TestObserver observerIsLogged = userRepository.isLogged().test(); 50 | observerIsLogged.awaitTerminalEvent(); 51 | 52 | assertThat(observerIsLogged.values().get(0), is(false)); 53 | observerIsLogged.assertValueCount(1); 54 | observerIsLogged.assertNoErrors(); 55 | observerIsLogged.assertComplete(); 56 | 57 | userRepository.login("") 58 | .test() 59 | .awaitTerminalEvent(); 60 | 61 | observerIsLogged = userRepository.isLogged().test(); 62 | observerIsLogged.awaitTerminalEvent(); 63 | 64 | assertThat(observerIsLogged.values().get(0), 65 | is(true)); 66 | 67 | TestObserver observerProfile = userRepository.profile().test(); 68 | observerProfile.awaitTerminalEvent(); 69 | 70 | observerProfile.assertNoErrors(); 71 | observerProfile.assertValueCount(1); 72 | observerProfile.assertComplete(); 73 | assertNotNull(observerProfile.values().get(0)); 74 | 75 | TestObserver observerUpdate = userRepository.updateUserName("aNewName").test(); 76 | observerUpdate.awaitTerminalEvent(); 77 | 78 | observerUpdate.assertComplete(); 79 | observerUpdate.assertNoErrors(); 80 | observerUpdate.assertNoValues(); 81 | 82 | observerProfile = userRepository.profile().test(); 83 | observerProfile.awaitTerminalEvent(); 84 | 85 | observerProfile.assertNoErrors(); 86 | observerProfile.assertValueCount(1); 87 | observerProfile.assertComplete(); 88 | assertNotNull(observerProfile.values().get(0).getName(), 89 | is("aNewName")); 90 | 91 | TestObserver observerLogout = userRepository.logout().test(); 92 | observerLogout.awaitTerminalEvent(); 93 | 94 | observerLogout.assertNoErrors(); 95 | observerLogout.assertNoValues(); 96 | observerLogout.assertComplete(); 97 | 98 | observerIsLogged = userRepository.isLogged().test(); 99 | observerIsLogged.awaitTerminalEvent(); 100 | 101 | assertThat(observerIsLogged.values().get(0), is(false)); 102 | } 103 | 104 | @Test public void Verify_Tasks() { 105 | TasksRepository tasksRepository = new TasksRepository(new ApiTasks(), reactiveCache); 106 | 107 | TestObserver>> observerTasks = tasksRepository.tasks(false).test(); 108 | observerTasks.awaitTerminalEvent(); 109 | 110 | observerTasks.assertComplete(); 111 | observerTasks.assertNoErrors(); 112 | observerTasks.assertValueCount(1); 113 | assertThat(observerTasks.values().get(0).getSource(), 114 | is(Source.CLOUD)); 115 | 116 | observerTasks = tasksRepository.tasks(false).test(); 117 | observerTasks.awaitTerminalEvent(); 118 | 119 | observerTasks.assertComplete(); 120 | observerTasks.assertNoErrors(); 121 | observerTasks.assertValueCount(1); 122 | assertThat(observerTasks.values().get(0).getSource(), 123 | is(Source.MEMORY)); 124 | 125 | observerTasks = tasksRepository.tasks(true).test(); 126 | observerTasks.awaitTerminalEvent(); 127 | 128 | observerTasks.assertComplete(); 129 | observerTasks.assertNoErrors(); 130 | observerTasks.assertValueCount(1); 131 | assertThat(observerTasks.values().get(0).getSource(), 132 | is(Source.CLOUD)); 133 | assertThat(observerTasks.values().get(0).getData().size(), 134 | is(0)); 135 | 136 | TestObserver observerAddTask = tasksRepository.addTask("", "").test(); 137 | observerAddTask.awaitTerminalEvent(); 138 | 139 | observerAddTask.assertComplete(); 140 | observerAddTask.assertNoErrors(); 141 | observerAddTask.assertNoValues(); 142 | 143 | observerTasks = tasksRepository.tasks(false).test(); 144 | observerTasks.awaitTerminalEvent(); 145 | 146 | observerTasks.assertComplete(); 147 | observerTasks.assertNoErrors(); 148 | observerTasks.assertValueCount(1); 149 | assertThat(observerTasks.values().get(0).getSource(), is(Source.MEMORY)); 150 | assertThat(observerTasks.values().get(0).getData().size(), is(1)); 151 | 152 | TestObserver observerRemoveTask = tasksRepository.removeTask(1).test(); 153 | 154 | observerRemoveTask.assertComplete(); 155 | observerRemoveTask.assertNoErrors(); 156 | observerRemoveTask.assertNoValues(); 157 | 158 | observerTasks = tasksRepository.tasks(false).test(); 159 | observerTasks.awaitTerminalEvent(); 160 | 161 | observerTasks.assertComplete(); 162 | observerTasks.assertNoErrors(); 163 | observerTasks.assertValueCount(1); 164 | assertThat(observerTasks.values().get(0).getSource(), is(Source.MEMORY)); 165 | assertThat(observerTasks.values().get(0).getData().size(), is(0)); 166 | 167 | observerTasks = tasksRepository.tasks(true).test(); 168 | observerTasks.awaitTerminalEvent(); 169 | 170 | observerTasks.assertComplete(); 171 | observerTasks.assertNoErrors(); 172 | observerTasks.assertValueCount(1); 173 | assertThat(observerTasks.values().get(0).getSource(), is(Source.CLOUD)); 174 | assertThat(observerTasks.values().get(0).getData().size(), is(0)); 175 | } 176 | 177 | @Test public void Verify_Events() { 178 | EventsRepository eventsRepository = new EventsRepository(new ApiEvents(), reactiveCache); 179 | TestObserver>> observerEvents = eventsRepository.events(false, 1).test(); 180 | observerEvents.awaitTerminalEvent(); 181 | 182 | observerEvents.assertComplete(); 183 | observerEvents.assertNoErrors(); 184 | observerEvents.assertValueCount(1); 185 | 186 | assertThat(observerEvents.values().get(0).getData().size(), is(1)); 187 | assertThat(observerEvents.values().get(0).getSource(), is(Source.CLOUD)); 188 | 189 | observerEvents = eventsRepository.events(false, 2).test(); 190 | observerEvents.awaitTerminalEvent(); 191 | 192 | observerEvents.assertComplete(); 193 | observerEvents.assertNoErrors(); 194 | observerEvents.assertValueCount(1); 195 | 196 | assertThat(observerEvents.values().get(0).getData().size(), is(2)); 197 | assertThat(observerEvents.values().get(0).getSource(), is(Source.CLOUD)); 198 | 199 | observerEvents = eventsRepository.events(false, 1).test(); 200 | observerEvents.awaitTerminalEvent(); 201 | 202 | observerEvents.assertComplete(); 203 | observerEvents.assertNoErrors(); 204 | observerEvents.assertValueCount(1); 205 | 206 | assertThat(observerEvents.values().get(0).getData().size(), is(1)); 207 | assertThat(observerEvents.values().get(0).getSource(), is(Source.MEMORY)); 208 | 209 | observerEvents = eventsRepository.events(false, 2).test(); 210 | observerEvents.awaitTerminalEvent(); 211 | 212 | observerEvents.assertComplete(); 213 | observerEvents.assertNoErrors(); 214 | observerEvents.assertValueCount(1); 215 | 216 | assertThat(observerEvents.values().get(0).getData().size(), is(2)); 217 | assertThat(observerEvents.values().get(0).getSource(), is(Source.MEMORY)); 218 | } 219 | 220 | /** 221 | * Managing user session. 222 | */ 223 | private static class UserRepository { 224 | private final Provider cacheProvider; 225 | private final ApiUser api; 226 | 227 | private UserRepository(ApiUser api, ReactiveCache reactiveCache) { 228 | this.api = api; 229 | this.cacheProvider = reactiveCache.provider() 230 | .withKey("user"); 231 | } 232 | 233 | Single login(String email) { 234 | return api.loginUser(email) 235 | .compose(cacheProvider.replace()); 236 | } 237 | 238 | Single isLogged() { 239 | return cacheProvider.read() 240 | .map(user -> true) 241 | .onErrorReturn(observer -> false); 242 | } 243 | 244 | Single profile() { 245 | return cacheProvider.read(); 246 | } 247 | 248 | Completable updateUserName(String name) { 249 | return cacheProvider.read() 250 | .map(user -> { 251 | user.setName(name); 252 | return user; 253 | }) 254 | .compose(cacheProvider.replace()) 255 | .toCompletable(); 256 | } 257 | 258 | Completable logout() { 259 | return api.logout().andThen(cacheProvider.evict()); 260 | } 261 | } 262 | 263 | /** 264 | * Managing tasks. 265 | */ 266 | private static class TasksRepository { 267 | private final ProviderList cacheProvider; 268 | private final ApiTasks api; 269 | 270 | private TasksRepository(ApiTasks api, ReactiveCache reactiveCache) { 271 | this.api = api; 272 | this.cacheProvider = reactiveCache.providerList() 273 | .withKey("tasks"); 274 | } 275 | 276 | Single>> tasks(boolean refresh) { 277 | return refresh ? api.tasks().compose(cacheProvider.replaceAsReply()) 278 | : api.tasks().compose(cacheProvider.readWithLoaderAsReply()); 279 | } 280 | 281 | Completable addTask(String name, String desc) { 282 | return api.addTask(1, name, desc) 283 | .andThen(cacheProvider.entries() 284 | .addFirst(new Task(1))); 285 | } 286 | 287 | Completable removeTask(int id) { 288 | return api.removeTask(id) 289 | .andThen(cacheProvider.entries() 290 | .evict((position, count, element) -> element.getId() == id)); 291 | } 292 | } 293 | 294 | /** 295 | * Managing events feed with pagination. 296 | */ 297 | private static class EventsRepository { 298 | private final ProviderGroup> cacheProvider; 299 | private final ApiEvents apiEvents; 300 | 301 | private EventsRepository(ApiEvents apiEvents, ReactiveCache reactiveCache) { 302 | this.apiEvents = apiEvents; 303 | this.cacheProvider = reactiveCache.>providerGroup() 304 | .withKey("events"); 305 | } 306 | 307 | Single>> events(boolean refresh, int page) { 308 | if (refresh) { 309 | return apiEvents.events(page) 310 | .compose(cacheProvider.replaceAsReply(page)); 311 | } 312 | 313 | return apiEvents.events(page) 314 | .compose(cacheProvider.readWithLoaderAsReply(page)); 315 | } 316 | } 317 | 318 | private static class User { 319 | String name; 320 | 321 | void setName(String name) { 322 | this.name = name; 323 | } 324 | 325 | String getName() { 326 | return name; 327 | } 328 | } 329 | 330 | private static class Task { 331 | private final int id; 332 | 333 | public Task(int id) { 334 | this.id = id; 335 | } 336 | 337 | public int getId() { 338 | return id; 339 | } 340 | } 341 | 342 | private static class Event { 343 | 344 | } 345 | 346 | private static class ApiUser { 347 | 348 | public Single loginUser(String email) { 349 | return Single.just(new User()); 350 | } 351 | 352 | public Completable logout() { 353 | return Completable.complete(); 354 | } 355 | } 356 | 357 | private static class ApiTasks { 358 | private final List tasks; 359 | 360 | public ApiTasks() { 361 | this.tasks = new ArrayList<>(); 362 | } 363 | 364 | public Single> tasks() { 365 | return Single.just(new ArrayList<>(tasks)); 366 | } 367 | 368 | public Completable addTask(int id, String name, String desc) { 369 | Task task = new Task(id); 370 | tasks.add(task); 371 | return Completable.complete(); 372 | } 373 | 374 | public Completable removeTask(int id) { 375 | Task candidate = null; 376 | for (Task task : tasks) { 377 | if (task.getId() == id) candidate = task; 378 | } 379 | tasks.remove(candidate); 380 | return Completable.complete(); 381 | } 382 | } 383 | 384 | private static class ApiEvents { 385 | private final HashMap> events = new HashMap>() {{ 386 | put(1, Arrays.asList(new Event())); 387 | put(2, Arrays.asList(new Event(), new Event())); 388 | }}; 389 | 390 | public Single> events(int page) { 391 | return Single.just(events.get(page)); 392 | } 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/common/BaseTestEvictingTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2.common; 18 | 19 | import io.reactivecache2.Mock; 20 | import io.reactivex.Single; 21 | import java.io.File; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | 25 | public class BaseTestEvictingTask { 26 | 27 | protected void waitTime(long millis) { 28 | try { 29 | Thread.sleep(millis); 30 | } catch (InterruptedException e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | 35 | protected int getSizeMB(File dir) { 36 | long bytes = 0; 37 | 38 | File[] files = dir.listFiles(); 39 | for (File file: files) { 40 | bytes += file.length(); 41 | } 42 | 43 | return (int) Math.ceil(bytes/1024.0/1024.0); 44 | } 45 | 46 | protected Single> createObservableMocks() { 47 | List mocks = new ArrayList(); 48 | 49 | for (int i = 0; i < 100; i++) { 50 | Mock mock = new Mock("Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC," + 51 | "making it over 2000 years old.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " + 52 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " + 53 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC."+ 54 | "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC," + 55 | "making it over 2000 years old.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " + 56 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " + 57 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC." + 58 | "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC," + 59 | "making it over 2000 years old.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " + 60 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " + 61 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC."+ 62 | "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC," + 63 | "making it over 2000 years old.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " + 64 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " + 65 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC."+ 66 | "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC," + 67 | "making it over 2000 years old.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " + 68 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " + 69 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC."+ 70 | "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC," + 71 | "making it over 2000 years old.Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " + 72 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, " + 73 | "making it over 2000 years old. Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC."); 74 | mocks.add(mock); 75 | } 76 | 77 | return Single.just(mocks); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/encript/EncryptGroupTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2.encript; 18 | 19 | import io.reactivecache2.Jolyglot$; 20 | import io.reactivecache2.Mock; 21 | import io.reactivecache2.ProviderGroup; 22 | import io.reactivecache2.ReactiveCache; 23 | import io.reactivex.Single; 24 | import io.reactivex.observers.TestObserver; 25 | import io.rx_cache2.Reply; 26 | import io.rx_cache2.Source; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | import org.junit.Before; 30 | import org.junit.ClassRule; 31 | import org.junit.FixMethodOrder; 32 | import org.junit.Test; 33 | import org.junit.rules.TemporaryFolder; 34 | import org.junit.runners.MethodSorters; 35 | 36 | import static org.hamcrest.CoreMatchers.is; 37 | import static org.junit.Assert.assertThat; 38 | 39 | @FixMethodOrder(MethodSorters.NAME_ASCENDING) 40 | public final class EncryptGroupTest { 41 | private final static int SIZE = 1000; 42 | @ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder(); 43 | private ProviderGroup> mocksEncryptedProvider; 44 | private ProviderGroup> mocksNoEncryptedProvider; 45 | private final static String GROUP = "GROUP"; 46 | 47 | @Before public void init() { 48 | ReactiveCache reactiveCache = new ReactiveCache.Builder() 49 | .encrypt("myStrongKey-1234") 50 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 51 | 52 | mocksEncryptedProvider = reactiveCache.>providerGroup() 53 | .encrypt(true) 54 | .withKey("mocksEncryptedProvider"); 55 | mocksNoEncryptedProvider = reactiveCache.>providerGroup() 56 | .withKey("mocksNoEncryptedProvider"); 57 | } 58 | 59 | @Test public void _00_Save_Record_On_Disk_In_Order_To_Test_Following_Tests() { 60 | TestObserver>> observer = createSingleMocks(SIZE) 61 | .compose(mocksEncryptedProvider.readWithLoaderAsReply(GROUP)) 62 | .test(); 63 | observer.awaitTerminalEvent(); 64 | 65 | observer = createSingleMocks(SIZE) 66 | .compose(mocksNoEncryptedProvider.readWithLoaderAsReply(GROUP)) 67 | .test(); 68 | 69 | observer.awaitTerminalEvent(); 70 | } 71 | 72 | @Test public void _01_When_Encrypted_Record_Has_Been_Persisted_And_Memory_Has_Been_Destroyed_Then_Retrieve_From_Disk() { 73 | TestObserver>> observer = Single.>just(new ArrayList<>()) 74 | .compose(mocksEncryptedProvider.readWithLoaderAsReply(GROUP)) 75 | .test(); 76 | observer.awaitTerminalEvent(); 77 | 78 | Reply> reply = observer.values().get(0); 79 | assertThat(reply.getSource(), is(Source.PERSISTENCE)); 80 | assertThat(reply.isEncrypted(), is(true)); 81 | } 82 | 83 | @Test public void _02_Verify_Encrypted_Does_Not_Propagate_To_Other_Providers() { 84 | TestObserver>> observer = createSingleMocks(SIZE) 85 | .compose(mocksEncryptedProvider.readWithLoaderAsReply(GROUP)) 86 | .test(); 87 | 88 | observer.awaitTerminalEvent(); 89 | 90 | Reply> reply = observer.values().get(0); 91 | assertThat(reply.getSource(), is(Source.PERSISTENCE)); 92 | assertThat(reply.isEncrypted(), is(true)); 93 | 94 | observer = createSingleMocks(SIZE) 95 | .compose(mocksNoEncryptedProvider.readWithLoaderAsReply(GROUP)) 96 | .test(); 97 | 98 | observer.awaitTerminalEvent(); 99 | 100 | reply = observer.values().get(0); 101 | assertThat(reply.getSource(), is(Source.PERSISTENCE)); 102 | assertThat(reply.isEncrypted(), is(false)); 103 | } 104 | 105 | 106 | private Single> createSingleMocks(int size) { 107 | long currentTime = System.currentTimeMillis(); 108 | 109 | List mocks = new ArrayList(size); 110 | for (int i = 0; i < size; i++) { 111 | mocks.add(new Mock("mock"+currentTime)); 112 | } 113 | 114 | return Single.just(mocks); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/encript/EncryptTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2.encript; 18 | 19 | import io.reactivecache2.Jolyglot$; 20 | import io.reactivecache2.Mock; 21 | import io.reactivecache2.Provider; 22 | import io.reactivecache2.ReactiveCache; 23 | import io.reactivex.Single; 24 | import io.reactivex.observers.TestObserver; 25 | import io.rx_cache2.Reply; 26 | import io.rx_cache2.Source; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | import org.junit.Before; 30 | import org.junit.ClassRule; 31 | import org.junit.FixMethodOrder; 32 | import org.junit.Test; 33 | import org.junit.rules.TemporaryFolder; 34 | import org.junit.runners.MethodSorters; 35 | 36 | import static org.hamcrest.CoreMatchers.is; 37 | import static org.junit.Assert.assertThat; 38 | 39 | @FixMethodOrder(MethodSorters.NAME_ASCENDING) 40 | public final class EncryptTest { 41 | private final static int SIZE = 1000; 42 | @ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder(); 43 | private Provider> mocksEncryptedProvider; 44 | private Provider> mocksNoEncryptedProvider; 45 | 46 | @Before public void init() { 47 | ReactiveCache reactiveCache = new ReactiveCache.Builder() 48 | .encrypt("myStrongKey-1234") 49 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 50 | 51 | mocksEncryptedProvider = reactiveCache.>provider() 52 | .encrypt(true) 53 | .withKey("mocksEncryptedProvider"); 54 | mocksNoEncryptedProvider = reactiveCache.>provider() 55 | .withKey("mocksNoEncryptedProvider"); 56 | } 57 | 58 | @Test public void _00_Save_Record_On_Disk_In_Order_To_Test_Following_Tests() { 59 | TestObserver>> observer = createSingleMocks(SIZE) 60 | .compose(mocksEncryptedProvider.readWithLoaderAsReply()) 61 | .test(); 62 | observer.awaitTerminalEvent(); 63 | 64 | observer = createSingleMocks(SIZE) 65 | .compose(mocksNoEncryptedProvider.readWithLoaderAsReply()) 66 | .test(); 67 | 68 | observer.awaitTerminalEvent(); 69 | } 70 | 71 | @Test public void _01_When_Encrypted_Record_Has_Been_Persisted_And_Memory_Has_Been_Destroyed_Then_Retrieve_From_Disk() { 72 | TestObserver>> observer = Single.>just(new ArrayList<>()) 73 | .compose(mocksEncryptedProvider.readWithLoaderAsReply()) 74 | .test(); 75 | observer.awaitTerminalEvent(); 76 | 77 | Reply> reply = observer.values().get(0); 78 | assertThat(reply.getSource(), is(Source.PERSISTENCE)); 79 | assertThat(reply.isEncrypted(), is(true)); 80 | } 81 | 82 | @Test public void _02_Verify_Encrypted_Does_Not_Propagate_To_Other_Providers() { 83 | TestObserver>> observer = createSingleMocks(SIZE) 84 | .compose(mocksEncryptedProvider.readWithLoaderAsReply()) 85 | .test(); 86 | 87 | observer.awaitTerminalEvent(); 88 | 89 | Reply> reply = observer.values().get(0); 90 | assertThat(reply.getSource(), is(Source.PERSISTENCE)); 91 | assertThat(reply.isEncrypted(), is(true)); 92 | 93 | observer = createSingleMocks(SIZE) 94 | .compose(mocksNoEncryptedProvider.readWithLoaderAsReply()) 95 | .test(); 96 | 97 | observer.awaitTerminalEvent(); 98 | 99 | reply = observer.values().get(0); 100 | assertThat(reply.getSource(), is(Source.PERSISTENCE)); 101 | assertThat(reply.isEncrypted(), is(false)); 102 | } 103 | 104 | 105 | private Single> createSingleMocks(int size) { 106 | long currentTime = System.currentTimeMillis(); 107 | 108 | List mocks = new ArrayList(size); 109 | for (int i = 0; i < size; i++) { 110 | mocks.add(new Mock("mock"+currentTime)); 111 | } 112 | 113 | return Single.just(mocks); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/expiration/EvictExpirableRecordsGroupTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2.expiration; 18 | 19 | import io.reactivecache2.Jolyglot$; 20 | import io.reactivecache2.Mock; 21 | import io.reactivecache2.ReactiveCache; 22 | import io.reactivecache2.common.BaseTestEvictingTask; 23 | import io.rx_cache2.internal.cache.EvictExpirableRecordsPersistence; 24 | import java.util.List; 25 | import org.junit.Before; 26 | import org.junit.FixMethodOrder; 27 | import org.junit.Rule; 28 | import org.junit.Test; 29 | import org.junit.rules.TemporaryFolder; 30 | import org.junit.runners.MethodSorters; 31 | 32 | import static org.hamcrest.CoreMatchers.is; 33 | import static org.junit.Assert.assertThat; 34 | 35 | @FixMethodOrder(MethodSorters.NAME_ASCENDING) 36 | public final class EvictExpirableRecordsGroupTest extends BaseTestEvictingTask { 37 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); 38 | private final static String GROUP = "GROUP"; 39 | private ReactiveCache reactiveCache; 40 | private int maxMgPersistenceCache = 7; 41 | 42 | @Before public void setUp() { 43 | reactiveCache = new ReactiveCache.Builder() 44 | .diskCacheSize(maxMgPersistenceCache) 45 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 46 | } 47 | 48 | @Test public void When_Expirable_Records_Evict() { 49 | assertThat(getSizeMB(temporaryFolder.getRoot()), is(0)); 50 | 51 | for (int i = 0; i < 50; i++) { 52 | waitTime(50); 53 | String key = i + ""; 54 | 55 | createObservableMocks() 56 | .compose(reactiveCache.>providerGroup() 57 | .withKey(key) 58 | .readWithLoader(GROUP)) 59 | .test() 60 | .awaitTerminalEvent(); 61 | } 62 | 63 | waitTime(5000); 64 | int expectedStoredMB = (int) (maxMgPersistenceCache 65 | * EvictExpirableRecordsPersistence.PERCENTAGE_MEMORY_STORED_TO_STOP); 66 | assertThat(getSizeMB(temporaryFolder.getRoot()), is(expectedStoredMB)); 67 | } 68 | 69 | @Test public void When_No_Expirable_Records_Do_Not_Evict() { 70 | assertThat(getSizeMB(temporaryFolder.getRoot()), is(0)); 71 | 72 | for (int i = 0; i < 50; i++) { 73 | waitTime(50); 74 | String key = i + ""; 75 | createObservableMocks() 76 | .compose(reactiveCache.>providerGroup() 77 | .expirable(false) 78 | .withKey(key) 79 | .readWithLoader(GROUP)) 80 | .test() 81 | .awaitTerminalEvent(); 82 | } 83 | 84 | waitTime(1000); 85 | assertThat(getSizeMB(temporaryFolder.getRoot()), is(maxMgPersistenceCache)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/expiration/EvictExpirableRecordsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2.expiration; 18 | 19 | import io.reactivecache2.Jolyglot$; 20 | import io.reactivecache2.Mock; 21 | import io.reactivecache2.ReactiveCache; 22 | import io.reactivecache2.common.BaseTestEvictingTask; 23 | import io.rx_cache2.internal.cache.EvictExpirableRecordsPersistence; 24 | import java.util.List; 25 | import org.junit.Before; 26 | import org.junit.FixMethodOrder; 27 | import org.junit.Rule; 28 | import org.junit.Test; 29 | import org.junit.rules.TemporaryFolder; 30 | import org.junit.runners.MethodSorters; 31 | 32 | import static org.hamcrest.CoreMatchers.is; 33 | import static org.junit.Assert.assertThat; 34 | 35 | @FixMethodOrder(MethodSorters.NAME_ASCENDING) 36 | public final class EvictExpirableRecordsTest extends BaseTestEvictingTask { 37 | @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); 38 | 39 | private ReactiveCache reactiveCache; 40 | private int maxMgPersistenceCache = 7; 41 | 42 | @Before public void setUp() { 43 | reactiveCache = new ReactiveCache.Builder() 44 | .diskCacheSize(maxMgPersistenceCache) 45 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 46 | } 47 | 48 | @Test public void When_Expirable_Records_Evict() { 49 | assertThat(getSizeMB(temporaryFolder.getRoot()), is(0)); 50 | 51 | for (int i = 0; i < 50; i++) { 52 | waitTime(50); 53 | String key = i + ""; 54 | 55 | createObservableMocks() 56 | .compose(reactiveCache.>provider() 57 | .withKey(key) 58 | .readWithLoader()) 59 | .test() 60 | .awaitTerminalEvent(); 61 | } 62 | 63 | waitTime(5000); 64 | int expectedStoredMB = (int) (maxMgPersistenceCache 65 | * EvictExpirableRecordsPersistence.PERCENTAGE_MEMORY_STORED_TO_STOP); 66 | assertThat(getSizeMB(temporaryFolder.getRoot()), is(expectedStoredMB)); 67 | } 68 | 69 | @Test public void When_No_Expirable_Records_Do_Not_Evict() { 70 | assertThat(getSizeMB(temporaryFolder.getRoot()), is(0)); 71 | 72 | for (int i = 0; i < 50; i++) { 73 | waitTime(50); 74 | String key = i + ""; 75 | createObservableMocks() 76 | .compose(reactiveCache.>provider() 77 | .expirable(false) 78 | .withKey(key) 79 | .readWithLoader()) 80 | .test() 81 | .awaitTerminalEvent(); 82 | } 83 | 84 | waitTime(1000); 85 | assertThat(getSizeMB(temporaryFolder.getRoot()), is(maxMgPersistenceCache)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/expiration/EvictExpiredRecordsGroupTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2.expiration; 18 | 19 | import io.reactivecache2.Jolyglot$; 20 | import io.reactivecache2.Mock; 21 | import io.reactivecache2.ReactiveCache; 22 | import io.reactivecache2.common.BaseTestEvictingTask; 23 | import java.io.File; 24 | import java.util.List; 25 | import java.util.concurrent.TimeUnit; 26 | import org.junit.Before; 27 | import org.junit.ClassRule; 28 | import org.junit.FixMethodOrder; 29 | import org.junit.Test; 30 | import org.junit.rules.TemporaryFolder; 31 | import org.junit.runners.MethodSorters; 32 | 33 | import static org.junit.Assert.assertEquals; 34 | import static org.junit.Assert.assertNotEquals; 35 | 36 | @FixMethodOrder(MethodSorters.NAME_ASCENDING) 37 | public final class EvictExpiredRecordsGroupTest extends BaseTestEvictingTask { 38 | @ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder(); 39 | private final static String GROUP = "GROUP"; 40 | private ReactiveCache reactiveCache; 41 | 42 | @Before public void setUp() { 43 | reactiveCache = new ReactiveCache.Builder() 44 | .encrypt("myStrongKey-1234") 45 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 46 | } 47 | 48 | @Test public void _1_Populate_Disk_With_Expired_Records() { 49 | assertEquals(0, getSizeMB(temporaryFolder.getRoot())); 50 | 51 | for (int i = 0; i < 50; i++) { 52 | waitTime(50); 53 | String key = System.currentTimeMillis() + i + ""; 54 | 55 | createObservableMocks() 56 | .compose(reactiveCache.>providerGroup() 57 | .lifeCache(1, TimeUnit.MILLISECONDS) 58 | .withKey(key) 59 | .readWithLoader(GROUP)) 60 | .test() 61 | .awaitTerminalEvent(); 62 | } 63 | 64 | assertNotEquals(0, getSizeMB(temporaryFolder.getRoot())); 65 | } 66 | 67 | @Test public void _2_Perform_Evicting_Task_And_Check_Results() { 68 | waitTime(1000); 69 | assertEquals(0, temporaryFolder.getRoot().listFiles().length); 70 | assertEquals(0, getSizeMB(temporaryFolder.getRoot())); 71 | } 72 | 73 | @Test public void _3_Populate_Disk_With_No_Expired_Records() { 74 | deleteAllFiles(); 75 | assertEquals(0, getSizeMB(temporaryFolder.getRoot())); 76 | 77 | for (int i = 0; i < 50; i++) { 78 | waitTime(50); 79 | String key = System.currentTimeMillis() + i + ""; 80 | createObservableMocks() 81 | .compose(reactiveCache.>providerGroup() 82 | .withKey(key) 83 | .readWithLoader(GROUP) 84 | ) 85 | .test() 86 | .awaitTerminalEvent(); 87 | } 88 | 89 | assertNotEquals(0, getSizeMB(temporaryFolder.getRoot())); 90 | } 91 | 92 | @Test public void _4_Perform_Evicting_Task_And_Check_Results() { 93 | waitTime(1000); 94 | assertNotEquals(0, getSizeMB(temporaryFolder.getRoot())); 95 | } 96 | 97 | @Test public void _5_Populate_Disk_With_Expired_Encrypted_Records() { 98 | deleteAllFiles(); 99 | assertEquals(0, temporaryFolder.getRoot().listFiles().length); 100 | 101 | for (int i = 0; i < 50; i++) { 102 | waitTime(50); 103 | String key = System.currentTimeMillis() + i + ""; 104 | createObservableMocks() 105 | .compose(reactiveCache.>providerGroup() 106 | .encrypt(true) 107 | .lifeCache(1, TimeUnit.MILLISECONDS) 108 | .withKey(key) 109 | .readWithLoader(GROUP)) 110 | .test() 111 | .awaitTerminalEvent(); 112 | } 113 | 114 | assertNotEquals(0, getSizeMB(temporaryFolder.getRoot())); 115 | } 116 | 117 | @Test public void _6_Perform_Evicting_Task_And_Check_Results() { 118 | waitTime(1000); 119 | assertEquals(0, temporaryFolder.getRoot().listFiles().length); 120 | assertEquals(0, getSizeMB(temporaryFolder.getRoot())); 121 | } 122 | 123 | private void deleteAllFiles() { 124 | File[] files = temporaryFolder.getRoot().listFiles(); 125 | 126 | for (File file : files) { 127 | file.delete(); 128 | waitTime(100); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/expiration/EvictExpiredRecordsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2.expiration; 18 | 19 | import io.reactivecache2.Jolyglot$; 20 | import io.reactivecache2.Mock; 21 | import io.reactivecache2.ReactiveCache; 22 | import io.reactivecache2.common.BaseTestEvictingTask; 23 | import java.io.File; 24 | import java.util.List; 25 | import java.util.concurrent.TimeUnit; 26 | import org.junit.Before; 27 | import org.junit.ClassRule; 28 | import org.junit.FixMethodOrder; 29 | import org.junit.Test; 30 | import org.junit.rules.TemporaryFolder; 31 | import org.junit.runners.MethodSorters; 32 | 33 | import static org.junit.Assert.assertEquals; 34 | import static org.junit.Assert.assertNotEquals; 35 | 36 | @FixMethodOrder(MethodSorters.NAME_ASCENDING) 37 | public final class EvictExpiredRecordsTest extends BaseTestEvictingTask { 38 | @ClassRule public static TemporaryFolder temporaryFolder = new TemporaryFolder(); 39 | private ReactiveCache reactiveCache; 40 | 41 | @Before public void setUp() { 42 | reactiveCache = new ReactiveCache.Builder() 43 | .encrypt("myStrongKey-1234") 44 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 45 | } 46 | 47 | @Test public void _1_Populate_Disk_With_Expired_Records() { 48 | assertEquals(0, getSizeMB(temporaryFolder.getRoot())); 49 | 50 | for (int i = 0; i < 50; i++) { 51 | waitTime(50); 52 | String key = System.currentTimeMillis() + i + ""; 53 | 54 | createObservableMocks() 55 | .compose(reactiveCache.>provider() 56 | .lifeCache(1, TimeUnit.MILLISECONDS) 57 | .withKey(key) 58 | .readWithLoader()) 59 | .test() 60 | .awaitTerminalEvent(); 61 | } 62 | 63 | assertNotEquals(0, getSizeMB(temporaryFolder.getRoot())); 64 | } 65 | 66 | @Test public void _2_Perform_Evicting_Task_And_Check_Results() { 67 | waitTime(1000); 68 | assertEquals(0, temporaryFolder.getRoot().listFiles().length); 69 | assertEquals(0, getSizeMB(temporaryFolder.getRoot())); 70 | } 71 | 72 | @Test public void _3_Populate_Disk_With_No_Expired_Records() { 73 | deleteAllFiles(); 74 | assertEquals(0, getSizeMB(temporaryFolder.getRoot())); 75 | 76 | for (int i = 0; i < 50; i++) { 77 | waitTime(50); 78 | String key = System.currentTimeMillis() + i + ""; 79 | createObservableMocks() 80 | .compose(reactiveCache.>provider() 81 | .withKey(key) 82 | .readWithLoader() 83 | ) 84 | .test() 85 | .awaitTerminalEvent(); 86 | } 87 | 88 | assertNotEquals(0, getSizeMB(temporaryFolder.getRoot())); 89 | } 90 | 91 | @Test public void _4_Perform_Evicting_Task_And_Check_Results() { 92 | waitTime(1000); 93 | assertNotEquals(0, getSizeMB(temporaryFolder.getRoot())); 94 | } 95 | 96 | @Test public void _5_Populate_Disk_With_Expired_Encrypted_Records() { 97 | deleteAllFiles(); 98 | assertEquals(0, temporaryFolder.getRoot().listFiles().length); 99 | 100 | for (int i = 0; i < 50; i++) { 101 | waitTime(50); 102 | String key = System.currentTimeMillis() + i + ""; 103 | 104 | createObservableMocks() 105 | .compose(reactiveCache.>provider() 106 | .encrypt(true) 107 | .lifeCache(1, TimeUnit.MILLISECONDS) 108 | .withKey(key) 109 | .readWithLoader()) 110 | .test() 111 | .awaitTerminalEvent(); 112 | } 113 | 114 | assertNotEquals(0, getSizeMB(temporaryFolder.getRoot())); 115 | } 116 | 117 | @Test public void _6_Perform_Evicting_Task_And_Check_Results() { 118 | waitTime(1000); 119 | assertEquals(0, temporaryFolder.getRoot().listFiles().length); 120 | assertEquals(0, getSizeMB(temporaryFolder.getRoot())); 121 | } 122 | 123 | private void deleteAllFiles() { 124 | File[] files = temporaryFolder.getRoot().listFiles(); 125 | 126 | for (File file : files) { 127 | file.delete(); 128 | waitTime(100); 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /reactive_cache/src/test/java/io/reactivecache2/migration/MigrationsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Victor Albertos 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.reactivecache2.migration; 18 | 19 | import io.reactivecache2.Jolyglot$; 20 | import io.reactivecache2.ReactiveCache; 21 | import io.reactivex.Single; 22 | import io.reactivex.observers.TestObserver; 23 | import io.rx_cache2.MigrationCache; 24 | import io.rx_cache2.RxCacheException; 25 | import java.util.ArrayList; 26 | import java.util.Arrays; 27 | import java.util.List; 28 | import org.junit.ClassRule; 29 | import org.junit.Test; 30 | import org.junit.rules.TemporaryFolder; 31 | 32 | import static org.hamcrest.MatcherAssert.assertThat; 33 | import static org.hamcrest.core.Is.is; 34 | 35 | public final class MigrationsTest { 36 | @ClassRule static public TemporaryFolder temporaryFolder = new TemporaryFolder(); 37 | 38 | @Test public void _1_Populate_Mocks() { 39 | populateMocks(); 40 | } 41 | 42 | @Test public void _2_When_Migrations_Working_Request_Will_Be_Hold_Until_Finish() { 43 | int countFiles = temporaryFolder.getRoot().listFiles().length; 44 | assert countFiles > 0; 45 | 46 | ReactiveCache reactiveCache = new ReactiveCache.Builder() 47 | .migrations(Arrays.asList( 48 | new MigrationCache(1, new Class[]{Mock1.class}) 49 | )) 50 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 51 | 52 | TestObserver> testObserver = reactiveCache.>provider() 53 | .withKey("getMocks") 54 | .read() 55 | .test(); 56 | 57 | testObserver.awaitTerminalEvent(); 58 | testObserver.assertNoValues(); 59 | testObserver.assertError(RxCacheException.class); 60 | } 61 | 62 | 63 | private void populateMocks() { 64 | ReactiveCache reactiveCache = new ReactiveCache.Builder() 65 | .using(temporaryFolder.getRoot(), Jolyglot$.newInstance()); 66 | 67 | TestObserver> testObserver = getMocks() 68 | .compose(reactiveCache.>provider() 69 | .withKey("getMocks") 70 | .readWithLoader()) 71 | .test(); 72 | 73 | testObserver.awaitTerminalEvent(); 74 | assertThat(testObserver.values().get(0).size(), is(SIZE_MOCKS)); 75 | } 76 | 77 | private static int SIZE_MOCKS = 1000; 78 | private Single> getMocks() { 79 | List mocks = new ArrayList<>(); 80 | 81 | for (int i = 0; i < SIZE_MOCKS; i++) { 82 | mocks.add(new Mock1()); 83 | } 84 | 85 | return Single.just(mocks); 86 | } 87 | 88 | public static class Mock1 { 89 | private final String payload = "Lorem Ipsum is simply dummy text of the printing and " + 90 | "typesetting industry. Lorem Ipsum has been the industry's standard dummy text " + 91 | "ever since the 1500s, when an unknown printer took a galley of type and scrambled " + 92 | "it to make a type specimen book. It has survived not only five centuries"; 93 | 94 | public Mock1() {} 95 | 96 | public String getPayload() { 97 | return payload; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 24 5 | buildToolsVersion '25.0.0' 6 | 7 | defaultConfig { 8 | applicationId "io.victoralbertos.reactivecache" 9 | minSdkVersion 16 10 | targetSdkVersion 24 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | testCompile 'junit:junit:4.12' 25 | compile 'com.android.support:appcompat-v7:24.1.1' 26 | compile 'com.android.support:design:24.1.1' 27 | } 28 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/victor/Documents/AndroidStudio/android_sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sample/src/androidTest/java/io/victoralbertos/reactivecache/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package io.victoralbertos.reactivecache; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /sample/src/main/java/io/victoralbertos/reactivecache/MainActivity.java: -------------------------------------------------------------------------------- 1 | package io.victoralbertos.reactivecache; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.FloatingActionButton; 5 | import android.support.design.widget.Snackbar; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.Toolbar; 8 | import android.view.View; 9 | import android.view.Menu; 10 | import android.view.MenuItem; 11 | 12 | public class MainActivity extends AppCompatActivity { 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_main); 18 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 19 | setSupportActionBar(toolbar); 20 | 21 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 22 | fab.setOnClickListener(new View.OnClickListener() { 23 | @Override 24 | public void onClick(View view) { 25 | Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG) 26 | .setAction("Action", null).show(); 27 | } 28 | }); 29 | } 30 | 31 | @Override 32 | public boolean onCreateOptionsMenu(Menu menu) { 33 | // Inflate the menu; this adds items to the action bar if it is present. 34 | getMenuInflater().inflate(R.menu.menu_main, menu); 35 | return true; 36 | } 37 | 38 | @Override 39 | public boolean onOptionsItemSelected(MenuItem item) { 40 | // Handle action bar item clicks here. The action bar will 41 | // automatically handle clicks on the Home/Up button, so long 42 | // as you specify a parent activity in AndroidManifest.xml. 43 | int id = item.getItemId(); 44 | 45 | //noinspection SimplifiableIfStatement 46 | if (id == R.id.action_settings) { 47 | return true; 48 | } 49 | 50 | return super.onOptionsItemSelected(item); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 15 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /sample/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VictorAlbertos/ReactiveCache/0053adb4dd3526a437aed048f7ccaf8ae6a29b61/sample/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VictorAlbertos/ReactiveCache/0053adb4dd3526a437aed048f7ccaf8ae6a29b61/sample/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VictorAlbertos/ReactiveCache/0053adb4dd3526a437aed048f7ccaf8ae6a29b61/sample/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VictorAlbertos/ReactiveCache/0053adb4dd3526a437aed048f7ccaf8ae6a29b61/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VictorAlbertos/ReactiveCache/0053adb4dd3526a437aed048f7ccaf8ae6a29b61/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /sample/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /sample/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 7 | -------------------------------------------------------------------------------- /sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ReactiveCache 3 | Settings 4 | 5 | -------------------------------------------------------------------------------- /sample/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 14 |