From 5c24c8f45d197b9cdba469c4f8d94be6590c85be Mon Sep 17 00:00:00 2001 From: Ramona Harrison Date: Mon, 30 Oct 2017 10:46:53 -0400 Subject: [PATCH 001/498] Update build tools to 26.0.2 (#283) --- .travis.yml | 2 +- build.gradle | 2 +- buildsystem/dependencies.gradle | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c6b631d7e..3a87a2139 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ android: components: - tools - platform-tools - - build-tools-26.0.1 + - build-tools-26.0.2 - android-25 - extra-android-m2repository licenses: diff --git a/build.gradle b/build.gradle index 2e29bb48a..1da8e8588 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ buildscript { ] dependencies { - classpath 'com.android.tools.build:gradle:3.0.0-beta2' + classpath 'com.android.tools.build:gradle:3.0.0' classpath 'com.google.gms:google-services:3.0.0' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.6' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11' diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index 529369268..4d5e59b7b 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -16,7 +16,7 @@ ext.versions = [ minSdk : 16, targetSdk : 25, compileSdk : 25, - buildTools : '26.0.1', + buildTools : '26.0.2', kotlin : '1.1.2-5', // UI libs. From a5e56593130af0247a5287defc7c0b9cb7b7c82c Mon Sep 17 00:00:00 2001 From: Mateusz Perlak Date: Tue, 31 Oct 2017 09:21:20 -0500 Subject: [PATCH 002/498] #275 feature - added ParsingFetcher that wraps Raw type Parser and Fetcher (#280) --- .../store3/base/impl/ParsingFetcher.java | 44 ++++++++++++ .../external/store3/ParsingFetcherTest.java | 71 +++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/ParsingFetcherTest.java diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.java new file mode 100644 index 000000000..3cc9c7657 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.java @@ -0,0 +1,44 @@ +package com.nytimes.android.external.store3.base.impl; + +import com.nytimes.android.external.store3.base.Fetcher; +import com.nytimes.android.external.store3.base.Parser; + +import javax.annotation.Nonnull; + +import io.reactivex.Single; + + +/** + * Parsing fetcher that takes parser of Raw type and fetcher of raw type returning parsed instance. + * Created on 10/20/17. + */ +public class ParsingFetcher implements Fetcher { + + private final Fetcher rawFetcher; + private final Parser parser; + + /** + * Creates instance of ParsingFetcher + * + * @param rawFetcher fetches raw data by key + * @param parsedParser parses raw data to the instance of 'Parsed' + */ + public ParsingFetcher(@Nonnull Fetcher rawFetcher, @Nonnull Parser parsedParser) { + this.rawFetcher = rawFetcher; + this.parser = parsedParser; + } + + /** + * Creates ParsingFetcher for raw data type Fetcher and Raw data Parser. + */ + public static final ParsingFetcher from( + @Nonnull Fetcher fetcher, @Nonnull Parser parser) { + return new ParsingFetcher(fetcher, parser); + } + + @Nonnull + @Override + public Single fetch(@Nonnull Key key) { + return rawFetcher.fetch(key).map(parser); + } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/ParsingFetcherTest.java b/store/src/test/java/com/nytimes/android/external/store3/ParsingFetcherTest.java new file mode 100644 index 000000000..909e753c5 --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/ParsingFetcherTest.java @@ -0,0 +1,71 @@ +package com.nytimes.android.external.store3; + +import com.nytimes.android.external.store3.base.Fetcher; +import com.nytimes.android.external.store3.base.Parser; +import com.nytimes.android.external.store3.base.Persister; +import com.nytimes.android.external.store3.base.impl.BarCode; +import com.nytimes.android.external.store3.base.impl.ParsingFetcher; +import com.nytimes.android.external.store3.base.impl.Store; +import com.nytimes.android.external.store3.base.impl.StoreBuilder; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import io.reactivex.Maybe; +import io.reactivex.Single; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class ParsingFetcherTest { + + static final String RAW_DATA = "Test data."; + static final String PARSED = "DATA PARSED"; + + @Mock + Fetcher fetcher; + @Mock + Parser parser; + @Mock + Persister persister; + private final BarCode barCode = new BarCode("key", "value"); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testPersistFetcher() { + + Store simpleStore = StoreBuilder.barcode() + .fetcher(ParsingFetcher.from(fetcher, parser)) + .persister(persister) + .open(); + + when(fetcher.fetch(barCode)) + .thenReturn(Single.just(RAW_DATA)); + + when(parser.apply(RAW_DATA)) + .thenReturn(PARSED); + + when(persister.read(barCode)) + .thenReturn(Maybe.just(PARSED)); + + when(persister.write(barCode, PARSED)) + .thenReturn(Single.just(true)); + + String value = simpleStore.fetch(barCode).blockingGet(); + + assertThat(value).isEqualTo(PARSED); + + verify(fetcher, times(1)).fetch(barCode); + verify(parser, times(1)).apply(RAW_DATA); + + verify(persister, times(1)).write(barCode, PARSED); + } +} From 89518af76488b1317849bf260d872bd83aee3e8d Mon Sep 17 00:00:00 2001 From: Stefan M Date: Tue, 14 Nov 2017 10:27:00 +0100 Subject: [PATCH 003/498] Changed weird MemoryPolicy to 24 hours --- .../nytimes/android/external/store3/util/NoopPersister.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.java b/store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.java index a4bee719f..11ea46ae4 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.java +++ b/store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.java @@ -38,8 +38,8 @@ public static NoopPersister create(MemoryPolicy memoryPolic if (memoryPolicy == null) { memPolicy = MemoryPolicy .builder() - .setExpireAfterWrite(TimeUnit.HOURS.toSeconds(24)) - .setExpireAfterTimeUnit(TimeUnit.SECONDS) + .setExpireAfterWrite(24) + .setExpireAfterTimeUnit(TimeUnit.HOURS) .build(); } else { memPolicy = memoryPolicy; From 2931f746242d1c8724a3dc2ca1ad5e0e3f5e59f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Antonio=20D=C3=ADaz-Benito=20Soriano?= Date: Sat, 18 Nov 2017 15:53:13 +0100 Subject: [PATCH 004/498] Fix a typo in the documentation (#289) --- .../android/external/store3/base/impl/StoreParameters.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/StoreParameters.kt b/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/StoreParameters.kt index d1714087a..9deb3b40d 100644 --- a/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/StoreParameters.kt +++ b/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/StoreParameters.kt @@ -7,7 +7,7 @@ import com.nytimes.android.external.store3.base.Persister import com.nytimes.android.external.store3.util.KeyParser /** - * A parameter box for Store instantiation, used for Stores the do not make use of parsing. + * A parameter box for Store instantiation, used for Stores that do not make use of parsing. * @param fetcher The fetcher for the Store. */ @Experimental From 9ae1e38ead477a63360770513a657d5b117cae55 Mon Sep 17 00:00:00 2001 From: Jorge Antonio Diaz-Benito Soriano Date: Sat, 18 Nov 2017 16:03:18 +0100 Subject: [PATCH 005/498] Remove @Experimental from store-kotlin API --- .../external/store3/base/impl/FluentMemoryPolicyBuilder.kt | 3 --- .../android/external/store3/base/impl/FluentStoreBuilder.kt | 2 -- .../external/store3/base/impl/MemoryPolicyParameters.kt | 2 -- .../android/external/store3/base/impl/StoreParameters.kt | 3 --- 4 files changed, 10 deletions(-) diff --git a/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/FluentMemoryPolicyBuilder.kt b/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/FluentMemoryPolicyBuilder.kt index a5b69c127..0edebcf6a 100644 --- a/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/FluentMemoryPolicyBuilder.kt +++ b/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/FluentMemoryPolicyBuilder.kt @@ -1,11 +1,8 @@ package com.nytimes.android.external.store3.base.impl -import com.nytimes.android.external.store3.annotations.Experimental - /** * Wraps methods for fluent MemoryPolicy instantiation. */ -@Experimental class FluentMemoryPolicyBuilder private constructor() { companion object { /** diff --git a/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/FluentStoreBuilder.kt b/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/FluentStoreBuilder.kt index ccaae34d3..6d187e8a1 100644 --- a/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/FluentStoreBuilder.kt +++ b/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/FluentStoreBuilder.kt @@ -1,12 +1,10 @@ package com.nytimes.android.external.store3.base.impl -import com.nytimes.android.external.store3.annotations.Experimental import com.nytimes.android.external.store3.base.Fetcher /** * Wraps methods for fluent Store instantiation. */ -@Experimental class FluentStoreBuilder private constructor() { companion object { /** diff --git a/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/MemoryPolicyParameters.kt b/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/MemoryPolicyParameters.kt index 432265f66..42179fd31 100644 --- a/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/MemoryPolicyParameters.kt +++ b/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/MemoryPolicyParameters.kt @@ -1,13 +1,11 @@ package com.nytimes.android.external.store3.base.impl -import com.nytimes.android.external.store3.annotations.Experimental import java.util.concurrent.TimeUnit import kotlin.properties.Delegates /** * A parameter box for MemoryPolicy instantiation. */ -@Experimental class MemoryPolicyParameters { var expireAfterWrite by Delegates.vetoable(MemoryPolicy.DEFAULT_POLICY) { _, _, newValue -> newValue >= 0 diff --git a/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/StoreParameters.kt b/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/StoreParameters.kt index 9deb3b40d..8805c39f1 100644 --- a/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/StoreParameters.kt +++ b/store-kotlin/src/main/kotlin/com/nytimes/android/external/store3/base/impl/StoreParameters.kt @@ -1,6 +1,5 @@ package com.nytimes.android.external.store3.base.impl -import com.nytimes.android.external.store3.annotations.Experimental import com.nytimes.android.external.store3.base.Fetcher import com.nytimes.android.external.store3.base.Parser import com.nytimes.android.external.store3.base.Persister @@ -10,7 +9,6 @@ import com.nytimes.android.external.store3.util.KeyParser * A parameter box for Store instantiation, used for Stores that do not make use of parsing. * @param fetcher The fetcher for the Store. */ -@Experimental open class StoreParameters internal constructor(private val fetcher: Fetcher) { var persister: Persister? = null var memoryPolicy: MemoryPolicy? = null @@ -21,7 +19,6 @@ open class StoreParameters internal constructor(private val fetcher: F * A parameter box for Store instantiation, used for Stores that can have parsing. * @param fetcher The fetcher for the Store. */ -@Experimental class ParsableStoreParameters internal constructor(fetcher: Fetcher) : StoreParameters(fetcher) { var parser: KeyParser? = null From 55d45131634b7bd424c3e2c4347457d608e26243 Mon Sep 17 00:00:00 2001 From: Rafael Moreno Date: Tue, 23 Jan 2018 20:14:18 -0600 Subject: [PATCH 006/498] fixed incorrect parameter issue #303 (#309) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2e87e495..88ca45dee 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ BarCode barcode = new BarCode("Article", "42"); ``` When using a Barcode as your key, you can use a StoreBuilder convenience method ``` java - Store store = StoreBuilder.barcode() + Store store = StoreBuilder.barcode() .fetcher(articleBarcode -> api.getAsset(articleBarcode.getKey(),articleBarcode.getType())) .open(); ``` From 415ef4faca7573974f4b7d71a30032923c2656b0 Mon Sep 17 00:00:00 2001 From: Rafael Moreno Date: Thu, 25 Jan 2018 12:35:29 -0600 Subject: [PATCH 007/498] updated deprecated `setExpireAfter` to `setExpireAfterWrite` (#310) --- app/src/main/java/com/nytimes/android/sample/SampleApp.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/nytimes/android/sample/SampleApp.java b/app/src/main/java/com/nytimes/android/sample/SampleApp.java index 6f9150664..e3f764cad 100644 --- a/app/src/main/java/com/nytimes/android/sample/SampleApp.java +++ b/app/src/main/java/com/nytimes/android/sample/SampleApp.java @@ -62,7 +62,7 @@ private Store provideRedditStore() { .memoryPolicy( MemoryPolicy .builder() - .setExpireAfter(10) + .setExpireAfterWrite(10) .setExpireAfterTimeUnit(TimeUnit.SECONDS) .build() ) From b6f4e6055433d792535641fb9badef5994f9ffa4 Mon Sep 17 00:00:00 2001 From: Ramona Harrison Date: Thu, 1 Feb 2018 13:45:53 -0500 Subject: [PATCH 008/498] Prepare for release 3.0.0. --- CHANGELOG.md | 15 +++++++++++++++ README.md | 2 +- build.gradle | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0069d3cb1..b65f2a5f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,21 @@ Change Log The change log for Store version 1.x can be found [here](https://github.com/NYTimes/Store/blob/develop/CHANGELOG.md). +Version 3.0.0 *(2018-02-01)* +---------------------------- + +**New Features** + +* (#275) Add ParsingFetcher that wraps Raw type Parser and Fetcher + +**Bug Fixes and Stability Improvements** + +* (#267) Kotlin 1.1.4 for store-kotlin +* (#290) Remove @Experimental from store-kotlin API +* (#283) Update build tools to 26.0.2 +* (#259, #261, #272, #289, #303) README + documentation updates +* (#310) Sample app fixes + Version 3.0.0-beta *(2017-07-26)* ---------------------------- diff --git a/README.md b/README.md index 88ca45dee..b90d2f181 100644 --- a/README.md +++ b/README.md @@ -281,7 +281,7 @@ public class SampleStore extends RealStore { ### Artifacts -**CurrentVersion = 3.0.0-beta** +**CurrentVersion = 3.0.0** + **Cache** Cache extracted from Guava (keeps method count to a minimum) diff --git a/build.gradle b/build.gradle index 1da8e8588..bdda375a0 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ allprojects { ext { // POM file GROUP = "com.nytimes.android" - VERSION_NAME = "3.0.0-SNAPSHOT" + VERSION_NAME = "3.0.0" POM_PACKAGING = "pom" POM_DESCRIPTION = "Store3 is built with RxJava2" From 69bee55706bea998244140c4e226d1b7ed88880d Mon Sep 17 00:00:00 2001 From: Ramona Harrison Date: Thu, 1 Feb 2018 14:00:19 -0500 Subject: [PATCH 009/498] Prepare next development version. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index bdda375a0..99ae7effe 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ allprojects { ext { // POM file GROUP = "com.nytimes.android" - VERSION_NAME = "3.0.0" + VERSION_NAME = "3.0.1-SNAPSHOT" POM_PACKAGING = "pom" POM_DESCRIPTION = "Store3 is built with RxJava2" From 1dccd7edc8adfd2f229eaf2a4dc62905fec34985 Mon Sep 17 00:00:00 2001 From: Pavlos-Petros Tournaris Date: Fri, 2 Feb 2018 21:40:45 +0200 Subject: [PATCH 010/498] Update Kotlin & AGP versions (#311) * Update AGP to stable 3.0.1 version * Update Kotlin to stable 1.2.21 version --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 99ae7effe..fd26bdd6e 100644 --- a/build.gradle +++ b/build.gradle @@ -18,11 +18,11 @@ buildscript { } rootProject.ext.versions = [ - kotlin: '1.1.4' + kotlin: '1.2.21' ] dependencies { - classpath 'com.android.tools.build:gradle:3.0.0' + classpath 'com.android.tools.build:gradle:3.0.1' classpath 'com.google.gms:google-services:3.0.0' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.6' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11' From 2f431b2a27547f47e7212ccd7c55b5413aea0caa Mon Sep 17 00:00:00 2001 From: Pavlos-Petros Tournaris Date: Tue, 20 Feb 2018 19:05:46 +0200 Subject: [PATCH 011/498] Fix issues occured from RxJava1 dependency (#314) * Remove RxJava1 dependencies * Fixed an ErrorProne warning --- buildsystem/dependencies.gradle | 6 ------ filesystem/build.gradle | 2 +- .../external/fs3/StoreNetworkBeforeStaleFailTest.java | 2 +- middleware-jackson/build.gradle | 1 - middleware-moshi/build.gradle | 1 - middleware/build.gradle | 1 - store/build.gradle | 2 +- 7 files changed, 3 insertions(+), 12 deletions(-) diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index 4d5e59b7b..7e0f36663 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -22,15 +22,12 @@ ext.versions = [ // UI libs. supportLibs : '25.1.1', picasso : '2.5.2', - rxBinding : '0.3.0', butterKnife : '7.0.1', // Reactive. - rxJava : '1.3.0', rxJava2 : '2.1.2', rxJavaProGuardRules : '1.1.6.0', rxJavaAsyncUtil : '0.21.0', - rxAndroid : '1.3.0', rxAndroid2 : '2.0.1', // Others. @@ -78,15 +75,12 @@ ext.libraries = [ supportVectorDrawable : "com.android.support:support-vector-drawable:$versions.supportLibs", supportAnimatedDrawable : "com.android.support:animated-vector-drawable:$versions.supportLibs", picasso : "com.squareup.picasso:picasso:$versions.picasso", - rxBinding : "com.jakewharton.rxbinding:rxbinding:$versions.rxBinding", butterKnife : "com.jakewharton:butterknife:$versions.butterKnife", // Reactive. - rxJava : "io.reactivex:rxjava:$versions.rxJava", rxJava2 : "io.reactivex.rxjava2:rxjava:$versions.rxJava2", rxJavaAsyncUtil : "io.reactivex:rxjava-async-util:$versions.rxJavaAsyncUtil", rxJavaProGuardRules : "com.artemzin.rxjava:proguard-rules:$versions.rxJavaProGuardRules", - rxAndroid : "io.reactivex:rxandroid:$versions.rxAndroid", rxAndroid2 : "io.reactivex.rxjava2:rxandroid:$versions.rxAndroid2", // Others. diff --git a/filesystem/build.gradle b/filesystem/build.gradle index f6f5b3d7d..71ec1f6d1 100644 --- a/filesystem/build.gradle +++ b/filesystem/build.gradle @@ -5,9 +5,9 @@ version = VERSION_NAME dependencies { implementation libraries.okio - implementation libraries.rxJava compileOnly libraries.jsr305 compileOnly libraries.javax + implementation libraries.rxJava2 implementation project(path: ':cache') implementation project(path: ':store') testImplementation libraries.mockito diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.java index af3f400b5..749cffc85 100644 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.java +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.java @@ -53,7 +53,7 @@ public void networkBeforeStaleNoNetworkResponse() { verify(fetcher, times(1)).fetch(barCode); } - final class TestPersister implements Persister, RecordProvider { + private static final class TestPersister implements Persister, RecordProvider { @Nonnull @Override public RecordState getRecordState(@Nonnull BarCode barCode) { diff --git a/middleware-jackson/build.gradle b/middleware-jackson/build.gradle index 2c2b08a73..199bc69bf 100644 --- a/middleware-jackson/build.gradle +++ b/middleware-jackson/build.gradle @@ -9,7 +9,6 @@ dependencies { compileOnly libraries.jsr305 implementation libraries.jacksonCore implementation libraries.jacksonDatabind - implementation libraries.rxJava implementation libraries.okio compileOnly libraries.javax testImplementation libraries.mockito diff --git a/middleware-moshi/build.gradle b/middleware-moshi/build.gradle index e57e1e6ab..bb6de1114 100644 --- a/middleware-moshi/build.gradle +++ b/middleware-moshi/build.gradle @@ -7,7 +7,6 @@ dependencies { implementation project(path: ':store') implementation project(path: ':filesystem') implementation libraries.moshi - implementation libraries.rxJava compileOnly libraries.jsr305 compileOnly libraries.javax testImplementation libraries.mockito diff --git a/middleware/build.gradle b/middleware/build.gradle index b4fb7baba..dbd9ab06d 100644 --- a/middleware/build.gradle +++ b/middleware/build.gradle @@ -9,7 +9,6 @@ dependencies { implementation project(path: ':filesystem') implementation libraries.okio implementation libraries.gson - implementation libraries.rxJava compileOnly libraries.jsr305 compileOnly libraries.javax testImplementation libraries.mockito diff --git a/store/build.gradle b/store/build.gradle index 3a5446b58..e40f96362 100644 --- a/store/build.gradle +++ b/store/build.gradle @@ -25,4 +25,4 @@ apply from: rootProject.file("gradle/maven-push.gradle") apply from: rootProject.file("gradle/checkstyle.gradle") apply from: rootProject.file("gradle/pmd.gradle") targetCompatibility = 1.8 -sourceCompatibility = 1.8 \ No newline at end of file +sourceCompatibility = 1.8 From d00da3961ca12699050e71cd4cca33bebdfa124e Mon Sep 17 00:00:00 2001 From: Ramona Harrison Date: Tue, 20 Mar 2018 13:40:54 -0400 Subject: [PATCH 012/498] Prepare for release 3.0.1. --- CHANGELOG.md | 8 ++++++++ README.md | 2 +- build.gradle | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b65f2a5f2..aedec4e25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ Change Log The change log for Store version 1.x can be found [here](https://github.com/NYTimes/Store/blob/develop/CHANGELOG.md). +Version 3.0.1 *(2018-03-20)* +---------------------------- + +**Bug Fixes and Stability Improvements** + +* (#311) Update Kotlin & AGP versions +* (#314) Fix issues occured from RxJava1 dependency + Version 3.0.0 *(2018-02-01)* ---------------------------- diff --git a/README.md b/README.md index b90d2f181..c60df1c97 100644 --- a/README.md +++ b/README.md @@ -281,7 +281,7 @@ public class SampleStore extends RealStore { ### Artifacts -**CurrentVersion = 3.0.0** +**CurrentVersion = 3.0.1** + **Cache** Cache extracted from Guava (keeps method count to a minimum) diff --git a/build.gradle b/build.gradle index fd26bdd6e..e7b7cadc8 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ allprojects { ext { // POM file GROUP = "com.nytimes.android" - VERSION_NAME = "3.0.1-SNAPSHOT" + VERSION_NAME = "3.0.1" POM_PACKAGING = "pom" POM_DESCRIPTION = "Store3 is built with RxJava2" From 31931b41153daa1bd59d1b140bfa7e2fb01d1da9 Mon Sep 17 00:00:00 2001 From: Ramona Harrison Date: Tue, 20 Mar 2018 14:13:44 -0400 Subject: [PATCH 013/498] Prepare next development version. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e7b7cadc8..7c9fee82b 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ allprojects { ext { // POM file GROUP = "com.nytimes.android" - VERSION_NAME = "3.0.1" + VERSION_NAME = "3.0.2-SNAPSHOT" POM_PACKAGING = "pom" POM_DESCRIPTION = "Store3 is built with RxJava2" From a0f26443a16b4eca76984a61f8bd8a4cd24aae70 Mon Sep 17 00:00:00 2001 From: Saket Narayan Date: Tue, 20 Mar 2018 23:44:55 +0530 Subject: [PATCH 014/498] Add missing reading of expire-after-policy when creating a NoopPersister (#315) * Set missing expire-after-access policy to NoopPersister's cache (#312) * Remove comment about PMD from NoopPersister#create() * Add regression test for #312 * Deprecate MemoryPolicy#isDefaultPolicy() in favor of isDefaultWritePolicy() and isDefaultAccessPolicy() --- .../store3/base/impl/MemoryPolicy.java | 20 ++++++++++++ .../external/store3/util/NoopParserFunc.java | 1 + .../external/store3/util/NoopPersister.java | 29 ++++++++--------- .../base/impl/MemoryPolicyBuilderTest.java | 4 +-- .../store3/util/NoopPersisterTest.java | 31 +++++++++++++++++-- 5 files changed, 67 insertions(+), 18 deletions(-) diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/MemoryPolicy.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/MemoryPolicy.java index 60af0806e..d48d0f1e4 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/MemoryPolicy.java +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/MemoryPolicy.java @@ -60,10 +60,30 @@ public long getMaxSize() { return maxSize; } + /** + * @deprecated Use {@link MemoryPolicy#isDefaultWritePolicy()} or {@link MemoryPolicy#isDefaultAccessPolicy()}. + */ + @Deprecated public boolean isDefaultPolicy() { return expireAfterWrite == DEFAULT_POLICY; } + public boolean isDefaultWritePolicy() { + return expireAfterWrite == DEFAULT_POLICY; + } + + public boolean isDefaultAccessPolicy() { + return expireAfterAccess == DEFAULT_POLICY; + } + + public boolean hasWritePolicy() { + return expireAfterWrite != DEFAULT_POLICY; + } + + public boolean hasAccessPolicy() { + return expireAfterAccess != DEFAULT_POLICY; + } + public static class MemoryPolicyBuilder { private long expireAfterWrite = DEFAULT_POLICY; private long expireAfterAccess = DEFAULT_POLICY; diff --git a/store/src/main/java/com/nytimes/android/external/store3/util/NoopParserFunc.java b/store/src/main/java/com/nytimes/android/external/store3/util/NoopParserFunc.java index 2137f4c86..9a87270f4 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/util/NoopParserFunc.java +++ b/store/src/main/java/com/nytimes/android/external/store3/util/NoopParserFunc.java @@ -11,6 +11,7 @@ public class NoopParserFunc implements Parser { @Override public Parsed apply(@NonNull Raw raw) throws ParserException { + //noinspection unchecked return (Parsed) raw; } } diff --git a/store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.java b/store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.java index 11ea46ae4..b889430e3 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.java +++ b/store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.java @@ -7,13 +7,11 @@ import com.nytimes.android.external.store3.base.impl.MemoryPolicy; import java.util.concurrent.TimeUnit; - import javax.annotation.Nonnull; import io.reactivex.Maybe; import io.reactivex.Single; - /** * Pass-through diskdao for stores that don't want to use persister */ @@ -21,10 +19,18 @@ public class NoopPersister implements Persister, Clearable> networkResponses; NoopPersister(MemoryPolicy memoryPolicy) { - this.networkResponses = CacheBuilder - .newBuilder() - .expireAfterWrite(memoryPolicy.getExpireAfterWrite(), memoryPolicy.getExpireAfterTimeUnit()) - .build(); + if (memoryPolicy.hasAccessPolicy()) { + networkResponses = CacheBuilder.newBuilder() + .expireAfterAccess(memoryPolicy.getExpireAfterAccess(), memoryPolicy.getExpireAfterTimeUnit()) + .build(); + + } else if (memoryPolicy.hasWritePolicy()) { + networkResponses = CacheBuilder.newBuilder() + .expireAfterWrite(memoryPolicy.getExpireAfterWrite(), memoryPolicy.getExpireAfterTimeUnit()) + .build(); + } else { + throw new IllegalArgumentException("No expiry policy set on memory-policy."); + } } public static NoopPersister create() { @@ -32,20 +38,15 @@ public static NoopPersister create() { } public static NoopPersister create(MemoryPolicy memoryPolicy) { - //For some reason PMD requires a local variable instead of modifying the passed one. - MemoryPolicy memPolicy; - if (memoryPolicy == null) { - memPolicy = MemoryPolicy + MemoryPolicy defaultPolicy = MemoryPolicy .builder() .setExpireAfterWrite(24) .setExpireAfterTimeUnit(TimeUnit.HOURS) .build(); - } else { - memPolicy = memoryPolicy; + return new NoopPersister<>(defaultPolicy); } - - return new NoopPersister<>(memPolicy); + return new NoopPersister<>(memoryPolicy); } @Nonnull diff --git a/store/src/test/java/com/nytimes/android/external/store3/base/impl/MemoryPolicyBuilderTest.java b/store/src/test/java/com/nytimes/android/external/store3/base/impl/MemoryPolicyBuilderTest.java index 680bbfece..4e0f97ce6 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/base/impl/MemoryPolicyBuilderTest.java +++ b/store/src/test/java/com/nytimes/android/external/store3/base/impl/MemoryPolicyBuilderTest.java @@ -16,7 +16,7 @@ public void testBuildExpireAfterWriteMemoryPolicy() { assertThat(policy.getExpireAfterWrite()).isEqualTo(4L); assertThat(policy.getExpireAfterTimeUnit()).isEqualTo(TimeUnit.SECONDS); - assertThat(policy.isDefaultPolicy()).isFalse(); + assertThat(policy.isDefaultWritePolicy()).isFalse(); assertThat(policy.getExpireAfterAccess()).isEqualTo(MemoryPolicy.DEFAULT_POLICY); } @@ -28,7 +28,7 @@ public void testBuildExpireAfterAccessMemoryPolicy() { assertThat(policy.getExpireAfterAccess()).isEqualTo(4L); assertThat(policy.getExpireAfterTimeUnit()).isEqualTo(TimeUnit.SECONDS); - assertThat(policy.isDefaultPolicy()).isTrue(); + assertThat(policy.isDefaultWritePolicy()).isTrue(); assertThat(policy.getExpireAfterWrite()).isEqualTo(MemoryPolicy.DEFAULT_POLICY); } diff --git a/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.java b/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.java index c88465b2e..659cf22f6 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.java +++ b/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.java @@ -1,17 +1,22 @@ package com.nytimes.android.external.store3.util; import com.nytimes.android.external.store3.base.impl.BarCode; +import com.nytimes.android.external.store3.base.impl.MemoryPolicy; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import static org.assertj.core.api.Assertions.assertThat; +import java.util.concurrent.TimeUnit; public class NoopPersisterTest { - private final BarCode barCode = new BarCode("key", "value"); + @Rule public ExpectedException exception = ExpectedException.none(); @Test public void writeReadTest() { + BarCode barCode = new BarCode("key", "value"); NoopPersister persister = NoopPersister.create(); boolean success = persister.write(barCode, "foo").blockingGet(); assertThat(success).isTrue(); @@ -23,10 +28,32 @@ public void writeReadTest() { public void noopParserFuncTest() { NoopParserFunc noopParserFunc = new NoopParserFunc<>(); String input = "foo"; - String output = (String) noopParserFunc.apply(input); + String output = noopParserFunc.apply(input); assertThat(input).isEqualTo(output); //intended object ref comparison assertThat(input).isSameAs(output); } + // https://github.com/NYTimes/Store/issues/312 + @Test + public void testReadingOfMemoryPolicies() { + MemoryPolicy expireAfterWritePolicy = MemoryPolicy.builder() + .setExpireAfterWrite(1) + .setExpireAfterTimeUnit(TimeUnit.HOURS) + .build(); + NoopPersister.create(expireAfterWritePolicy); + + MemoryPolicy expireAfterAccessPolicy = MemoryPolicy.builder() + .setExpireAfterAccess(1) + .setExpireAfterTimeUnit(TimeUnit.HOURS) + .build(); + NoopPersister.create(expireAfterAccessPolicy); + + exception.expect(IllegalArgumentException.class); + exception.expectMessage("No expiry policy set"); + MemoryPolicy incompletePolicy = MemoryPolicy.builder() + .setExpireAfterTimeUnit(TimeUnit.HOURS) + .build(); + NoopPersister.create(incompletePolicy); + } } From cc1a34017e2616e09a4bc52223c423f1afb2b031 Mon Sep 17 00:00:00 2001 From: Ramona Harrison Date: Tue, 20 Mar 2018 17:39:51 -0400 Subject: [PATCH 015/498] Bump sample app to 3.0.1 --- app/build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6e0a3a6ad..eb3599aa8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,9 +48,9 @@ dependencies { annotationProcessor libraries.immutablesValue // <-- for annotation processor compileOnly libraries.immutablesValue // <-- for annotation API compileOnly libraries.immutablesGson // for annotations - implementation 'com.nytimes.android:store3:3.0.0-beta' - implementation 'com.nytimes.android:cache3:3.0.0-beta' - implementation 'com.nytimes.android:middleware3:3.0.0-beta' - implementation 'com.nytimes.android:filesystem3:3.0.0-beta' + implementation 'com.nytimes.android:store3:3.0.1' + implementation 'com.nytimes.android:cache3:3.0.1' + implementation 'com.nytimes.android:middleware3:3.0.1' + implementation 'com.nytimes.android:filesystem3:3.0.1' implementation libraries.rxAndroid2 } From b6a011bfd1856f31799de54d8e6cd75d87594287 Mon Sep 17 00:00:00 2001 From: pavlospt Date: Thu, 29 Mar 2018 16:55:33 +0300 Subject: [PATCH 016/498] Update AGP to 3.1.0 stable --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7c9fee82b..6ccac9dce 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ buildscript { ] dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.android.tools.build:gradle:3.1.0' classpath 'com.google.gms:google-services:3.0.0' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.6' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11' From f3ba42bfa78f2be51d69fcd9be5e5e38bb2b9205 Mon Sep 17 00:00:00 2001 From: pavlospt Date: Thu, 29 Mar 2018 16:55:43 +0300 Subject: [PATCH 017/498] Update Gradle version to 4.4 --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 79f86c81e..0fcbb13de 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Aug 11 18:17:09 CEST 2017 +#Thu Mar 29 16:46:38 EEST 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip From 870b79e33c53574e3d8e2305cf43e61eec12f5a8 Mon Sep 17 00:00:00 2001 From: pavlospt Date: Thu, 29 Mar 2018 17:13:54 +0300 Subject: [PATCH 018/498] Return standard maxSize if not set --- .../external/store3/base/impl/MemoryPolicy.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/MemoryPolicy.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/MemoryPolicy.java index d48d0f1e4..d10812be6 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/MemoryPolicy.java +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/MemoryPolicy.java @@ -57,6 +57,9 @@ public TimeUnit getExpireAfterTimeUnit() { } public long getMaxSize() { + if (isDefaultMaxSize()) { + return 1; + } return maxSize; } @@ -76,6 +79,10 @@ public boolean isDefaultAccessPolicy() { return expireAfterAccess == DEFAULT_POLICY; } + public boolean isDefaultMaxSize() { + return maxSize == DEFAULT_POLICY; + } + public boolean hasWritePolicy() { return expireAfterWrite != DEFAULT_POLICY; } @@ -84,11 +91,15 @@ public boolean hasAccessPolicy() { return expireAfterAccess != DEFAULT_POLICY; } + public boolean hasMaxSize() { + return maxSize != DEFAULT_POLICY; + } + public static class MemoryPolicyBuilder { private long expireAfterWrite = DEFAULT_POLICY; private long expireAfterAccess = DEFAULT_POLICY; private TimeUnit expireAfterTimeUnit = TimeUnit.SECONDS; - private long maxSize = 1; + private long maxSize = -1; /** * @deprecated Use {@link MemoryPolicyBuilder#setExpireAfterWrite(long)} or From 241c99bb911582df280af4441e3f3dd06859c2b0 Mon Sep 17 00:00:00 2001 From: Joe Cyboski Date: Thu, 29 Mar 2018 12:25:03 -0400 Subject: [PATCH 019/498] Adding docs to readme for setting 1.8 compatibility --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index c60df1c97..e35d7efc8 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,25 @@ A Store is responsible for managing a particular data request. When you create a Store leverages RxJava and multiple request throttling to prevent excessive calls to the network and disk cache. By utilizing Store, you eliminate the possibility of flooding your network with the same request while adding two layers of caching (memory and disk). +### How to include in your project + +###### Include gradle dependency +``` + implementation 'com.nytimes.android:store3:3.0.1' +``` +###### Set the source & target compatibilities to `1.8` +Starting with Store 3.0, `retrolambda` is no longer uses. Therefore to allow support for lambdas the Java sourceCompatibility and targetCompatibility need to be set to `1.8` + +``` +android { + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + ... +} +``` + ### Fully Configured Store Let's start by looking at what a fully configured Store looks like. We will then walk through simpler examples showing each piece: ```java From 5005529768d274a6dac8d0a76ca7fe991ff5a673 Mon Sep 17 00:00:00 2001 From: Tournaris Pavlos-Petros Date: Thu, 29 Mar 2018 23:00:15 +0300 Subject: [PATCH 020/498] Add test for MemoryPolicy default size --- .../store3/base/impl/MemoryPolicyBuilderTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/store/src/test/java/com/nytimes/android/external/store3/base/impl/MemoryPolicyBuilderTest.java b/store/src/test/java/com/nytimes/android/external/store3/base/impl/MemoryPolicyBuilderTest.java index 4e0f97ce6..0a3131d5b 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/base/impl/MemoryPolicyBuilderTest.java +++ b/store/src/test/java/com/nytimes/android/external/store3/base/impl/MemoryPolicyBuilderTest.java @@ -57,4 +57,12 @@ public void testBuilderSetsMemorySize() { assertThat(policy.getMaxSize()).isEqualTo(10L); } + + @Test + public void testDefaultMemorySizeIfNotSet() { + MemoryPolicy policy = MemoryPolicy.builder() + .build(); + + assertThat(policy.getMaxSize()).isEqualTo(1L); + } } From b66438fb1108b47f5d9a45b3725144298d3d86a3 Mon Sep 17 00:00:00 2001 From: Tournaris Pavlos-Petros Date: Thu, 29 Mar 2018 23:07:18 +0300 Subject: [PATCH 021/498] Update Travis file for BuildTools 27.0.3 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3a87a2139..cb0990f0b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ android: components: - tools - platform-tools - - build-tools-26.0.2 + - build-tools-27.0.3 - android-25 - extra-android-m2repository licenses: From 810e7ee3c9d5b0a84fa1866d0d79eeb142675420 Mon Sep 17 00:00:00 2001 From: Tournaris Pavlos-Petros Date: Thu, 29 Mar 2018 23:22:08 +0300 Subject: [PATCH 022/498] Add google() repository * Needed for the lint-gradle dependency in :app project --- buildsystem/dependencies.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index 7e0f36663..800bef4c5 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -8,6 +8,7 @@ allprojects { maven { url = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' } + google() jcenter() } } From d5fe0cbf4719c249bf46acf441f2be9f58b37727 Mon Sep 17 00:00:00 2001 From: Tournaris Pavlos-Petros Date: Thu, 29 Mar 2018 23:22:59 +0300 Subject: [PATCH 023/498] Update BuildTool version * Version 27.0.3 is already used by AGP 3.1, but I updated the version in dependencies.gradle as well. --- buildsystem/dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index 800bef4c5..bc4fa42a8 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -17,7 +17,7 @@ ext.versions = [ minSdk : 16, targetSdk : 25, compileSdk : 25, - buildTools : '26.0.2', + buildTools : '27.0.3', kotlin : '1.1.2-5', // UI libs. From 2654ce03855acaacd42a2db198120b75d9b736c8 Mon Sep 17 00:00:00 2001 From: Tournaris Pavlos-Petros Date: Thu, 29 Mar 2018 23:29:24 +0300 Subject: [PATCH 024/498] Fix checkstyle warnings --- .../external/fs3/StoreNetworkBeforeStaleFailTest.java | 8 ++++---- .../middleware/jackson/JacksonReaderParserStoreTest.java | 2 +- .../middleware/jackson/JacksonSourceParserStoreTest.java | 2 +- .../store3/middleware/moshi/MoshiSourceParserTest.java | 2 +- .../middleware/moshi/MoshiStringParserStoreTest.java | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.java index 749cffc85..06c1f7968 100644 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.java +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.java @@ -27,7 +27,7 @@ @RunWith(MockitoJUnitRunner.class) public class StoreNetworkBeforeStaleFailTest { - static final Exception sorry = new Exception("sorry"); + static final Exception SORRY = new Exception("sorry"); private static final BarCode barCode = new BarCode("key", "value"); @Mock Fetcher fetcher; @@ -46,10 +46,10 @@ public void setUp() { @Test public void networkBeforeStaleNoNetworkResponse() { - Single exception = Single.error(sorry); + Single exception = Single.error(SORRY); when(fetcher.fetch(barCode)) .thenReturn(exception); - store.get(barCode).test().assertError(sorry); + store.get(barCode).test().assertError(SORRY); verify(fetcher, times(1)).fetch(barCode); } @@ -63,7 +63,7 @@ public RecordState getRecordState(@Nonnull BarCode barCode) { @Nonnull @Override public Maybe read(@Nonnull BarCode barCode) { - return Maybe.error(sorry); + return Maybe.error(SORRY); } @Nonnull diff --git a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParserStoreTest.java b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParserStoreTest.java index 913a94c7d..2186d8ff9 100644 --- a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParserStoreTest.java +++ b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParserStoreTest.java @@ -5,8 +5,8 @@ import com.nytimes.android.external.store3.base.Fetcher; import com.nytimes.android.external.store3.base.Parser; import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.Store; import com.nytimes.android.external.store3.base.impl.BarCode; +import com.nytimes.android.external.store3.base.impl.Store; import com.nytimes.android.external.store3.base.impl.StoreBuilder; import com.nytimes.android.external.store3.middleware.jackson.data.Foo; diff --git a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParserStoreTest.java b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParserStoreTest.java index be0b40b9f..e13902d5f 100644 --- a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParserStoreTest.java +++ b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParserStoreTest.java @@ -5,8 +5,8 @@ import com.nytimes.android.external.store3.base.Fetcher; import com.nytimes.android.external.store3.base.Parser; import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.Store; import com.nytimes.android.external.store3.base.impl.BarCode; +import com.nytimes.android.external.store3.base.impl.Store; import com.nytimes.android.external.store3.base.impl.StoreBuilder; import com.nytimes.android.external.store3.middleware.jackson.data.Foo; diff --git a/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParserTest.java b/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParserTest.java index ce5ac6d26..4fce3969c 100644 --- a/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParserTest.java +++ b/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParserTest.java @@ -4,8 +4,8 @@ import com.nytimes.android.external.store3.base.Parser; import com.nytimes.android.external.store3.base.Persister; import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; import com.nytimes.android.external.store3.base.impl.ParsingStoreBuilder; +import com.nytimes.android.external.store3.base.impl.Store; import com.nytimes.android.external.store3.middleware.moshi.data.Foo; import org.junit.Before; diff --git a/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParserStoreTest.java b/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParserStoreTest.java index 7f9fb06f6..ccfc2890b 100644 --- a/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParserStoreTest.java +++ b/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParserStoreTest.java @@ -3,8 +3,8 @@ import com.nytimes.android.external.store3.base.Fetcher; import com.nytimes.android.external.store3.base.Persister; import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; import com.nytimes.android.external.store3.base.impl.ParsingStoreBuilder; +import com.nytimes.android.external.store3.base.impl.Store; import com.nytimes.android.external.store3.middleware.moshi.data.Foo; import com.squareup.moshi.Moshi; From 889f0397d4466f7005153e7ebb5c69a548f2f8ae Mon Sep 17 00:00:00 2001 From: Kevin Cronly Date: Mon, 30 Apr 2018 09:58:15 -0400 Subject: [PATCH 025/498] 273: Adds comments to the sample app (#335) --- .../com/nytimes/android/sample/SampleApp.java | 15 +++++++++++++++ .../sample/activity/PersistingStoreActivity.java | 4 ++++ .../android/sample/activity/StoreActivity.java | 8 ++++++++ .../android/sample/reddit/PostViewHolder.java | 1 - .../android/sample/util/BitmapTransform.java | 8 ++++---- 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/nytimes/android/sample/SampleApp.java b/app/src/main/java/com/nytimes/android/sample/SampleApp.java index e3f764cad..08f0dafd6 100644 --- a/app/src/main/java/com/nytimes/android/sample/SampleApp.java +++ b/app/src/main/java/com/nytimes/android/sample/SampleApp.java @@ -1,6 +1,7 @@ package com.nytimes.android.sample; import android.app.Application; +import android.support.annotation.NonNull; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -56,6 +57,9 @@ public Store getPersistedStore() { return this.persistedStore; } + /** + * Provides a Store which only retains RedditData for 10 seconds in memory. + */ private Store provideRedditStore() { return StoreBuilder.barcode() .fetcher(barCode -> provideRetrofit().fetchSubreddit(barCode.getKey(), "10")) @@ -69,6 +73,10 @@ private Store provideRedditStore() { .open(); } + /** + * Provides a Store which will persist RedditData to the cache, and use Gson to parse the JSON + * that comes back from the network into RedditData. + */ private Store providePersistedRedditStore() { return StoreBuilder.parsedWithKey() .fetcher(this::fetcher) @@ -77,10 +85,17 @@ private Store providePersistedRedditStore() { .open(); } + /** + * Returns a new Persister with the cache as the root. + */ private Persister newPersister() throws IOException { return SourcePersisterFactory.create(getApplicationContext().getCacheDir()); } + /** + * Returns a "fetcher" which will retrieve new data from the network. + */ + @NonNull private Single fetcher(BarCode barCode) { return provideRetrofit().fetchSubredditForPersister(barCode.getKey(), "10") .map(ResponseBody::source); diff --git a/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java b/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java index 6d41ce3d6..8c3e18813 100644 --- a/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java +++ b/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java @@ -57,6 +57,10 @@ private void initStore() { public void loadPosts() { BarCode awwRequest = new BarCode(RedditData.class.getSimpleName(), "aww"); + /* + First call to get(awwRequest) will use the network, then save response in the in-memory + cache. Subsequent calls will retrieve the cached version of the data. + */ this.persistedStore .get(awwRequest) .flatMapObservable(this::sanitizeData) diff --git a/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java b/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java index 4edcb6ff4..de0703bca 100644 --- a/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java +++ b/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java @@ -58,6 +58,14 @@ private void initStore() { public void loadPosts() { BarCode awwRequest = new BarCode(RedditData.class.getSimpleName(), "aww"); + /* + First call to get(awwRequest) will use the network, then save response in the in-memory + cache. Subsequent calls will retrieve the cached version of the data. + + But, since the policy of this store is to expire after 10 seconds, the cache will + only be used for subsequent requests that happen within 10 seconds of the initial request. + After that, the request will use the network. + */ this.nonPersistedStore .get(awwRequest) .flatMapObservable(new Function>() { diff --git a/app/src/main/java/com/nytimes/android/sample/reddit/PostViewHolder.java b/app/src/main/java/com/nytimes/android/sample/reddit/PostViewHolder.java index 72e3dd025..5bd97a0bf 100644 --- a/app/src/main/java/com/nytimes/android/sample/reddit/PostViewHolder.java +++ b/app/src/main/java/com/nytimes/android/sample/reddit/PostViewHolder.java @@ -6,7 +6,6 @@ import android.widget.ImageView; import android.widget.TextView; - import com.nytimes.android.sample.R; import com.nytimes.android.sample.data.model.Image; import com.nytimes.android.sample.data.model.ImmutableImage; diff --git a/app/src/main/java/com/nytimes/android/sample/util/BitmapTransform.java b/app/src/main/java/com/nytimes/android/sample/util/BitmapTransform.java index 54b878b1b..61bea070f 100644 --- a/app/src/main/java/com/nytimes/android/sample/util/BitmapTransform.java +++ b/app/src/main/java/com/nytimes/android/sample/util/BitmapTransform.java @@ -6,10 +6,10 @@ import com.squareup.picasso.Transformation; -public class BitmapTransform implements Transformation -{ - int maxWidth, maxHeight; - Image key; +public class BitmapTransform implements Transformation { + private int maxWidth; + private int maxHeight; + private Image key; public int targetWidth; public int targetHeight; private final String picassoKey; From fd8bdf597d533c6ad1149f771b904fd10fb27d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Antonio=20D=C3=ADaz-Benito=20Soriano?= Date: Thu, 3 May 2018 15:39:37 +0200 Subject: [PATCH 026/498] Remove unnecessary Android-specific stuff (#319) --- README.md | 4 +- cache/src/main/AndroidManifest.xml | 9 -- filesystem/src/main/AndroidManifest.xml | 9 -- gradle/maven-push.gradle | 135 +++++------------- .../src/main/AndroidManifest.xml | 11 -- middleware-moshi/src/main/AndroidManifest.xml | 6 - middleware/src/main/AndroidManifest.xml | 9 -- store-kotlin/src/main/AndroidManifest.xml | 3 - store/src/main/AndroidManifest.xml | 9 -- 9 files changed, 41 insertions(+), 154 deletions(-) delete mode 100644 cache/src/main/AndroidManifest.xml delete mode 100644 filesystem/src/main/AndroidManifest.xml delete mode 100644 middleware-jackson/src/main/AndroidManifest.xml delete mode 100644 middleware-moshi/src/main/AndroidManifest.xml delete mode 100644 middleware/src/main/AndroidManifest.xml delete mode 100644 store-kotlin/src/main/AndroidManifest.xml delete mode 100644 store/src/main/AndroidManifest.xml diff --git a/README.md b/README.md index e35d7efc8..d5f22ed03 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ ![Store Logo](https://raw.githubusercontent.com/NYTimes/Store/feature/rx2/Images/store-logo.png) -Store is an Android library for effortless, reactive data loading. +Store is a Java library for effortless, reactive data loading. ### The Problems: -+ Modern Android Apps need their data representations to be fluid and always available. ++ Modern software needs data representations to be fluid and always available. + Users expect their UI experience to never be compromised (blocked) by new data loads. Whether an application is social, news, or business-to-business, users expect a seamless experience both online and offline. + International users expect minimal data downloads as many megabytes of downloaded data can quickly result in astronomical phone bills. diff --git a/cache/src/main/AndroidManifest.xml b/cache/src/main/AndroidManifest.xml deleted file mode 100644 index 2ba61d496..000000000 --- a/cache/src/main/AndroidManifest.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/filesystem/src/main/AndroidManifest.xml b/filesystem/src/main/AndroidManifest.xml deleted file mode 100644 index 7fd05e9f6..000000000 --- a/filesystem/src/main/AndroidManifest.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/gradle/maven-push.gradle b/gradle/maven-push.gradle index 47ed256be..51f653d37 100644 --- a/gradle/maven-push.gradle +++ b/gradle/maven-push.gradle @@ -94,111 +94,55 @@ afterEvaluate { project -> sign configurations.archives } - if (project.getPlugins().hasPlugin('com.android.application') || - project.getPlugins().hasPlugin('com.android.library')) { - task install(type: Upload, dependsOn: assemble) { - repositories.mavenInstaller { - configuration = configurations.archives - - pom.groupId = GROUP - pom.artifactId = POM_ARTIFACT_ID - pom.version = VERSION_NAME - - pom.project { - name POM_NAME - packaging POM_PACKAGING - description POM_DESCRIPTION - url POM_URL - - scm { - url POM_SCM_URL - connection POM_SCM_CONNECTION - developerConnection POM_SCM_DEV_CONNECTION - } - - licenses { - license { - name POM_LICENCE_NAME - url POM_LICENCE_URL - distribution POM_LICENCE_DIST - } - } - - developers { - developer { - id POM_DEVELOPER_ID - name POM_DEVELOPER_NAME - } - } + install { + repositories.mavenInstaller { + pom.groupId = GROUP + pom.artifactId = POM_ARTIFACT_ID + pom.version = VERSION_NAME + + pom.project { + name POM_NAME + packaging POM_PACKAGING + description POM_DESCRIPTION + url POM_URL + + scm { + url POM_SCM_URL + connection POM_SCM_CONNECTION + developerConnection POM_SCM_DEV_CONNECTION } - } - } - - task androidJavadocs(type: Javadoc) { - source = android.sourceSets.main.java.source - classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) - } - - task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { - classifier = 'javadoc' - from androidJavadocs.destinationDir - } - - task androidSourcesJar(type: Jar) { - classifier = 'sources' - from android.sourceSets.main.java.source - } - } else { - install { - repositories.mavenInstaller { - pom.groupId = GROUP - pom.artifactId = POM_ARTIFACT_ID - pom.version = VERSION_NAME - - pom.project { - name POM_NAME - packaging POM_PACKAGING - description POM_DESCRIPTION - url POM_URL - - scm { - url POM_SCM_URL - connection POM_SCM_CONNECTION - developerConnection POM_SCM_DEV_CONNECTION - } - licenses { - license { - name POM_LICENCE_NAME - url POM_LICENCE_URL - distribution POM_LICENCE_DIST - } + licenses { + license { + name POM_LICENCE_NAME + url POM_LICENCE_URL + distribution POM_LICENCE_DIST } + } - developers { - developer { - id POM_DEVELOPER_ID - name POM_DEVELOPER_NAME - } + developers { + developer { + id POM_DEVELOPER_ID + name POM_DEVELOPER_NAME } } } } + } - task sourcesJar(type: Jar, dependsOn:classes) { - classifier = 'sources' - from sourceSets.main.allSource - } + task sourcesJar(type: Jar, dependsOn:classes) { + classifier = 'sources' + from sourceSets.main.allSource + } - task javadocJar(type: Jar, dependsOn:javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir - } + task javadocJar(type: Jar, dependsOn:javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir + } - task kotlinJavadocJar(type: Jar, dependsOn:javadoc) { - classifier = 'javadoc' - from "$buildDir/dokka" - } + task kotlinJavadocJar(type: Jar, dependsOn:javadoc) { + classifier = 'javadoc' + from "$buildDir/dokka" } if (JavaVersion.current().isJava8Compatible()) { @@ -210,8 +154,7 @@ afterEvaluate { project -> } artifacts { - if (project.getPlugins().hasPlugin('com.android.application') || - project.getPlugins().hasPlugin('com.android.library')) { + if (project.getPlugins().hasPlugin('com.android.application')) { archives androidSourcesJar archives androidJavadocsJar } else { diff --git a/middleware-jackson/src/main/AndroidManifest.xml b/middleware-jackson/src/main/AndroidManifest.xml deleted file mode 100644 index f2de38a63..000000000 --- a/middleware-jackson/src/main/AndroidManifest.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - diff --git a/middleware-moshi/src/main/AndroidManifest.xml b/middleware-moshi/src/main/AndroidManifest.xml deleted file mode 100644 index 4355cc82c..000000000 --- a/middleware-moshi/src/main/AndroidManifest.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/middleware/src/main/AndroidManifest.xml b/middleware/src/main/AndroidManifest.xml deleted file mode 100644 index 053b5bc33..000000000 --- a/middleware/src/main/AndroidManifest.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/store-kotlin/src/main/AndroidManifest.xml b/store-kotlin/src/main/AndroidManifest.xml deleted file mode 100644 index 0b685409b..000000000 --- a/store-kotlin/src/main/AndroidManifest.xml +++ /dev/null @@ -1,3 +0,0 @@ - - diff --git a/store/src/main/AndroidManifest.xml b/store/src/main/AndroidManifest.xml deleted file mode 100644 index 6f1cb25cc..000000000 --- a/store/src/main/AndroidManifest.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - From e3c590906e86bda62abe1791b7d448138f859aea Mon Sep 17 00:00:00 2001 From: Tournaris Pavlos-Petros Date: Sun, 13 May 2018 22:35:44 +0300 Subject: [PATCH 027/498] Bump AGP to 3.1.2 stable --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 6ccac9dce..5fe5f11e8 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ buildscript { ] dependencies { - classpath 'com.android.tools.build:gradle:3.1.0' + classpath 'com.android.tools.build:gradle:3.1.2' classpath 'com.google.gms:google-services:3.0.0' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.6' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11' From 13ee94895f69e9c01aaff978429aa1e746c82b15 Mon Sep 17 00:00:00 2001 From: Mike Nakhimovich Date: Wed, 30 May 2018 12:56:08 -0400 Subject: [PATCH 028/498] StoreRoom (#338) * roomStore * kt the app class * refactor * fix compilation * Update .travis.yml * migrate relevant tests to RoomStore, mark new Room API with @Experimental * pr feedback * bump minor rev up * cleanup store * create factory, clean up example * pr comments --- .travis.yml | 4 +- app/build.gradle | 28 ++- .../com/nytimes/android/sample/SampleApp.java | 119 --------- .../com/nytimes/android/sample/SampleApp.kt | 133 +++++++++++ .../nytimes/android/sample/SampleRoomStore.kt | 60 +++++ build.gradle | 6 +- buildsystem/dependencies.gradle | 6 +- gradle/wrapper/gradle-wrapper.properties | 4 +- store/gradle.properties | 1 + .../external/store3/base/BasePersister.java | 8 + .../external/store3/base/Persister.java | 8 +- .../store3/base/impl/CacheFactory.java | 66 +++-- .../external/store3/base/impl/StoreUtil.java | 14 +- .../store3/base/impl/room/RealStoreRoom.java | 225 ++++++++++++++++++ .../store3/base/impl/room/StoreRoom.java | 70 ++++++ .../store3/base/room/RoomDiskRead.java | 13 + .../store3/base/room/RoomDiskWrite.java | 16 ++ .../store3/base/room/RoomPersister.java | 36 +++ .../store3/room/ClearStoreRoomTest.java | 112 +++++++++ .../external/store3/room/StoreRoomTest.java | 98 ++++++++ 20 files changed, 862 insertions(+), 165 deletions(-) delete mode 100644 app/src/main/java/com/nytimes/android/sample/SampleApp.java create mode 100644 app/src/main/java/com/nytimes/android/sample/SampleApp.kt create mode 100644 app/src/main/java/com/nytimes/android/sample/SampleRoomStore.kt create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/BasePersister.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/impl/room/RealStoreRoom.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/impl/room/StoreRoom.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/room/RoomDiskRead.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/room/RoomDiskWrite.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/room/RoomPersister.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.java diff --git a/.travis.yml b/.travis.yml index cb0990f0b..2d0ff2cc5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ android: - tools - platform-tools - build-tools-27.0.3 - - android-25 + - android-27 - extra-android-m2repository licenses: - 'android-sdk-license-.+' @@ -14,6 +14,8 @@ script: - ./gradlew check --stacktrace after_success: - gradle/deploy_snapshot.sh +before_install: +- yes | sdkmanager "platforms;android-27" branches: except: - gh-pages diff --git a/app/build.gradle b/app/build.gradle index eb3599aa8..581446576 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' apply plugin: 'com.getkeepsafe.dexcount' - +apply plugin: 'kotlin-kapt' android { compileSdkVersion versions.compileSdk buildToolsVersion versions.buildTools @@ -31,6 +32,7 @@ android { exclude 'META-INF/rxjava.properties' } } +def room_version = "1.1.0" // or, for latest rc, use "1.1.1-rc1" dependencies { @@ -48,9 +50,25 @@ dependencies { annotationProcessor libraries.immutablesValue // <-- for annotation processor compileOnly libraries.immutablesValue // <-- for annotation API compileOnly libraries.immutablesGson // for annotations - implementation 'com.nytimes.android:store3:3.0.1' - implementation 'com.nytimes.android:cache3:3.0.1' - implementation 'com.nytimes.android:middleware3:3.0.1' - implementation 'com.nytimes.android:filesystem3:3.0.1' + implementation project(':store') + implementation project(':cache') + implementation project(':middleware') + implementation project(':filesystem') implementation libraries.rxAndroid2 + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + + implementation "android.arch.persistence.room:runtime:$room_version" + annotationProcessor "android.arch.persistence.room:compiler:$room_version" + kapt "android.arch.persistence.room:compiler:$room_version" + +// androidTestImplementation "android.arch.persistence.room:testing:$room_version" + // optional - RxJava support for Room + implementation "android.arch.persistence.room:rxjava2:$room_version" } +repositories { + mavenCentral() +} + +configurations.all { + resolutionStrategy.force 'com.android.support:support-v4:26.1.0' +} \ No newline at end of file diff --git a/app/src/main/java/com/nytimes/android/sample/SampleApp.java b/app/src/main/java/com/nytimes/android/sample/SampleApp.java deleted file mode 100644 index 08f0dafd6..000000000 --- a/app/src/main/java/com/nytimes/android/sample/SampleApp.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.nytimes.android.sample; - -import android.app.Application; -import android.support.annotation.NonNull; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.nytimes.android.external.fs3.SourcePersisterFactory; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.MemoryPolicy; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; -import com.nytimes.android.external.store3.middleware.GsonParserFactory; -import com.nytimes.android.sample.data.model.GsonAdaptersModel; -import com.nytimes.android.sample.data.model.RedditData; -import com.nytimes.android.sample.data.remote.Api; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import io.reactivex.Single; -import okhttp3.ResponseBody; -import okio.BufferedSource; -import retrofit2.Retrofit; -import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; -import retrofit2.converter.gson.GsonConverterFactory; - -public class SampleApp extends Application { - - private Store nonPersistedStore; - private Store persistedStore; - private Persister persister; - - @Override - public void onCreate() { - super.onCreate(); - - initPersister(); - this.nonPersistedStore = provideRedditStore(); - this.persistedStore = providePersistedRedditStore(); - } - - private void initPersister() { - try { - persister = newPersister(); - } catch (IOException exception) { - throw new RuntimeException(exception); - } - } - - public Store getNonPersistedStore() { - return this.nonPersistedStore; - } - - public Store getPersistedStore() { - return this.persistedStore; - } - - /** - * Provides a Store which only retains RedditData for 10 seconds in memory. - */ - private Store provideRedditStore() { - return StoreBuilder.barcode() - .fetcher(barCode -> provideRetrofit().fetchSubreddit(barCode.getKey(), "10")) - .memoryPolicy( - MemoryPolicy - .builder() - .setExpireAfterWrite(10) - .setExpireAfterTimeUnit(TimeUnit.SECONDS) - .build() - ) - .open(); - } - - /** - * Provides a Store which will persist RedditData to the cache, and use Gson to parse the JSON - * that comes back from the network into RedditData. - */ - private Store providePersistedRedditStore() { - return StoreBuilder.parsedWithKey() - .fetcher(this::fetcher) - .persister(persister) - .parser(GsonParserFactory.createSourceParser(provideGson(), RedditData.class)) - .open(); - } - - /** - * Returns a new Persister with the cache as the root. - */ - private Persister newPersister() throws IOException { - return SourcePersisterFactory.create(getApplicationContext().getCacheDir()); - } - - /** - * Returns a "fetcher" which will retrieve new data from the network. - */ - @NonNull - private Single fetcher(BarCode barCode) { - return provideRetrofit().fetchSubredditForPersister(barCode.getKey(), "10") - .map(ResponseBody::source); - } - - private Api provideRetrofit() { - return new Retrofit.Builder() - .baseUrl("http://reddit.com/") - .addConverterFactory(GsonConverterFactory.create(provideGson())) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .validateEagerly(BuildConfig.DEBUG) // Fail early: check Retrofit configuration at creation time in Debug build. - .build() - .create(Api.class); - } - - Gson provideGson() { - return new GsonBuilder() - .registerTypeAdapterFactory(new GsonAdaptersModel()) - .create(); - } -} diff --git a/app/src/main/java/com/nytimes/android/sample/SampleApp.kt b/app/src/main/java/com/nytimes/android/sample/SampleApp.kt new file mode 100644 index 000000000..8005d9842 --- /dev/null +++ b/app/src/main/java/com/nytimes/android/sample/SampleApp.kt @@ -0,0 +1,133 @@ +package com.nytimes.android.sample + +import android.app.Application +import android.content.Context +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.nytimes.android.external.fs3.SourcePersisterFactory +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.MemoryPolicy +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import com.nytimes.android.external.store3.middleware.GsonParserFactory +import com.nytimes.android.sample.data.model.GsonAdaptersModel +import com.nytimes.android.sample.data.model.RedditData +import com.nytimes.android.sample.data.remote.Api +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import okio.BufferedSource +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory +import java.io.IOException +import java.util.concurrent.TimeUnit + +class SampleApp : Application() { + + var nonPersistedStore: Store? = null + var persistedStore: Store? =null + private var persister: Persister? =null + private val sampleRoomStore=SampleRoomStore(this) + + override fun onCreate() { + super.onCreate() + appContext = this + initPersister(); + nonPersistedStore = provideRedditStore(); + persistedStore=providePersistedRedditStore(); + RoomSample() + } + + private fun RoomSample() { + var foo = sampleRoomStore.store.get("") + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ strings1 -> val success = strings1 != null }) { throwable -> throwable.stackTrace } + + foo = Observable.timer(15, TimeUnit.SECONDS) + .subscribe { makeFetchRequest() } + } + + private fun makeFetchRequest() { + val bar = sampleRoomStore.store.fetch("") + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ strings1 -> val success = strings1 != null }) { throwable -> throwable.stackTrace } + } + + private fun initPersister() { + try { + persister = newPersister() + } catch (exception: IOException) { + throw RuntimeException(exception) + } + + } + + /** + * Provides a Store which only retains RedditData for 10 seconds in memory. + */ + private fun provideRedditStore(): Store { + return StoreBuilder.barcode() + .fetcher { barCode -> provideRetrofit().fetchSubreddit(barCode.key, "10") } + .memoryPolicy( + MemoryPolicy + .builder() + .setExpireAfterWrite(10) + .setExpireAfterTimeUnit(TimeUnit.SECONDS) + .build() + ) + .open() + } + + /** + * Provides a Store which will persist RedditData to the cache, and use Gson to parse the JSON + * that comes back from the network into RedditData. + */ + private fun providePersistedRedditStore(): Store { + return StoreBuilder.parsedWithKey() + .fetcher({ this.fetcher(it) }) + .persister(newPersister()) + .parser(GsonParserFactory.createSourceParser(provideGson(), RedditData::class.java)) + .open() + } + + /** + * Returns a new Persister with the cache as the root. + */ + @Throws(IOException::class) + private fun newPersister(): Persister { + return SourcePersisterFactory.create(this.cacheDir) + } + + /** + * Returns a "fetcher" which will retrieve new data from the network. + */ + private fun fetcher(barCode: BarCode): Single { + return provideRetrofit().fetchSubredditForPersister(barCode.key, "10") + .map({ it.source() }) + } + + private fun provideRetrofit(): Api { + return Retrofit.Builder() + .baseUrl("http://reddit.com/") + .addConverterFactory(GsonConverterFactory.create(provideGson())) + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .validateEagerly(BuildConfig.DEBUG) // Fail early: check Retrofit configuration at creation time in Debug build. + .build() + .create(Api::class.java) + } + + internal fun provideGson(): Gson { + return GsonBuilder() + .registerTypeAdapterFactory(GsonAdaptersModel()) + .create() + } + + companion object { + var appContext: Context? = null + } +} diff --git a/app/src/main/java/com/nytimes/android/sample/SampleRoomStore.kt b/app/src/main/java/com/nytimes/android/sample/SampleRoomStore.kt new file mode 100644 index 000000000..f3bf00f97 --- /dev/null +++ b/app/src/main/java/com/nytimes/android/sample/SampleRoomStore.kt @@ -0,0 +1,60 @@ +package com.nytimes.android.sample + +import android.arch.persistence.room.Dao +import android.arch.persistence.room.Database +import android.arch.persistence.room.Entity +import android.arch.persistence.room.Insert +import android.arch.persistence.room.PrimaryKey +import android.arch.persistence.room.Query +import android.arch.persistence.room.Room +import android.arch.persistence.room.RoomDatabase +import android.content.Context +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.impl.room.StoreRoom +import com.nytimes.android.external.store3.base.room.RoomPersister +import io.reactivex.Flowable +import io.reactivex.Observable +import io.reactivex.Single + +@Entity +data class User( + @PrimaryKey(autoGenerate = true) + var uid: Int = 0, + val name: String) + +@Dao +interface UserDao { + @Query("SELECT name FROM user") + fun loadAll(): Flowable> + + @Insert + fun insertAll(user: User) + +} + +@Database(entities = arrayOf(User::class), version = 1) +abstract class AppDatabase : RoomDatabase() { + abstract fun userDao(): UserDao +} + +class SampleRoomStore(context: Context){ + val db = Room.databaseBuilder(context, AppDatabase::class.java, "db").build() + + val fetcher = Fetcher { Single.just(User(name = "Mike")) } + val persister = object : RoomPersister, String> { + + override fun read(key: String): Observable> { + return db.userDao().loadAll().toObservable() + } + + override fun write(key: String, user: User) { + db.userDao().insertAll(user) + } + } + + val store = StoreRoom.from(fetcher, persister) +} + + + + diff --git a/build.gradle b/build.gradle index 5fe5f11e8..9fc426037 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,7 @@ apply from: 'buildsystem/dependencies.gradle' // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.2.41' repositories { mavenLocal() maven { @@ -22,12 +23,13 @@ buildscript { ] dependencies { - classpath 'com.android.tools.build:gradle:3.1.2' + classpath 'com.android.tools.build:gradle:3.2.0-alpha15' classpath 'com.google.gms:google-services:3.0.0' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.6' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$rootProject.ext.versions.kotlin" classpath 'org.jetbrains.dokka:dokka-gradle-plugin:0.9.14' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -54,7 +56,7 @@ allprojects { ext { // POM file GROUP = "com.nytimes.android" - VERSION_NAME = "3.0.2-SNAPSHOT" + VERSION_NAME = "3.1.0-SNAPSHOT" POM_PACKAGING = "pom" POM_DESCRIPTION = "Store3 is built with RxJava2" diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index bc4fa42a8..f10c12f52 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -15,13 +15,13 @@ allprojects { ext.versions = [ minSdk : 16, - targetSdk : 25, - compileSdk : 25, + targetSdk : 27, + compileSdk : 27, buildTools : '27.0.3', kotlin : '1.1.2-5', // UI libs. - supportLibs : '25.1.1', + supportLibs : '27.1.0', picasso : '2.5.2', butterKnife : '7.0.1', diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0fcbb13de..b5e6f6083 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Mar 29 16:46:38 EEST 2018 +#Thu May 24 12:01:07 EDT 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip diff --git a/store/gradle.properties b/store/gradle.properties index 6e7128dd6..edef291d3 100644 --- a/store/gradle.properties +++ b/store/gradle.properties @@ -1,3 +1,4 @@ POM_NAME=com.nytimes.android POM_ARTIFACT_ID=store3 POM_PACKAGING=aar +android.enableAapt2=false \ No newline at end of file diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/BasePersister.java b/store/src/main/java/com/nytimes/android/external/store3/base/BasePersister.java new file mode 100644 index 000000000..89bfd866b --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/BasePersister.java @@ -0,0 +1,8 @@ +package com.nytimes.android.external.store3.base; + + + + +public interface BasePersister { + +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/Persister.java b/store/src/main/java/com/nytimes/android/external/store3/base/Persister.java index d82749435..e404b9914 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/Persister.java +++ b/store/src/main/java/com/nytimes/android/external/store3/base/Persister.java @@ -11,12 +11,12 @@ * * @param data type before parsing */ -public interface Persister extends DiskRead, DiskWrite { +public interface Persister extends DiskRead, DiskWrite, BasePersister { /** * @param key to use to get data from persister - * If data is not available implementer needs to - * either return Observable.empty or throw an exception + * If data is not available implementer needs to + * either return Observable.empty or throw an exception */ @Override @Nonnull @@ -24,7 +24,7 @@ public interface Persister extends DiskRead, DiskWrite Cache> createCache(MemoryPolicy memoryPolicy) { - if (memoryPolicy == null) { - return CacheBuilder - .newBuilder() - .maximumSize(StoreDefaults.getCacheSize()) - .expireAfterWrite(StoreDefaults.getCacheTTL(), StoreDefaults.getCacheTTLTimeUnit()) - .build(); - } else { - if (memoryPolicy.getExpireAfterAccess() == memoryPolicy.DEFAULT_POLICY) { - return CacheBuilder - .newBuilder() - .maximumSize(memoryPolicy.getMaxSize()) - .expireAfterWrite(memoryPolicy.getExpireAfterWrite(), memoryPolicy.getExpireAfterTimeUnit()) - .build(); - } else { - return CacheBuilder - .newBuilder() - .maximumSize(memoryPolicy.getMaxSize()) - .expireAfterAccess(memoryPolicy.getExpireAfterAccess(), memoryPolicy.getExpireAfterTimeUnit()) - .build(); - } - } + return createBaseCache(memoryPolicy); } static Cache> createInflighter(MemoryPolicy memoryPolicy) { + return createBaseInFlighter(memoryPolicy); + } + + public static Cache> createRoomCache(MemoryPolicy memoryPolicy) { + return createBaseCache(memoryPolicy); + } + + + + public static Cache> createRoomInflighter(MemoryPolicy memoryPolicy) { + return createBaseInFlighter(memoryPolicy); + } + + + private static Cache createBaseInFlighter(MemoryPolicy memoryPolicy) { long expireAfterToSeconds = memoryPolicy == null ? StoreDefaults.getCacheTTLTimeUnit() .toSeconds(StoreDefaults.getCacheTTL()) : memoryPolicy.getExpireAfterTimeUnit().toSeconds(memoryPolicy.getExpireAfterWrite()); @@ -58,4 +54,30 @@ static Cache> createInflighter(MemoryPolicy me .build(); } } + + + private static Cache createBaseCache(MemoryPolicy memoryPolicy){ + if (memoryPolicy == null) { + return CacheBuilder + .newBuilder() + .maximumSize(StoreDefaults.getCacheSize()) + .expireAfterWrite(StoreDefaults.getCacheTTL(), StoreDefaults.getCacheTTLTimeUnit()) + .build(); + } else { + if (memoryPolicy.getExpireAfterAccess() == memoryPolicy.DEFAULT_POLICY) { + return CacheBuilder + .newBuilder() + .maximumSize(memoryPolicy.getMaxSize()) + .expireAfterWrite(memoryPolicy.getExpireAfterWrite(), memoryPolicy.getExpireAfterTimeUnit()) + .build(); + } else { + return CacheBuilder + .newBuilder() + .maximumSize(memoryPolicy.getMaxSize()) + .expireAfterAccess(memoryPolicy.getExpireAfterAccess(), memoryPolicy.getExpireAfterTimeUnit()) + .build(); + } + } + } + } diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/StoreUtil.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/StoreUtil.java index b03801d18..639827d0b 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/StoreUtil.java +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/StoreUtil.java @@ -1,7 +1,7 @@ package com.nytimes.android.external.store3.base.impl; +import com.nytimes.android.external.store3.base.BasePersister; import com.nytimes.android.external.store3.base.Clearable; -import com.nytimes.android.external.store3.base.Persister; import com.nytimes.android.external.store3.base.RecordProvider; import com.nytimes.android.external.store3.base.RecordState; @@ -13,24 +13,24 @@ import static com.nytimes.android.external.store3.base.RecordState.STALE; -final class StoreUtil { +public final class StoreUtil { private StoreUtil() { } @Nonnull - static ObservableTransformer + public static ObservableTransformer repeatWhenSubjectEmits(PublishSubject refreshSubject, @Nonnull final Key keyForRepeat) { Observable filter = refreshSubject.filter(key -> key.equals(keyForRepeat)); return RepeatWhenEmits.from(filter); } - static boolean shouldReturnNetworkBeforeStale( - Persister persister, StalePolicy stalePolicy, Key key) { + public static boolean shouldReturnNetworkBeforeStale( + BasePersister persister, StalePolicy stalePolicy, Key key) { return stalePolicy == StalePolicy.NETWORK_BEFORE_STALE && persisterIsStale(key, persister); } - static boolean persisterIsStale(@Nonnull Key key, Persister persister) { + public static boolean persisterIsStale(@Nonnull Key key, BasePersister persister) { if (persister instanceof RecordProvider) { RecordProvider provider = (RecordProvider) persister; RecordState recordState = provider.getRecordState(key); @@ -39,7 +39,7 @@ static boolean persisterIsStale(@Nonnull Key key, Persister return false; } - static void clearPersister(Persister persister, @Nonnull Key key) { + public static void clearPersister(BasePersister persister, @Nonnull Key key) { boolean isPersisterClearable = persister instanceof Clearable; if (isPersisterClearable) { diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/room/RealStoreRoom.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/room/RealStoreRoom.java new file mode 100644 index 000000000..8c93cd2b2 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/room/RealStoreRoom.java @@ -0,0 +1,225 @@ +package com.nytimes.android.external.store3.base.impl.room; + +import com.nytimes.android.external.cache3.Cache; +import com.nytimes.android.external.store3.annotations.Experimental; +import com.nytimes.android.external.store3.base.Fetcher; +import com.nytimes.android.external.store3.base.impl.CacheFactory; +import com.nytimes.android.external.store3.base.impl.MemoryPolicy; +import com.nytimes.android.external.store3.base.impl.StalePolicy; +import com.nytimes.android.external.store3.base.impl.StoreUtil; +import com.nytimes.android.external.store3.base.room.RoomPersister; + +import java.util.Collection; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import io.reactivex.Observable; + +/** + * Store to be used for loading an object from different data sources + * + * @param data type before parsing, usually a String, Reader or BufferedSource + * @param data type after parsing + *

+ */ +@Experimental +class RealStoreRoom extends StoreRoom { + private final Fetcher fetcher; + private final RoomPersister persister; + private final Cache> memCache; + private final StalePolicy stalePolicy; + private final Cache> inFlightRequests; + + + RealStoreRoom(Fetcher fetcher, + RoomPersister persister) { + this(fetcher, persister, null, StalePolicy.UNSPECIFIED); + } + + RealStoreRoom(Fetcher fetcher, + RoomPersister persister, + StalePolicy stalePolicy) { + this(fetcher, persister, null, stalePolicy); + } + + RealStoreRoom(Fetcher fetcher, + RoomPersister persister, + MemoryPolicy memoryPolicy, + StalePolicy stalePolicy) { + this.fetcher = fetcher; + this.persister = persister; + this.stalePolicy = stalePolicy; + this.memCache = CacheFactory.createRoomCache(memoryPolicy); + this.inFlightRequests = CacheFactory.createRoomInflighter(memoryPolicy); + } + + /** + * @param key + * @return an observable from the first data source that is available + */ + @Nonnull + @Override + public Observable get(@Nonnull final Key key) { + return lazyCache(key).switchIfEmpty(fetch(key)); + } + + /** + * @return data from memory + */ + private Observable lazyCache(@Nonnull final Key key) { + return Observable.defer(() -> cache(key)).onErrorResumeNext(Observable.empty()); + } + + Observable cache(@Nonnull final Key key) { + try { + return memCache.get(key, () -> disk(key)); + } catch (ExecutionException e) { + return Observable.empty(); + } + } + + @Nonnull + public Observable memory(@Nonnull Key key) { + Observable cachedValue = memCache.getIfPresent(key); + return cachedValue == null ? Observable.empty() : cachedValue; + } + + /** + * Fetch data from persister and update memory after. If an error occurs, emit an empty observable + * so that the concat call in {@link #get(Key)} moves on to {@link #fetch(Key)} + * + * @param key + * @return + */ + @Nonnull + public Observable disk(@Nonnull final Key key) { + if (StoreUtil.shouldReturnNetworkBeforeStale(persister, stalePolicy, key)) { + return Observable.empty(); + } + return readDisk(key); + } + + Observable readDisk(@Nonnull final Key key) { + return persister() + .read(key) + .doOnNext(this::guardAgainstEmptyCollection) + .onErrorResumeNext( + Observable.empty()) + .doOnNext(parsed -> { + updateMemory(key, parsed); + if (stalePolicy == StalePolicy.REFRESH_ON_STALE + && StoreUtil.persisterIsStale(key, persister)) { + backfillCache(key); + } + }).cache(); + } + + @SuppressWarnings("CheckReturnValue") + void backfillCache(@Nonnull Key key) { + fetch(key).subscribe(it -> { + }, it -> { + }); + } + + + /** + * Will check to see if there exists an in flight observable and return it before + * going to network + * + * @return data from fetch and store it in memory and persister + */ + @Nonnull + @Override + public Observable fetch(@Nonnull final Key key) { + return Observable.defer(() -> fetchAndPersist(key)); + } + + + /** + * There should only be one fetch request in flight at any give time. + *

+ * Return cached request in the form of a Behavior Subject which will emit to its subscribers + * the last value it gets. Subject/Observable is cached in a {@link ConcurrentMap} to maintain + * thread safety. + * + * @param key resource identifier + * @return observable that emits a {@link Parsed} value + */ + @Nullable + Observable fetchAndPersist(@Nonnull final Key key) { + try { + return inFlightRequests.get(key, () -> response(key)); + } catch (ExecutionException e) { + return Observable.error(e); + } + } + + @Nonnull + Observable response(@Nonnull final Key key) { + return fetcher() + .fetch(key) + .doOnSuccess(it -> persister().write(key, it)) + .flatMapObservable(it -> readDisk(key)) + .onErrorResumeNext(throwable -> { + if (stalePolicy == StalePolicy.NETWORK_BEFORE_STALE) { + return readDisk(key).switchIfEmpty(Observable.error(throwable)); + } + return Observable.error(throwable); + }) + .doAfterTerminate(() -> inFlightRequests.invalidate(key)) + .cache(); + } + + + /** + * Only update memory after persister has been successfully updated + * + * @param key + * @param data + */ + void updateMemory(@Nonnull final Key key, final Parsed data) { + memCache.put(key, Observable.just(data)); + } + + + @Override + //need to create a clearable override to clear all + // since room knows what table associated with data + public void clear() { + for (Key cachedKey : memCache.asMap().keySet()) { + clear(cachedKey); + } + } + + @Override + public void clear(@Nonnull Key key) { + inFlightRequests.invalidate(key); + memCache.invalidate(key); + StoreUtil.clearPersister(persister(), key); + } + + + /** + * @return DiskDAO that stores and stores data + */ + RoomPersister persister() { + return persister; + } + + /** + * + */ + Fetcher fetcher() { + return fetcher; + } + + private void guardAgainstEmptyCollection(Parsed v) { + if (v instanceof Collection && ((Collection) v).isEmpty()) { + throw new IllegalStateException("empty result set"); + } + } +} + diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/room/StoreRoom.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/room/StoreRoom.java new file mode 100644 index 000000000..4c28017a3 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/room/StoreRoom.java @@ -0,0 +1,70 @@ +package com.nytimes.android.external.store3.base.impl.room; + +import com.nytimes.android.external.store3.annotations.Experimental; +import com.nytimes.android.external.store3.base.Fetcher; +import com.nytimes.android.external.store3.base.impl.MemoryPolicy; +import com.nytimes.android.external.store3.base.impl.StalePolicy; +import com.nytimes.android.external.store3.base.impl.StoreBuilder; +import com.nytimes.android.external.store3.base.room.RoomPersister; + +import javax.annotation.Nonnull; + +import io.reactivex.Observable; + +/** + * a {@link StoreBuilder StoreBuilder} + * will return an instance of a store + *

+ * A {@link StoreRoom Store} can + * {@link StoreRoom#get(V) Store.get() } cached data or + * force a call to {@link StoreRoom#fetch(V) Store.fetch() } + * (skipping cache) + */ +@Experimental +public abstract class StoreRoom { + + /** + * Return an Observable of T for request Barcode + * Data will be returned from oldest non expired source + * Sources are Memory Cache, Disk Cache, Inflight, Network Response + */ + @Nonnull + public abstract Observable get(@Nonnull V key); + + /** + * Return an Observable of T for requested Barcode skipping Memory & Disk Cache + */ + @Nonnull + public abstract Observable fetch(@Nonnull V key); + + /** + * purges all entries from memory and disk cache + * Persister will only be cleared if they implements Clearable + */ + public abstract void clear(); + + /** + * Purge a particular entry from memory and disk cache. + * Persister will only be cleared if they implements Clearable + */ + public abstract void clear(@Nonnull V key); + + + public static StoreRoom from + (Fetcher fetcher, RoomPersister persister) { + return new RealStoreRoom<>(fetcher, persister); + } + + public static StoreRoom from( + Fetcher fetcher, + RoomPersister persister, + StalePolicy policy) { + return new RealStoreRoom<>(fetcher, persister, policy); + } + + public static StoreRoom from + (Fetcher fetcher, RoomPersister persister, + StalePolicy stalePolicy, MemoryPolicy memoryPolicy) { + return new RealStoreRoom<>(fetcher, persister, memoryPolicy, stalePolicy); + } +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/room/RoomDiskRead.java b/store/src/main/java/com/nytimes/android/external/store3/base/room/RoomDiskRead.java new file mode 100644 index 000000000..92b5d5702 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/room/RoomDiskRead.java @@ -0,0 +1,13 @@ +package com.nytimes.android.external.store3.base.room; + +import com.nytimes.android.external.store3.annotations.Experimental; + +import javax.annotation.Nonnull; + +import io.reactivex.Observable; + + @Experimental +public interface RoomDiskRead { + @Nonnull + Observable read(@Nonnull Key key); +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/room/RoomDiskWrite.java b/store/src/main/java/com/nytimes/android/external/store3/base/room/RoomDiskWrite.java new file mode 100644 index 000000000..aa8bb3041 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/room/RoomDiskWrite.java @@ -0,0 +1,16 @@ +package com.nytimes.android.external.store3.base.room; + +import com.nytimes.android.external.store3.annotations.Experimental; + +import javax.annotation.Nonnull; + +@Experimental +public interface RoomDiskWrite { + /** + * @param key to use to get data from persister + * If data is not available implementer needs to + * either return Observable.empty or throw an exception + */ + @Nonnull + void write(@Nonnull Key key, @Nonnull Raw raw); +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/room/RoomPersister.java b/store/src/main/java/com/nytimes/android/external/store3/base/room/RoomPersister.java new file mode 100644 index 000000000..fc6bfbdb6 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/room/RoomPersister.java @@ -0,0 +1,36 @@ +package com.nytimes.android.external.store3.base.room; + +import com.nytimes.android.external.store3.annotations.Experimental; +import com.nytimes.android.external.store3.base.BasePersister; + +import javax.annotation.Nonnull; + +import io.reactivex.Observable; + +/** + * Interface for fetching data from persister + * when implementing also think about implementing PathResolver to ease in creating primary keys + * + * @param data type before parsing + */ +@Experimental +public interface RoomPersister extends + RoomDiskRead, RoomDiskWrite, BasePersister { + + /** + * @param key to use to get data from persister + * If data is not available implementer needs to + * either return Observable.empty or throw an exception + */ + @Override + @Nonnull + Observable read(@Nonnull final Key key); + + /** + * @param key to use to store data to persister + * @param raw raw string to be stored + */ + @Override + @Nonnull + void write(@Nonnull final Key key, @Nonnull final Raw raw); +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.java b/store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.java new file mode 100644 index 000000000..e398845e9 --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.java @@ -0,0 +1,112 @@ +package com.nytimes.android.external.store3.room; + +import com.nytimes.android.external.store3.base.Clearable; +import com.nytimes.android.external.store3.base.impl.BarCode; +import com.nytimes.android.external.store3.base.impl.StalePolicy; +import com.nytimes.android.external.store3.base.impl.room.StoreRoom; +import com.nytimes.android.external.store3.base.room.RoomPersister; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Nonnull; + +import io.reactivex.Observable; +import io.reactivex.Single; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class ClearStoreRoomTest { + @Mock + RoomClearingPersister persister; + private AtomicInteger networkCalls; + private StoreRoom store; + + @Before + public void setUp() { + networkCalls = new AtomicInteger(0); + store = StoreRoom.from( + barCode -> Single.fromCallable(() -> networkCalls.incrementAndGet()), + persister, + StalePolicy.UNSPECIFIED); + } + + @Test + public void testClearSingleBarCode() { + // one request should produce one call + BarCode barcode = new BarCode("type", "key"); + + when(persister.read(barcode)) + .thenReturn(Observable.empty()) //read from disk on get + .thenReturn(Observable.just(1)) //read from disk after fetching from network + .thenReturn(Observable.empty()) //read from disk after clearing + .thenReturn(Observable.just(1)); //read from disk after making additional network call + + store.get(barcode).test().awaitTerminalEvent(); + assertThat(networkCalls.intValue()).isEqualTo(1); + + // after clearing the memory another call should be made + store.clear(barcode); + store.get(barcode).test().awaitTerminalEvent(); + verify(persister).clear(barcode); + assertThat(networkCalls.intValue()).isEqualTo(2); + } + + @Test + public void testClearAllBarCodes() { + BarCode barcode1 = new BarCode("type1", "key1"); + BarCode barcode2 = new BarCode("type2", "key2"); + + when(persister.read(barcode1)) + .thenReturn(Observable.empty()) //read from disk + .thenReturn(Observable.just(1)) //read from disk after fetching from network + .thenReturn(Observable.empty()) //read from disk after clearing disk cache + .thenReturn(Observable.just(1)); //read from disk after making additional network call + + when(persister.read(barcode2)) + .thenReturn(Observable.empty()) //read from disk + .thenReturn(Observable.just(1)) //read from disk after fetching from network + .thenReturn(Observable.empty()) //read from disk after clearing disk cache + .thenReturn(Observable.just(1)); //read from disk after making additional network call + + + // each request should produce one call + store.get(barcode1).test().awaitTerminalEvent(); + store.get(barcode2).test().awaitTerminalEvent(); + assertThat(networkCalls.intValue()).isEqualTo(2); + + store.clear(); + + // after everything is cleared each request should produce another 2 calls + store.get(barcode1).test().awaitTerminalEvent(); + store.get(barcode2).test().awaitTerminalEvent(); + assertThat(networkCalls.intValue()).isEqualTo(4); + } + + //everything will be mocked + static class RoomClearingPersister implements RoomPersister, Clearable { + @Override + public void clear(@Nonnull BarCode key) { + throw new RuntimeException(); + } + + @Nonnull + @Override + public Observable read(@Nonnull BarCode barCode) { + throw new RuntimeException(); + } + + @Override + public void write(@Nonnull BarCode barCode, @Nonnull Integer integer) { + //noop + } + } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.java b/store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.java new file mode 100644 index 000000000..3f2e6e701 --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.java @@ -0,0 +1,98 @@ +package com.nytimes.android.external.store3.room; + +import com.nytimes.android.external.store3.base.Fetcher; +import com.nytimes.android.external.store3.base.impl.BarCode; +import com.nytimes.android.external.store3.base.impl.StalePolicy; +import com.nytimes.android.external.store3.base.impl.room.StoreRoom; +import com.nytimes.android.external.store3.base.room.RoomPersister; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.atomic.AtomicInteger; + +import io.reactivex.Observable; +import io.reactivex.Single; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class StoreRoomTest { + + private static final String DISK = "disk"; + private static final String NETWORK = "fetch"; + final AtomicInteger counter = new AtomicInteger(0); + @Mock + Fetcher fetcher; + @Mock + RoomPersister persister; + private final BarCode barCode = new BarCode("key", "value"); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testSimple() { + + StoreRoom simpleStore = StoreRoom.from( + fetcher, + persister, + StalePolicy.UNSPECIFIED + ); + + + when(fetcher.fetch(barCode)) + .thenReturn(Single.just(NETWORK)); + + when(persister.read(barCode)) + .thenReturn(Observable.empty()) + .thenReturn(Observable.just(DISK)); + + + String value = simpleStore.get(barCode).blockingFirst(); + + assertThat(value).isEqualTo(DISK); + value = simpleStore.get(barCode).blockingFirst(); + assertThat(value).isEqualTo(DISK); + verify(fetcher, times(1)).fetch(barCode); + } + + + @Test + public void testDoubleTap() { + StoreRoom simpleStore = StoreRoom.from( + fetcher, + persister, + StalePolicy.UNSPECIFIED + ); + + Single networkSingle = + Single.create(emitter -> { + if (counter.incrementAndGet() == 1) { + emitter.onSuccess(NETWORK); + } else { + emitter.onError(new RuntimeException("Yo Dawg your inflight is broken")); + } + }); + + when(fetcher.fetch(barCode)) + .thenReturn(networkSingle); + + when(persister.read(barCode)) + .thenReturn(Observable.empty()) + .thenReturn(Observable.just(DISK)); + + + String response = simpleStore.get(barCode) + .zipWith(simpleStore.get(barCode), (s, s2) -> "hello") + .blockingFirst(); + assertThat(response).isEqualTo("hello"); + verify(fetcher, times(1)).fetch(barCode); + } +} From 865b8c418705f8e360d24e47843d107504664354 Mon Sep 17 00:00:00 2001 From: Kevin Cronly Date: Tue, 5 Jun 2018 09:58:38 -0400 Subject: [PATCH 029/498] Fixes errors in README (#336) --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d5f22ed03..bbc7e9811 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Store leverages RxJava and multiple request throttling to prevent excessive call implementation 'com.nytimes.android:store3:3.0.1' ``` ###### Set the source & target compatibilities to `1.8` -Starting with Store 3.0, `retrolambda` is no longer uses. Therefore to allow support for lambdas the Java sourceCompatibility and targetCompatibility need to be set to `1.8` +Starting with Store 3.0, `retrolambda` is no longer used. Therefore to allow support for lambdas the Java sourceCompatibility and targetCompatibility need to be set to `1.8` ``` android { @@ -71,7 +71,7 @@ You create a Store using a builder. The only requirement is to include a `.Fetch Stores use generic keys as identifiers for data. A key can be any value object that properly implements toString and equals and hashCode. When your Fetcher function is called, it will be passed a particular Key value. Similarly, the key will be used as a primary identifier within caches (Make sure to have a proper hashCode!!) ### Our Key implementation - Barcodes -For convenience we included our own key implementation called a BarCode. Barcode has two fields `String key and String type` +For convenience we included our own key implementation called a BarCode. Barcode has two fields `String key` and `String type` ``` java BarCode barcode = new BarCode("Article", "42"); ``` @@ -119,20 +119,20 @@ Calls to both `fetch()` and `get()` emit one value and then call `onCompleted()` For real-time updates, you may also call `store.stream()` which returns an Observable that emits each time a new item is added to the Store. You can think of stream as an Event Bus-like feature that allows you to know when any new network hits happen for a particular Store. You can leverage the Rx operator `filter()` to only subscribe to a subset of emissions. ### Get Refreshing -There is another special way to subscribe to a Store: getRefreshing(key). Get Refreshing will subscribe to get() which returns a single response, but unlike Get, Get Refreshing will stay subscribed. Anytime you call store.clear(key) anyone subscribed to getRefreshing(key) will resubscribe and force a new network response. +There is another special way to subscribe to a Store: `getRefreshing(key)`. Get Refreshing will subscribe to `get()` which returns a single response, but unlike Get, Get Refreshing will stay subscribed. Anytime you call store.`clear(key)` anyone subscribed to `getRefreshing(key)` will resubscribe and force a new network response. ### Inflight Debouncer -To prevent duplicative requests for the same data, Store offers an inflight debouncer. If the same request is made within a minute of a previous identical request, the same response will be returned. This is useful for situations when your app needs to make many async calls for the same data at startup or for when users are obsessively pulling to refresh. As an example, The New York Times news app asynchronously calls `ConfigStore.get()` from 12 different places on startup. The first call blocks while all others wait for the data to arrive. We have seen a dramatic decrease in the app's the data usage after implementing this in flight logic. +To prevent duplicate requests for the same data, Store offers an inflight debouncer. If the same request is made within a minute of a previous identical request, the same response will be returned. This is useful for situations when your app needs to make many async calls for the same data at startup or when users are obsessively pulling to refresh. As an example, The New York Times news app asynchronously calls `ConfigStore.get()` from 12 different places on startup. The first call blocks while all others wait for the data to arrive. We have seen a dramatic decrease in the app's the data usage after implementing this in flight logic. ### Adding a Parser -Since it is rare for data to arrive from the network in the format that your views need, Stores can delegate to a parser by using a `StoreBuilder.parsedWithKey() +Since it is rare for data to arrive from the network in the format that your views need, Stores can delegate to a parser by using a `StoreBuilder.parsedWithKey()` ```java -Store store = StoreBuilder.parsedWithKey() +Store store = StoreBuilder.parsedWithKey() .fetcher(articleId -> api.getArticle(articleId)) .parser(source -> { try (InputStreamReader reader = new InputStreamReader(source.inputStream())) { @@ -230,7 +230,7 @@ We've found the fastest form of persistence is streaming network responses direc Now back to our first example: ```java -Store store = StoreBuilder.parsedWithKey() +Store store = StoreBuilder.parsedWithKey() .fetcher(articleId -> api.getArticles(articleId)) .persister(FileSystemPersister.create(FileSystemFactory.create(context.getFilesDir()),pathResolver)) .parser(GsonParserFactory.createSourceParser(gson, String.class)) @@ -242,7 +242,7 @@ As mentioned, the above builder is how we work with network operations at the Ne + Disk caching with FileSystem (you can reuse the same file system implementation for all stores) + Parsing from a BufferedSource to a (Article in our case) with Gson + In-flight request management -+ Ability to get cached data or bust through your caches (get vs. fresh) ++ Ability to get cached data or bust through your caches (`get` vs. `fetch`) + Ability to listen for any new emissions from network (stream) + Ability to be notified and resubscribed when caches are cleared (helpful for times when you need to do a post request and update another screen, such as with `getRefreshing`) From df2ebe7bf716c02b9e0626a65db6752ff8b00de5 Mon Sep 17 00:00:00 2001 From: Ramona Harrison Date: Thu, 7 Jun 2018 13:43:16 -0400 Subject: [PATCH 030/498] Prepare for release 3.1.0. --- CHANGELOG.md | 18 ++++++++++++++++++ README.md | 2 +- build.gradle | 6 +++--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aedec4e25..07ddfaf55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,24 @@ Change Log The change log for Store version 1.x can be found [here](https://github.com/NYTimes/Store/blob/develop/CHANGELOG.md). + +Version 3.1.0 *(2018-06-07)* +---------------------------- + +**New Features** + +* (#319) Store can now be used in Java (non-Android) projects +* (#338) Room integration for Store + +**Bug Fixes and Stability Improvements** + +* (#315) Add missing reading of expire-after-policy when creating a NoopPersister +* (#311) Update Kotlin & AGP versions +* (#328) Fix memory policy default size +* (#329) Adding docs to README for setting 1.8 compatibility +* (#273) Adds comments to the sample app +* (#336) Fixes errors in README + Version 3.0.1 *(2018-03-20)* ---------------------------- diff --git a/README.md b/README.md index bbc7e9811..45756df38 100644 --- a/README.md +++ b/README.md @@ -300,7 +300,7 @@ public class SampleStore extends RealStore { ### Artifacts -**CurrentVersion = 3.0.1** +**CurrentVersion = 3.1.0** + **Cache** Cache extracted from Guava (keeps method count to a minimum) diff --git a/build.gradle b/build.gradle index 9fc426037..be153dcdf 100644 --- a/build.gradle +++ b/build.gradle @@ -23,12 +23,12 @@ buildscript { ] dependencies { - classpath 'com.android.tools.build:gradle:3.2.0-alpha15' + classpath 'com.android.tools.build:gradle:3.2.0-alpha16' classpath 'com.google.gms:google-services:3.0.0' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.6' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$rootProject.ext.versions.kotlin" - classpath 'org.jetbrains.dokka:dokka-gradle-plugin:0.9.14' + classpath 'org.jetbrains.dokka:dokka-gradle-plugin:0.9.16' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -56,7 +56,7 @@ allprojects { ext { // POM file GROUP = "com.nytimes.android" - VERSION_NAME = "3.1.0-SNAPSHOT" + VERSION_NAME = "3.1.0" POM_PACKAGING = "pom" POM_DESCRIPTION = "Store3 is built with RxJava2" From 833c9cade5152756b1721ec4c314cc55fa36b420 Mon Sep 17 00:00:00 2001 From: Ramona Harrison Date: Thu, 7 Jun 2018 14:13:38 -0400 Subject: [PATCH 031/498] Prepare next development version. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index be153dcdf..e93762f35 100644 --- a/build.gradle +++ b/build.gradle @@ -56,7 +56,7 @@ allprojects { ext { // POM file GROUP = "com.nytimes.android" - VERSION_NAME = "3.1.0" + VERSION_NAME = "3.1.1-SNAPSHOT" POM_PACKAGING = "pom" POM_DESCRIPTION = "Store3 is built with RxJava2" From 6800e0693ad807b3c469319fd2867a91ee35c9f5 Mon Sep 17 00:00:00 2001 From: Artem Viter Date: Mon, 18 Jun 2018 16:57:41 +0300 Subject: [PATCH 032/498] Updating images urls for Store/docs/ru/README.md (#341) --- docs/ru/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ru/README.md b/docs/ru/README.md index e525960f9..0cc89fef5 100644 --- a/docs/ru/README.md +++ b/docs/ru/README.md @@ -95,7 +95,7 @@ Observable

article = store.get(barCode); Поток данных, которым управляет Store, на данный момент будет выглядеть следующим образом: -![Simple Store Flow](https://github.com/nytm/Store/blob/master/Images/store-1.jpg) +![Simple Store Flow](https://github.com/nytm/Store/blob/feature/rx2/Images/store-1.jpg) По умолчанию, 100 элементов будут сохранены в памяти на 24 часа. @@ -108,7 +108,7 @@ Observable
article = store.get(barCode); Получение свежих данных выглядит следующим образом: `store.fetch()` -![Simple Store Flow](https://github.com/nytm/Store/blob/master/Images/store-2.jpg) +![Simple Store Flow](https://github.com/nytm/Store/blob/feature/rx2/Images/store-2.jpg) В приложении The New York Times фоновое обновление, выполняемое ночью, использует `fetch()`, чтобы во время обычного использования приложения вызов `store.get()` не приводил к сетевым запросам. @@ -158,7 +158,7 @@ Store store = StoreBuilder.pa Теперь обновленный поток данных будет выглядеть следующим образом: -`store.get()` -> ![Simple Store Flow](https://github.com/nytm/Store/blob/master/Images/store-3.jpg) +`store.get()` -> ![Simple Store Flow](https://github.com/nytm/Store/blob/feature/rx2/Images/store-3.jpg) @@ -199,7 +199,7 @@ Store,Integer> store = StoreBuilder. ![Simple Store Flow](https://github.com/nytm/Store/blob/master/Images/store-5.jpg) +`store.get()` -> ![Simple Store Flow](https://github.com/nytm/Store/blob/feature/rx2/Images/store-5.jpg) Идеальным вариантом будет, если данные будут передаваться из сети на диск с использованием BufferedSource или Reader в качестве типа данных (а не String). From 51a5cd3c9c35ea5db0bf7430f7f4a952ebc2f72c Mon Sep 17 00:00:00 2001 From: Tyler Kindy Date: Thu, 5 Jul 2018 15:01:48 +0100 Subject: [PATCH 033/498] Update version in README (#343) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 45756df38..24371cf18 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Store leverages RxJava and multiple request throttling to prevent excessive call ###### Include gradle dependency ``` - implementation 'com.nytimes.android:store3:3.0.1' + implementation 'com.nytimes.android:store3:3.1.0' ``` ###### Set the source & target compatibilities to `1.8` Starting with Store 3.0, `retrolambda` is no longer used. Therefore to allow support for lambdas the Java sourceCompatibility and targetCompatibility need to be set to `1.8` From 350d08e5bb8f47fadf05ff5661b3e638d3a517d1 Mon Sep 17 00:00:00 2001 From: Brian Plummer Date: Thu, 5 Jul 2018 21:52:53 -0400 Subject: [PATCH 034/498] issue #345 sample crashes on launch, create SampleRoomStore during onCreate (#346) --- app/src/main/java/com/nytimes/android/sample/SampleApp.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/nytimes/android/sample/SampleApp.kt b/app/src/main/java/com/nytimes/android/sample/SampleApp.kt index 8005d9842..5f280b9e3 100644 --- a/app/src/main/java/com/nytimes/android/sample/SampleApp.kt +++ b/app/src/main/java/com/nytimes/android/sample/SampleApp.kt @@ -30,11 +30,12 @@ class SampleApp : Application() { var nonPersistedStore: Store? = null var persistedStore: Store? =null private var persister: Persister? =null - private val sampleRoomStore=SampleRoomStore(this) + lateinit var sampleRoomStore:SampleRoomStore override fun onCreate() { super.onCreate() appContext = this + sampleRoomStore = SampleRoomStore(this) initPersister(); nonPersistedStore = provideRedditStore(); persistedStore=providePersistedRedditStore(); From 7d55678da9f62a3dd05a79fead8eac0e7c906d82 Mon Sep 17 00:00:00 2001 From: Brian Plummer Date: Tue, 10 Jul 2018 14:07:50 +0200 Subject: [PATCH 035/498] Convert sample to kotlin (#347) * start of sample app conversion to kotlin * go with moshi kotlin codegen instead of immutables * pr feedback, use lateinit and make non-null * pr feedback, unwind method --- app/build.gradle | 9 +- app/src/main/AndroidManifest.xml | 2 +- .../android/sample/PersistingStoreActivity.kt | 78 +++++++++++++ .../com/nytimes/android/sample/SampleApp.kt | 35 +++--- .../activity/PersistingStoreActivity.java | 94 ---------------- .../sample/activity/StoreActivity.java | 104 ------------------ .../android/sample/data/model/Children.java | 8 -- .../android/sample/data/model/Data.java | 10 -- .../android/sample/data/model/Image.java | 10 -- .../android/sample/data/model/Images.java | 8 -- .../android/sample/data/model/Model.kt | 50 +++++++++ .../android/sample/data/model/Post.java | 31 ------ .../android/sample/data/model/Preview.java | 12 -- .../android/sample/data/model/RedditData.java | 9 -- .../sample/data/model/package-info.java | 4 - .../android/sample/data/remote/Api.java | 20 ---- .../nytimes/android/sample/data/remote/Api.kt | 20 ++++ .../android/sample/reddit/PostAdapter.java | 42 ------- .../android/sample/reddit/PostAdapter.kt | 33 ++++++ .../android/sample/reddit/PostViewHolder.java | 104 ------------------ .../android/sample/reddit/PostViewHolder.kt | 24 ++++ .../android/sample/util/BitmapTransform.java | 49 --------- .../android/sample/util/DeviceUtils.java | 32 ------ app/src/main/res/layout/article_item.xml | 5 +- build.gradle | 4 +- buildsystem/dependencies.gradle | 6 +- 26 files changed, 234 insertions(+), 569 deletions(-) create mode 100644 app/src/main/java/com/nytimes/android/sample/PersistingStoreActivity.kt delete mode 100644 app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java delete mode 100644 app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java delete mode 100644 app/src/main/java/com/nytimes/android/sample/data/model/Children.java delete mode 100644 app/src/main/java/com/nytimes/android/sample/data/model/Data.java delete mode 100644 app/src/main/java/com/nytimes/android/sample/data/model/Image.java delete mode 100644 app/src/main/java/com/nytimes/android/sample/data/model/Images.java create mode 100644 app/src/main/java/com/nytimes/android/sample/data/model/Model.kt delete mode 100644 app/src/main/java/com/nytimes/android/sample/data/model/Post.java delete mode 100644 app/src/main/java/com/nytimes/android/sample/data/model/Preview.java delete mode 100644 app/src/main/java/com/nytimes/android/sample/data/model/RedditData.java delete mode 100644 app/src/main/java/com/nytimes/android/sample/data/model/package-info.java delete mode 100644 app/src/main/java/com/nytimes/android/sample/data/remote/Api.java create mode 100644 app/src/main/java/com/nytimes/android/sample/data/remote/Api.kt delete mode 100644 app/src/main/java/com/nytimes/android/sample/reddit/PostAdapter.java create mode 100644 app/src/main/java/com/nytimes/android/sample/reddit/PostAdapter.kt delete mode 100644 app/src/main/java/com/nytimes/android/sample/reddit/PostViewHolder.java create mode 100644 app/src/main/java/com/nytimes/android/sample/reddit/PostViewHolder.kt delete mode 100644 app/src/main/java/com/nytimes/android/sample/util/BitmapTransform.java delete mode 100644 app/src/main/java/com/nytimes/android/sample/util/DeviceUtils.java diff --git a/app/build.gradle b/app/build.gradle index 581446576..bf6709c95 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'com.getkeepsafe.dexcount' apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-android-extensions' android { compileSdkVersion versions.compileSdk buildToolsVersion versions.buildTools @@ -43,16 +44,18 @@ dependencies { implementation libraries.supportCardView implementation libraries.supportDesign implementation libraries.retrofit - implementation libraries.retrofitGsonConverter + implementation libraries.retrofitRx2 implementation libraries.picasso implementation libraries.guava annotationProcessor libraries.immutablesValue // <-- for annotation processor - compileOnly libraries.immutablesValue // <-- for annotation API - compileOnly libraries.immutablesGson // for annotations + implementation libraries.moshi + implementation libraries.retrofitMoshiConverter + kapt(libraries.moshiCodegen) implementation project(':store') implementation project(':cache') implementation project(':middleware') + implementation project(':middleware-moshi') implementation project(':filesystem') implementation libraries.rxAndroid2 compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index af1966639..cfe18ffb3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,7 +11,7 @@ android:name=".SampleApp" android:theme="@style/AppTheme"> + android:name=".PersistingStoreActivity"> diff --git a/app/src/main/java/com/nytimes/android/sample/PersistingStoreActivity.kt b/app/src/main/java/com/nytimes/android/sample/PersistingStoreActivity.kt new file mode 100644 index 000000000..ae333eb69 --- /dev/null +++ b/app/src/main/java/com/nytimes/android/sample/PersistingStoreActivity.kt @@ -0,0 +1,78 @@ +package com.nytimes.android.sample + +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.Toolbar +import android.util.Log +import android.view.View +import android.widget.Toast +import android.widget.Toast.makeText +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.sample.R.id.postRecyclerView +import com.nytimes.android.sample.data.model.Post +import com.nytimes.android.sample.data.model.RedditData +import com.nytimes.android.sample.reddit.PostAdapter +import com.squareup.moshi.Moshi +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import kotlinx.android.synthetic.main.activity_store.* + + +class PersistingStoreActivity : AppCompatActivity() { + + lateinit var postAdapter: PostAdapter + lateinit var persistedStore: Store + lateinit var moshi: Moshi + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContentView(R.layout.activity_store) + setSupportActionBar(findViewById(R.id.toolbar) as Toolbar) + + postAdapter = PostAdapter() + val layoutManager = LinearLayoutManager(this) + layoutManager.orientation = LinearLayoutManager.VERTICAL + postRecyclerView.layoutManager = layoutManager + postRecyclerView.adapter = postAdapter + persistedStore = (applicationContext as SampleApp).persistedStore + moshi = (applicationContext as SampleApp).moshi + } + + fun loadPosts() { + val awwRequest = BarCode(RedditData::class.java.simpleName, "aww") + + /* + First call to get(awwRequest) will use the network, then save response in the in-memory + cache. Subsequent calls will retrieve the cached version of the data. + */ + this.persistedStore + .get(awwRequest) + .flatMapObservable { sanitizeData(it) } + .toList() + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ showPosts(it) }, {it -> Log.e(PersistingStoreActivity::class.java.simpleName, it.message, it)}) + } + + private fun showPosts(posts: List) { + postAdapter.setPosts(posts) + makeText(this@PersistingStoreActivity, + "Loaded ${posts.size} posts", + Toast.LENGTH_SHORT) + .show() + } + + private fun sanitizeData(redditData: RedditData): Observable { + return Observable.fromIterable(redditData.data.children) + .map({ it.data }) + } + + override fun onResume() { + super.onResume() + loadPosts() + } +} diff --git a/app/src/main/java/com/nytimes/android/sample/SampleApp.kt b/app/src/main/java/com/nytimes/android/sample/SampleApp.kt index 5f280b9e3..a3b1a86bf 100644 --- a/app/src/main/java/com/nytimes/android/sample/SampleApp.kt +++ b/app/src/main/java/com/nytimes/android/sample/SampleApp.kt @@ -2,34 +2,30 @@ package com.nytimes.android.sample import android.app.Application import android.content.Context -import com.google.gson.Gson -import com.google.gson.GsonBuilder import com.nytimes.android.external.fs3.SourcePersisterFactory import com.nytimes.android.external.store3.base.Persister import com.nytimes.android.external.store3.base.impl.BarCode import com.nytimes.android.external.store3.base.impl.MemoryPolicy import com.nytimes.android.external.store3.base.impl.Store import com.nytimes.android.external.store3.base.impl.StoreBuilder -import com.nytimes.android.external.store3.middleware.GsonParserFactory -import com.nytimes.android.sample.data.model.GsonAdaptersModel +import com.nytimes.android.external.store3.middleware.moshi.MoshiParserFactory import com.nytimes.android.sample.data.model.RedditData import com.nytimes.android.sample.data.remote.Api -import io.reactivex.Observable +import com.squareup.moshi.Moshi import io.reactivex.Single -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers import okio.BufferedSource import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory -import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.converter.moshi.MoshiConverterFactory import java.io.IOException import java.util.concurrent.TimeUnit class SampleApp : Application() { - var nonPersistedStore: Store? = null - var persistedStore: Store? =null - private var persister: Persister? =null + lateinit var nonPersistedStore: Store + lateinit var persistedStore: Store + val moshi = Moshi.Builder().build() + lateinit var persister: Persister lateinit var sampleRoomStore:SampleRoomStore override fun onCreate() { @@ -38,11 +34,11 @@ class SampleApp : Application() { sampleRoomStore = SampleRoomStore(this) initPersister(); nonPersistedStore = provideRedditStore(); - persistedStore=providePersistedRedditStore(); - RoomSample() + persistedStore = providePersistedRedditStore(); + //RoomSample() } - private fun RoomSample() { + /*private fun RoomSample() { var foo = sampleRoomStore.store.get("") .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) @@ -57,7 +53,7 @@ class SampleApp : Application() { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ strings1 -> val success = strings1 != null }) { throwable -> throwable.stackTrace } - } + }*/ private fun initPersister() { try { @@ -92,7 +88,7 @@ class SampleApp : Application() { return StoreBuilder.parsedWithKey() .fetcher({ this.fetcher(it) }) .persister(newPersister()) - .parser(GsonParserFactory.createSourceParser(provideGson(), RedditData::class.java)) + .parser(MoshiParserFactory.createSourceParser(moshi, RedditData::class.java)) .open() } @@ -115,18 +111,13 @@ class SampleApp : Application() { private fun provideRetrofit(): Api { return Retrofit.Builder() .baseUrl("http://reddit.com/") - .addConverterFactory(GsonConverterFactory.create(provideGson())) + .addConverterFactory(MoshiConverterFactory.create(moshi)) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .validateEagerly(BuildConfig.DEBUG) // Fail early: check Retrofit configuration at creation time in Debug build. .build() .create(Api::class.java) } - internal fun provideGson(): Gson { - return GsonBuilder() - .registerTypeAdapterFactory(GsonAdaptersModel()) - .create() - } companion object { var appContext: Context? = null diff --git a/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java b/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java deleted file mode 100644 index 8c3e18813..000000000 --- a/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.nytimes.android.sample.activity; - -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.util.Log; -import android.widget.Toast; - -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.sample.R; -import com.nytimes.android.sample.SampleApp; -import com.nytimes.android.sample.data.model.Children; -import com.nytimes.android.sample.data.model.Post; -import com.nytimes.android.sample.data.model.RedditData; -import com.nytimes.android.sample.reddit.PostAdapter; - -import java.util.List; - -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; - -import static android.widget.Toast.makeText; - - -public class PersistingStoreActivity extends AppCompatActivity { - - private RecyclerView recyclerView; - private PostAdapter postAdapter; - private Store persistedStore; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_store); - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - - postAdapter = new PostAdapter(); - recyclerView = (RecyclerView) findViewById(R.id.postRecyclerView); - LinearLayoutManager layoutManager = new LinearLayoutManager(this); - layoutManager.setOrientation(LinearLayoutManager.VERTICAL); - recyclerView.setLayoutManager(layoutManager); - recyclerView.setAdapter(postAdapter); - } - - private void initStore() { - if (this.persistedStore == null) { - this.persistedStore = ((SampleApp) getApplicationContext()).getPersistedStore(); - } - } - - @SuppressWarnings("CheckReturnValue") - public void loadPosts() { - BarCode awwRequest = new BarCode(RedditData.class.getSimpleName(), "aww"); - - /* - First call to get(awwRequest) will use the network, then save response in the in-memory - cache. Subsequent calls will retrieve the cached version of the data. - */ - this.persistedStore - .get(awwRequest) - .flatMapObservable(this::sanitizeData) - .toList() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::showPosts, throwable -> - Log.e(StoreActivity.class.getSimpleName(), throwable.getMessage(), - throwable)); - } - - private void showPosts(List posts) { - postAdapter.setPosts(posts); - makeText(PersistingStoreActivity.this, - "Loaded " + posts.size() + " posts", - Toast.LENGTH_SHORT) - .show(); - } - - private Observable sanitizeData(RedditData redditData) { - return Observable.fromIterable(redditData.data().children()) - .map(Children::data); - } - - @Override - protected void onResume() { - super.onResume(); - initStore(); - loadPosts(); - } -} diff --git a/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java b/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java deleted file mode 100644 index de0703bca..000000000 --- a/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.nytimes.android.sample.activity; - -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.util.Log; -import android.widget.Toast; - -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.sample.R; -import com.nytimes.android.sample.SampleApp; -import com.nytimes.android.sample.data.model.Children; -import com.nytimes.android.sample.data.model.Post; -import com.nytimes.android.sample.data.model.RedditData; -import com.nytimes.android.sample.reddit.PostAdapter; - -import java.util.List; - -import io.reactivex.Observable; -import io.reactivex.ObservableSource; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.annotations.NonNull; -import io.reactivex.functions.Function; -import io.reactivex.schedulers.Schedulers; - -import static android.widget.Toast.makeText; - - -public class StoreActivity extends AppCompatActivity { - - private RecyclerView recyclerView; - private PostAdapter postAdapter; - private Store nonPersistedStore; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_store); - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - postAdapter = new PostAdapter(); - recyclerView = (RecyclerView) findViewById(R.id.postRecyclerView); - LinearLayoutManager layoutManager = new LinearLayoutManager(this); - layoutManager.setOrientation(LinearLayoutManager.VERTICAL); - recyclerView.setLayoutManager(layoutManager); - recyclerView.setAdapter(postAdapter); - } - - private void initStore() { - if (this.nonPersistedStore == null) { - this.nonPersistedStore = ((SampleApp) getApplicationContext()).getNonPersistedStore(); - } - } - - @SuppressWarnings("CheckReturnValue") - public void loadPosts() { - BarCode awwRequest = new BarCode(RedditData.class.getSimpleName(), "aww"); - - /* - First call to get(awwRequest) will use the network, then save response in the in-memory - cache. Subsequent calls will retrieve the cached version of the data. - - But, since the policy of this store is to expire after 10 seconds, the cache will - only be used for subsequent requests that happen within 10 seconds of the initial request. - After that, the request will use the network. - */ - this.nonPersistedStore - .get(awwRequest) - .flatMapObservable(new Function>() { - @Override - public ObservableSource apply(@NonNull RedditData redditData) throws Exception { - return sanitizeData(redditData); - } - }) - .toList() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::showPosts, throwable -> { - Log.e(StoreActivity.class.getSimpleName(), throwable.getMessage(), throwable); - }); - } - - private void showPosts(List posts) { - postAdapter.setPosts(posts); - makeText(StoreActivity.this, - "Loaded " + posts.size() + " posts", - Toast.LENGTH_SHORT) - .show(); - } - - private Observable sanitizeData(RedditData redditData) { - return Observable.fromIterable(redditData.data().children()) - .map(Children::data); - } - - @Override - protected void onResume() { - super.onResume(); - initStore(); - loadPosts(); - } -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/Children.java b/app/src/main/java/com/nytimes/android/sample/data/model/Children.java deleted file mode 100644 index 74183d952..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/Children.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.nytimes.android.sample.data.model; - -import org.immutables.value.Value; - -@Value.Immutable -public abstract class Children { - public abstract Post data(); -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/Data.java b/app/src/main/java/com/nytimes/android/sample/data/model/Data.java deleted file mode 100644 index 4e0fd2cf1..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/Data.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.nytimes.android.sample.data.model; - -import org.immutables.value.Value; - -import java.util.List; - -@Value.Immutable -public abstract class Data { - public abstract List children(); -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/Image.java b/app/src/main/java/com/nytimes/android/sample/data/model/Image.java deleted file mode 100644 index 786406399..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/Image.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.nytimes.android.sample.data.model; - -import org.immutables.value.Value; - -@Value.Immutable -public abstract class Image { - public abstract String url(); - public abstract int height(); - public abstract int width(); -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/Images.java b/app/src/main/java/com/nytimes/android/sample/data/model/Images.java deleted file mode 100644 index 06ab2cbab..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/Images.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.nytimes.android.sample.data.model; - -import org.immutables.value.Value; - -@Value.Immutable -public abstract class Images { - public abstract Image source(); -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/Model.kt b/app/src/main/java/com/nytimes/android/sample/data/model/Model.kt new file mode 100644 index 000000000..4c32664bb --- /dev/null +++ b/app/src/main/java/com/nytimes/android/sample/data/model/Model.kt @@ -0,0 +1,50 @@ +package com.nytimes.android.sample.data.model + +import com.google.common.base.Optional +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class RedditData( + val data: Data, + val kind: String +) + +@JsonClass(generateAdapter = true) +data class Children ( + val data: Post +) + +@JsonClass(generateAdapter = true) +data class Data ( + val children: List +) + +@JsonClass(generateAdapter = true) +data class Post ( + val preview: Preview?, + val title: String, + val url: String, + val height: Int?, + val width: Int? +){ + fun nestedThumbnail() : Image? { + return preview?.images?.getOrNull(0)?.source + } +} + +@JsonClass(generateAdapter = true) +data class Preview ( + val images: List +) + +@JsonClass(generateAdapter = true) +data class Images ( + val source: Image +) + +@JsonClass(generateAdapter = true) +data class Image ( + val url: String, + val height: Int, + val width: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/Post.java b/app/src/main/java/com/nytimes/android/sample/data/model/Post.java deleted file mode 100644 index 9d408fca4..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/Post.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.nytimes.android.sample.data.model; - -import android.support.annotation.Nullable; - -import com.google.common.base.Optional; - -import org.immutables.value.Value; - -@Value.Immutable -public abstract class Post { - @Nullable - public abstract Preview preview(); - - public abstract String title(); - - public abstract String url(); - - @Nullable - public abstract Integer height(); - - @Nullable - public abstract Integer width(); - - @Value.Derived - public Optional nestedThumbnail() { - if (preview() == null || preview().images() == null || preview().images().get(0).source() == null) - return Optional.absent(); - return Optional.of(preview().images().get(0).source()); - } - -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/Preview.java b/app/src/main/java/com/nytimes/android/sample/data/model/Preview.java deleted file mode 100644 index 95c7f952d..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/Preview.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.nytimes.android.sample.data.model; - -import org.immutables.value.Value; - -import java.util.List; - -@Value.Immutable -@Value.Style(allParameters = true) -public abstract class Preview { - @Value.Parameter(false) - public abstract List images(); -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/RedditData.java b/app/src/main/java/com/nytimes/android/sample/data/model/RedditData.java deleted file mode 100644 index 686faf8de..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/RedditData.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.nytimes.android.sample.data.model; - -import org.immutables.value.Value; - -@Value.Immutable -public abstract class RedditData { - public abstract Data data(); - public abstract String kind(); -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/model/package-info.java b/app/src/main/java/com/nytimes/android/sample/data/model/package-info.java deleted file mode 100644 index 9417bd0fe..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/model/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@Gson.TypeAdapters -package com.nytimes.android.sample.data.model; - -import org.immutables.gson.Gson; diff --git a/app/src/main/java/com/nytimes/android/sample/data/remote/Api.java b/app/src/main/java/com/nytimes/android/sample/data/remote/Api.java deleted file mode 100644 index 65e8d5a83..000000000 --- a/app/src/main/java/com/nytimes/android/sample/data/remote/Api.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.nytimes.android.sample.data.remote; - -import com.nytimes.android.sample.data.model.RedditData; - -import io.reactivex.Single; -import okhttp3.ResponseBody; -import retrofit2.http.GET; -import retrofit2.http.Path; -import retrofit2.http.Query; - -public interface Api { - - @GET("r/{subredditName}/new/.json") - Single fetchSubreddit(@Path("subredditName") String subredditName, - @Query("limit") String limit); - - @GET("r/{subredditName}/new/.json") - Single fetchSubredditForPersister(@Path("subredditName") String subredditName, - @Query("limit") String limit); -} diff --git a/app/src/main/java/com/nytimes/android/sample/data/remote/Api.kt b/app/src/main/java/com/nytimes/android/sample/data/remote/Api.kt new file mode 100644 index 000000000..15c01e77c --- /dev/null +++ b/app/src/main/java/com/nytimes/android/sample/data/remote/Api.kt @@ -0,0 +1,20 @@ +package com.nytimes.android.sample.data.remote + +import com.nytimes.android.sample.data.model.RedditData + +import io.reactivex.Single +import okhttp3.ResponseBody +import retrofit2.http.GET +import retrofit2.http.Path +import retrofit2.http.Query + +interface Api { + + @GET("r/{subredditName}/new/.json") + fun fetchSubreddit(@Path("subredditName") subredditName: String, + @Query("limit") limit: String): Single + + @GET("r/{subredditName}/new/.json") + fun fetchSubredditForPersister(@Path("subredditName") subredditName: String, + @Query("limit") limit: String): Single +} diff --git a/app/src/main/java/com/nytimes/android/sample/reddit/PostAdapter.java b/app/src/main/java/com/nytimes/android/sample/reddit/PostAdapter.java deleted file mode 100644 index 93989228b..000000000 --- a/app/src/main/java/com/nytimes/android/sample/reddit/PostAdapter.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.nytimes.android.sample.reddit; - -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.nytimes.android.sample.R; -import com.nytimes.android.sample.data.model.Post; - -import java.util.ArrayList; -import java.util.List; - - - -public class PostAdapter extends RecyclerView.Adapter { - - private final List articles = new ArrayList<>(); - - @Override - public PostViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - View itemView = LayoutInflater.from( - parent.getContext()).inflate(R.layout.article_item, parent, false); - return new PostViewHolder(itemView); - } - - @Override - public void onBindViewHolder(PostViewHolder holder, int position) { - holder.onBind(articles.get(position)); - } - - @Override - public int getItemCount() { - return articles.size(); - } - - public void setPosts(List articlesToAdd) { - articles.clear(); - articles.addAll(articlesToAdd); - notifyDataSetChanged(); - } -} diff --git a/app/src/main/java/com/nytimes/android/sample/reddit/PostAdapter.kt b/app/src/main/java/com/nytimes/android/sample/reddit/PostAdapter.kt new file mode 100644 index 000000000..8525522b2 --- /dev/null +++ b/app/src/main/java/com/nytimes/android/sample/reddit/PostAdapter.kt @@ -0,0 +1,33 @@ +package com.nytimes.android.sample.reddit + +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.ViewGroup +import com.nytimes.android.sample.R +import com.nytimes.android.sample.data.model.Post + + +class PostAdapter : RecyclerView.Adapter() { + + private val articles = ArrayList() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostViewHolder { + val itemView = LayoutInflater.from( + parent.context).inflate(R.layout.article_item, parent, false) + return PostViewHolder(itemView) + } + + override fun onBindViewHolder(holder: PostViewHolder, position: Int) { + holder.onBind(articles[position]) + } + + override fun getItemCount(): Int { + return articles.size + } + + fun setPosts(articlesToAdd: List) { + articles.clear() + articles.addAll(articlesToAdd) + notifyDataSetChanged() + } +} diff --git a/app/src/main/java/com/nytimes/android/sample/reddit/PostViewHolder.java b/app/src/main/java/com/nytimes/android/sample/reddit/PostViewHolder.java deleted file mode 100644 index 5bd97a0bf..000000000 --- a/app/src/main/java/com/nytimes/android/sample/reddit/PostViewHolder.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.nytimes.android.sample.reddit; - -import android.app.Application; -import android.support.v7.widget.RecyclerView; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import com.nytimes.android.sample.R; -import com.nytimes.android.sample.data.model.Image; -import com.nytimes.android.sample.data.model.ImmutableImage; -import com.nytimes.android.sample.data.model.Post; -import com.nytimes.android.sample.util.BitmapTransform; -import com.nytimes.android.sample.util.DeviceUtils; -import com.squareup.picasso.Picasso; - - -public class PostViewHolder extends RecyclerView.ViewHolder { - - private int maxHeight; - private int maxWidth; - private TextView title; - private ImageView thumbnail; - private View topSpacer; - private final DeviceUtils deviceUtils; - - public PostViewHolder(View itemView) { - super(itemView); - deviceUtils = new DeviceUtils((Application) itemView.getContext().getApplicationContext()); - findViews(itemView); - setMaxDimensions(itemView); - } - - public void onBind(Post article) { - title.setText(article.title()); - if (article.nestedThumbnail().isPresent()) { - showImage(article); - } - } - - private void showImage(Post article) { - Image nestedImage = article.nestedThumbnail().get(); - Image image = ImmutableImage - .builder() - .height(nestedImage.height()) - .width(nestedImage.width()) - .url(nestedImage.url()) - .build(); - BitmapTransform bitmapTransform = new BitmapTransform(maxWidth, maxHeight, image); - - int targetWidth = bitmapTransform.targetWidth; - int targetHeight = bitmapTransform.targetHeight; - - setSpacer(targetWidth, targetHeight); - - setupThumbnail(targetWidth, targetHeight); - - Picasso.with(itemView.getContext()) - .load(image.url()) - .transform(bitmapTransform) - .resize(targetWidth, targetHeight) - .centerInside() - .placeholder(R.color.gray80) - .into(thumbnail); - } - - private void setSpacer(int targetWidth, int targetHeight) { - if (targetWidth >= targetHeight) { - topSpacer.setVisibility(View.GONE); - } else { - topSpacer.setVisibility(View.VISIBLE); - } - } - - private void setupThumbnail(int targetWidth, int targetHeight) { - thumbnail.setMaxWidth(targetWidth); - thumbnail.setMaxHeight(targetHeight); - thumbnail.setMinimumWidth(targetWidth); - thumbnail.setMinimumHeight(targetHeight); - thumbnail.requestLayout(); - } - - private void setMaxDimensions(View itemView) { - int screenWidth; - int screenHeight; - screenWidth = deviceUtils.getScreenWidth(); - screenHeight = deviceUtils.getScreenHeight(); - - if (screenWidth > screenHeight) { - screenHeight = deviceUtils.getScreenWidth(); - screenWidth = deviceUtils.getScreenHeight(); - } - - maxHeight = (int) (screenHeight * .55f); - int margin = itemView.getContext().getResources().getDimensionPixelSize(R.dimen.post_horizontal_margin); - maxWidth = screenWidth - (2 * margin); - } - - private void findViews(View itemView) { - title = (TextView) itemView.findViewById(R.id.title); - thumbnail = (ImageView) itemView.findViewById(R.id.thumbnail); - topSpacer = itemView.findViewById(R.id.topSpacer); - } -} diff --git a/app/src/main/java/com/nytimes/android/sample/reddit/PostViewHolder.kt b/app/src/main/java/com/nytimes/android/sample/reddit/PostViewHolder.kt new file mode 100644 index 000000000..d4fb50b38 --- /dev/null +++ b/app/src/main/java/com/nytimes/android/sample/reddit/PostViewHolder.kt @@ -0,0 +1,24 @@ +package com.nytimes.android.sample.reddit + +import android.support.v7.widget.RecyclerView +import android.view.View +import com.nytimes.android.sample.R +import com.nytimes.android.sample.data.model.Post +import com.squareup.picasso.Picasso +import kotlinx.android.synthetic.main.article_item.view.* + + +class PostViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + fun onBind(article: Post) { + itemView.title!!.text = article.title + article.nestedThumbnail()?.url?.let { showImage(it) } + } + + private fun showImage(url: String) { + Picasso.with(itemView.context) + .load(url) + .placeholder(R.color.gray80) + .into(itemView.thumbnail) + } +} diff --git a/app/src/main/java/com/nytimes/android/sample/util/BitmapTransform.java b/app/src/main/java/com/nytimes/android/sample/util/BitmapTransform.java deleted file mode 100644 index 61bea070f..000000000 --- a/app/src/main/java/com/nytimes/android/sample/util/BitmapTransform.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.nytimes.android.sample.util; - -import android.graphics.Bitmap; - -import com.nytimes.android.sample.data.model.Image; -import com.squareup.picasso.Transformation; - - -public class BitmapTransform implements Transformation { - private int maxWidth; - private int maxHeight; - private Image key; - public int targetWidth; - public int targetHeight; - private final String picassoKey; - - public BitmapTransform(int maxWidth, int maxHeight, Image image) { - this.maxWidth = maxWidth; - this.maxHeight = maxHeight; - this.key = image; - this.picassoKey = key.url() + "_" + targetWidth + ":" + targetHeight; - - double aspectRatio; - if (image.width() >= image.height()) { - targetWidth = maxWidth; - aspectRatio = (double) image.height() / (double) image.width(); - targetHeight = (int) (targetWidth * aspectRatio); - } else { - targetHeight = maxHeight; - aspectRatio = (double) image.width() / (double) image.height(); - targetWidth = (int) (targetHeight * aspectRatio); - } - } - - @Override - public Bitmap transform(Bitmap source) { - Bitmap result = Bitmap.createScaledBitmap(source, targetWidth, - targetHeight, true); - if (result != source) { - source.recycle(); - } - return result; - } - - @Override - public String key() { - return picassoKey; - } -} diff --git a/app/src/main/java/com/nytimes/android/sample/util/DeviceUtils.java b/app/src/main/java/com/nytimes/android/sample/util/DeviceUtils.java deleted file mode 100644 index 7a9424d7c..000000000 --- a/app/src/main/java/com/nytimes/android/sample/util/DeviceUtils.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.nytimes.android.sample.util; - -import android.app.Application; -import android.content.Context; -import android.graphics.Point; -import android.view.Display; -import android.view.WindowManager; - -public class DeviceUtils { - - private WindowManager windowManager; - - public DeviceUtils(Application context) { - windowManager = - (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - } - - public int getScreenWidth() { - return getScreenSize().x; - } - - public int getScreenHeight() { - return getScreenSize().y; - } - - public Point getScreenSize() { - Display display = windowManager.getDefaultDisplay(); - Point result = new Point(); - display.getSize(result); - return result; - } -} diff --git a/app/src/main/res/layout/article_item.xml b/app/src/main/res/layout/article_item.xml index dffb1d1c5..73ed40e5f 100644 --- a/app/src/main/res/layout/article_item.xml +++ b/app/src/main/res/layout/article_item.xml @@ -20,14 +20,15 @@ Date: Tue, 10 Jul 2018 15:13:44 +0200 Subject: [PATCH 036/498] convert moshi middleware to kotlin (#349) * start conversion of middleware to moshi, tests first * convert data model of tests * convert source to kotlin * pr feedback, change mocks to lateinit --- build.gradle | 4 +- middleware-moshi/build.gradle | 33 ++++-- .../moshi/MoshiBufferedSourceAdapter.java | 37 ------- .../moshi/MoshiBufferedSourceAdapter.kt | 35 ++++++ .../middleware/moshi/MoshiParserFactory.java | 69 ------------ .../middleware/moshi/MoshiParserFactory.kt | 58 ++++++++++ .../middleware/moshi/MoshiSourceParser.java | 34 ------ .../middleware/moshi/MoshiSourceParser.kt | 33 ++++++ .../middleware/moshi/MoshiStringParser.java | 34 ------ .../middleware/moshi/MoshiStringParser.kt | 32 ++++++ .../moshi/MoshiTransformerFactory.java | 30 ------ .../moshi/MoshiTransformerFactory.kt | 24 +++++ .../external/store3/middleware/moshi/Model.kt | 9 ++ .../moshi/MoshiSourceParserTest.java | 102 ------------------ .../middleware/moshi/MoshiSourceParserTest.kt | 98 +++++++++++++++++ .../moshi/MoshiStringParserStoreTest.java | 85 --------------- .../moshi/MoshiStringParserStoreTest.kt | 82 ++++++++++++++ .../store3/middleware/moshi/data/Bar.java | 9 -- .../store3/middleware/moshi/data/Foo.java | 15 --- 19 files changed, 399 insertions(+), 424 deletions(-) delete mode 100644 middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiBufferedSourceAdapter.java create mode 100644 middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiBufferedSourceAdapter.kt delete mode 100644 middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiParserFactory.java create mode 100644 middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiParserFactory.kt delete mode 100644 middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParser.java create mode 100644 middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParser.kt delete mode 100644 middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParser.java create mode 100644 middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParser.kt delete mode 100644 middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiTransformerFactory.java create mode 100644 middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiTransformerFactory.kt create mode 100644 middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/Model.kt delete mode 100644 middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParserTest.java create mode 100644 middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParserTest.kt delete mode 100644 middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParserStoreTest.java create mode 100644 middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParserStoreTest.kt delete mode 100644 middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/data/Bar.java delete mode 100644 middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/data/Foo.java diff --git a/build.gradle b/build.gradle index a8bcbbf3e..71b746f61 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ buildscript { } rootProject.ext.versions = [ - kotlin: '1.2.21' + kotlin: '1.2.51' ] dependencies { @@ -95,4 +95,4 @@ subprojects { task gitHooksInit (type:Exec) { workingDir "$projectDir" commandLine './init-git-hooks' -} +} \ No newline at end of file diff --git a/middleware-moshi/build.gradle b/middleware-moshi/build.gradle index bb6de1114..bf8373d7e 100644 --- a/middleware-moshi/build.gradle +++ b/middleware-moshi/build.gradle @@ -1,3 +1,14 @@ +buildscript { + tasks.withType(JavaCompile) { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +plugins { + id 'org.jetbrains.kotlin.jvm' +} + apply plugin: 'java' group = GROUP @@ -11,15 +22,23 @@ dependencies { compileOnly libraries.javax testImplementation libraries.mockito testImplementation libraries.junit -} - -buildscript { - tasks.withType(JavaCompile) { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" } apply from: rootProject.file("gradle/maven-push.gradle") apply from: rootProject.file("gradle/checkstyle.gradle") apply from: rootProject.file("gradle/pmd.gradle") + +repositories { + mavenCentral() +} +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} \ No newline at end of file diff --git a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiBufferedSourceAdapter.java b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiBufferedSourceAdapter.java deleted file mode 100644 index 33adede56..000000000 --- a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiBufferedSourceAdapter.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.nytimes.android.external.store3.middleware.moshi; - -import com.nytimes.android.external.fs3.BufferedSourceAdapter; -import com.squareup.moshi.JsonAdapter; -import com.squareup.moshi.Moshi; -import java.io.IOException; -import java.lang.reflect.Type; -import javax.annotation.Nonnull; -import javax.inject.Inject; -import okio.Buffer; -import okio.BufferedSource; - -/** - * An implementation of {@link BufferedSourceAdapter BufferedSourceAdapter} that uses - * {@link Moshi} to convert Java values to JSON. - */ -public class MoshiBufferedSourceAdapter implements BufferedSourceAdapter { - - private final JsonAdapter jsonAdapter; - - @Inject - public MoshiBufferedSourceAdapter(@Nonnull Moshi moshi, @Nonnull Type type) { - this.jsonAdapter = moshi.adapter(type); - } - - @Nonnull - @Override - public BufferedSource toJson(@Nonnull Parsed value) { - Buffer buffer = new Buffer(); - try { - jsonAdapter.toJson(buffer, value); - } catch (IOException e) { - throw new AssertionError(e); // No I/O writing to a Buffer. - } - return buffer; - } -} diff --git a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiBufferedSourceAdapter.kt b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiBufferedSourceAdapter.kt new file mode 100644 index 000000000..c60d74ad6 --- /dev/null +++ b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiBufferedSourceAdapter.kt @@ -0,0 +1,35 @@ +package com.nytimes.android.external.store3.middleware.moshi + +import com.nytimes.android.external.fs3.BufferedSourceAdapter +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import java.io.IOException +import java.lang.reflect.Type +import javax.inject.Inject +import okio.Buffer +import okio.BufferedSource + +/** + * An implementation of [BufferedSourceAdapter] that uses + * [Moshi] to convert Java values to JSON. + */ +class MoshiBufferedSourceAdapter @Inject +constructor(moshi: Moshi, type: Type) : BufferedSourceAdapter { + + private val jsonAdapter: JsonAdapter + + init { + this.jsonAdapter = moshi.adapter(type) + } + + override fun toJson(value: Parsed): BufferedSource { + val buffer = Buffer() + try { + jsonAdapter.toJson(buffer, value) + } catch (e: IOException) { + throw AssertionError(e) // No I/O writing to a Buffer. + } + + return buffer + } +} diff --git a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiParserFactory.java b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiParserFactory.java deleted file mode 100644 index 6b468c5ff..000000000 --- a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiParserFactory.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.nytimes.android.external.store3.middleware.moshi; - -import com.nytimes.android.external.store3.base.Parser; -import com.squareup.moshi.Moshi; - -import java.lang.reflect.Type; - -import javax.annotation.Nonnull; - -import okio.BufferedSource; - -/** - * Factory which returns various Moshi {@link Parser} implementations. - */ -public final class MoshiParserFactory { - - private MoshiParserFactory() { - } - - /** - * Returns a new Parser which parses from a String to the specified type, using - * the provided {@link Moshi} instance. - */ - @Nonnull - @SuppressWarnings("PMD.AvoidThrowingNullPointerException") - public static Parser createStringParser(@Nonnull Moshi moshi, @Nonnull Type type) { - if (moshi == null) { - throw new NullPointerException("moshi cannot be null."); - } - if (type == null) { - throw new NullPointerException("type cannot be null."); - } - return new MoshiStringParser<>(moshi, type); - } - - /** - * Returns a new Parser which parses from a String to the specified type, using - * a new default {@link Moshi} instance. - */ - @Nonnull - public static Parser createStringParser(@Nonnull Class type) { - return createStringParser(new Moshi.Builder().build(), type); - } - - /** - * Returns a new Parser which parses from {@link BufferedSource} to the specified type, using - * the provided {@link Moshi} instance. - */ - @Nonnull - @SuppressWarnings("PMD.AvoidThrowingNullPointerException") - public static Parser createSourceParser(@Nonnull Moshi moshi, @Nonnull Type type) { - if (moshi == null) { - throw new NullPointerException("moshi cannot be null."); - } - if (type == null) { - throw new NullPointerException("type cannot be null."); - } - return new MoshiSourceParser<>(moshi, type); - } - - /** - * Returns a new Parser which parses from {@link BufferedSource} to the specified type, using - * a new default configured {@link Moshi} instance. - */ - @Nonnull - public static Parser createSourceParser(@Nonnull Type type) { - return createSourceParser(new Moshi.Builder().build(), type); - } -} diff --git a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiParserFactory.kt b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiParserFactory.kt new file mode 100644 index 000000000..da81a3174 --- /dev/null +++ b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiParserFactory.kt @@ -0,0 +1,58 @@ +package com.nytimes.android.external.store3.middleware.moshi + +import com.nytimes.android.external.store3.base.Parser +import com.squareup.moshi.Moshi + +import java.lang.reflect.Type + +import okio.BufferedSource + +/** + * Factory which returns various Moshi [Parser] implementations. + */ +object MoshiParserFactory { + + /** + * Returns a new Parser which parses from a String to the specified type, using + * the provided [Moshi] instance. + */ + fun createStringParser(moshi: Moshi, type: Type): Parser { + if (moshi == null) { + throw NullPointerException("moshi cannot be null.") + } + if (type == null) { + throw NullPointerException("type cannot be null.") + } + return MoshiStringParser(moshi, type) + } + + /** + * Returns a new Parser which parses from a String to the specified type, using + * a new default [Moshi] instance. + */ + fun createStringParser(type: Class): Parser { + return createStringParser(Moshi.Builder().build(), type) + } + + /** + * Returns a new Parser which parses from [BufferedSource] to the specified type, using + * the provided [Moshi] instance. + */ + fun createSourceParser(moshi: Moshi, type: Type): Parser { + if (moshi == null) { + throw NullPointerException("moshi cannot be null.") + } + if (type == null) { + throw NullPointerException("type cannot be null.") + } + return MoshiSourceParser(moshi, type) + } + + /** + * Returns a new Parser which parses from [BufferedSource] to the specified type, using + * a new default configured [Moshi] instance. + */ + fun createSourceParser(type: Type): Parser { + return createSourceParser(Moshi.Builder().build(), type) + } +} diff --git a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParser.java b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParser.java deleted file mode 100644 index 459fac36b..000000000 --- a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParser.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.nytimes.android.external.store3.middleware.moshi; - -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.util.ParserException; -import com.squareup.moshi.JsonAdapter; -import com.squareup.moshi.Moshi; - -import java.io.IOException; -import java.lang.reflect.Type; - -import javax.annotation.Nonnull; -import javax.inject.Inject; - -import io.reactivex.annotations.NonNull; -import okio.BufferedSource; - -public class MoshiSourceParser implements Parser { - - private final JsonAdapter jsonAdapter; - - @Inject - public MoshiSourceParser(@Nonnull Moshi moshi, @Nonnull Type type) { - jsonAdapter = moshi.adapter(type); - } - - @Override - public Parsed apply(@NonNull BufferedSource bufferedSource) throws ParserException { - try { - return jsonAdapter.fromJson(bufferedSource); - } catch (IOException e) { - throw new ParserException(e.getMessage(), e); - } - } -} diff --git a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParser.kt b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParser.kt new file mode 100644 index 000000000..618e09fda --- /dev/null +++ b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParser.kt @@ -0,0 +1,33 @@ +package com.nytimes.android.external.store3.middleware.moshi + +import com.nytimes.android.external.store3.base.Parser +import com.nytimes.android.external.store3.util.ParserException +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi + +import java.io.IOException +import java.lang.reflect.Type +import javax.inject.Inject + +import io.reactivex.annotations.NonNull +import okio.BufferedSource + +class MoshiSourceParser @Inject +constructor(moshi: Moshi, type: Type) : Parser { + + private val jsonAdapter: JsonAdapter + + init { + jsonAdapter = moshi.adapter(type) + } + + @Throws(ParserException::class) + override fun apply(@NonNull bufferedSource: BufferedSource): Parsed? { + try { + return jsonAdapter.fromJson(bufferedSource) + } catch (e: IOException) { + throw ParserException(e.message, e) + } + + } +} diff --git a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParser.java b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParser.java deleted file mode 100644 index 8ad1788ee..000000000 --- a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParser.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.nytimes.android.external.store3.middleware.moshi; - -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.util.ParserException; -import com.squareup.moshi.JsonAdapter; -import com.squareup.moshi.Moshi; - -import java.io.IOException; -import java.lang.reflect.Type; - -import javax.annotation.Nonnull; -import javax.inject.Inject; - -import io.reactivex.annotations.NonNull; - -public class MoshiStringParser implements Parser { - - private final JsonAdapter jsonAdapter; - - @Inject - public MoshiStringParser(@Nonnull Moshi moshi, @Nonnull Type type) { - jsonAdapter = moshi.adapter(type); - } - - - @Override - public Parsed apply(@NonNull String s) throws ParserException { - try { - return jsonAdapter.fromJson(s); - } catch (IOException e) { - throw new ParserException(e.getMessage(), e); - } - } -} diff --git a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParser.kt b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParser.kt new file mode 100644 index 000000000..a5d105038 --- /dev/null +++ b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParser.kt @@ -0,0 +1,32 @@ +package com.nytimes.android.external.store3.middleware.moshi + +import com.nytimes.android.external.store3.base.Parser +import com.nytimes.android.external.store3.util.ParserException +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi + +import java.io.IOException +import java.lang.reflect.Type +import javax.inject.Inject + +import io.reactivex.annotations.NonNull + +class MoshiStringParser @Inject +constructor(moshi: Moshi, type: Type) : Parser { + + private val jsonAdapter: JsonAdapter + + init { + jsonAdapter = moshi.adapter(type) + } + + @Throws(ParserException::class) + override fun apply(@NonNull s: String): Parsed? { + try { + return jsonAdapter.fromJson(s) + } catch (e: IOException) { + throw ParserException(e.message, e) + } + + } +} diff --git a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiTransformerFactory.java b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiTransformerFactory.java deleted file mode 100644 index d43b348d4..000000000 --- a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiTransformerFactory.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.nytimes.android.external.store3.middleware.moshi; - -import com.nytimes.android.external.fs3.ObjectToSourceTransformer; -import com.nytimes.android.external.store3.annotations.Experimental; -import com.squareup.moshi.Moshi; - -import java.lang.reflect.Type; - -import javax.annotation.Nonnull; - -/** - * Factory which returns Moshi {@link io.reactivex.SingleTransformer} implementations. - */ -public final class MoshiTransformerFactory { - - private MoshiTransformerFactory() { - } - - /** - * Returns a new {@link ObjectToSourceTransformer}, which uses a {@link MoshiBufferedSourceAdapter} to parse from - * objects of the specified type. - */ - @Nonnull - @Experimental - public static ObjectToSourceTransformer createObjectToSourceTransformer(@Nonnull Type type) { - return new ObjectToSourceTransformer<>(new MoshiBufferedSourceAdapter(new Moshi.Builder().build(), - type)); - } - -} diff --git a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiTransformerFactory.kt b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiTransformerFactory.kt new file mode 100644 index 000000000..e7216582b --- /dev/null +++ b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiTransformerFactory.kt @@ -0,0 +1,24 @@ +package com.nytimes.android.external.store3.middleware.moshi + +import com.nytimes.android.external.fs3.ObjectToSourceTransformer +import com.nytimes.android.external.store3.annotations.Experimental +import com.squareup.moshi.Moshi + +import java.lang.reflect.Type + +/** + * Factory which returns Moshi [io.reactivex.SingleTransformer] implementations. + */ +object MoshiTransformerFactory { + + /** + * Returns a new [ObjectToSourceTransformer], which uses a [MoshiBufferedSourceAdapter] to parse from + * objects of the specified type. + */ + @Experimental + fun createObjectToSourceTransformer(type: Type): ObjectToSourceTransformer { + return ObjectToSourceTransformer(MoshiBufferedSourceAdapter(Moshi.Builder().build(), + type)) + } + +} diff --git a/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/Model.kt b/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/Model.kt new file mode 100644 index 000000000..907c22379 --- /dev/null +++ b/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/Model.kt @@ -0,0 +1,9 @@ +package com.nytimes.android.external.store3.middleware.moshi + +data class Bar(val string:String) + +data class Foo( + val number: Int, + val string: String, + val bars: List +) \ No newline at end of file diff --git a/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParserTest.java b/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParserTest.java deleted file mode 100644 index 4fce3969c..000000000 --- a/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParserTest.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.nytimes.android.external.store3.middleware.moshi; - -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.ParsingStoreBuilder; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.middleware.moshi.data.Foo; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.ByteArrayInputStream; -import java.nio.charset.Charset; - -import io.reactivex.Maybe; -import io.reactivex.Single; -import okio.BufferedSource; -import okio.Okio; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class MoshiSourceParserTest { - - private static final String KEY = "key"; - private static final String sourceString = - "{\"number\":123,\"string\":\"abc\",\"bars\":[{\"string\":\"def\"},{\"string\":\"ghi\"}]}"; - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Mock - Fetcher fetcher; - @Mock - Persister persister; - private final BarCode barCode = new BarCode("value", KEY); - - private static BufferedSource source(String data) { - return Okio.buffer(Okio.source(new ByteArrayInputStream(data.getBytes(Charset.defaultCharset())))); - } - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - BufferedSource bufferedSource = source(sourceString); - assertNotNull(bufferedSource); - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(bufferedSource)); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(bufferedSource)); - - when(persister.write(barCode, bufferedSource)) - .thenReturn(Single.just(true)); - } - - @Test - public void testSourceParser() throws Exception { - - Parser parser = MoshiParserFactory.createSourceParser(Foo.class); - - Store store = ParsingStoreBuilder.builder() - .persister(persister) - .fetcher(fetcher) - .parser(parser) - .open(); - - Foo result = store.get(barCode).blockingGet(); - - assertEquals(result.number, 123); - assertEquals(result.string, "abc"); - assertEquals(result.bars.size(), 2); - assertEquals(result.bars.get(0).string, "def"); - assertEquals(result.bars.get(1).string, "ghi"); - - verify(fetcher, times(1)).fetch(barCode); - - } - - @Test - public void testNullMoshi() { - expectedException.expect(NullPointerException.class); - MoshiParserFactory.createSourceParser(null, Foo.class); - } - - @Test - public void testNullType() { - expectedException.expect(NullPointerException.class); - MoshiParserFactory.createSourceParser(null, Foo.class); - } - -} diff --git a/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParserTest.kt b/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParserTest.kt new file mode 100644 index 000000000..fb2a8e8d2 --- /dev/null +++ b/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParserTest.kt @@ -0,0 +1,98 @@ +package com.nytimes.android.external.store3.middleware.moshi + +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.ParsingStoreBuilder +import io.reactivex.Maybe +import io.reactivex.Single +import okio.BufferedSource +import okio.Okio +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import org.mockito.Mock +import org.mockito.Mockito.* +import org.mockito.MockitoAnnotations +import java.io.ByteArrayInputStream +import java.nio.charset.Charset + +class MoshiSourceParserTest { + @Rule + @JvmField + var expectedException = ExpectedException.none() + @Mock + lateinit var fetcher: Fetcher + @Mock + lateinit var persister: Persister + private val barCode = BarCode("value", KEY) + + @Before + @Throws(Exception::class) + fun setUp() { + MockitoAnnotations.initMocks(this) + + val bufferedSource = source(sourceString) + assertNotNull(bufferedSource) + + `when`(fetcher.fetch(barCode)) + .thenReturn(Single.just(bufferedSource)) + + `when`(persister.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(Maybe.just(bufferedSource)) + + `when`(persister.write(barCode, bufferedSource)) + .thenReturn(Single.just(true)) + } + + @Test + @Throws(Exception::class) + fun testSourceParser() { + + val parser = MoshiParserFactory.createSourceParser(Foo::class.java) + + val store = ParsingStoreBuilder.builder() + .persister(persister) + .fetcher(fetcher) + .parser(parser) + .open() + + val result = store.get(barCode).blockingGet() + + assertEquals(result.number.toLong(), 123) + assertEquals(result.string, "abc") + assertEquals(result.bars.size.toLong(), 2) + assertEquals(result.bars[0].string, "def") + assertEquals(result.bars[1].string, "ghi") + + verify>(fetcher, times(1)).fetch(barCode) + + } + + @Test + fun testNullMoshi() { + expectedException.expect(NullPointerException::class.java) + MoshiParserFactory.createSourceParser(null!!, Foo::class.java) + } + + @Test + fun testNullType() { + expectedException.expect(NullPointerException::class.java) + MoshiParserFactory.createSourceParser(null!!, Foo::class.java) + } + + companion object { + + private val KEY = "key" + private val sourceString = "{\"number\":123,\"string\":\"abc\",\"bars\":[{\"string\":\"def\"},{\"string\":\"ghi\"}]}" + + private fun source(data: String): BufferedSource { + return Okio.buffer(Okio.source(ByteArrayInputStream(data.toByteArray(Charset.defaultCharset())))) + } + } + +} diff --git a/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParserStoreTest.java b/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParserStoreTest.java deleted file mode 100644 index ccfc2890b..000000000 --- a/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParserStoreTest.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.nytimes.android.external.store3.middleware.moshi; - -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.ParsingStoreBuilder; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.middleware.moshi.data.Foo; -import com.squareup.moshi.Moshi; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import io.reactivex.Maybe; -import io.reactivex.Single; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class MoshiStringParserStoreTest { - - private static final String KEY = "key"; - private static final String source = - "{\"number\":123,\"string\":\"abc\",\"bars\":[{\"string\":\"def\"},{\"string\":\"ghi\"}]}"; - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Mock - Fetcher fetcher; - @Mock - Persister persister; - private final BarCode barCode = new BarCode("value", KEY); - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(source)); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(source)); - - when(persister.write(barCode, source)) - .thenReturn(Single.just(true)); - } - - @Test - public void testMoshiString() { - Store store = ParsingStoreBuilder.builder() - .persister(persister) - .fetcher(fetcher) - .parser(MoshiParserFactory.createStringParser(Foo.class)) - .open(); - - Foo result = store.get(barCode).blockingGet(); - - assertEquals(result.number, 123); - assertEquals(result.string, "abc"); - assertEquals(result.bars.size(), 2); - assertEquals(result.bars.get(0).string, "def"); - assertEquals(result.bars.get(1).string, "ghi"); - - verify(fetcher, times(1)).fetch(barCode); - } - - @Test - public void testNullMoshi() { - expectedException.expect(NullPointerException.class); - MoshiParserFactory.createStringParser(null, Foo.class); - } - - @Test - public void testNullType() { - expectedException.expect(NullPointerException.class); - MoshiParserFactory.createStringParser(new Moshi.Builder().build(), null); - } - -} diff --git a/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParserStoreTest.kt b/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParserStoreTest.kt new file mode 100644 index 000000000..35855ae6a --- /dev/null +++ b/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParserStoreTest.kt @@ -0,0 +1,82 @@ +package com.nytimes.android.external.store3.middleware.moshi + +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.ParsingStoreBuilder +import com.squareup.moshi.Moshi +import io.reactivex.Maybe +import io.reactivex.Single +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import org.mockito.Mock +import org.mockito.Mockito.* +import org.mockito.MockitoAnnotations + +class MoshiStringParserStoreTest { + @Rule + @JvmField + var expectedException = ExpectedException.none() + @Mock + lateinit var fetcher: Fetcher + @Mock + lateinit var persister: Persister + private val barCode = BarCode("value", KEY) + + @Before + @Throws(Exception::class) + fun setUp() { + MockitoAnnotations.initMocks(this) + + `when`(fetcher.fetch(barCode)) + .thenReturn(Single.just(source)) + + `when`(persister.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(Maybe.just(source)) + + `when`(persister.write(barCode, source)) + .thenReturn(Single.just(true)) + } + + @Test + fun testMoshiString() { + val store = ParsingStoreBuilder.builder() + .persister(persister) + .fetcher(fetcher) + .parser(MoshiParserFactory.createStringParser(Foo::class.java)) + .open() + + val result = store.get(barCode).blockingGet() + + assertEquals(result.number.toLong(), 123) + assertEquals(result.string, "abc") + assertEquals(result.bars.size.toLong(), 2) + assertEquals(result.bars[0].string, "def") + assertEquals(result.bars[1].string, "ghi") + + verify>(fetcher, times(1)).fetch(barCode) + } + + @Test + fun testNullMoshi() { + expectedException.expect(NullPointerException::class.java) + MoshiParserFactory.createStringParser(null!!, Foo::class.java) + } + + @Test + fun testNullType() { + expectedException.expect(NullPointerException::class.java) + MoshiParserFactory.createStringParser(Moshi.Builder().build(), null!!) + } + + companion object { + + private val KEY = "key" + private val source = "{\"number\":123,\"string\":\"abc\",\"bars\":[{\"string\":\"def\"},{\"string\":\"ghi\"}]}" + } + +} diff --git a/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/data/Bar.java b/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/data/Bar.java deleted file mode 100644 index 035b2cac0..000000000 --- a/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/data/Bar.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.nytimes.android.external.store3.middleware.moshi.data; - -public class Bar { - public String string; - - public Bar(String string) { - this.string = string; - } -} diff --git a/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/data/Foo.java b/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/data/Foo.java deleted file mode 100644 index ab3fbf9e0..000000000 --- a/middleware-moshi/src/test/java/com/nytimes/android/external/store3/middleware/moshi/data/Foo.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.nytimes.android.external.store3.middleware.moshi.data; - -import java.util.List; - -public class Foo { - public int number; - public String string; - public List bars; - - public Foo(int number, String string, List bars) { - this.number = number; - this.string = string; - this.bars = bars; - } -} From 6c69f1d9b9592a8bbaf59f20521e05d6639f3bca Mon Sep 17 00:00:00 2001 From: Brian Plummer Date: Wed, 11 Jul 2018 20:02:33 +0200 Subject: [PATCH 037/498] middleware conversion to kotlin (#351) * convert middleware to kotlin, start with tests * convert source * pr feedback, change mocks to lateinit * change compile to implementation * change rest of compile to use implementation, and append version of kotlin * remove obsolete constructor checks * pr feedback convert one liners to assignments --- app/build.gradle | 2 +- build.gradle | 4 +- middleware-moshi/build.gradle | 2 +- middleware/build.gradle | 33 +++++-- .../middleware/GsonBufferedSourceAdapter.java | 34 ------- .../middleware/GsonBufferedSourceAdapter.kt | 24 +++++ .../store3/middleware/GsonParserFactory.java | 75 --------------- .../store3/middleware/GsonParserFactory.kt | 54 +++++++++++ .../store3/middleware/GsonReaderParser.java | 33 ------- .../store3/middleware/GsonReaderParser.kt | 16 ++++ .../store3/middleware/GsonSourceParser.java | 53 ----------- .../store3/middleware/GsonSourceParser.kt | 39 ++++++++ .../store3/middleware/GsonStringParser.java | 32 ------- .../store3/middleware/GsonStringParser.kt | 15 +++ .../middleware/GsonTransformerFactory.java | 27 ------ .../middleware/GsonTransformerFactory.kt | 19 ++++ .../store3/GenericParserStoreTest.java | 84 ---------------- .../external/store3/GenericParserStoreTest.kt | 84 ++++++++++++++++ .../store3/GsonParserFactoryTest.java | 71 -------------- .../external/store3/GsonParserFactoryTest.kt | 73 ++++++++++++++ .../store3/GsonSourceListParserTest.java | 95 ------------------- .../store3/GsonSourceListParserTest.kt | 90 ++++++++++++++++++ 22 files changed, 444 insertions(+), 515 deletions(-) delete mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonBufferedSourceAdapter.java create mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonBufferedSourceAdapter.kt delete mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.java create mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.kt delete mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.java create mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.kt delete mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.java create mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.kt delete mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.java create mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.kt delete mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonTransformerFactory.java create mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonTransformerFactory.kt delete mode 100644 middleware/src/test/java/com/nytimes/android/external/store3/GenericParserStoreTest.java create mode 100644 middleware/src/test/java/com/nytimes/android/external/store3/GenericParserStoreTest.kt delete mode 100644 middleware/src/test/java/com/nytimes/android/external/store3/GsonParserFactoryTest.java create mode 100644 middleware/src/test/java/com/nytimes/android/external/store3/GsonParserFactoryTest.kt delete mode 100644 middleware/src/test/java/com/nytimes/android/external/store3/GsonSourceListParserTest.java create mode 100644 middleware/src/test/java/com/nytimes/android/external/store3/GsonSourceListParserTest.kt diff --git a/app/build.gradle b/app/build.gradle index bf6709c95..3a44beade 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -58,7 +58,7 @@ dependencies { implementation project(':middleware-moshi') implementation project(':filesystem') implementation libraries.rxAndroid2 - compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "android.arch.persistence.room:runtime:$room_version" annotationProcessor "android.arch.persistence.room:compiler:$room_version" diff --git a/build.gradle b/build.gradle index 71b746f61..f1eed3d29 100644 --- a/build.gradle +++ b/build.gradle @@ -19,11 +19,11 @@ buildscript { } rootProject.ext.versions = [ - kotlin: '1.2.51' + kotlin: '1.2.51' ] dependencies { - classpath 'com.android.tools.build:gradle:3.2.0-beta02' + classpath 'com.android.tools.build:gradle:3.2.0-beta01' classpath 'com.google.gms:google-services:3.0.0' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.6' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11' diff --git a/middleware-moshi/build.gradle b/middleware-moshi/build.gradle index bf8373d7e..ba63589d7 100644 --- a/middleware-moshi/build.gradle +++ b/middleware-moshi/build.gradle @@ -22,7 +22,7 @@ dependencies { compileOnly libraries.javax testImplementation libraries.mockito testImplementation libraries.junit - compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" } apply from: rootProject.file("gradle/maven-push.gradle") diff --git a/middleware/build.gradle b/middleware/build.gradle index dbd9ab06d..3da82172d 100644 --- a/middleware/build.gradle +++ b/middleware/build.gradle @@ -1,3 +1,14 @@ +buildscript { + tasks.withType(JavaCompile) { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +plugins { + id 'org.jetbrains.kotlin.jvm' +} + apply plugin: 'java' group = GROUP @@ -15,15 +26,23 @@ dependencies { testImplementation libraries.assertJ testImplementation libraries.junit testImplementation libraries.guava -} - -buildscript { - tasks.withType(JavaCompile) { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" } apply from: rootProject.file("gradle/maven-push.gradle") apply from: rootProject.file("gradle/checkstyle.gradle") apply from: rootProject.file("gradle/pmd.gradle") + +repositories { + mavenCentral() +} +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} \ No newline at end of file diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonBufferedSourceAdapter.java b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonBufferedSourceAdapter.java deleted file mode 100644 index 1dfb76c3e..000000000 --- a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonBufferedSourceAdapter.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.nytimes.android.external.store3.middleware; - -import com.google.gson.Gson; -import com.nytimes.android.external.fs3.BufferedSourceAdapter; - -import java.io.ByteArrayInputStream; -import java.nio.charset.Charset; - -import javax.annotation.Nonnull; -import javax.inject.Inject; - -import okio.BufferedSource; -import okio.Okio; - -/** - * An implementation of {@link BufferedSourceAdapter BufferedSourceAdapter} that uses - * {@link com.google.gson.Gson Gson} to convert Java values to JSON. - */ -public class GsonBufferedSourceAdapter implements BufferedSourceAdapter { - - private final Gson gson; - - @Inject - public GsonBufferedSourceAdapter(@Nonnull Gson gson) { - this.gson = gson; - } - - @Nonnull - @Override - public BufferedSource toJson(@Nonnull Parsed value) { - return Okio.buffer(Okio.source(new ByteArrayInputStream(gson.toJson(value).getBytes( - Charset.forName("UTF-8"))))); - } -} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonBufferedSourceAdapter.kt b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonBufferedSourceAdapter.kt new file mode 100644 index 000000000..a9ea36ff4 --- /dev/null +++ b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonBufferedSourceAdapter.kt @@ -0,0 +1,24 @@ +package com.nytimes.android.external.store3.middleware + +import com.google.gson.Gson +import com.nytimes.android.external.fs3.BufferedSourceAdapter + +import java.io.ByteArrayInputStream +import java.nio.charset.Charset +import javax.inject.Inject + +import okio.BufferedSource +import okio.Okio + +/** + * An implementation of [BufferedSourceAdapter] that uses + * [Gson][com.google.gson.Gson] to convert Java values to JSON. + */ +class GsonBufferedSourceAdapter @Inject +constructor(private val gson: Gson) : BufferedSourceAdapter { + + override fun toJson(value: Parsed): BufferedSource { + return Okio.buffer(Okio.source(ByteArrayInputStream(gson.toJson(value).toByteArray( + Charset.forName("UTF-8"))))) + } +} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.java b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.java deleted file mode 100644 index e40e2cdcc..000000000 --- a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.nytimes.android.external.store3.middleware; - - -import com.google.gson.Gson; -import com.nytimes.android.external.store3.base.Parser; - -import java.io.Reader; -import java.lang.reflect.Type; - -import javax.annotation.Nonnull; - -import okio.BufferedSource; - -/** - * Factory which returns various Gson {@link Parser} implementations. - */ -public final class GsonParserFactory { - private GsonParserFactory() { - } - - /** - * Returns a new Parser which parses from {@link Reader} to the specified type, using - * a new default configured {@link Gson} instance. - */ - @Nonnull - public static Parser createReaderParser(@Nonnull Type type) { - return createReaderParser(new Gson(), type); - } - - /** - * Returns a new Parser which parses from {@link Reader} to the specified type, using - * the provided {@link Gson} instance. - */ - @Nonnull - public static Parser createReaderParser(@Nonnull Gson gson, @Nonnull Type type) { - return new GsonReaderParser<>(gson, type); - } - - /** - * Returns a new Parser which parses from {@link Reader} to the specified type, using - * a new default configured {@link Gson} instance. - */ - @Nonnull - public static Parser createSourceParser(@Nonnull Type type) { - return createSourceParser(new Gson(), type); - } - - /** - * Returns a new Parser which parses from {@link BufferedSource} to the specified type, using - * the provided {@link Gson} instance. - */ - @Nonnull - public static Parser createSourceParser(@Nonnull Gson gson, @Nonnull Type type) { - return new GsonSourceParser<>(gson, type); - } - - /** - * Returns a new Parser which parses from a String to the specified type, using - * a new default {@link Gson} instance. - */ - @Nonnull - public static Parser createStringParser(@Nonnull Class type) { - return createStringParser(new Gson(), type); - } - - /** - * Returns a new Parser which parses from a String to the specified type, using - * the provided {@link Gson} instance. - */ - @Nonnull - public static Parser createStringParser(@Nonnull Gson gson, @Nonnull Type type) { - return new GsonStringParser<>(gson, type); - } - -} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.kt b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.kt new file mode 100644 index 000000000..aa8b6494a --- /dev/null +++ b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.kt @@ -0,0 +1,54 @@ +package com.nytimes.android.external.store3.middleware + + +import com.google.gson.Gson +import com.nytimes.android.external.store3.base.Parser + +import java.io.Reader +import java.lang.reflect.Type + +import okio.BufferedSource + +/** + * Factory which returns various Gson [Parser] implementations. + */ +object GsonParserFactory { + + /** + * Returns a new Parser which parses from [Reader] to the specified type, using + * a new default configured [Gson] instance. + */ + fun createReaderParser(type: Type): Parser = createReaderParser(Gson(), type) + + /** + * Returns a new Parser which parses from [Reader] to the specified type, using + * the provided [Gson] instance. + */ + + fun createReaderParser(gson: Gson, type: Type): Parser = GsonReaderParser(gson, type) + + /** + * Returns a new Parser which parses from [Reader] to the specified type, using + * a new default configured [Gson] instance. + */ + fun createSourceParser(type: Type): Parser = createSourceParser(Gson(), type) + + /** + * Returns a new Parser which parses from [BufferedSource] to the specified type, using + * the provided [Gson] instance. + */ + fun createSourceParser(gson: Gson, type: Type): Parser = GsonSourceParser(gson, type) + + /** + * Returns a new Parser which parses from a String to the specified type, using + * a new default [Gson] instance. + */ + fun createStringParser(type: Class): Parser = createStringParser(Gson(), type) + + /** + * Returns a new Parser which parses from a String to the specified type, using + * the provided [Gson] instance. + */ + fun createStringParser(gson: Gson, type: Type): Parser = GsonStringParser(gson, type) + +} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.java b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.java deleted file mode 100644 index 345e58b72..000000000 --- a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.nytimes.android.external.store3.middleware; - -import com.google.gson.Gson; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.util.ParserException; - -import java.io.Reader; -import java.lang.reflect.Type; - -import javax.inject.Inject; - -import io.reactivex.annotations.NonNull; - -import static com.nytimes.android.external.cache3.Preconditions.checkNotNull; - -public class GsonReaderParser implements Parser { - - private final Gson gson; - private final Type type; - - @Inject - public GsonReaderParser(Gson gson, Type type) { - checkNotNull(gson, "Gson can't be null"); - checkNotNull(type, "Type can't be null"); - this.gson = gson; - this.type = type; - } - - @Override - public Parsed apply(@NonNull Reader reader) throws ParserException { - return gson.fromJson(reader, type); - } -} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.kt b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.kt new file mode 100644 index 000000000..cc9a21ea9 --- /dev/null +++ b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.kt @@ -0,0 +1,16 @@ +package com.nytimes.android.external.store3.middleware + +import com.google.gson.Gson +import com.nytimes.android.external.store3.base.Parser +import com.nytimes.android.external.store3.util.ParserException +import io.reactivex.annotations.NonNull +import java.io.Reader +import java.lang.reflect.Type +import javax.inject.Inject + +class GsonReaderParser @Inject +constructor(private val gson: Gson, private val type: Type) : Parser { + + @Throws(ParserException::class) + override fun apply(@NonNull reader: Reader): Parsed = gson.fromJson(reader, type) +} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.java b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.java deleted file mode 100644 index 527a6ada2..000000000 --- a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.nytimes.android.external.store3.middleware; - - -import com.google.gson.Gson; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.util.ParserException; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.reflect.Type; -import java.nio.charset.Charset; - -import javax.inject.Inject; - -import io.reactivex.annotations.NonNull; -import okio.BufferedSource; - -import static com.nytimes.android.external.cache3.Preconditions.checkNotNull; - - -/** - * Parser to be used when going from a BufferedSource to any Parsed Type - * example usage: - * ParsingStoreBuilder.builder() - * .fetcher(fetcher) - * .persister(SourcePersisterFactory.create(getApplicationContext().getCacheDir())) - * .parser(GsonParserFactory.createSourceParser(new Gson(),BookResult.class) - * .open(); - */ - - -public class GsonSourceParser implements Parser { - - private final Gson gson; - private final Type type; - - @Inject - public GsonSourceParser(Gson gson, Type type) { - checkNotNull(gson, "Gson can't be null"); - checkNotNull(type, "Type can't be null"); - this.gson = gson; - this.type = type; - } - - @Override - public Parsed apply(@NonNull BufferedSource bufferedSource) throws ParserException { - try (InputStreamReader reader = new InputStreamReader(bufferedSource.inputStream(), Charset.forName("UTF-8"))) { - return gson.fromJson(reader, type); - } catch (IOException e) { - throw new ParserException(e.getMessage(), e); - } - } -} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.kt b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.kt new file mode 100644 index 000000000..0a3de66f3 --- /dev/null +++ b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.kt @@ -0,0 +1,39 @@ +package com.nytimes.android.external.store3.middleware + + +import com.google.gson.Gson +import com.nytimes.android.external.store3.base.Parser +import com.nytimes.android.external.store3.util.ParserException +import io.reactivex.annotations.NonNull +import okio.BufferedSource +import java.io.IOException +import java.io.InputStreamReader +import java.lang.reflect.Type +import java.nio.charset.Charset +import javax.inject.Inject + + +/** + * Parser to be used when going from a BufferedSource to any Parsed Type + * example usage: + * ParsingStoreBuilder., BookResults>builder() + * .fetcher(fetcher) + * .persister(SourcePersisterFactory.create(getApplicationContext().getCacheDir())) + * .parser(GsonParserFactory.createSourceParser(new Gson(),BookResult.class) + * .open(); + */ + + +class GsonSourceParser @Inject +constructor(private val gson: Gson, private val type: Type) : Parser { + + @Throws(ParserException::class) + override fun apply(@NonNull bufferedSource: BufferedSource): Parsed { + try { + InputStreamReader(bufferedSource.inputStream(), Charset.forName("UTF-8")).use { reader -> return gson.fromJson(reader, type) } + } catch (e: IOException) { + throw ParserException(e.message, e) + } + + } +} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.java b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.java deleted file mode 100644 index 9fde01dfc..000000000 --- a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.nytimes.android.external.store3.middleware; - -import com.google.gson.Gson; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.util.ParserException; - -import java.lang.reflect.Type; - -import javax.inject.Inject; - -import io.reactivex.annotations.NonNull; - -import static com.nytimes.android.external.cache3.Preconditions.checkNotNull; - -public class GsonStringParser implements Parser { - - private final Gson gson; - private final Type type; - - @Inject - public GsonStringParser(Gson gson, Type parsedClass) { - checkNotNull(gson, "Gson can't be null"); - checkNotNull(parsedClass, "Type can't be null"); - this.gson = gson; - this.type = parsedClass; - } - - @Override - public Parsed apply(@NonNull String s) throws ParserException { - return gson.fromJson(s, type); - } -} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.kt b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.kt new file mode 100644 index 000000000..7415291a1 --- /dev/null +++ b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.kt @@ -0,0 +1,15 @@ +package com.nytimes.android.external.store3.middleware + +import com.google.gson.Gson +import com.nytimes.android.external.store3.base.Parser +import com.nytimes.android.external.store3.util.ParserException +import io.reactivex.annotations.NonNull +import java.lang.reflect.Type +import javax.inject.Inject + +class GsonStringParser @Inject +constructor(private val gson: Gson, private val type: Type) : Parser { + + @Throws(ParserException::class) + override fun apply(@NonNull s: String): Parsed? = gson.fromJson(s, type) +} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonTransformerFactory.java b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonTransformerFactory.java deleted file mode 100644 index c545f84cc..000000000 --- a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonTransformerFactory.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.nytimes.android.external.store3.middleware; - -import com.google.gson.Gson; -import com.nytimes.android.external.fs3.ObjectToSourceTransformer; -import com.nytimes.android.external.store3.annotations.Experimental; - -import javax.annotation.Nonnull; - -/** - * Factory which returns Gson {@link io.reactivex.SingleTransformer} implementations. - */ -public final class GsonTransformerFactory { - - private GsonTransformerFactory() { - } - - /** - * Returns a new {@link ObjectToSourceTransformer}, which uses a {@link GsonBufferedSourceAdapter} to parse from - * objects of the specified type to JSON using the provided {@link Gson} instance. - */ - @Nonnull - @Experimental - public static ObjectToSourceTransformer createObjectToSourceTransformer(@Nonnull Gson gson) { - return new ObjectToSourceTransformer<>(new GsonBufferedSourceAdapter(gson)); - } - -} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonTransformerFactory.kt b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonTransformerFactory.kt new file mode 100644 index 000000000..9115b8c6c --- /dev/null +++ b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonTransformerFactory.kt @@ -0,0 +1,19 @@ +package com.nytimes.android.external.store3.middleware + +import com.google.gson.Gson +import com.nytimes.android.external.fs3.ObjectToSourceTransformer +import com.nytimes.android.external.store3.annotations.Experimental + +/** + * Factory which returns Gson [io.reactivex.SingleTransformer] implementations. + */ +object GsonTransformerFactory { + + /** + * Returns a new [ObjectToSourceTransformer], which uses a [GsonBufferedSourceAdapter] to parse from + * objects of the specified type to JSON using the provided [Gson] instance. + */ + @Experimental + fun createObjectToSourceTransformer(gson: Gson): ObjectToSourceTransformer = ObjectToSourceTransformer(GsonBufferedSourceAdapter(gson)) + +} diff --git a/middleware/src/test/java/com/nytimes/android/external/store3/GenericParserStoreTest.java b/middleware/src/test/java/com/nytimes/android/external/store3/GenericParserStoreTest.java deleted file mode 100644 index 069416e8b..000000000 --- a/middleware/src/test/java/com/nytimes/android/external/store3/GenericParserStoreTest.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.google.gson.Gson; -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; -import com.nytimes.android.external.store3.middleware.GsonParserFactory; - -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.ByteArrayInputStream; - -import io.reactivex.Maybe; -import io.reactivex.Single; -import okio.BufferedSource; -import okio.Okio; - -import static com.google.common.base.Charsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class GenericParserStoreTest { - public static final String KEY = "key"; - @Mock - Fetcher fetcher; - @Mock - Persister persister; - private final BarCode barCode = new BarCode("value", KEY); - - private static BufferedSource source(String data) { - return Okio.buffer(Okio.source(new ByteArrayInputStream(data.getBytes(UTF_8)))); - } - - @Test - public void testSimple() { - MockitoAnnotations.initMocks(this); - - Parser parser = GsonParserFactory.createSourceParser(new Gson(), Foo.class); - - Store simpleStore = StoreBuilder.parsedWithKey() - .persister(persister) - .fetcher(fetcher) - .parser(parser) - .open(); - - Foo foo = new Foo(); - foo.bar = barCode.getKey(); - - String sourceData = new Gson().toJson(foo); - - - BufferedSource source = source(sourceData); - Single value = Single.just(source); - when(fetcher.fetch(barCode)) - .thenReturn(value); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(value.toMaybe()); - - when(persister.write(barCode, source)) - .thenReturn(Single.just(true)); - - Foo result = simpleStore.get(barCode).blockingGet(); - assertThat(result.bar).isEqualTo(KEY); - result = simpleStore.get(barCode).blockingGet(); - assertThat(result.bar).isEqualTo(KEY); - verify(fetcher, times(1)).fetch(barCode); - } - - private static class Foo { - String bar; - - Foo() { - } - } -} diff --git a/middleware/src/test/java/com/nytimes/android/external/store3/GenericParserStoreTest.kt b/middleware/src/test/java/com/nytimes/android/external/store3/GenericParserStoreTest.kt new file mode 100644 index 000000000..fd6935c14 --- /dev/null +++ b/middleware/src/test/java/com/nytimes/android/external/store3/GenericParserStoreTest.kt @@ -0,0 +1,84 @@ +package com.nytimes.android.external.store3 + +import com.google.gson.Gson +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Parser +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import com.nytimes.android.external.store3.middleware.GsonParserFactory + +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +import java.io.ByteArrayInputStream + +import io.reactivex.Maybe +import io.reactivex.Single +import okio.BufferedSource +import okio.Okio + +import com.google.common.base.Charsets.UTF_8 +import org.assertj.core.api.Assertions.assertThat +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` + +class GenericParserStoreTest { + @Mock + lateinit var fetcher: Fetcher + @Mock + lateinit var persister: Persister + private val barCode = BarCode("value", KEY) + + @Test + fun testSimple() { + MockitoAnnotations.initMocks(this) + + val parser = GsonParserFactory.createSourceParser(Gson(), Foo::class.java) + + val simpleStore = StoreBuilder.parsedWithKey() + .persister(persister) + .fetcher(fetcher) + .parser(parser) + .open() + + val foo = Foo() + foo.bar = barCode.key + + val sourceData = Gson().toJson(foo) + + + val source = source(sourceData) + val value = Single.just(source) + `when`(fetcher.fetch(barCode)) + .thenReturn(value) + + `when`(persister.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(value.toMaybe()) + + `when`(persister.write(barCode, source)) + .thenReturn(Single.just(true)) + + var result = simpleStore.get(barCode).blockingGet() + assertThat(result.bar).isEqualTo(KEY) + result = simpleStore.get(barCode).blockingGet() + assertThat(result.bar).isEqualTo(KEY) + verify>(fetcher, times(1)).fetch(barCode) + } + + private class Foo internal constructor() { + internal var bar: String? = null + } + + companion object { + val KEY = "key" + + private fun source(data: String): BufferedSource { + return Okio.buffer(Okio.source(ByteArrayInputStream(data.toByteArray(UTF_8)))) + } + } +} diff --git a/middleware/src/test/java/com/nytimes/android/external/store3/GsonParserFactoryTest.java b/middleware/src/test/java/com/nytimes/android/external/store3/GsonParserFactoryTest.java deleted file mode 100644 index 60aae06f0..000000000 --- a/middleware/src/test/java/com/nytimes/android/external/store3/GsonParserFactoryTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.google.gson.Gson; -import com.nytimes.android.external.store3.middleware.GsonParserFactory; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.lang.reflect.Type; - -public class GsonParserFactoryTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Mock - Type type; - private final Gson gson = new Gson(); - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - } - - @Test - public void shouldCreateParsersProperly() { - GsonParserFactory.createReaderParser(gson, type); - GsonParserFactory.createSourceParser(gson, type); - GsonParserFactory.createStringParser(gson, type); - } - - @Test - public void shouldThrowExceptionWhenCreatingReaderWithNullType() { - expectedException.expect(NullPointerException.class); - GsonParserFactory.createReaderParser(gson, null); - } - - @Test - public void shouldThrowExceptionWhenCreatingReaderWithNullGson() { - expectedException.expect(NullPointerException.class); - GsonParserFactory.createReaderParser(null, type); - } - - @Test - public void shouldThrowExceptionWhenCreatingSourceWithNullType() { - expectedException.expect(NullPointerException.class); - GsonParserFactory.createSourceParser(gson, null); - } - - @Test - public void shouldThrowExceptionWhenCreatingSourceWithNullGson() { - expectedException.expect(NullPointerException.class); - GsonParserFactory.createSourceParser(null, type); - } - - @Test - public void shouldThrowExceptionWhenCreatingStringWithNullType() { - expectedException.expect(NullPointerException.class); - GsonParserFactory.createStringParser(gson, null); - } - - @Test - public void shouldThrowExceptionWhenCreatingStringWithNullGson() { - expectedException.expect(NullPointerException.class); - GsonParserFactory.createStringParser(null, type); - } -} diff --git a/middleware/src/test/java/com/nytimes/android/external/store3/GsonParserFactoryTest.kt b/middleware/src/test/java/com/nytimes/android/external/store3/GsonParserFactoryTest.kt new file mode 100644 index 000000000..205e98a9a --- /dev/null +++ b/middleware/src/test/java/com/nytimes/android/external/store3/GsonParserFactoryTest.kt @@ -0,0 +1,73 @@ +package com.nytimes.android.external.store3 + +import com.google.gson.Gson +import com.nytimes.android.external.store3.middleware.GsonParserFactory + +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +import java.lang.reflect.Type + +class GsonParserFactoryTest { + + @Rule + @JvmField + var expectedException = ExpectedException.none() + + @Mock + lateinit var type: Type + private val gson = Gson() + + @Before + @Throws(Exception::class) + fun setUp() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun shouldCreateParsersProperly() { + GsonParserFactory.createReaderParser(gson, type) + GsonParserFactory.createSourceParser(gson, type) + GsonParserFactory.createStringParser(gson, type) + } + + @Test + fun shouldThrowExceptionWhenCreatingReaderWithNullType() { + expectedException.expect(NullPointerException::class.java) + GsonParserFactory.createReaderParser(gson, null!!) + } + + @Test + fun shouldThrowExceptionWhenCreatingReaderWithNullGson() { + expectedException.expect(NullPointerException::class.java) + GsonParserFactory.createReaderParser(null!!, type) + } + + @Test + fun shouldThrowExceptionWhenCreatingSourceWithNullType() { + expectedException.expect(NullPointerException::class.java) + GsonParserFactory.createSourceParser(gson, null!!) + } + + @Test + fun shouldThrowExceptionWhenCreatingSourceWithNullGson() { + expectedException.expect(NullPointerException::class.java) + GsonParserFactory.createSourceParser(null!!, type) + } + + @Test + fun shouldThrowExceptionWhenCreatingStringWithNullType() { + expectedException.expect(NullPointerException::class.java) + GsonParserFactory.createStringParser(gson, null!!) + } + + @Test + fun shouldThrowExceptionWhenCreatingStringWithNullGson() { + expectedException.expect(NullPointerException::class.java) + GsonParserFactory.createStringParser(null!!, type) + } +} diff --git a/middleware/src/test/java/com/nytimes/android/external/store3/GsonSourceListParserTest.java b/middleware/src/test/java/com/nytimes/android/external/store3/GsonSourceListParserTest.java deleted file mode 100644 index 62e9a44a5..000000000 --- a/middleware/src/test/java/com/nytimes/android/external/store3/GsonSourceListParserTest.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.google.gson.Gson; -import com.google.gson.reflect.TypeToken; -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; -import com.nytimes.android.external.store3.middleware.GsonParserFactory; - -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.ByteArrayInputStream; -import java.util.Arrays; -import java.util.List; - -import io.reactivex.Maybe; -import io.reactivex.Single; -import okio.BufferedSource; -import okio.Okio; - -import static com.google.common.base.Charsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class GsonSourceListParserTest { - public static final String KEY = "key"; - @Mock - Fetcher fetcher; - @Mock - Persister persister; - private final BarCode barCode = new BarCode("value", KEY); - - private static BufferedSource source(String data) { - return Okio.buffer(Okio.source(new ByteArrayInputStream(data.getBytes(UTF_8)))); - } - - @Test - public void testSimple() { - MockitoAnnotations.initMocks(this); - - Parser> parser = - GsonParserFactory.createSourceParser(new Gson(), new TypeToken>() { - }.getType()); - - - Store, BarCode> simpleStore = - StoreBuilder.>parsedWithKey() - .persister(persister) - .fetcher(fetcher) - .parser(parser) - .open(); - - Foo foo = new Foo("a"); - Foo foo2 = new Foo("b"); - Foo foo3 = new Foo("c"); - List data = Arrays.asList(foo, foo2, foo3); - - String sourceData = new Gson().toJson(data); - - - BufferedSource source = source(sourceData); - Single value = Single.just(source); - when(fetcher.fetch(barCode)) - .thenReturn(value); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(value.toMaybe()); - - when(persister.write(barCode, source)) - .thenReturn(Single.just(true)); - - List result = simpleStore.get(barCode).blockingGet(); - assertThat(result.get(0).value).isEqualTo("a"); - assertThat(result.get(1).value).isEqualTo("b"); - assertThat(result.get(2).value).isEqualTo("c"); - - verify(fetcher, times(1)).fetch(barCode); - } - - private static class Foo { - String value; - - Foo(String value) { - this.value = value; - } - } -} diff --git a/middleware/src/test/java/com/nytimes/android/external/store3/GsonSourceListParserTest.kt b/middleware/src/test/java/com/nytimes/android/external/store3/GsonSourceListParserTest.kt new file mode 100644 index 000000000..cc34ef925 --- /dev/null +++ b/middleware/src/test/java/com/nytimes/android/external/store3/GsonSourceListParserTest.kt @@ -0,0 +1,90 @@ +package com.nytimes.android.external.store3 + +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Parser +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import com.nytimes.android.external.store3.middleware.GsonParserFactory + +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +import java.io.ByteArrayInputStream +import java.util.Arrays + +import io.reactivex.Maybe +import io.reactivex.Single +import okio.BufferedSource +import okio.Okio + +import com.google.common.base.Charsets.UTF_8 +import org.assertj.core.api.Assertions.assertThat +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` + +class GsonSourceListParserTest { + @Mock + lateinit var fetcher: Fetcher + @Mock + lateinit var persister: Persister + private val barCode = BarCode("value", KEY) + + @Test + fun testSimple() { + MockitoAnnotations.initMocks(this) + + val parser = GsonParserFactory.createSourceParser>(Gson(), object : TypeToken>() { + + }.type) + + + val simpleStore = StoreBuilder.parsedWithKey>() + .persister(persister) + .fetcher(fetcher) + .parser(parser) + .open() + + val foo = Foo("a") + val foo2 = Foo("b") + val foo3 = Foo("c") + val data = Arrays.asList(foo, foo2, foo3) + + val sourceData = Gson().toJson(data) + + + val source = source(sourceData) + val value = Single.just(source) + `when`(fetcher.fetch(barCode)) + .thenReturn(value) + + `when`(persister.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(value.toMaybe()) + + `when`(persister.write(barCode, source)) + .thenReturn(Single.just(true)) + + val result = simpleStore.get(barCode).blockingGet() + assertThat(result[0].value).isEqualTo("a") + assertThat(result[1].value).isEqualTo("b") + assertThat(result[2].value).isEqualTo("c") + + verify>(fetcher, times(1)).fetch(barCode) + } + + private class Foo internal constructor(internal var value: String) + + companion object { + val KEY = "key" + + private fun source(data: String): BufferedSource { + return Okio.buffer(Okio.source(ByteArrayInputStream(data.toByteArray(UTF_8)))) + } + } +} From bdacb32f11798ef698800f5afe1feb1bfa7c4dec Mon Sep 17 00:00:00 2001 From: Brian Plummer Date: Fri, 13 Jul 2018 11:08:16 +0200 Subject: [PATCH 038/498] convert first batch of filesystem module tests (#352) * configure kotlin, convert first test * convert half the tests * some pr feedback * pull up source func --- filesystem/build.gradle | 19 ++-- .../external/fs3/FSAllOperationTest.java | 61 ----------- .../external/fs3/FSAllOperationTest.kt | 59 ++++++++++ .../external/fs3/FilePersisterTest.java | 72 ------------ .../android/external/fs3/FilePersisterTest.kt | 68 ++++++++++++ .../fs3/FileSystemRecordPersisterTest.java | 103 ------------------ .../fs3/FileSystemRecordPersisterTest.kt | 95 ++++++++++++++++ .../android/external/fs3/MultiTest.java | 69 ------------ .../nytimes/android/external/fs3/MultiTest.kt | 61 +++++++++++ .../fs3/ObjectToSourceTransformerTest.java | 38 ------- .../fs3/ObjectToSourceTransformerTest.kt | 40 +++++++ .../external/fs3/RecordPersisterTest.java | 89 --------------- .../external/fs3/RecordPersisterTest.kt | 91 ++++++++++++++++ 13 files changed, 426 insertions(+), 439 deletions(-) delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.kt delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/FilePersisterTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/FilePersisterTest.kt delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/FileSystemRecordPersisterTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/FileSystemRecordPersisterTest.kt delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/MultiTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/MultiTest.kt delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/ObjectToSourceTransformerTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/ObjectToSourceTransformerTest.kt delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/RecordPersisterTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/RecordPersisterTest.kt diff --git a/filesystem/build.gradle b/filesystem/build.gradle index 71ec1f6d1..ac3b1b1ee 100644 --- a/filesystem/build.gradle +++ b/filesystem/build.gradle @@ -1,9 +1,21 @@ +buildscript { + tasks.withType(JavaCompile) { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +plugins { + id 'org.jetbrains.kotlin.jvm' +} + apply plugin: 'java' group = GROUP version = VERSION_NAME dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation libraries.okio compileOnly libraries.jsr305 compileOnly libraries.javax @@ -19,13 +31,6 @@ dependencies { testCompileOnly libraries.jsr305 } -buildscript { - tasks.withType(JavaCompile) { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } -} - apply from: rootProject.file("gradle/maven-push.gradle") apply from: rootProject.file("gradle/checkstyle.gradle") apply from: rootProject.file("gradle/pmd.gradle") diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java deleted file mode 100644 index d36217ae7..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.nytimes.android.external.fs3; - - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.fs3.filesystem.FileSystemFactory; - -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; - -import io.reactivex.Observable; -import okio.BufferedSource; -import okio.Okio; - -import static com.google.common.base.Charsets.UTF_8; -import static com.google.common.io.Files.createTempDir; -import static org.assertj.core.api.Assertions.assertThat; - -public class FSAllOperationTest { - - public static final String FOLDER = "type"; - public static final String INNER_FOLDER = "type2"; - public static final String CHALLAH = "Challah"; - public static final String CHALLAH_CHALLAH = "Challah_CHALLAH"; - - - private static BufferedSource source(String data) { - return Okio.buffer(Okio.source(new ByteArrayInputStream(data.getBytes(UTF_8)))); - } - - @Test - public void readAll() throws IOException { - File tempDir = createTempDir(); - FileSystem fileSystem = FileSystemFactory.create(tempDir); - - //write different data to File System for each barcode - fileSystem.write(FOLDER + "/key.txt", source(CHALLAH)); - fileSystem.write(FOLDER + "/" + INNER_FOLDER + "/key2.txt", source(CHALLAH_CHALLAH)); - FSAllReader reader = new FSAllReader(fileSystem); - //read back all values for the FOLDER - Observable observable = reader.readAll(FOLDER); - assertThat(observable.blockingFirst().readUtf8()).isEqualTo(CHALLAH); - assertThat(observable.blockingLast().readUtf8()).isEqualTo(CHALLAH_CHALLAH); - } - - @Test - public void deleteAll() throws IOException { - File tempDir = createTempDir(); - FileSystem fileSystem = FileSystemFactory.create(tempDir); - //write different data to File System for each barcode - fileSystem.write(FOLDER + "/key.txt", source(CHALLAH)); - fileSystem.write(FOLDER + "/" + INNER_FOLDER + "/key2.txt", source(CHALLAH_CHALLAH)); - - FSAllEraser eraser = new FSAllEraser(fileSystem); - Observable observable = eraser.deleteAll(FOLDER); - assertThat(observable.blockingFirst()).isEqualTo(true); - } - -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.kt new file mode 100644 index 000000000..72727d42b --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/FSAllOperationTest.kt @@ -0,0 +1,59 @@ +package com.nytimes.android.external.fs3 + + +import com.google.common.base.Charsets.UTF_8 +import com.google.common.io.Files.createTempDir +import com.nytimes.android.external.fs3.filesystem.FileSystemFactory +import okio.BufferedSource +import okio.Okio +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import java.io.ByteArrayInputStream +import java.io.IOException + +class FSAllOperationTest { + + @Test + @Throws(IOException::class) + fun readAll() { + val tempDir = createTempDir() + val fileSystem = FileSystemFactory.create(tempDir) + + //write different data to File System for each barcode + fileSystem.write("$FOLDER/key.txt", source(CHALLAH)) + fileSystem.write("$FOLDER/$INNER_FOLDER/key2.txt", source(CHALLAH_CHALLAH)) + val reader = FSAllReader(fileSystem) + //read back all values for the FOLDER + val observable = reader.readAll(FOLDER) + assertThat(observable.blockingFirst().readUtf8()).isEqualTo(CHALLAH) + assertThat(observable.blockingLast().readUtf8()).isEqualTo(CHALLAH_CHALLAH) + } + + @Test + @Throws(IOException::class) + fun deleteAll() { + val tempDir = createTempDir() + val fileSystem = FileSystemFactory.create(tempDir) + //write different data to File System for each barcode + fileSystem.write("$FOLDER/key.txt", source(CHALLAH)) + fileSystem.write("$FOLDER/$INNER_FOLDER/key2.txt", source(CHALLAH_CHALLAH)) + + val eraser = FSAllEraser(fileSystem) + val observable = eraser.deleteAll(FOLDER) + assertThat(observable.blockingFirst()).isEqualTo(true) + } + + companion object { + + val FOLDER = "type" + val INNER_FOLDER = "type2" + val CHALLAH = "Challah" + val CHALLAH_CHALLAH = "Challah_CHALLAH" + + + private fun source(data: String): BufferedSource { + return Okio.buffer(Okio.source(ByteArrayInputStream(data.toByteArray(UTF_8)))) + } + } + +} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/FilePersisterTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/FilePersisterTest.java deleted file mode 100644 index 770a1576a..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/FilePersisterTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.FileNotFoundException; -import java.io.IOException; - -import okio.BufferedSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.when; - -public class FilePersisterTest { - - @Mock - FileSystem fileSystem; - @Mock - BufferedSource bufferedSource; - - private final BarCode simple = new BarCode("type", "key"); - private final String resolvedPath = new BarCodePathResolver().resolve(simple); - private Persister fileSystemPersister; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - fileSystemPersister = FileSystemPersister.create(fileSystem, new BarCodePathResolver()); - } - - @Test - public void readExists() throws FileNotFoundException { - when(fileSystem.exists(resolvedPath)) - .thenReturn(true); - when(fileSystem.read(resolvedPath)).thenReturn(bufferedSource); - - BufferedSource returnedValue = fileSystemPersister.read(simple).blockingGet(); - assertThat(returnedValue).isEqualTo(bufferedSource); - } - - @Test - @SuppressWarnings("CheckReturnValue") - public void readDoesNotExist() throws FileNotFoundException { - when(fileSystem.exists(resolvedPath)) - .thenReturn(false); - - fileSystemPersister.read(simple).test().assertError(FileNotFoundException.class); - } - - @Test - @SuppressWarnings("CheckReturnValue") - public void writeThenRead() throws IOException { - when(fileSystem.read(resolvedPath)).thenReturn(bufferedSource); - when(fileSystem.exists(resolvedPath)).thenReturn(true); - fileSystemPersister.write(simple, bufferedSource).blockingGet(); - BufferedSource source = fileSystemPersister.read(simple).blockingGet(); - InOrder inOrder = inOrder(fileSystem); - inOrder.verify(fileSystem).write(resolvedPath, bufferedSource); - inOrder.verify(fileSystem).exists(resolvedPath); - inOrder.verify(fileSystem).read(resolvedPath); - - assertThat(source).isEqualTo(bufferedSource); - } -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/FilePersisterTest.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/FilePersisterTest.kt new file mode 100644 index 000000000..ad2589a39 --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/FilePersisterTest.kt @@ -0,0 +1,68 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import okio.BufferedSource +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.inOrder +import org.mockito.MockitoAnnotations +import java.io.FileNotFoundException +import java.io.IOException + +class FilePersisterTest { + + @Mock + lateinit var fileSystem: FileSystem + @Mock + lateinit var bufferedSource: BufferedSource + + private val simple = BarCode("type", "key") + private val resolvedPath = BarCodePathResolver().resolve(simple) + lateinit var fileSystemPersister: Persister + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + fileSystemPersister = FileSystemPersister.create(fileSystem, BarCodePathResolver()) + } + + @Test + @Throws(FileNotFoundException::class) + fun readExists() { + `when`(fileSystem.exists(resolvedPath)) + .thenReturn(true) + `when`(fileSystem.read(resolvedPath)).thenReturn(bufferedSource) + + val returnedValue = fileSystemPersister.read(simple).blockingGet() + assertThat(returnedValue).isEqualTo(bufferedSource) + } + + @Test + @Throws(FileNotFoundException::class) + fun readDoesNotExist() { + `when`(fileSystem.exists(resolvedPath)) + .thenReturn(false) + + fileSystemPersister.read(simple).test().assertError(FileNotFoundException::class.java) + } + + @Test + @Throws(IOException::class) + fun writeThenRead() { + `when`(fileSystem.read(resolvedPath)).thenReturn(bufferedSource) + `when`(fileSystem.exists(resolvedPath)).thenReturn(true) + fileSystemPersister.write(simple, bufferedSource).blockingGet() + val source = fileSystemPersister.read(simple).blockingGet() + val inOrder = inOrder(fileSystem) + inOrder.verify(fileSystem).write(resolvedPath, bufferedSource) + inOrder.verify(fileSystem).exists(resolvedPath) + inOrder.verify(fileSystem).read(resolvedPath) + + assertThat(source).isEqualTo(bufferedSource) + } +} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/FileSystemRecordPersisterTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/FileSystemRecordPersisterTest.java deleted file mode 100644 index 9a2086702..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/FileSystemRecordPersisterTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.store3.base.RecordState; -import com.nytimes.android.external.store3.base.impl.BarCode; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import okio.BufferedSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.when; - -public class FileSystemRecordPersisterTest { - - @Mock - FileSystem fileSystem; - @Mock - BufferedSource bufferedSource; - - private final BarCode simple = new BarCode("type", "key"); - private final String resolvedPath = new BarCodePathResolver().resolve(simple); - private FileSystemRecordPersister fileSystemPersister; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - fileSystemPersister = FileSystemRecordPersister.create(fileSystem, - new BarCodePathResolver(), - 1, TimeUnit.DAYS); - } - - @Test - public void readExists() throws FileNotFoundException { - when(fileSystem.exists(resolvedPath)) - .thenReturn(true); - when(fileSystem.read(resolvedPath)).thenReturn(bufferedSource); - - BufferedSource returnedValue = fileSystemPersister.read(simple).blockingGet(); - assertThat(returnedValue).isEqualTo(bufferedSource); - } - - @Test - @SuppressWarnings("CheckReturnValue") - public void readDoesNotExist() throws FileNotFoundException { - when(fileSystem.exists(resolvedPath)) - .thenReturn(false); - - fileSystemPersister.read(simple).test().assertError(FileNotFoundException.class); - } - - @Test - @SuppressWarnings("CheckReturnValue") - public void writeThenRead() throws IOException { - when(fileSystem.read(resolvedPath)).thenReturn(bufferedSource); - when(fileSystem.exists(resolvedPath)).thenReturn(true); - fileSystemPersister.write(simple, bufferedSource).blockingGet(); - BufferedSource source = fileSystemPersister.read(simple).blockingGet(); - InOrder inOrder = inOrder(fileSystem); - inOrder.verify(fileSystem).write(resolvedPath, bufferedSource); - inOrder.verify(fileSystem).exists(resolvedPath); - inOrder.verify(fileSystem).read(resolvedPath); - - assertThat(source).isEqualTo(bufferedSource); - - - } - - @Test - public void freshTest() { - when(fileSystem.getRecordState(TimeUnit.DAYS, 1L, resolvedPath)) - .thenReturn(RecordState.FRESH); - - assertThat(fileSystemPersister.getRecordState(simple)).isEqualTo(RecordState.FRESH); - } - - @Test - public void staleTest() { - when(fileSystem.getRecordState(TimeUnit.DAYS, 1L, resolvedPath)) - .thenReturn(RecordState.STALE); - - assertThat(fileSystemPersister.getRecordState(simple)).isEqualTo(RecordState.STALE); - } - - @Test - public void missingTest() { - when(fileSystem.getRecordState(TimeUnit.DAYS, 1L, resolvedPath)) - .thenReturn(RecordState.MISSING); - - assertThat(fileSystemPersister.getRecordState(simple)).isEqualTo(RecordState.MISSING); - } - - -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/FileSystemRecordPersisterTest.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/FileSystemRecordPersisterTest.kt new file mode 100644 index 000000000..817fa0b71 --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/FileSystemRecordPersisterTest.kt @@ -0,0 +1,95 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.store3.base.RecordState +import com.nytimes.android.external.store3.base.impl.BarCode +import okio.BufferedSource +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.inOrder +import org.mockito.MockitoAnnotations +import java.io.FileNotFoundException +import java.io.IOException +import java.util.concurrent.TimeUnit + +class FileSystemRecordPersisterTest { + + @Mock + lateinit var fileSystem: FileSystem + @Mock + lateinit var bufferedSource: BufferedSource + + private val simple = BarCode("type", "key") + private val resolvedPath = BarCodePathResolver().resolve(simple) + lateinit var fileSystemPersister: FileSystemRecordPersister + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + fileSystemPersister = FileSystemRecordPersister.create(fileSystem, + BarCodePathResolver(), + 1, TimeUnit.DAYS) + } + + @Test + @Throws(FileNotFoundException::class) + fun readExists() { + `when`(fileSystem.exists(resolvedPath)) + .thenReturn(true) + `when`(fileSystem.read(resolvedPath)).thenReturn(bufferedSource) + + val returnedValue = fileSystemPersister.read(simple).blockingGet() + assertThat(returnedValue).isEqualTo(bufferedSource) + } + + @Test + @Throws(FileNotFoundException::class) + fun readDoesNotExist() { + `when`(fileSystem.exists(resolvedPath)) + .thenReturn(false) + + fileSystemPersister.read(simple).test().assertError(FileNotFoundException::class.java) + } + + @Test + @Throws(IOException::class) + fun writeThenRead() { + `when`(fileSystem.read(resolvedPath)).thenReturn(bufferedSource) + `when`(fileSystem.exists(resolvedPath)).thenReturn(true) + fileSystemPersister.write(simple, bufferedSource).blockingGet() + val source = fileSystemPersister.read(simple).blockingGet() + val inOrder = inOrder(fileSystem) + inOrder.verify(fileSystem).write(resolvedPath, bufferedSource) + inOrder.verify(fileSystem).exists(resolvedPath) + inOrder.verify(fileSystem).read(resolvedPath) + + assertThat(source).isEqualTo(bufferedSource) + } + + @Test + fun freshTest() { + `when`(fileSystem.getRecordState(TimeUnit.DAYS, 1L, resolvedPath)) + .thenReturn(RecordState.FRESH) + + assertThat(fileSystemPersister.getRecordState(simple)).isEqualTo(RecordState.FRESH) + } + + @Test + fun staleTest() { + `when`(fileSystem.getRecordState(TimeUnit.DAYS, 1L, resolvedPath)) + .thenReturn(RecordState.STALE) + + assertThat(fileSystemPersister.getRecordState(simple)).isEqualTo(RecordState.STALE) + } + + @Test + fun missingTest() { + `when`(fileSystem.getRecordState(TimeUnit.DAYS, 1L, resolvedPath)) + .thenReturn(RecordState.MISSING) + + assertThat(fileSystemPersister.getRecordState(simple)).isEqualTo(RecordState.MISSING) + } +} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/MultiTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/MultiTest.java deleted file mode 100644 index 8b47c5654..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/MultiTest.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.google.common.collect.ImmutableMap; -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.fs3.filesystem.FileSystemFactory; - -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.Map; - -import okio.BufferedSource; -import okio.Okio; - -import static com.google.common.base.Charsets.UTF_8; -import static com.google.common.io.Files.createTempDir; -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; - -public class MultiTest { - - private static final Map> fileData - = ImmutableMap.>builder() - .put("/foo/bar.txt", asList("sfvSFv", "AsfgasFgae", "szfvzsfbzdsfb")) - .put("/foo/bar/baz.xyz", asList("sasffvSFv", "AsfgsdvzsfbvasFgae", "szfvzsfszfvzsvbzdsfb")) - .build(); - - private static BufferedSource source(String data) { - return Okio.buffer(Okio.source(new ByteArrayInputStream(data.getBytes(UTF_8)))); - } - - private FileSystem createAndPopulateTestFileSystem() throws IOException { - File baseDir = createTempDir(); - FileSystem fileSystem = FileSystemFactory.create(baseDir); - for (String path : fileData.keySet()) { - for (String data : fileData.get(path)) { - BufferedSource source = source(data); - fileSystem.write(path, source); - source.close(); - } - } - assertThat(fileSystem.list("/").size()).isEqualTo(fileData.size()); - return fileSystem; - } - - @Test - public void testDeleteAll() throws IOException { - FileSystem fileSystem = createAndPopulateTestFileSystem(); - fileSystem.deleteAll("/"); - assertThat(fileSystem.list("/").size()).isZero(); - } - - @Test - public void listNCompare() throws IOException { - FileSystem fileSystem = createAndPopulateTestFileSystem(); - int assertCount = 0; - for (String path : fileSystem.list("/")) { - String data = fileSystem.read(path).readUtf8(); - List written = fileData.get(path); - String writtenData = written.get(written.size() - 1); - assertThat(data).isEqualTo(writtenData); - assertCount++; - } - assertThat(assertCount).isEqualTo(fileData.size()); - } -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/MultiTest.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/MultiTest.kt new file mode 100644 index 000000000..9202cffac --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/MultiTest.kt @@ -0,0 +1,61 @@ +package com.nytimes.android.external.fs3 + +import com.google.common.base.Charsets.UTF_8 +import com.google.common.io.Files.createTempDir +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.fs3.filesystem.FileSystemFactory +import okio.BufferedSource +import okio.Okio +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import java.io.ByteArrayInputStream +import java.io.IOException + +class MultiTest { + + @Throws(IOException::class) + private fun createAndPopulateTestFileSystem(): FileSystem { + val baseDir = createTempDir() + val fileSystem = FileSystemFactory.create(baseDir) + for (path in fileData.keys) { + for (data in fileData[path]!!) { + val source = source(data) + fileSystem.write(path, source) + source.close() + } + } + assertThat(fileSystem.list("/").size).isEqualTo(fileData.size) + return fileSystem + } + + @Test + @Throws(IOException::class) + fun testDeleteAll() { + val fileSystem = createAndPopulateTestFileSystem() + fileSystem.deleteAll("/") + assertThat(fileSystem.list("/").size).isZero() + } + + @Test + @Throws(IOException::class) + fun listNCompare() { + val fileSystem = createAndPopulateTestFileSystem() + var assertCount = 0 + for (path in fileSystem.list("/")) { + val data = fileSystem.read(path).readUtf8() + val written = fileData[path] + val writtenData = written?.get(written.size - 1) + assertThat(data).isEqualTo(writtenData) + assertCount++ + } + assertThat(assertCount).isEqualTo(fileData.size) + } + + companion object { + + private val fileData: Map> = mapOf("/foo/bar.txt" to listOf("sfvSFv", "AsfgasFgae", "szfvzsfbzdsfb"), + "/foo/bar/baz.xyz" to listOf("sasffvSFv", "AsfgsdvzsfbvasFgae", "szfvzsfszfvzsvbzdsfb")) + + private fun source(data: String): BufferedSource = Okio.buffer(Okio.source(ByteArrayInputStream(data.toByteArray(UTF_8)))) + } +} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/ObjectToSourceTransformerTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/ObjectToSourceTransformerTest.java deleted file mode 100644 index db2b6f0f0..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/ObjectToSourceTransformerTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.nytimes.android.external.fs3; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import io.reactivex.Single; -import okio.BufferedSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class ObjectToSourceTransformerTest { - - @Mock - BufferedSourceAdapter mockBufferedParser; - - @Mock - BufferedSource mockBufferedSource; - - @Before - public void setUp() throws Exception { - when(mockBufferedParser.toJson(any())).thenReturn(mockBufferedSource); - } - - @Test - public void testTransformer() throws Exception { - BufferedSource source = Single.just("test") - .compose(new ObjectToSourceTransformer<>(mockBufferedParser)) - .blockingGet(); - - assertThat(source).isEqualTo(mockBufferedSource); - } -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/ObjectToSourceTransformerTest.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/ObjectToSourceTransformerTest.kt new file mode 100644 index 000000000..545270f96 --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/ObjectToSourceTransformerTest.kt @@ -0,0 +1,40 @@ +package com.nytimes.android.external.fs3 + +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.runners.MockitoJUnitRunner + +import io.reactivex.Single +import okio.BufferedSource + +import org.assertj.core.api.Assertions.assertThat +import org.mockito.Matchers.any +import org.mockito.Mockito.`when` + +@RunWith(MockitoJUnitRunner::class) +class ObjectToSourceTransformerTest { + + @Mock + lateinit var mockBufferedParser: BufferedSourceAdapter + + @Mock + lateinit var mockBufferedSource: BufferedSource + + @Before + @Throws(Exception::class) + fun setUp() { + `when`(mockBufferedParser.toJson(any())).thenReturn(mockBufferedSource) + } + + @Test + @Throws(Exception::class) + fun testTransformer() { + val source = Single.just("test") + .compose(ObjectToSourceTransformer(mockBufferedParser)) + .blockingGet() + + assertThat(source).isEqualTo(mockBufferedSource) + } +} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/RecordPersisterTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/RecordPersisterTest.java deleted file mode 100644 index 4807097da..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/RecordPersisterTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.store3.base.RecordState; -import com.nytimes.android.external.store3.base.impl.BarCode; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import okio.BufferedSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -public class RecordPersisterTest { - - @Mock - FileSystem fileSystem; - @Mock - BufferedSource bufferedSource; - - private RecordPersister sourcePersister; - private final BarCode simple = new BarCode("type", "key"); - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - sourcePersister = new RecordPersister(fileSystem, 1L, TimeUnit.DAYS); - } - - @Test - public void readExists() throws FileNotFoundException { - when(fileSystem.exists(simple.toString())) - .thenReturn(true); - when(fileSystem.read(simple.toString())).thenReturn(bufferedSource); - - BufferedSource returnedValue = sourcePersister.read(simple).blockingGet(); - assertThat(returnedValue).isEqualTo(bufferedSource); - } - - @Test - public void freshTest() { - when(fileSystem.getRecordState(TimeUnit.DAYS, 1L, SourcePersister.pathForBarcode(simple))) - .thenReturn(RecordState.FRESH); - - assertThat(sourcePersister.getRecordState(simple)).isEqualTo(RecordState.FRESH); - } - - @Test - public void staleTest() { - when(fileSystem.getRecordState(TimeUnit.DAYS, 1L, SourcePersister.pathForBarcode(simple))) - .thenReturn(RecordState.STALE); - - assertThat(sourcePersister.getRecordState(simple)).isEqualTo(RecordState.STALE); - } - - @Test - public void missingTest() { - when(fileSystem.getRecordState(TimeUnit.DAYS, 1L, SourcePersister.pathForBarcode(simple))) - .thenReturn(RecordState.MISSING); - - assertThat(sourcePersister.getRecordState(simple)).isEqualTo(RecordState.MISSING); - } - - @Test - @SuppressWarnings("CheckReturnValue") - public void readDoesNotExist() throws FileNotFoundException { - when(fileSystem.exists(SourcePersister.pathForBarcode(simple))) - .thenReturn(false); - - sourcePersister.read(simple).test().assertError(FileNotFoundException.class); - } - - @Test - public void write() throws IOException { - assertThat(sourcePersister.write(simple, bufferedSource).blockingGet()).isTrue(); - } - - @Test - public void pathForBarcode() { - assertThat(SourcePersister.pathForBarcode(simple)).isEqualTo("typekey"); - } -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/RecordPersisterTest.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/RecordPersisterTest.kt new file mode 100644 index 000000000..f5b62a5bc --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/RecordPersisterTest.kt @@ -0,0 +1,91 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.store3.base.RecordState +import com.nytimes.android.external.store3.base.impl.BarCode + +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +import java.io.FileNotFoundException +import java.io.IOException +import java.util.concurrent.TimeUnit + +import okio.BufferedSource + +import org.assertj.core.api.Assertions.assertThat +import org.mockito.Mockito.`when` + +class RecordPersisterTest { + + @Mock + lateinit var fileSystem: FileSystem + @Mock + lateinit var bufferedSource: BufferedSource + + lateinit var sourcePersister: RecordPersister + private val simple = BarCode("type", "key") + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + sourcePersister = RecordPersister(fileSystem, 1L, TimeUnit.DAYS) + } + + @Test + @Throws(FileNotFoundException::class) + fun readExists() { + `when`(fileSystem.exists(simple.toString())) + .thenReturn(true) + `when`(fileSystem.read(simple.toString())).thenReturn(bufferedSource) + + val returnedValue = sourcePersister.read(simple).blockingGet() + assertThat(returnedValue).isEqualTo(bufferedSource) + } + + @Test + fun freshTest() { + `when`(fileSystem.getRecordState(TimeUnit.DAYS, 1L, SourcePersister.pathForBarcode(simple))) + .thenReturn(RecordState.FRESH) + + assertThat(sourcePersister.getRecordState(simple)).isEqualTo(RecordState.FRESH) + } + + @Test + fun staleTest() { + `when`(fileSystem.getRecordState(TimeUnit.DAYS, 1L, SourcePersister.pathForBarcode(simple))) + .thenReturn(RecordState.STALE) + + assertThat(sourcePersister.getRecordState(simple)).isEqualTo(RecordState.STALE) + } + + @Test + fun missingTest() { + `when`(fileSystem.getRecordState(TimeUnit.DAYS, 1L, SourcePersister.pathForBarcode(simple))) + .thenReturn(RecordState.MISSING) + + assertThat(sourcePersister.getRecordState(simple)).isEqualTo(RecordState.MISSING) + } + + @Test + @Throws(FileNotFoundException::class) + fun readDoesNotExist() { + `when`(fileSystem.exists(SourcePersister.pathForBarcode(simple))) + .thenReturn(false) + + sourcePersister.read(simple).test().assertError(FileNotFoundException::class.java) + } + + @Test + @Throws(IOException::class) + fun write() { + assertThat(sourcePersister.write(simple, bufferedSource).blockingGet()).isTrue() + } + + @Test + fun pathForBarcode() { + assertThat(SourcePersister.pathForBarcode(simple)).isEqualTo("typekey") + } +} From 9b7c9c43a61bd3492e54d6d9fce0688d3bb43962 Mon Sep 17 00:00:00 2001 From: Brian Plummer Date: Tue, 17 Jul 2018 12:35:40 +0200 Subject: [PATCH 039/498] second part of filesystem tests conversion to kotlin (#354) * second part of filesystem tests * pr feedback * pr feedback, rename tests --- .../external/fs3/SourceDiskDaoStoreTest.java | 83 ----------- .../external/fs3/SourceDiskDaoStoreTest.kt | 72 ++++++++++ .../fs3/SourceFilerReaderWriterStoreTest.java | 83 ----------- .../fs3/SourceFilerReaderWriterStoreTest.kt | 75 ++++++++++ .../external/fs3/SourcePersisterTest.java | 68 --------- .../external/fs3/SourcePersisterTest1.kt | 71 ++++++++++ .../fs3/StoreNetworkBeforeStaleFailTest.java | 76 ---------- .../fs3/StoreNetworkBeforeStaleFailTest.kt | 70 ++++++++++ .../fs3/StoreNetworkBeforeStaleTest.java | 132 ------------------ .../fs3/StoreNetworkBeforeStaleTest.kt | 119 ++++++++++++++++ .../android/external/fs3/UtilTest.java | 46 ------ .../nytimes/android/external/fs3/UtilTest.kt | 35 +++++ 12 files changed, 442 insertions(+), 488 deletions(-) delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/SourceDiskDaoStoreTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/SourceDiskDaoStoreTest.kt delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/SourceFilerReaderWriterStoreTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/SourceFilerReaderWriterStoreTest.kt delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/SourcePersisterTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/SourcePersisterTest1.kt delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.kt delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleTest.kt delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/UtilTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/UtilTest.kt diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/SourceDiskDaoStoreTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/SourceDiskDaoStoreTest.java deleted file mode 100644 index 671888d2f..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/SourceDiskDaoStoreTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.nytimes.android.external.fs3; - - -import com.google.gson.Gson; -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; -import com.nytimes.android.external.store3.middleware.GsonSourceParser; - -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.ByteArrayInputStream; - -import io.reactivex.Maybe; -import io.reactivex.Single; -import okio.BufferedSource; -import okio.Okio; - -import static com.google.common.base.Charsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class SourceDiskDaoStoreTest { - public static final String KEY = "key"; - @Mock - Fetcher fetcher; - @Mock - SourcePersister diskDAO; - private final BarCode barCode = new BarCode("value", KEY); - - - private static BufferedSource source(String data) { - return Okio.buffer(Okio.source(new ByteArrayInputStream(data.getBytes(UTF_8)))); - } - - @Test - public void testSimple() { - MockitoAnnotations.initMocks(this); - GsonSourceParser parser = new GsonSourceParser<>(new Gson(), Foo.class); - Store store = StoreBuilder.parsedWithKey() - .persister(diskDAO) - .fetcher(fetcher) - .parser(parser) - .open(); - - Foo foo = new Foo(); - foo.bar = barCode.getKey(); - - String sourceData = new Gson().toJson(foo); - - - BufferedSource source = source(sourceData); - Single value = Single.just(source); - when(fetcher.fetch(barCode)) - .thenReturn(value); - - when(diskDAO.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(value.toMaybe()); - - when(diskDAO.write(barCode, source)) - .thenReturn(Single.just(true)); - - Foo result = store.get(barCode).blockingGet(); - assertThat(result.bar).isEqualTo(KEY); - result = store.get(barCode).blockingGet(); - assertThat(result.bar).isEqualTo(KEY); - verify(fetcher, times(1)).fetch(barCode); - } - - private static class Foo { - String bar; - - Foo() { - } - } - -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/SourceDiskDaoStoreTest.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/SourceDiskDaoStoreTest.kt new file mode 100644 index 000000000..bc57b4467 --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/SourceDiskDaoStoreTest.kt @@ -0,0 +1,72 @@ +package com.nytimes.android.external.fs3 + + +import com.google.common.base.Charsets.UTF_8 +import com.google.gson.Gson +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import com.nytimes.android.external.store3.middleware.GsonSourceParser +import io.reactivex.Maybe +import io.reactivex.Single +import okio.BufferedSource +import okio.Okio +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.io.ByteArrayInputStream + +class SourceDiskDaoStoreTest { + @Mock + lateinit var fetcher: Fetcher + @Mock + lateinit var diskDAO: SourcePersister + private val barCode = BarCode("value", KEY) + + @Test + fun fetcherOnlyCalledOnce() { + MockitoAnnotations.initMocks(this) + val parser = GsonSourceParser(Gson(), Foo::class.java) + val store = StoreBuilder.parsedWithKey() + .persister(diskDAO) + .fetcher(fetcher) + .parser(parser) + .open() + + val foo = Foo() + foo.bar = barCode.getKey() + + val sourceData = Gson().toJson(foo) + val source = source(sourceData) + val value = Single.just(source) + `when`(fetcher.fetch(barCode)) + .thenReturn(value) + + `when`(diskDAO.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(value.toMaybe()) + + `when`(diskDAO.write(barCode, source)) + .thenReturn(Single.just(true)) + + var result = store.get(barCode).blockingGet() + assertThat(result.bar).isEqualTo(KEY) + result = store.get(barCode).blockingGet() + assertThat(result.bar).isEqualTo(KEY) + verify(fetcher, times(1)).fetch(barCode) + } + + private class Foo internal constructor() { + internal var bar: String? = null + } + + companion object { + private const val KEY = "key" + + private fun source(data: String): BufferedSource = Okio.buffer(Okio.source(ByteArrayInputStream(data.toByteArray(UTF_8)))) + } +} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/SourceFilerReaderWriterStoreTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/SourceFilerReaderWriterStoreTest.java deleted file mode 100644 index cd4cc96b3..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/SourceFilerReaderWriterStoreTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.google.gson.Gson; -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; -import com.nytimes.android.external.store3.middleware.GsonSourceParser; - -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.ByteArrayInputStream; - -import io.reactivex.Maybe; -import io.reactivex.Single; -import okio.BufferedSource; -import okio.Okio; - -import static com.google.common.base.Charsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class SourceFilerReaderWriterStoreTest { - public static final String KEY = "key"; - @Mock - Fetcher fetcher; - @Mock - SourceFileReader fileReader; - @Mock - SourceFileWriter fileWriter; - private final BarCode barCode = new BarCode("value", KEY); - - - private static BufferedSource source(String data) { - return Okio.buffer(Okio.source(new ByteArrayInputStream(data.getBytes(UTF_8)))); - } - - @Test - public void testSimple() { - MockitoAnnotations.initMocks(this); - GsonSourceParser parser = new GsonSourceParser<>(new Gson(), Foo.class); - Store simpleStore = StoreBuilder.parsedWithKey() - .persister(fileReader, fileWriter) - .fetcher(fetcher) - .parser(parser) - .open(); - - Foo foo = new Foo(); - foo.bar = barCode.getKey(); - - String sourceData = new Gson().toJson(foo); - - BufferedSource source = source(sourceData); - Single value = Single.just(source); - when(fetcher.fetch(barCode)) - .thenReturn(value); - - when(fileReader.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(value.toMaybe()); - - when(fileWriter.write(barCode, source)) - .thenReturn(Single.just(true)); - - Foo result = simpleStore.get(barCode).blockingGet(); - assertThat(result.bar).isEqualTo(KEY); - result = simpleStore.get(barCode).blockingGet(); - assertThat(result.bar).isEqualTo(KEY); - verify(fetcher, times(1)).fetch(barCode); - } - - private static class Foo { - String bar; - - Foo() { - } - } - -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/SourceFilerReaderWriterStoreTest.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/SourceFilerReaderWriterStoreTest.kt new file mode 100644 index 000000000..712ebd13a --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/SourceFilerReaderWriterStoreTest.kt @@ -0,0 +1,75 @@ +package com.nytimes.android.external.fs3 + +import com.google.common.base.Charsets.UTF_8 +import com.google.gson.Gson +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import com.nytimes.android.external.store3.middleware.GsonSourceParser +import io.reactivex.Maybe +import io.reactivex.Single +import okio.BufferedSource +import okio.Okio +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.io.ByteArrayInputStream + +class SourceFilerReaderWriterStoreTest { + @Mock + lateinit var fetcher: Fetcher + @Mock + lateinit var fileReader: SourceFileReader + @Mock + lateinit var fileWriter: SourceFileWriter + private val barCode = BarCode("value", KEY) + + @Test + fun fetcherOnlyCalledOnce() { + MockitoAnnotations.initMocks(this) + val parser = GsonSourceParser(Gson(), Foo::class.java) + val simpleStore = StoreBuilder.parsedWithKey() + .persister(fileReader, fileWriter) + .fetcher(fetcher) + .parser(parser) + .open() + + val foo = Foo() + foo.bar = barCode.key + + val sourceData = Gson().toJson(foo) + + val source = source(sourceData) + val value = Single.just(source) + `when`(fetcher.fetch(barCode)) + .thenReturn(value) + + `when`(fileReader.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(value.toMaybe()) + + `when`(fileWriter.write(barCode, source)) + .thenReturn(Single.just(true)) + + var result = simpleStore.get(barCode).blockingGet() + assertThat(result.bar).isEqualTo(KEY) + result = simpleStore.get(barCode).blockingGet() + assertThat(result.bar).isEqualTo(KEY) + verify>(fetcher, times(1)).fetch(barCode) + } + + private class Foo internal constructor() { + internal var bar: String? = null + } + + companion object { + private const val KEY = "key" + + private fun source(data: String): BufferedSource = Okio.buffer(Okio.source(ByteArrayInputStream(data.toByteArray(UTF_8)))) + } + +} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/SourcePersisterTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/SourcePersisterTest.java deleted file mode 100644 index b3940bc7f..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/SourcePersisterTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.store3.base.impl.BarCode; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.FileNotFoundException; -import java.io.IOException; - -import okio.BufferedSource; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -public class SourcePersisterTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Mock - FileSystem fileSystem; - @Mock - BufferedSource bufferedSource; - - private SourcePersister sourcePersister; - private final BarCode simple = new BarCode("type", "key"); - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - sourcePersister = new SourcePersister(fileSystem); - } - - @Test - public void readExists() throws FileNotFoundException { - when(fileSystem.exists(simple.toString())) - .thenReturn(true); - when(fileSystem.read(simple.toString())).thenReturn(bufferedSource); - - BufferedSource returnedValue = sourcePersister.read(simple).blockingGet(); - assertThat(returnedValue).isEqualTo(bufferedSource); - } - - @Test - @SuppressWarnings("CheckReturnValue") - public void readDoesNotExist() throws FileNotFoundException { - when(fileSystem.exists(SourcePersister.pathForBarcode(simple))) - .thenReturn(false); - - sourcePersister.read(simple).test().assertError(FileNotFoundException.class); - } - - @Test - public void write() throws IOException { - assertThat(sourcePersister.write(simple, bufferedSource).blockingGet()).isTrue(); - } - - @Test - public void pathForBarcode() { - assertThat(SourcePersister.pathForBarcode(simple)).isEqualTo("typekey"); - } -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/SourcePersisterTest1.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/SourcePersisterTest1.kt new file mode 100644 index 000000000..91fcc47ed --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/SourcePersisterTest1.kt @@ -0,0 +1,71 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.store3.base.impl.BarCode + +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +import java.io.FileNotFoundException +import java.io.IOException + +import okio.BufferedSource + +import org.assertj.core.api.Assertions.assertThat +import org.mockito.Mockito.`when` + +class SourcePersisterTest { + + @Rule + @JvmField + var expectedException = ExpectedException.none() + + @Mock + lateinit var fileSystem: FileSystem + @Mock + lateinit var bufferedSource: BufferedSource + + private lateinit var sourcePersister: SourcePersister + private val simple = BarCode("type", "key") + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + sourcePersister = SourcePersister(fileSystem) + } + + @Test + @Throws(FileNotFoundException::class) + fun readExists() { + `when`(fileSystem.exists(simple.toString())) + .thenReturn(true) + `when`(fileSystem.read(simple.toString())).thenReturn(bufferedSource) + + val returnedValue = sourcePersister.read(simple).blockingGet() + assertThat(returnedValue).isEqualTo(bufferedSource) + } + + @Test + @Throws(FileNotFoundException::class) + fun readDoesNotExist() { + `when`(fileSystem.exists(SourcePersister.pathForBarcode(simple))) + .thenReturn(false) + + sourcePersister.read(simple).test().assertError(FileNotFoundException::class.java) + } + + @Test + @Throws(IOException::class) + fun write() { + assertThat(sourcePersister.write(simple, bufferedSource).blockingGet()).isTrue() + } + + @Test + fun pathForBarcode() { + assertThat(SourcePersister.pathForBarcode(simple)).isEqualTo("typekey") + } +} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.java deleted file mode 100644 index 06c1f7968..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.RecordProvider; -import com.nytimes.android.external.store3.base.RecordState; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.runners.MockitoJUnitRunner; - -import javax.annotation.Nonnull; - -import io.reactivex.Maybe; -import io.reactivex.Single; -import okio.BufferedSource; - -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class StoreNetworkBeforeStaleFailTest { - static final Exception SORRY = new Exception("sorry"); - private static final BarCode barCode = new BarCode("key", "value"); - @Mock - Fetcher fetcher; - Store store; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - store = StoreBuilder.barcode() - .fetcher(fetcher) - .persister(new TestPersister()) - .networkBeforeStale() - .open(); - - } - - @Test - public void networkBeforeStaleNoNetworkResponse() { - Single exception = Single.error(SORRY); - when(fetcher.fetch(barCode)) - .thenReturn(exception); - store.get(barCode).test().assertError(SORRY); - verify(fetcher, times(1)).fetch(barCode); - } - - private static final class TestPersister implements Persister, RecordProvider { - @Nonnull - @Override - public RecordState getRecordState(@Nonnull BarCode barCode) { - return RecordState.MISSING; - } - - @Nonnull - @Override - public Maybe read(@Nonnull BarCode barCode) { - return Maybe.error(SORRY); - } - - @Nonnull - @Override - public Single write(@Nonnull BarCode barCode, - @Nonnull BufferedSource bufferedSource) { - return Single.just(true); - } - } -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.kt new file mode 100644 index 000000000..531fe23f9 --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleFailTest.kt @@ -0,0 +1,70 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.RecordProvider +import com.nytimes.android.external.store3.base.RecordState +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder + +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.runners.MockitoJUnitRunner + +import io.reactivex.Maybe +import io.reactivex.Single +import okio.BufferedSource + +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` + +@RunWith(MockitoJUnitRunner::class) +class StoreNetworkBeforeStaleFailTest { + @Mock + lateinit var fetcher: Fetcher + private lateinit var store: Store + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + store = StoreBuilder.barcode() + .fetcher(fetcher) + .persister(TestPersister()) + .networkBeforeStale() + .open() + } + + @Test + fun networkBeforeStaleNoNetworkResponse() { + val exception = Single.error(SORRY) + `when`(fetcher.fetch(barCode)) + .thenReturn(exception) + store.get(barCode).test().assertError(SORRY) + verify>(fetcher, times(1)).fetch(barCode) + } + + private class TestPersister : Persister, RecordProvider { + override fun getRecordState(barCode: BarCode): RecordState { + return RecordState.MISSING + } + + override fun read(barCode: BarCode): Maybe { + return Maybe.error(SORRY) + } + + override fun write(barCode: BarCode, + bufferedSource: BufferedSource): Single { + return Single.just(true) + } + } + + companion object { + private val SORRY = Exception("sorry") + private val barCode = BarCode("key", "value") + } +} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleTest.java deleted file mode 100644 index 668f57c1a..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.RecordState; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.runners.MockitoJUnitRunner; - -import io.reactivex.Maybe; -import io.reactivex.Single; -import okio.BufferedSource; - -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class StoreNetworkBeforeStaleTest { - - Exception sorry = new Exception("sorry"); - @Mock - Fetcher fetcher; - @Mock - RecordPersister persister; - @Mock - BufferedSource network1; - @Mock - BufferedSource network2; - @Mock - BufferedSource disk1; - @Mock - BufferedSource disk2; - - private final BarCode barCode = new BarCode("key", "value"); - private Store store; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - store = StoreBuilder.barcode() - .fetcher(fetcher) - .persister(persister) - .networkBeforeStale() - .open(); - - } - - @Test - public void networkBeforeDiskWhenStale() { - when(fetcher.fetch(barCode)) - .thenReturn(Single.error(new Exception())); - when(persister.read(barCode)) - .thenReturn(Maybe.just(disk1)); //get should return from disk - when(persister.getRecordState(barCode)).thenReturn(RecordState.STALE); - - when(persister.write(barCode, network1)) - .thenReturn(Single.just(true)); - - store.get(barCode).test().awaitTerminalEvent(); - - InOrder inOrder = inOrder(fetcher, persister); - inOrder.verify(fetcher, times(1)).fetch(barCode); - inOrder.verify(persister, times(1)).read(barCode); - verify(persister, never()).write(barCode, network1); - } - - @Test - public void noNetworkBeforeStaleWhenMissingRecord() { - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(network1)); - when(persister.read(barCode)) - .thenReturn(Maybe.empty(), Maybe.just(disk1)); //first call should return - // empty, second call after network should return the network value - when(persister.getRecordState(barCode)).thenReturn(RecordState.MISSING); - - when(persister.write(barCode, network1)) - .thenReturn(Single.just(true)); - - store.get(barCode).test().awaitTerminalEvent(); - - InOrder inOrder = inOrder(fetcher, persister); - inOrder.verify(persister, times(1)).read(barCode); - inOrder.verify(fetcher, times(1)).fetch(barCode); - inOrder.verify(persister, times(1)).write(barCode, network1); - inOrder.verify(persister, times(1)).read(barCode); - } - - @Test - public void noNetworkBeforeStaleWhenFreshRecord() { - when(persister.read(barCode)) - .thenReturn(Maybe.just(disk1)); //get should return from disk - when(persister.getRecordState(barCode)).thenReturn(RecordState.FRESH); - - store.get(barCode).test().awaitTerminalEvent(); - - verify(fetcher, never()).fetch(barCode); - verify(persister, never()).write(barCode, network1); - verify(persister, times(1)).read(barCode); - } - - @Test - public void networkBeforeStaleNoNetworkResponse() { - Single singleError = Single.error(sorry); - Maybe maybeError = Maybe.error(sorry); - when(fetcher.fetch(barCode)) - .thenReturn(singleError); - when(persister.read(barCode)) - .thenReturn(maybeError, maybeError); //first call should return - // empty, second call after network should return the network value - when(persister.getRecordState(barCode)).thenReturn(RecordState.MISSING); - - when(persister.write(barCode, network1)) - .thenReturn(Single.just(true)); - - store.get(barCode).test().assertError(sorry); - - InOrder inOrder = inOrder(fetcher, persister); - inOrder.verify(persister, times(1)).read(barCode); - inOrder.verify(fetcher, times(1)).fetch(barCode); - inOrder.verify(persister, times(1)).read(barCode); - } -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleTest.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleTest.kt new file mode 100644 index 000000000..207a0eb24 --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreNetworkBeforeStaleTest.kt @@ -0,0 +1,119 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.RecordState +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import io.reactivex.Maybe +import io.reactivex.Single +import okio.BufferedSource +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.* +import org.mockito.MockitoAnnotations +import org.mockito.runners.MockitoJUnitRunner + +@RunWith(MockitoJUnitRunner::class) +class StoreNetworkBeforeStaleTest { + + private val sorry = Exception("sorry") + @Mock + lateinit var fetcher: Fetcher + @Mock + lateinit var persister: RecordPersister + @Mock + lateinit var network1: BufferedSource + @Mock + lateinit var disk1: BufferedSource + + private val barCode = BarCode("key", "value") + private lateinit var store: Store + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + store = StoreBuilder.barcode() + .fetcher(fetcher) + .persister(persister) + .networkBeforeStale() + .open() + } + + @Test + fun networkBeforeDiskWhenStale() { + `when`(fetcher.fetch(barCode)) + .thenReturn(Single.error(Exception())) + `when`(persister.read(barCode)) + .thenReturn(Maybe.just(disk1)) //get should return from disk + `when`(persister.getRecordState(barCode)).thenReturn(RecordState.STALE) + + `when`(persister.write(barCode, network1)) + .thenReturn(Single.just(true)) + + store.get(barCode).test().awaitTerminalEvent() + + val inOrder = inOrder(fetcher, persister) + inOrder.verify>(fetcher, times(1)).fetch(barCode) + inOrder.verify(persister, times(1)).read(barCode) + verify(persister, never()).write(barCode, network1) + } + + @Test + fun noNetworkBeforeStaleWhenMissingRecord() { + `when`(fetcher.fetch(barCode)) + .thenReturn(Single.just(network1)) + `when`(persister.read(barCode)) + .thenReturn(Maybe.empty(), Maybe.just(disk1)) //first call should return + // empty, second call after network should return the network value + `when`(persister.getRecordState(barCode)).thenReturn(RecordState.MISSING) + + `when`(persister.write(barCode, network1)) + .thenReturn(Single.just(true)) + + store.get(barCode).test().awaitTerminalEvent() + + val inOrder = inOrder(fetcher, persister) + inOrder.verify(persister, times(1)).read(barCode) + inOrder.verify>(fetcher, times(1)).fetch(barCode) + inOrder.verify(persister, times(1)).write(barCode, network1) + inOrder.verify(persister, times(1)).read(barCode) + } + + @Test + fun noNetworkBeforeStaleWhenFreshRecord() { + `when`(persister.read(barCode)) + .thenReturn(Maybe.just(disk1)) //get should return from disk + `when`(persister.getRecordState(barCode)).thenReturn(RecordState.FRESH) + + store.get(barCode).test().awaitTerminalEvent() + + verify>(fetcher, never()).fetch(barCode) + verify(persister, never()).write(barCode, network1) + verify(persister, times(1)).read(barCode) + } + + @Test + fun networkBeforeStaleNoNetworkResponse() { + val singleError = Single.error(sorry) + val maybeError = Maybe.error(sorry) + `when`(fetcher.fetch(barCode)) + .thenReturn(singleError) + `when`(persister.read(barCode)) + .thenReturn(maybeError, maybeError) //first call should return + // empty, second call after network should return the network value + `when`(persister.getRecordState(barCode)).thenReturn(RecordState.MISSING) + + `when`(persister.write(barCode, network1)) + .thenReturn(Single.just(true)) + + store.get(barCode).test().assertError(sorry) + + val inOrder = inOrder(fetcher, persister) + inOrder.verify(persister, times(1)).read(barCode) + inOrder.verify>(fetcher, times(1)).fetch(barCode) + inOrder.verify(persister, times(1)).read(barCode) + } +} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/UtilTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/UtilTest.java deleted file mode 100644 index b4c782887..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/UtilTest.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.nytimes.android.external.fs3; - -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; - -import okio.BufferedSource; -import okio.Okio; - -import static com.google.common.base.Charsets.UTF_8; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class UtilTest { - - private final Util util = new Util(); - - @Test - public void testSimplifyPath() { - assertThat(util.simplifyPath("/a/b/c/d")).isEqualTo("/a/b/c/d"); - assertThat(util.simplifyPath("/a/../b/")).isEqualTo("/b"); - assertThat(util.simplifyPath("/a/./b/c/../d")).isEqualTo("/a/b/d"); - assertThat(util.simplifyPath("./a")).isEqualTo("/a"); - assertThat(util.simplifyPath(null)).isEqualTo(""); - assertThat(util.simplifyPath("")).isEqualTo(""); - } - - @Test - public void createParentDirTest() throws IOException { - File child = mock(File.class); - File parent = mock(File.class); - when(child.getCanonicalFile()).thenReturn(child); - when(child.getParentFile()).thenReturn(parent); - when(parent.isDirectory()).thenReturn(true); - util.createParentDirs(child); - verify(parent).mkdirs(); - } - - static BufferedSource source(String data) { - return Okio.buffer(Okio.source(new ByteArrayInputStream(data.getBytes(UTF_8)))); - } -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/UtilTest.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/UtilTest.kt new file mode 100644 index 000000000..54b6a4e56 --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/UtilTest.kt @@ -0,0 +1,35 @@ +package com.nytimes.android.external.fs3 + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.mockito.Mockito.`when` +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import java.io.File +import java.io.IOException + +class UtilTest { + + private val util = Util() + + @Test + fun testSimplifyPath() { + assertThat(util.simplifyPath("/a/b/c/d")).isEqualTo("/a/b/c/d") + assertThat(util.simplifyPath("/a/../b/")).isEqualTo("/b") + assertThat(util.simplifyPath("/a/./b/c/../d")).isEqualTo("/a/b/d") + assertThat(util.simplifyPath("./a")).isEqualTo("/a") + assertThat(util.simplifyPath("")).isEqualTo("") + } + + @Test + @Throws(IOException::class) + fun createParentDirTest() { + val child = mock(File::class.java) + val parent = mock(File::class.java) + `when`(child.canonicalFile).thenReturn(child) + `when`(child.parentFile).thenReturn(parent) + `when`(parent.isDirectory).thenReturn(true) + util.createParentDirs(child) + verify(parent).mkdirs() + } +} From 40a70c6c0183700be4547116c65f20cb8a7ab688 Mon Sep 17 00:00:00 2001 From: Brian Plummer Date: Wed, 18 Jul 2018 21:52:04 +0200 Subject: [PATCH 040/498] convert jackson middleware tests to kotlin (#355) * bump jackson rev, include jackson kotlin * configure jackson for kotlin data classes * convert tests to kotlin --- buildsystem/dependencies.gradle | 3 +- middleware-jackson/build.gradle | 29 +++- .../jackson/JacksonParserFactory.java | 7 +- .../jackson/JacksonReaderParser.java | 3 +- .../jackson/JacksonSourceParser.java | 3 +- .../jackson/JacksonStringParser.java | 3 +- .../jackson/JacksonReaderParserStoreTest.java | 130 ----------------- .../jackson/JacksonReaderParserStoreTest.kt | 121 ++++++++++++++++ .../jackson/JacksonSourceParserStoreTest.java | 137 ------------------ .../jackson/JacksonSourceParserStoreTest.kt | 125 ++++++++++++++++ .../jackson/JacksonStringParserStoreTest.java | 124 ---------------- .../jackson/JacksonStringParserStoreTest.kt | 117 +++++++++++++++ .../store3/middleware/jackson/Model.kt | 9 ++ .../store3/middleware/jackson/data/Bar.java | 12 -- .../store3/middleware/jackson/data/Foo.java | 18 --- 15 files changed, 407 insertions(+), 434 deletions(-) delete mode 100644 middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParserStoreTest.java create mode 100644 middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParserStoreTest.kt delete mode 100644 middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParserStoreTest.java create mode 100644 middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParserStoreTest.kt delete mode 100644 middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonStringParserStoreTest.java create mode 100644 middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonStringParserStoreTest.kt create mode 100644 middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/Model.kt delete mode 100644 middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/data/Bar.java delete mode 100644 middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/data/Foo.java diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index 20e08db22..1b329a81a 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -39,7 +39,7 @@ ext.versions = [ okio : '1.13.0', gson : '2.8.1', moshi : '1.6.0', - jackson : '2.8.8', + jackson : '2.9.6', guava : '19.0', javapoet : '1.8.0', immutables : '2.2.1', @@ -98,6 +98,7 @@ ext.libraries = [ moshi : "com.squareup.moshi:moshi:$versions.moshi", moshiCodegen : "com.squareup.moshi:moshi-kotlin-codegen:$versions.moshi", jacksonCore : "com.fasterxml.jackson.core:jackson-core:$versions.jackson", + jacksonKotlin : "com.fasterxml.jackson.module:jackson-module-kotlin:$versions.jackson", jacksonDatabind : "com.fasterxml.jackson.core:jackson-databind:$versions.jackson", guava : "com.google.guava:guava:$versions.guava", javapoet : "com.squareup:javapoet:$versions.javapoet", diff --git a/middleware-jackson/build.gradle b/middleware-jackson/build.gradle index 199bc69bf..1ef030f2a 100644 --- a/middleware-jackson/build.gradle +++ b/middleware-jackson/build.gradle @@ -1,3 +1,14 @@ +buildscript { + tasks.withType(JavaCompile) { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +plugins { + id 'org.jetbrains.kotlin.jvm' +} + apply plugin: 'java' group = GROUP @@ -8,6 +19,7 @@ dependencies { implementation project(path: ':filesystem') compileOnly libraries.jsr305 implementation libraries.jacksonCore + implementation libraries.jacksonKotlin implementation libraries.jacksonDatabind implementation libraries.okio compileOnly libraries.javax @@ -15,13 +27,18 @@ dependencies { testImplementation libraries.junit } -buildscript { - tasks.withType(JavaCompile) { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } -} apply from: rootProject.file("gradle/maven-push.gradle") apply from: rootProject.file("gradle/checkstyle.gradle") apply from: rootProject.file("gradle/pmd.gradle") + +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} \ No newline at end of file diff --git a/middleware-jackson/src/main/java/com/nytimes/android/external/store3/middleware/jackson/JacksonParserFactory.java b/middleware-jackson/src/main/java/com/nytimes/android/external/store3/middleware/jackson/JacksonParserFactory.java index 570d1e185..fae6af2e5 100644 --- a/middleware-jackson/src/main/java/com/nytimes/android/external/store3/middleware/jackson/JacksonParserFactory.java +++ b/middleware-jackson/src/main/java/com/nytimes/android/external/store3/middleware/jackson/JacksonParserFactory.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.module.kotlin.KotlinModule; import com.nytimes.android.external.store3.base.Parser; import java.io.Reader; @@ -57,7 +58,7 @@ public static Parser createStringParser(@Nonnull ObjectMapper obj */ @Nonnull public static Parser createStringParser(@Nonnull Class type) { - return createStringParser(new ObjectMapper(), type); + return createStringParser(new ObjectMapper().registerModule(new KotlinModule()), type); } /** @@ -100,7 +101,7 @@ public static Parser createSourceParser(@Nonnull ObjectMa */ @Nonnull public static Parser createSourceParser(@Nonnull Type type) { - return createSourceParser(new ObjectMapper(), type); + return createSourceParser(new ObjectMapper().registerModule(new KotlinModule()), type); } /** @@ -143,6 +144,6 @@ public static Parser createReaderParser(@Nonnull ObjectMapper obj */ @Nonnull public static Parser createReaderParser(@Nonnull Type type) { - return createReaderParser(new ObjectMapper(), type); + return createReaderParser(new ObjectMapper().registerModule(new KotlinModule()), type); } } diff --git a/middleware-jackson/src/main/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParser.java b/middleware-jackson/src/main/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParser.java index e53e76742..b68bfa778 100644 --- a/middleware-jackson/src/main/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParser.java +++ b/middleware-jackson/src/main/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParser.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.module.kotlin.KotlinModule; import com.nytimes.android.external.store3.base.Parser; import com.nytimes.android.external.store3.util.ParserException; @@ -22,7 +23,7 @@ public class JacksonReaderParser implements Parser { private final JavaType parsedType; public JacksonReaderParser(@Nonnull JsonFactory jsonFactory, @Nonnull Type type) { - objectMapper = new ObjectMapper(jsonFactory); + objectMapper = new ObjectMapper(jsonFactory).registerModule(new KotlinModule()); parsedType = objectMapper.constructType(type); } diff --git a/middleware-jackson/src/main/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParser.java b/middleware-jackson/src/main/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParser.java index a0c460550..71429f5d1 100644 --- a/middleware-jackson/src/main/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParser.java +++ b/middleware-jackson/src/main/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParser.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.module.kotlin.KotlinModule; import com.nytimes.android.external.store3.base.Parser; import com.nytimes.android.external.store3.util.ParserException; @@ -22,7 +23,7 @@ public class JacksonSourceParser implements Parser implements Parser { private final JavaType parsedType; public JacksonStringParser(@Nonnull JsonFactory jsonFactory, @Nonnull Type type) { - objectMapper = new ObjectMapper(jsonFactory); + objectMapper = new ObjectMapper(jsonFactory).registerModule(new KotlinModule()); parsedType = objectMapper.constructType(type); } diff --git a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParserStoreTest.java b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParserStoreTest.java deleted file mode 100644 index 2186d8ff9..000000000 --- a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParserStoreTest.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.nytimes.android.external.store3.middleware.jackson; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; -import com.nytimes.android.external.store3.middleware.jackson.data.Foo; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.Reader; -import java.io.StringReader; - - -import io.reactivex.Maybe; -import io.reactivex.Single; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class JacksonReaderParserStoreTest { - - private static final String KEY = "key"; - private static final String sourceString = - "{\"number\":123,\"string\":\"abc\",\"bars\":[{\"string\":\"def\"},{\"string\":\"ghi\"}]}"; - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Mock - Fetcher fetcher; - @Mock - Persister persister; - private final BarCode barCode = new BarCode("value", KEY); - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - Reader source = new StringReader(sourceString); - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(source)); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(source)); - - when(persister.write(barCode, source)) - .thenReturn(Single.just(true)); - } - - @Test - public void testDefaultJacksonReaderParser() { - Parser parser = JacksonParserFactory.createReaderParser(Foo.class); - Store store = StoreBuilder.parsedWithKey() - .persister(persister) - .fetcher(fetcher) - .parser(parser) - .open(); - - Foo result = store.get(barCode).blockingGet(); - - validateFoo(result); - - verify(fetcher, times(1)).fetch(barCode); - } - - @Test - public void testCustomJsonFactoryReaderParser() { - JsonFactory jsonFactory = new JsonFactory(); - - Parser parser = JacksonParserFactory.createReaderParser(jsonFactory, Foo.class); - - Store store = StoreBuilder.parsedWithKey() - .persister(persister) - .fetcher(fetcher) - .parser(parser) - .open(); - - Foo result = store.get(barCode).blockingGet(); - - validateFoo(result); - - verify(fetcher, times(1)).fetch(barCode); - } - - private void validateFoo(Foo foo) { - assertNotNull(foo); - assertEquals(foo.number, 123); - assertEquals(foo.string, "abc"); - assertEquals(foo.bars.size(), 2); - assertEquals(foo.bars.get(0).string, "def"); - assertEquals(foo.bars.get(1).string, "ghi"); - } - - @Test - public void testNullJsonFactory() { - expectedException.expect(NullPointerException.class); - JacksonParserFactory.createReaderParser((JsonFactory) null, Foo.class); - } - - @Test - public void testNullTypeWithValidJsonFactory() { - expectedException.expect(NullPointerException.class); - JacksonParserFactory.createReaderParser(new JsonFactory(), null); - } - - @Test - public void testNullObjectMapper() { - expectedException.expect(NullPointerException.class); - JacksonParserFactory.createReaderParser((ObjectMapper) null, Foo.class); - } - - @Test - public void testNullType() { - expectedException.expect(NullPointerException.class); - JacksonParserFactory.createStringParser(null); - } - -} diff --git a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParserStoreTest.kt b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParserStoreTest.kt new file mode 100644 index 000000000..aca4cd638 --- /dev/null +++ b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonReaderParserStoreTest.kt @@ -0,0 +1,121 @@ +package com.nytimes.android.external.store3.middleware.jackson + + +import com.fasterxml.jackson.core.JsonFactory +import com.fasterxml.jackson.databind.ObjectMapper +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import io.reactivex.Maybe +import io.reactivex.Single +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.io.Reader +import java.io.StringReader + +class JacksonReaderParserStoreTest { + @Rule + @JvmField + var expectedException = ExpectedException.none() + @Mock + lateinit var fetcher: Fetcher + @Mock + lateinit var persister: Persister + private val barCode = BarCode("value", KEY) + + @Before + @Throws(Exception::class) + fun setUp() { + MockitoAnnotations.initMocks(this) + + val source = StringReader(sourceString) + `when`(fetcher.fetch(barCode)) + .thenReturn(Single.just(source as Reader)) + + `when`(persister.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(Maybe.just(source)) + + `when`(persister.write(barCode, source)) + .thenReturn(Single.just(true)) + } + + @Test + fun testDefaultJacksonReaderParser() { + val parser = JacksonParserFactory.createReaderParser(Foo::class.java) + val store = StoreBuilder.parsedWithKey() + .persister(persister) + .fetcher(fetcher) + .parser(parser) + .open() + + val result = store.get(barCode).blockingGet() + validateFoo(result) + verify>(fetcher, times(1)).fetch(barCode) + } + + @Test + fun testCustomJsonFactoryReaderParser() { + val jsonFactory = JsonFactory() + + val parser = JacksonParserFactory.createReaderParser(jsonFactory, Foo::class.java) + + val store = StoreBuilder.parsedWithKey() + .persister(persister) + .fetcher(fetcher) + .parser(parser) + .open() + + val result = store.get(barCode).blockingGet() + validateFoo(result) + verify>(fetcher, times(1)).fetch(barCode) + } + + private fun validateFoo(foo: Foo) { + assertNotNull(foo) + assertEquals(foo.number.toLong(), 123) + assertEquals(foo.string, "abc") + assertEquals(foo.bars.size.toLong(), 2) + assertEquals(foo.bars[0].string, "def") + assertEquals(foo.bars[1].string, "ghi") + } + + @Test + fun testNullJsonFactory() { + expectedException.expect(NullPointerException::class.java) + JacksonParserFactory.createReaderParser((null as JsonFactory?)!!, Foo::class.java) + } + + @Test + fun testNullTypeWithValidJsonFactory() { + expectedException.expect(NullPointerException::class.java) + JacksonParserFactory.createReaderParser(JsonFactory(), null!!) + } + + @Test + fun testNullObjectMapper() { + expectedException.expect(NullPointerException::class.java) + JacksonParserFactory.createReaderParser((null as ObjectMapper?)!!, Foo::class.java) + } + + @Test + fun testNullType() { + expectedException.expect(NullPointerException::class.java) + JacksonParserFactory.createStringParser(null!!) + } + + companion object { + private val KEY = "key" + private val sourceString = "{\"number\":123,\"string\":\"abc\",\"bars\":[{\"string\":\"def\"},{\"string\":\"ghi\"}]}" + } +} diff --git a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParserStoreTest.java b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParserStoreTest.java deleted file mode 100644 index e13902d5f..000000000 --- a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParserStoreTest.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.nytimes.android.external.store3.middleware.jackson; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; -import com.nytimes.android.external.store3.middleware.jackson.data.Foo; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.ByteArrayInputStream; -import java.nio.charset.Charset; - -import io.reactivex.Maybe; -import io.reactivex.Single; -import okio.BufferedSource; -import okio.Okio; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class JacksonSourceParserStoreTest { - - private static final String KEY = "key"; - private static final String sourceString = - "{\"number\":123,\"string\":\"abc\",\"bars\":[{\"string\":\"def\"},{\"string\":\"ghi\"}]}"; - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Mock - Fetcher fetcher; - @Mock - Persister persister; - private final BarCode barCode = new BarCode("value", KEY); - - private static BufferedSource source(String data) { - return Okio.buffer(Okio.source(new ByteArrayInputStream(data.getBytes(Charset.defaultCharset())))); - } - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - BufferedSource bufferedSource = source(sourceString); - assertNotNull(bufferedSource); - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(bufferedSource)); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(bufferedSource)); - - when(persister.write(barCode, bufferedSource)) - .thenReturn(Single.just(true)); - } - - @Test - public void testDefaultJacksonSourceParser() { - Parser parser = JacksonParserFactory.createSourceParser(Foo.class); - Store store = StoreBuilder.parsedWithKey() - .persister(persister) - .fetcher(fetcher) - .parser(parser) - .open(); - - Foo result = store.get(barCode).blockingGet(); - - validateFoo(result); - - verify(fetcher, times(1)).fetch(barCode); - } - - @Test - public void testCustomJsonFactorySourceParser() { - JsonFactory jsonFactory = new JsonFactory(); - - Parser parser = JacksonParserFactory.createSourceParser(jsonFactory, Foo.class); - - Store store = StoreBuilder.parsedWithKey() - .persister(persister) - .fetcher(fetcher) - .parser(parser) - .open(); - - Foo result = store.get(barCode).blockingGet(); - - validateFoo(result); - - verify(fetcher, times(1)).fetch(barCode); - } - - private void validateFoo(Foo foo) { - assertNotNull(foo); - assertEquals(foo.number, 123); - assertEquals(foo.string, "abc"); - assertEquals(foo.bars.size(), 2); - assertEquals(foo.bars.get(0).string, "def"); - assertEquals(foo.bars.get(1).string, "ghi"); - } - - @Test - public void testNullJsonFactory() { - expectedException.expect(NullPointerException.class); - JacksonParserFactory.createStringParser((JsonFactory) null, Foo.class); - } - - @Test - public void testNullTypeWithValidJsonFactory() { - expectedException.expect(NullPointerException.class); - JacksonParserFactory.createStringParser(new JsonFactory(), null); - } - - @Test - public void testNullObjectMapper() { - expectedException.expect(NullPointerException.class); - JacksonParserFactory.createStringParser((ObjectMapper) null, Foo.class); - } - - @Test - public void testNullType() { - expectedException.expect(NullPointerException.class); - JacksonParserFactory.createStringParser(null); - } - -} diff --git a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParserStoreTest.kt b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParserStoreTest.kt new file mode 100644 index 000000000..7791c2b90 --- /dev/null +++ b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonSourceParserStoreTest.kt @@ -0,0 +1,125 @@ +package com.nytimes.android.external.store3.middleware.jackson + +import com.fasterxml.jackson.core.JsonFactory +import com.fasterxml.jackson.databind.ObjectMapper +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import io.reactivex.Maybe +import io.reactivex.Single +import okio.BufferedSource +import okio.Okio +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.io.ByteArrayInputStream +import java.nio.charset.Charset + +class JacksonSourceParserStoreTest { + @Rule + @JvmField + var expectedException = ExpectedException.none() + @Mock + lateinit var fetcher: Fetcher + @Mock + lateinit var persister: Persister + private val barCode = BarCode("value", KEY) + + @Before + @Throws(Exception::class) + fun setUp() { + MockitoAnnotations.initMocks(this) + + val bufferedSource = source(sourceString) + assertNotNull(bufferedSource) + + `when`(fetcher.fetch(barCode)) + .thenReturn(Single.just(bufferedSource)) + + `when`(persister.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(Maybe.just(bufferedSource)) + + `when`(persister.write(barCode, bufferedSource)) + .thenReturn(Single.just(true)) + } + + @Test + fun testDefaultJacksonSourceParser() { + val parser = JacksonParserFactory.createSourceParser(Foo::class.java) + val store = StoreBuilder.parsedWithKey() + .persister(persister) + .fetcher(fetcher) + .parser(parser) + .open() + + val result = store.get(barCode).blockingGet() + validateFoo(result) + verify>(fetcher, times(1)).fetch(barCode) + } + + @Test + fun testCustomJsonFactorySourceParser() { + val jsonFactory = JsonFactory() + + val parser = JacksonParserFactory.createSourceParser(jsonFactory, Foo::class.java) + val store = StoreBuilder.parsedWithKey() + .persister(persister) + .fetcher(fetcher) + .parser(parser) + .open() + + val result = store.get(barCode).blockingGet() + validateFoo(result) + verify>(fetcher, times(1)).fetch(barCode) + } + + private fun validateFoo(foo: Foo) { + assertNotNull(foo) + assertEquals(foo.number.toLong(), 123) + assertEquals(foo.string, "abc") + assertEquals(foo.bars.size.toLong(), 2) + assertEquals(foo.bars[0].string, "def") + assertEquals(foo.bars[1].string, "ghi") + } + + @Test + fun testNullJsonFactory() { + expectedException.expect(NullPointerException::class.java) + JacksonParserFactory.createStringParser((null as JsonFactory?)!!, Foo::class.java) + } + + @Test + fun testNullTypeWithValidJsonFactory() { + expectedException.expect(NullPointerException::class.java) + JacksonParserFactory.createStringParser(JsonFactory(), null!!) + } + + @Test + fun testNullObjectMapper() { + expectedException.expect(NullPointerException::class.java) + JacksonParserFactory.createStringParser((null as ObjectMapper?)!!, Foo::class.java) + } + + @Test + fun testNullType() { + expectedException.expect(NullPointerException::class.java) + JacksonParserFactory.createStringParser(null!!) + } + + companion object { + + private val KEY = "key" + private val sourceString = "{\"number\":123,\"string\":\"abc\",\"bars\":[{\"string\":\"def\"},{\"string\":\"ghi\"}]}" + private fun source(data: String): BufferedSource = Okio.buffer(Okio.source(ByteArrayInputStream(data.toByteArray(Charset.defaultCharset())))) + } +} diff --git a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonStringParserStoreTest.java b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonStringParserStoreTest.java deleted file mode 100644 index 58598b838..000000000 --- a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonStringParserStoreTest.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.nytimes.android.external.store3.middleware.jackson; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; -import com.nytimes.android.external.store3.middleware.jackson.data.Foo; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import io.reactivex.Maybe; -import io.reactivex.Single; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class JacksonStringParserStoreTest { - - private static final String KEY = "key"; - private static final String source = - "{\"number\":123,\"string\":\"abc\",\"bars\":[{\"string\":\"def\"},{\"string\":\"ghi\"}]}"; - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Mock - Fetcher fetcher; - @Mock - Persister persister; - private final BarCode barCode = new BarCode("value", KEY); - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(source)); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(source)); - - when(persister.write(barCode, source)) - .thenReturn(Single.just(true)); - } - - @Test - public void testDefaultJacksonStringParser() { - Store store = StoreBuilder.parsedWithKey() - .persister(persister) - .fetcher(fetcher) - .parser(JacksonParserFactory.createStringParser(Foo.class)) - .open(); - - Foo result = store.get(barCode).blockingGet(); - - validateFoo(result); - - verify(fetcher, times(1)).fetch(barCode); - } - - @Test - public void testCustomJsonFactoryStringParser() { - JsonFactory jsonFactory = new JsonFactory(); - - Parser parser = JacksonParserFactory.createStringParser(jsonFactory, Foo.class); - - Store store = StoreBuilder.parsedWithKey() - .persister(persister) - .fetcher(fetcher) - .parser(parser) - .open(); - - Foo result = store.get(barCode).blockingGet(); - - validateFoo(result); - - verify(fetcher, times(1)).fetch(barCode); - } - - private void validateFoo(Foo foo) { - assertNotNull(foo); - assertEquals(foo.number, 123); - assertEquals(foo.string, "abc"); - assertEquals(foo.bars.size(), 2); - assertEquals(foo.bars.get(0).string, "def"); - assertEquals(foo.bars.get(1).string, "ghi"); - } - - @Test - public void testNullJsonFactory() { - expectedException.expect(NullPointerException.class); - JacksonParserFactory.createStringParser((JsonFactory) null, Foo.class); - } - - @Test - public void testNullTypeWithValidJsonFactory() { - expectedException.expect(NullPointerException.class); - JacksonParserFactory.createStringParser(new JsonFactory(), null); - } - - @Test - public void testNullObjectMapper() { - expectedException.expect(NullPointerException.class); - JacksonParserFactory.createStringParser((ObjectMapper) null, Foo.class); - } - - @Test - public void testNullType() { - expectedException.expect(NullPointerException.class); - JacksonParserFactory.createStringParser(null); - } - -} diff --git a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonStringParserStoreTest.kt b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonStringParserStoreTest.kt new file mode 100644 index 000000000..15d6caf33 --- /dev/null +++ b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/JacksonStringParserStoreTest.kt @@ -0,0 +1,117 @@ +package com.nytimes.android.external.store3.middleware.jackson + +import com.fasterxml.jackson.core.JsonFactory +import com.fasterxml.jackson.databind.ObjectMapper +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import io.reactivex.Maybe +import io.reactivex.Single +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +class JacksonStringParserStoreTest { + @Rule + @JvmField + var expectedException = ExpectedException.none() + @Mock + lateinit var fetcher: Fetcher + @Mock + lateinit var persister: Persister + private val barCode = BarCode("value", KEY) + + @Before + @Throws(Exception::class) + fun setUp() { + MockitoAnnotations.initMocks(this) + + `when`(fetcher.fetch(barCode)) + .thenReturn(Single.just(source)) + + `when`(persister.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(Maybe.just(source)) + + `when`(persister.write(barCode, source)) + .thenReturn(Single.just(true)) + } + + @Test + fun testDefaultJacksonStringParser() { + val store = StoreBuilder.parsedWithKey() + .persister(persister) + .fetcher(fetcher) + .parser(JacksonParserFactory.createStringParser(Foo::class.java)) + .open() + + val result = store.get(barCode).blockingGet() + validateFoo(result) + verify>(fetcher, times(1)).fetch(barCode) + } + + @Test + fun testCustomJsonFactoryStringParser() { + val jsonFactory = JsonFactory() + + val parser = JacksonParserFactory.createStringParser(jsonFactory, Foo::class.java) + + val store = StoreBuilder.parsedWithKey() + .persister(persister) + .fetcher(fetcher) + .parser(parser) + .open() + + val result = store.get(barCode).blockingGet() + validateFoo(result) + verify>(fetcher, times(1)).fetch(barCode) + } + + private fun validateFoo(foo: Foo) { + assertNotNull(foo) + assertEquals(foo.number.toLong(), 123) + assertEquals(foo.string, "abc") + assertEquals(foo.bars.size.toLong(), 2) + assertEquals(foo.bars[0].string, "def") + assertEquals(foo.bars[1].string, "ghi") + } + + @Test + fun testNullJsonFactory() { + expectedException.expect(NullPointerException::class.java) + JacksonParserFactory.createStringParser((null as JsonFactory?)!!, Foo::class.java) + } + + @Test + fun testNullTypeWithValidJsonFactory() { + expectedException.expect(NullPointerException::class.java) + JacksonParserFactory.createStringParser(JsonFactory(), null!!) + } + + @Test + fun testNullObjectMapper() { + expectedException.expect(NullPointerException::class.java) + JacksonParserFactory.createStringParser((null as ObjectMapper?)!!, Foo::class.java) + } + + @Test + fun testNullType() { + expectedException.expect(NullPointerException::class.java) + JacksonParserFactory.createStringParser(null!!) + } + + companion object { + + private val KEY = "key" + private val source = "{\"number\":123,\"string\":\"abc\",\"bars\":[{\"string\":\"def\"},{\"string\":\"ghi\"}]}" + } +} diff --git a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/Model.kt b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/Model.kt new file mode 100644 index 000000000..b6df4fc1d --- /dev/null +++ b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/Model.kt @@ -0,0 +1,9 @@ +package com.nytimes.android.external.store3.middleware.jackson + +data class Bar (val string: String) + +data class Foo ( + val number: Int, + val string: String, + val bars: List +) diff --git a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/data/Bar.java b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/data/Bar.java deleted file mode 100644 index afac2fdfe..000000000 --- a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/data/Bar.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.nytimes.android.external.store3.middleware.jackson.data; - -public class Bar { - public String string; - - public Bar() { - } - - public Bar(String string) { - this.string = string; - } -} diff --git a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/data/Foo.java b/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/data/Foo.java deleted file mode 100644 index 699e516e8..000000000 --- a/middleware-jackson/src/test/java/com/nytimes/android/external/store3/middleware/jackson/data/Foo.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.nytimes.android.external.store3.middleware.jackson.data; - -import java.util.List; - -public class Foo { - public int number; - public String string; - public List bars; - - public Foo() { - } - - public Foo(int number, String string, List bars) { - this.number = number; - this.string = string; - this.bars = bars; - } -} From 05be0916c4f84141dc1a0b979b389314b11dccc9 Mon Sep 17 00:00:00 2001 From: Brian Plummer Date: Wed, 18 Jul 2018 21:54:59 +0200 Subject: [PATCH 041/498] convert first half of store tests to kotlin (#353) * configure kotlin, first test * first half of store tests * remove unused imports * pr feedback, scope and el * remove charsetTest --- store/build.gradle | 18 ++-- .../android/external/store3/CharsetTest.java | 36 ------- .../external/store3/ClearStoreMemoryTest.java | 57 ----------- .../external/store3/ClearStoreMemoryTest.kt | 57 +++++++++++ .../external/store3/ClearStoreTest.java | 98 ------------------- .../android/external/store3/ClearStoreTest.kt | 91 +++++++++++++++++ .../external/store3/ClearingPersister.java | 29 ++++++ .../external/store3/DontCacheErrorsTest.java | 62 ------------ .../external/store3/DontCacheErrorsTest1.kt | 66 +++++++++++++ .../external/store3/GetRefreshingTest.java | 24 ----- .../external/store3/KeyParserTest.java | 39 -------- .../android/external/store3/KeyParserTest.kt | 39 ++++++++ .../external/store3/NoNetworkTest.java | 41 -------- .../android/external/store3/NoNetworkTest.kt | 41 ++++++++ 14 files changed, 335 insertions(+), 363 deletions(-) delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/CharsetTest.java delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/ClearStoreMemoryTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/ClearStoreMemoryTest.kt delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/ClearStoreTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/ClearStoreTest.kt create mode 100644 store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.java delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.kt delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/NoNetworkTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/NoNetworkTest.kt diff --git a/store/build.gradle b/store/build.gradle index e40f96362..23379933e 100644 --- a/store/build.gradle +++ b/store/build.gradle @@ -1,9 +1,21 @@ +buildscript { + tasks.withType(JavaCompile) { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } +} + +plugins { + id 'org.jetbrains.kotlin.jvm' +} + apply plugin: 'java' group = GROUP version = VERSION_NAME dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation project(path: ':cache') implementation libraries.rxJava2 apiElements libraries.rxJava2 @@ -15,12 +27,6 @@ dependencies { testCompileOnly libraries.jsr305 } -buildscript { - tasks.withType(JavaCompile) { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } -} apply from: rootProject.file("gradle/maven-push.gradle") apply from: rootProject.file("gradle/checkstyle.gradle") apply from: rootProject.file("gradle/pmd.gradle") diff --git a/store/src/test/java/com/nytimes/android/external/store3/CharsetTest.java b/store/src/test/java/com/nytimes/android/external/store3/CharsetTest.java deleted file mode 100644 index 5cb2a92e1..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/CharsetTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.nytimes.android.external.store; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.MockitoAnnotations; - -import java.nio.charset.Charset; -import java.nio.charset.UnsupportedCharsetException; - -import static org.assertj.core.api.Assertions.assertThat; - -public class CharsetTest { - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - } - - @Test - public void charsetUtf8() { - Charset charset = Charset.forName("UTF-8"); - assertThat(charset).isNotNull(); - } - - @Test - public void shouldThrowExceptionWhenCreatingInvalidCharset() { - expectedException.expect(UnsupportedCharsetException.class); - Charset.forName("UTF-6"); - } - -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/ClearStoreMemoryTest.java b/store/src/test/java/com/nytimes/android/external/store3/ClearStoreMemoryTest.java deleted file mode 100644 index fee9d8dd4..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/ClearStoreMemoryTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; - -import org.junit.Before; -import org.junit.Test; - -import io.reactivex.Single; - -import static org.assertj.core.api.Assertions.assertThat; - -public class ClearStoreMemoryTest { - - int networkCalls = 0; - private Store store; - - @Before - public void setUp() { - networkCalls = 0; - store = StoreBuilder.barcode() - .fetcher(barCode -> Single.fromCallable(() -> networkCalls++)) - .open(); - } - - @Test - public void testClearSingleBarCode() { - //one request should produce one call - BarCode barcode = new BarCode("type", "key"); - store.get(barcode).test().awaitTerminalEvent(); - assertThat(networkCalls).isEqualTo(1); - - // after clearing the memory another call should be made - store.clearMemory(barcode); - store.get(barcode).test().awaitTerminalEvent(); - assertThat(networkCalls).isEqualTo(2); - } - - @Test - public void testClearAllBarCodes() { - BarCode b1 = new BarCode("type1", "key1"); - BarCode b2 = new BarCode("type2", "key2"); - - //each request should produce one call - store.get(b1).test().awaitTerminalEvent(); - store.get(b2).test().awaitTerminalEvent(); - assertThat(networkCalls).isEqualTo(2); - - store.clearMemory(); - - //after everything is cleared each request should produce another 2 calls - store.get(b1).test().awaitTerminalEvent(); - store.get(b2).test().awaitTerminalEvent(); - assertThat(networkCalls).isEqualTo(4); - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/ClearStoreMemoryTest.kt b/store/src/test/java/com/nytimes/android/external/store3/ClearStoreMemoryTest.kt new file mode 100644 index 000000000..988440955 --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/ClearStoreMemoryTest.kt @@ -0,0 +1,57 @@ +package com.nytimes.android.external.store3 + +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder + +import org.junit.Before +import org.junit.Test + +import io.reactivex.Single + +import org.assertj.core.api.Assertions.assertThat + +class ClearStoreMemoryTest { + + private var networkCalls = 0 + private lateinit var store: Store + + @Before + fun setUp() { + networkCalls = 0 + store = StoreBuilder.barcode() + .fetcher { barCode -> Single.fromCallable { networkCalls++ } } + .open() + } + + @Test + fun testClearSingleBarCode() { + //one request should produce one call + val barcode = BarCode("type", "key") + store.get(barcode).test().awaitTerminalEvent() + assertThat(networkCalls).isEqualTo(1) + + // after clearing the memory another call should be made + store.clearMemory(barcode) + store.get(barcode).test().awaitTerminalEvent() + assertThat(networkCalls).isEqualTo(2) + } + + @Test + fun testClearAllBarCodes() { + val b1 = BarCode("type1", "key1") + val b2 = BarCode("type2", "key2") + + //each request should produce one call + store.get(b1).test().awaitTerminalEvent() + store.get(b2).test().awaitTerminalEvent() + assertThat(networkCalls).isEqualTo(2) + + store.clearMemory() + + //after everything is cleared each request should produce another 2 calls + store.get(b1).test().awaitTerminalEvent() + store.get(b2).test().awaitTerminalEvent() + assertThat(networkCalls).isEqualTo(4) + } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/ClearStoreTest.java b/store/src/test/java/com/nytimes/android/external/store3/ClearStoreTest.java deleted file mode 100644 index 3a3a26fc9..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/ClearStoreTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import java.util.concurrent.atomic.AtomicInteger; - -import io.reactivex.Maybe; -import io.reactivex.Single; - -import static com.nytimes.android.external.store3.GetRefreshingTest.ClearingPersister; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class ClearStoreTest { - @Mock - ClearingPersister persister; - AtomicInteger networkCalls; - private Store store; - - @Before - public void setUp() { - networkCalls = new AtomicInteger(0); - store = StoreBuilder.barcode() - .fetcher(barCode -> Single.fromCallable(() -> networkCalls.incrementAndGet())) - .persister(persister) - .open(); - } - - @Test - public void testClearSingleBarCode() { - // one request should produce one call - BarCode barcode = new BarCode("type", "key"); - - when(persister.read(barcode)) - .thenReturn(Maybe.empty()) //read from disk on get - .thenReturn(Maybe.just(1)) //read from disk after fetching from network - .thenReturn(Maybe.empty()) //read from disk after clearing - .thenReturn(Maybe.just(1)); //read from disk after making additional network call - when(persister.write(barcode, 1)).thenReturn(Single.just(true)); - when(persister.write(barcode, 2)).thenReturn(Single.just(true)); - - - store.get(barcode).test().awaitTerminalEvent(); - assertThat(networkCalls.intValue()).isEqualTo(1); - - // after clearing the memory another call should be made - store.clear(barcode); - store.get(barcode).test().awaitTerminalEvent(); - verify(persister).clear(barcode); - assertThat(networkCalls.intValue()).isEqualTo(2); - } - - @Test - public void testClearAllBarCodes() { - BarCode barcode1 = new BarCode("type1", "key1"); - BarCode barcode2 = new BarCode("type2", "key2"); - - when(persister.read(barcode1)) - .thenReturn(Maybe.empty()) //read from disk - .thenReturn(Maybe.just(1)) //read from disk after fetching from network - .thenReturn(Maybe.empty()) //read from disk after clearing disk cache - .thenReturn(Maybe.just(1)); //read from disk after making additional network call - when(persister.write(barcode1, 1)).thenReturn(Single.just(true)); - when(persister.write(barcode1, 2)).thenReturn(Single.just(true)); - - when(persister.read(barcode2)) - .thenReturn(Maybe.empty()) //read from disk - .thenReturn(Maybe.just(1)) //read from disk after fetching from network - .thenReturn(Maybe.empty()) //read from disk after clearing disk cache - .thenReturn(Maybe.just(1)); //read from disk after making additional network call - - when(persister.write(barcode2, 1)).thenReturn(Single.just(true)); - when(persister.write(barcode2, 2)).thenReturn(Single.just(true)); - - - // each request should produce one call - store.get(barcode1).test().awaitTerminalEvent(); - store.get(barcode2).test().awaitTerminalEvent(); - assertThat(networkCalls.intValue()).isEqualTo(2); - - store.clear(); - - // after everything is cleared each request should produce another 2 calls - store.get(barcode1).test().awaitTerminalEvent(); - store.get(barcode2).test().awaitTerminalEvent(); - assertThat(networkCalls.intValue()).isEqualTo(4); - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/ClearStoreTest.kt b/store/src/test/java/com/nytimes/android/external/store3/ClearStoreTest.kt new file mode 100644 index 000000000..dd0533089 --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/ClearStoreTest.kt @@ -0,0 +1,91 @@ +package com.nytimes.android.external.store3 + +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import io.reactivex.Maybe +import io.reactivex.Single +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify +import org.mockito.runners.MockitoJUnitRunner +import java.util.concurrent.atomic.AtomicInteger + +@RunWith(MockitoJUnitRunner::class) +class ClearStoreTest { + @Mock + lateinit var persister: ClearingPersister + private lateinit var networkCalls: AtomicInteger + private lateinit var store: Store + + @Before + fun setUp() { + networkCalls = AtomicInteger(0) + store = StoreBuilder.barcode() + .fetcher { barCode -> Single.fromCallable { networkCalls.incrementAndGet() } } + .persister(persister) + .open() + } + + @Test + fun testClearSingleBarCode() { + // one request should produce one call + val barcode = BarCode("type", "key") + + `when`(persister.read(barcode)) + .thenReturn(Maybe.empty()) //read from disk on get + .thenReturn(Maybe.just(1)) //read from disk after fetching from network + .thenReturn(Maybe.empty()) //read from disk after clearing + .thenReturn(Maybe.just(1)) //read from disk after making additional network call + `when`(persister.write(barcode, 1)).thenReturn(Single.just(true)) + `when`(persister.write(barcode, 2)).thenReturn(Single.just(true)) + + store.get(barcode).test().awaitTerminalEvent() + assertThat(networkCalls.toInt()).isEqualTo(1) + + // after clearing the memory another call should be made + store.clear(barcode) + store.get(barcode).test().awaitTerminalEvent() + verify(persister).clear(barcode) + assertThat(networkCalls.toInt()).isEqualTo(2) + } + + @Test + fun testClearAllBarCodes() { + val barcode1 = BarCode("type1", "key1") + val barcode2 = BarCode("type2", "key2") + + `when`(persister.read(barcode1)) + .thenReturn(Maybe.empty()) //read from disk + .thenReturn(Maybe.just(1)) //read from disk after fetching from network + .thenReturn(Maybe.empty()) //read from disk after clearing disk cache + .thenReturn(Maybe.just(1)) //read from disk after making additional network call + `when`(persister.write(barcode1, 1)).thenReturn(Single.just(true)) + `when`(persister.write(barcode1, 2)).thenReturn(Single.just(true)) + + `when`(persister.read(barcode2)) + .thenReturn(Maybe.empty()) //read from disk + .thenReturn(Maybe.just(1)) //read from disk after fetching from network + .thenReturn(Maybe.empty()) //read from disk after clearing disk cache + .thenReturn(Maybe.just(1)) //read from disk after making additional network call + + `when`(persister.write(barcode2, 1)).thenReturn(Single.just(true)) + `when`(persister.write(barcode2, 2)).thenReturn(Single.just(true)) + + // each request should produce one call + store.get(barcode1).test().awaitTerminalEvent() + store.get(barcode2).test().awaitTerminalEvent() + assertThat(networkCalls.toInt()).isEqualTo(2) + + store.clear() + + // after everything is cleared each request should produce another 2 calls + store.get(barcode1).test().awaitTerminalEvent() + store.get(barcode2).test().awaitTerminalEvent() + assertThat(networkCalls.toInt()).isEqualTo(4) + } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.java b/store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.java new file mode 100644 index 000000000..8a884a729 --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.java @@ -0,0 +1,29 @@ +package com.nytimes.android.external.store3; + +import com.nytimes.android.external.store3.base.Clearable; +import com.nytimes.android.external.store3.base.Persister; +import com.nytimes.android.external.store3.base.impl.BarCode; + +import javax.annotation.Nonnull; + +import io.reactivex.Maybe; +import io.reactivex.Single; + +public class ClearingPersister implements Persister, Clearable { + @Override + public void clear(@Nonnull BarCode key) { + throw new RuntimeException(); + } + + @Nonnull + @Override + public Maybe read(@Nonnull BarCode barCode) { + throw new RuntimeException(); + } + + @Nonnull + @Override + public Single write(@Nonnull BarCode barCode, @Nonnull Integer integer) { + throw new RuntimeException(); + } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest.java b/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest.java deleted file mode 100644 index 9134d23d2..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; - -import org.junit.Before; -import org.junit.Test; - -import io.reactivex.Single; - - -public class DontCacheErrorsTest { - - boolean shouldThrow; - private Store store; - - @Before - public void setUp() { - store = StoreBuilder.barcode() - .fetcher(barCode -> Single.fromCallable(() -> { - if (shouldThrow) { - throw new RuntimeException(); - } else { - return 0; - } - })) - .open(); - } - - @Test - public void testStoreDoesntCacheErrors() throws InterruptedException { - BarCode barcode = new BarCode("bar", "code"); - - shouldThrow = true; - store.get(barcode).test() - .assertTerminated() - .assertError(Exception.class) - .awaitTerminalEvent(); - - shouldThrow = false; - store.get(barcode).test() - .assertNoErrors() - .awaitTerminalEvent(); - } - - @Test - public void testStoreDoesntCacheErrorsWithResult() throws InterruptedException { - BarCode barcode = new BarCode("bar", "code"); - - shouldThrow = true; - store.getWithResult(barcode).test() - .assertTerminated() - .assertError(Exception.class) - .awaitTerminalEvent(); - - shouldThrow = false; - store.get(barcode).test() - .assertNoErrors() - .awaitTerminalEvent(); - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt b/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt new file mode 100644 index 000000000..bd4e74d70 --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt @@ -0,0 +1,66 @@ +package com.nytimes.android.external.store3 + +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder + +import org.junit.Before +import org.junit.Test + +import io.reactivex.Single + + +class DontCacheErrorsTest { + + private var shouldThrow: Boolean = false + private lateinit var store: Store + + @Before + fun setUp() { + store = StoreBuilder.barcode() + .fetcher { barCode -> + Single.fromCallable { + if (shouldThrow) { + throw RuntimeException() + } else { + 0 + } + } + } + .open() + } + + @Test + @Throws(InterruptedException::class) + fun testStoreDoesntCacheErrors() { + val barcode = BarCode("bar", "code") + + shouldThrow = true + store.get(barcode).test() + .assertTerminated() + .assertError(Exception::class.java) + .awaitTerminalEvent() + + shouldThrow = false + store.get(barcode).test() + .assertNoErrors() + .awaitTerminalEvent() + } + + @Test + @Throws(InterruptedException::class) + fun testStoreDoesntCacheErrorsWithResult() { + val barcode = BarCode("bar", "code") + + shouldThrow = true + store.getWithResult(barcode).test() + .assertTerminated() + .assertError(Exception::class.java) + .awaitTerminalEvent() + + shouldThrow = false + store.get(barcode).test() + .assertNoErrors() + .awaitTerminalEvent() + } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/GetRefreshingTest.java b/store/src/test/java/com/nytimes/android/external/store3/GetRefreshingTest.java index 1f9ea7a31..94d788f54 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/GetRefreshingTest.java +++ b/store/src/test/java/com/nytimes/android/external/store3/GetRefreshingTest.java @@ -1,7 +1,5 @@ package com.nytimes.android.external.store3; -import com.nytimes.android.external.store3.base.Clearable; -import com.nytimes.android.external.store3.base.Persister; import com.nytimes.android.external.store3.base.impl.BarCode; import com.nytimes.android.external.store3.base.impl.Store; import com.nytimes.android.external.store3.base.impl.StoreBuilder; @@ -14,8 +12,6 @@ import java.util.concurrent.atomic.AtomicInteger; -import javax.annotation.Nonnull; - import io.reactivex.Maybe; import io.reactivex.Single; import io.reactivex.observers.TestObserver; @@ -99,24 +95,4 @@ public void testRefreshOnClearAll() { } - //everything will be mocked - static class ClearingPersister implements Persister, Clearable { - @Override - public void clear(@Nonnull BarCode key) { - throw new RuntimeException(); - } - - @Nonnull - @Override - public Maybe read(@Nonnull BarCode barCode) { - throw new RuntimeException(); - } - - @Nonnull - @Override - public Single write(@Nonnull BarCode barCode, @Nonnull Integer integer) { - throw new RuntimeException(); - } - } - } diff --git a/store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.java b/store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.java deleted file mode 100644 index 816dbe831..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; - -import io.reactivex.Single; -import io.reactivex.observers.TestObserver; - - -@RunWith(MockitoJUnitRunner.class) -public class KeyParserTest { - - public static final String NETWORK = "Network"; - public static final int KEY = 5; - private Store store; - - @Before - public void setUp() throws Exception { - store = StoreBuilder.parsedWithKey() - .parser((integer, s) -> s + integer) - .fetcher(integer -> Single.just(NETWORK)) - .open(); - - } - - @Test - @SuppressWarnings("PMD.SignatureDeclareThrowsException") - public void testStoreWithKeyParserFuncNoPersister() throws Exception { - TestObserver testObservable = store.get(KEY).test().await(); - testObservable.assertNoErrors() - .assertValues(NETWORK + KEY) - .awaitTerminalEvent(); - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.kt b/store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.kt new file mode 100644 index 000000000..620b5a22f --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.kt @@ -0,0 +1,39 @@ +package com.nytimes.android.external.store3 + +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import io.reactivex.Single +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.runners.MockitoJUnitRunner + + +@RunWith(MockitoJUnitRunner::class) +class KeyParserTest { + lateinit var store: Store + + @Before + @Throws(Exception::class) + fun setUp() { + store = StoreBuilder.parsedWithKey() + .parser { integer, s -> s + integer } + .fetcher { integer -> Single.just(NETWORK) } + .open() + } + + @Test + @Throws(Exception::class) + fun testStoreWithKeyParserFuncNoPersister() { + val testObservable = store.get(KEY).test().await() + testObservable.assertNoErrors() + .assertValues(NETWORK + KEY) + .awaitTerminalEvent() + } + + companion object { + + private const val NETWORK = "Network" + val KEY = 5 + } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/NoNetworkTest.java b/store/src/test/java/com/nytimes/android/external/store3/NoNetworkTest.java deleted file mode 100644 index 44dbecdfc..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/NoNetworkTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; - -import org.junit.Before; -import org.junit.Test; - -import io.reactivex.Single; - -/** - * Created by 206847 on 5/3/17. - */ - -public class NoNetworkTest { - - private static final RuntimeException EXCEPTION = new RuntimeException(); - private Store store; - - @Before - public void setUp() { - store = StoreBuilder.barcode() - .fetcher(barcode -> Single.error(EXCEPTION)) - .open(); - } - - @Test - public void testNoNetwork() throws Exception { - store.get(new BarCode("test", "test")) - .test() - .assertError(EXCEPTION); - } - - @Test - public void testNoNetworkWithResult() throws Exception { - store.getWithResult(new BarCode("test", "test")) - .test() - .assertError(EXCEPTION); - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/NoNetworkTest.kt b/store/src/test/java/com/nytimes/android/external/store3/NoNetworkTest.kt new file mode 100644 index 000000000..fdea5a156 --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/NoNetworkTest.kt @@ -0,0 +1,41 @@ +package com.nytimes.android.external.store3 + +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder + +import org.junit.Before +import org.junit.Test + +import io.reactivex.Single + +class NoNetworkTest { + private lateinit var store: Store + + @Before + fun setUp() { + store = StoreBuilder.barcode() + .fetcher { barcode -> Single.error(EXCEPTION) } + .open() + } + + @Test + @Throws(Exception::class) + fun testNoNetwork() { + store.get(BarCode("test", "test")) + .test() + .assertError(EXCEPTION) + } + + @Test + @Throws(Exception::class) + fun testNoNetworkWithResult() { + store.getWithResult(BarCode("test", "test")) + .test() + .assertError(EXCEPTION) + } + + companion object { + private val EXCEPTION = RuntimeException() + } +} From 19d4294430af42e6c198900cd30dc6bd9313b65d Mon Sep 17 00:00:00 2001 From: Brian Plummer Date: Wed, 18 Jul 2018 23:18:46 +0200 Subject: [PATCH 042/498] last part of filesystem tests conversion (#356) * rest of filesystem tests * remove el --- build.gradle | 2 +- .../fs3/StoreRefreshWhenStaleTest.java | 103 -------------- .../external/fs3/StoreRefreshWhenStaleTest.kt | 99 +++++++++++++ .../BreadthFirstFileTreeIteratorTest.java | 78 ---------- .../BreadthFirstFileTreeIteratorTest.kt | 84 +++++++++++ .../external/fs3/filesystem/FSFileTest.java | 48 ------- .../external/fs3/filesystem/FSFileTest.kt | 47 +++++++ .../android/external/fs3/impl/SimpleTest.java | 132 ----------------- .../android/external/fs3/impl/SimpleTest.kt | 133 ++++++++++++++++++ 9 files changed, 364 insertions(+), 362 deletions(-) delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/StoreRefreshWhenStaleTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/StoreRefreshWhenStaleTest.kt delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/BreadthFirstFileTreeIteratorTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/BreadthFirstFileTreeIteratorTest.kt delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/FSFileTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/FSFileTest.kt delete mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/impl/SimpleTest.java create mode 100644 filesystem/src/test/java/com/nytimes/android/external/fs3/impl/SimpleTest.kt diff --git a/build.gradle b/build.gradle index f1eed3d29..1c3c1136c 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ buildscript { ] dependencies { - classpath 'com.android.tools.build:gradle:3.2.0-beta01' + classpath 'com.android.tools.build:gradle:3.2.0-beta04' classpath 'com.google.gms:google-services:3.0.0' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.6' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11' diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreRefreshWhenStaleTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreRefreshWhenStaleTest.java deleted file mode 100644 index e29fb3159..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreRefreshWhenStaleTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.RecordState; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.runners.MockitoJUnitRunner; - -import io.reactivex.Maybe; -import io.reactivex.Single; -import io.reactivex.observers.TestObserver; -import okio.BufferedSource; - -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class StoreRefreshWhenStaleTest { - @Mock - Fetcher fetcher; - @Mock - RecordPersister persister; - @Mock - BufferedSource network1; - @Mock - BufferedSource network2; - @Mock - BufferedSource disk1; - @Mock - BufferedSource disk2; - - private final BarCode barCode = new BarCode("key", "value"); - private Store store; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - store = StoreBuilder.barcode() - .fetcher(fetcher) - .persister(persister) - .refreshOnStale() - .open(); - - } - - @Test - public void diskWasRefreshedWhenStaleRecord() { - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(network1)); - when(persister.read(barCode)) - .thenReturn(Maybe.just(disk1)); //get should return from disk - when(persister.getRecordState(barCode)).thenReturn(RecordState.STALE); - - when(persister.write(barCode, network1)) - .thenReturn(Single.just(true)); - - store.get(barCode).test().awaitTerminalEvent(); - verify(fetcher, times(1)).fetch(barCode); - verify(persister, times(2)).getRecordState(barCode); - verify(persister, times(1)).write(barCode, network1); - verify(persister, times(2)).read(barCode); //reads from disk a second time when backfilling - - } - - @Test - public void diskWasNotRefreshedWhenFreshRecord() { - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(network1)); - when(persister.read(barCode)) - .thenReturn(Maybe.just(disk1)) //get should return from disk - .thenReturn(Maybe.just(disk2)); //backfill should read from disk again - when(persister.getRecordState(barCode)).thenReturn(RecordState.FRESH); - - when(persister.write(barCode, network1)) - .thenReturn(Single.just(true)); - - TestObserver testObserver = store - .get(barCode) - .test(); - testObserver.awaitTerminalEvent(); - testObserver.assertNoErrors(); - testObserver.assertResult(disk1); - verify(fetcher, times(0)).fetch(barCode); - verify(persister, times(1)).getRecordState(barCode); - - store.clear(barCode); - testObserver = store - .get(barCode) - .test(); - testObserver.awaitTerminalEvent(); - testObserver.assertResult(disk2); - verify(fetcher, times(0)).fetch(barCode); - verify(persister, times(2)).getRecordState(barCode); - } -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreRefreshWhenStaleTest.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreRefreshWhenStaleTest.kt new file mode 100644 index 000000000..cf2e7b3a4 --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/StoreRefreshWhenStaleTest.kt @@ -0,0 +1,99 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.RecordState +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder + +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.runners.MockitoJUnitRunner + +import io.reactivex.Maybe +import io.reactivex.Single +import io.reactivex.observers.TestObserver +import okio.BufferedSource + +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` + +@RunWith(MockitoJUnitRunner::class) +class StoreRefreshWhenStaleTest { + @Mock + internal lateinit var fetcher: Fetcher + @Mock + internal lateinit var persister: RecordPersister + @Mock + internal lateinit var network1: BufferedSource + @Mock + internal lateinit var disk1: BufferedSource + @Mock + internal lateinit var disk2: BufferedSource + + private val barCode = BarCode("key", "value") + private lateinit var store: Store + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + store = StoreBuilder.barcode() + .fetcher(fetcher) + .persister(persister) + .refreshOnStale() + .open() + } + + @Test + fun diskWasRefreshedWhenStaleRecord() { + `when`(fetcher.fetch(barCode)) + .thenReturn(Single.just(network1)) + `when`(persister.read(barCode)) + .thenReturn(Maybe.just(disk1)) //get should return from disk + `when`(persister.getRecordState(barCode)).thenReturn(RecordState.STALE) + + `when`(persister.write(barCode, network1)) + .thenReturn(Single.just(true)) + + store.get(barCode).test().awaitTerminalEvent() + verify>(fetcher, times(1)).fetch(barCode) + verify(persister, times(2)).getRecordState(barCode) + verify(persister, times(1)).write(barCode, network1) + verify(persister, times(2)).read(barCode) //reads from disk a second time when backfilling + } + + @Test + fun diskWasNotRefreshedWhenFreshRecord() { + `when`(fetcher.fetch(barCode)) + .thenReturn(Single.just(network1)) + `when`(persister.read(barCode)) + .thenReturn(Maybe.just(disk1)) //get should return from disk + .thenReturn(Maybe.just(disk2)) //backfill should read from disk again + `when`(persister.getRecordState(barCode)).thenReturn(RecordState.FRESH) + + `when`(persister.write(barCode, network1)) + .thenReturn(Single.just(true)) + + var testObserver: TestObserver = store + .get(barCode) + .test() + testObserver.awaitTerminalEvent() + testObserver.assertNoErrors() + testObserver.assertResult(disk1) + verify>(fetcher, times(0)).fetch(barCode) + verify(persister, times(1)).getRecordState(barCode) + + store.clear(barCode) + testObserver = store + .get(barCode) + .test() + testObserver.awaitTerminalEvent() + testObserver.assertResult(disk2) + verify>(fetcher, times(0)).fetch(barCode) + verify(persister, times(2)).getRecordState(barCode) + } +} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/BreadthFirstFileTreeIteratorTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/BreadthFirstFileTreeIteratorTest.java deleted file mode 100644 index d0abda301..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/BreadthFirstFileTreeIteratorTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.nytimes.android.external.fs3.filesystem; - -import org.junit.Before; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; - -import static org.assertj.core.api.Assertions.assertThat; - -public class BreadthFirstFileTreeIteratorTest { - - private File systemTempDir; - - @Before - public void setUp() throws IOException { - String property = "java.io.tmpdir"; - systemTempDir = createDirWithSubFiles(new File(System.getProperty(property)), 0); - } - - @Test - public void testHasNextEmpty() throws IOException { - File hasNextDir = createDirWithSubFiles(systemTempDir, 0); - BreadthFirstFileTreeIterator btfti = new BreadthFirstFileTreeIterator(hasNextDir); - assertThat(btfti.hasNext()).isFalse(); - } - - @Test - public void testHasNextOne() throws IOException { - File hasNextDir = createDirWithSubFiles(systemTempDir, 1); - BreadthFirstFileTreeIterator btfti = new BreadthFirstFileTreeIterator(hasNextDir); - assertThat(btfti.hasNext()).isTrue(); - assertThat(btfti.next()).isNotNull(); - assertThat(btfti.hasNext()).isFalse(); - } - - @Test - public void testHastNextMany() throws IOException { - int fileCount = 30; - File hasNextDir = createDirWithSubFiles(systemTempDir, fileCount); - createDirWithSubFiles(hasNextDir, fileCount); - BreadthFirstFileTreeIterator btfti = new BreadthFirstFileTreeIterator(hasNextDir); - int counter = 0; - while (btfti.hasNext()) { - btfti.next(); - counter++; - } - assertThat(counter).isEqualTo(fileCount * 2); - } - - private File createDirWithSubFiles(File root, int fileCount) throws IOException { - assertThat(root).exists(); - assertThat(root.isDirectory()).isTrue(); - - File tempDir = createDir(root); - for (int i = 0; i < fileCount; i++) { - createFile(tempDir); - } - return tempDir; - } - - private void createFile(File root) throws IOException { - File someFile = new File(root, "somefile" + System.nanoTime()); - assertThat(someFile.createNewFile()).isTrue(); - someFile.deleteOnExit(); - } - - private File createDir(File root) { - String label = "BFTI_test_" + System.nanoTime(); - File tempDir = new File(root, label); - - assertThat(tempDir.mkdir()).isTrue(); - assertThat(tempDir.exists()).isTrue(); - assertThat(tempDir.isDirectory()).isTrue(); - tempDir.deleteOnExit(); - return tempDir; - } -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/BreadthFirstFileTreeIteratorTest.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/BreadthFirstFileTreeIteratorTest.kt new file mode 100644 index 000000000..0b0918151 --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/BreadthFirstFileTreeIteratorTest.kt @@ -0,0 +1,84 @@ +package com.nytimes.android.external.fs3.filesystem + +import org.junit.Before +import org.junit.Test + +import java.io.File +import java.io.IOException + +import org.assertj.core.api.Assertions.assertThat + +class BreadthFirstFileTreeIteratorTest { + + private lateinit var systemTempDir: File + + @Before + @Throws(IOException::class) + fun setUp() { + val property = "java.io.tmpdir" + systemTempDir = createDirWithSubFiles(File(System.getProperty(property)), 0) + } + + @Test + @Throws(IOException::class) + fun testHasNextEmpty() { + val hasNextDir = createDirWithSubFiles(systemTempDir, 0) + val btfti = BreadthFirstFileTreeIterator(hasNextDir) + assertThat(btfti.hasNext()).isFalse() + } + + @Test + @Throws(IOException::class) + fun testHasNextOne() { + val hasNextDir = createDirWithSubFiles(systemTempDir, 1) + val btfti = BreadthFirstFileTreeIterator(hasNextDir) + assertThat(btfti.hasNext()).isTrue() + assertThat(btfti.next()).isNotNull() + assertThat(btfti.hasNext()).isFalse() + } + + @Test + @Throws(IOException::class) + fun testHastNextMany() { + val fileCount = 30 + val hasNextDir = createDirWithSubFiles(systemTempDir, fileCount) + createDirWithSubFiles(hasNextDir, fileCount) + val btfti = BreadthFirstFileTreeIterator(hasNextDir) + var counter = 0 + while (btfti.hasNext()) { + btfti.next() + counter++ + } + assertThat(counter).isEqualTo(fileCount * 2) + } + + @Throws(IOException::class) + private fun createDirWithSubFiles(root: File, fileCount: Int): File { + assertThat(root).exists() + assertThat(root.isDirectory).isTrue() + + val tempDir = createDir(root) + for (i in 0 until fileCount) { + createFile(tempDir) + } + return tempDir + } + + @Throws(IOException::class) + private fun createFile(root: File) { + val someFile = File(root, "somefile" + System.nanoTime()) + assertThat(someFile.createNewFile()).isTrue() + someFile.deleteOnExit() + } + + private fun createDir(root: File): File { + val label = "BFTI_test_" + System.nanoTime() + val tempDir = File(root, label) + + assertThat(tempDir.mkdir()).isTrue() + assertThat(tempDir.exists()).isTrue() + assertThat(tempDir.isDirectory).isTrue() + tempDir.deleteOnExit() + return tempDir + } +} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/FSFileTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/FSFileTest.java deleted file mode 100644 index df33b2ee2..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/FSFileTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.nytimes.android.external.fs3.filesystem; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.io.File; -import java.io.IOException; - -import okio.Buffer; -import okio.BufferedSource; - -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyByte; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class FSFileTest { - private static final String TEST_FILE_PATH = "/test_file"; - - @Rule - public TemporaryFolder folder = new TemporaryFolder(); - - @Mock - File root; - @Mock - BufferedSource source; - - private FSFile fsFile; - - @Before - public void setUp() throws IOException { - MockitoAnnotations.initMocks(this); - File root = folder.newFolder(); - fsFile = new FSFile(root, TEST_FILE_PATH); - } - - - @Test - public void closeSourceAfterWrite() throws IOException { - when(source.read(any(Buffer.class), anyByte())).thenReturn(Long.valueOf(-1)); - fsFile.write(source); - verify(source).close(); - } -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/FSFileTest.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/FSFileTest.kt new file mode 100644 index 000000000..c7b5fb326 --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/filesystem/FSFileTest.kt @@ -0,0 +1,47 @@ +package com.nytimes.android.external.fs3.filesystem + +import okio.Buffer +import okio.BufferedSource +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.mockito.Matchers.any +import org.mockito.Matchers.anyByte +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import java.io.IOException + +class FSFileTest { + + @Rule + @JvmField + var folder = TemporaryFolder() + + @Mock + internal lateinit var source: BufferedSource + + private lateinit var fsFile: FSFile + + @Before + @Throws(IOException::class) + fun setUp() { + MockitoAnnotations.initMocks(this) + val root = folder.newFolder() + fsFile = FSFile(root, TEST_FILE_PATH) + } + + @Test + @Throws(IOException::class) + fun closeSourceAfterWrite() { + `when`(source.read(any(Buffer::class.java), anyByte().toLong())).thenReturn(java.lang.Long.valueOf(-1)) + fsFile.write(source) + verify(source).close() + } + + companion object { + private const val TEST_FILE_PATH = "/test_file" + } +} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/impl/SimpleTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/impl/SimpleTest.java deleted file mode 100644 index b92b810ce..000000000 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/impl/SimpleTest.java +++ /dev/null @@ -1,132 +0,0 @@ -package com.nytimes.android.external.fs3.impl; - - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.fs3.filesystem.FileSystemFactory; -import com.nytimes.android.external.store3.base.RecordState; - -import org.junit.Before; -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import okio.BufferedSource; -import okio.Okio; - -import static com.google.common.base.Charsets.UTF_8; -import static com.google.common.io.Files.createTempDir; -import static org.assertj.core.api.Assertions.assertThat; - -public class SimpleTest { - - private static final String testString1 = "aszfbW#$%#$^&*5 r7ytjdfbv!@#R$\n@!#$%2354 wtyebfsdv\n"; - private static final String testString2 = "#%^sdfvb#W%EtsdfbSER@#$%dsfb\nASRG \n #dsfvb \n"; - - private static FileSystem fileSystem; - - @Before - public void start() throws IOException { - File baseDir = createTempDir(); - fileSystem = FileSystemFactory.create(baseDir); - } - - @Test(expected = FileNotFoundException.class) - public void loadFileNotFound() throws IOException { - fileSystem.read("/loadFileNotFound.txt").readUtf8(); - } - - @Test - public void saveNload() throws IOException { - diffMe("/flibber.txt", "/flibber.txt"); - diffMe("/blarg/flibber.txt", "/blarg/flibber.txt"); - diffMe("/blubber.txt", "blubber.txt"); - diffMe("/blarg/blubber.txt", "blarg/blubber.txt"); - } - - @Test - public void delete() throws IOException { - fileSystem.write("/boo", source(testString1)); - assertThat(fileSystem.read("/boo").readUtf8()).isEqualTo(testString1); - fileSystem.delete("/boo"); - assertThat(fileSystem.exists("/boo")).isFalse(); - } - - @Test - public void testIsRecordStale() throws IOException { - fileSystem.write("/boo", source(testString1)); - assertThat(fileSystem.read("/boo").readUtf8()).isEqualTo(testString1); - assertThat(fileSystem.getRecordState(TimeUnit.MINUTES, 1, "/boo")).isEqualTo(RecordState.FRESH); - assertThat(fileSystem.getRecordState(TimeUnit.MICROSECONDS, 1, "/boo")).isEqualTo(RecordState.STALE); - assertThat(fileSystem.getRecordState(TimeUnit.DAYS, 1, "/notfound")).isEqualTo(RecordState.MISSING); - } - - @Test - public void testDeleteWhileReading() throws IOException { - - fileSystem.write("/boo", source(testString1)); - BufferedSource source = fileSystem.read("/boo"); - fileSystem.delete("/boo"); - - assertThat(fileSystem.exists("/boo")).isFalse(); - assertThat(source.readUtf8()).isEqualTo(testString1); - assertThat(fileSystem.exists("/boo")).isFalse(); - } - - @Test - public void deleteWhileReadingThenWrite() throws IOException { - - fileSystem.write("/boo", source(testString1)); - - BufferedSource source1 = fileSystem.read("/boo"); // open a source and hang onto it - fileSystem.delete("/boo"); // now delete the file - - assertThat(fileSystem.exists("/boo")).isFalse(); // exists() should say it's gone even though - // we still have a source to it - fileSystem.write("/boo", source(testString2)); // and now un-delete it by writing a new version - assertThat(fileSystem.exists("/boo")).isTrue(); // exists() should say it's back - BufferedSource source2 = fileSystem.read("/boo"); // open another source and hang onto it - fileSystem.delete("/boo"); // now delete the file *again* - - // the sources should have the correct data even though the file was deleted/re-written/deleted - assertThat(source1.readUtf8()).isEqualTo(testString1); - assertThat(source2.readUtf8()).isEqualTo(testString2); - - // now that the 2 sources have been fully read, you shouldn't be able to read it - assertThat(fileSystem.exists("/boo")).isFalse(); - } - - private void diffMe(String first, String second) { - try { - fileSystem.write(first, source(testString1)); - } catch (IOException error) { - throw new RuntimeException("unable to write to " + first, error); - } - - try { - assertThat(fileSystem.read(second).readUtf8()).isEqualTo(testString1); - } catch (IOException error) { - throw new RuntimeException("unable to read from " + second, error); - } - - try { - fileSystem.write(second, source(testString2)); - } catch (IOException error) { - throw new RuntimeException("unable to write to " + second, error); - } - - try { - assertThat(fileSystem.read(first).readUtf8()).isEqualTo(testString2); - } catch (IOException error) { - throw new RuntimeException("unable to read from " + first, error); - } - } - - private static BufferedSource source(String data) { - return Okio.buffer(Okio.source(new ByteArrayInputStream(data.getBytes(UTF_8)))); - } - -} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/impl/SimpleTest.kt b/filesystem/src/test/java/com/nytimes/android/external/fs3/impl/SimpleTest.kt new file mode 100644 index 000000000..f57ac3182 --- /dev/null +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/impl/SimpleTest.kt @@ -0,0 +1,133 @@ +package com.nytimes.android.external.fs3.impl + + +import com.google.common.base.Charsets.UTF_8 +import com.google.common.io.Files.createTempDir +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.fs3.filesystem.FileSystemFactory +import com.nytimes.android.external.store3.base.RecordState +import okio.BufferedSource +import okio.Okio +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import java.io.ByteArrayInputStream +import java.io.FileNotFoundException +import java.io.IOException +import java.util.concurrent.TimeUnit + +class SimpleTest { + + private lateinit var fileSystem: FileSystem + + @Before + @Throws(IOException::class) + fun start() { + val baseDir = createTempDir() + fileSystem = FileSystemFactory.create(baseDir) + } + + @Test(expected = FileNotFoundException::class) + @Throws(IOException::class) + fun loadFileNotFound() { + fileSystem.read("/loadFileNotFound.txt").readUtf8() + } + + @Test + @Throws(IOException::class) + fun saveNload() { + diffMe("/flibber.txt", "/flibber.txt") + diffMe("/blarg/flibber.txt", "/blarg/flibber.txt") + diffMe("/blubber.txt", "blubber.txt") + diffMe("/blarg/blubber.txt", "blarg/blubber.txt") + } + + @Test + @Throws(IOException::class) + fun delete() { + fileSystem.write("/boo", source(testString1)) + assertThat(fileSystem.read("/boo").readUtf8()).isEqualTo(testString1) + fileSystem.delete("/boo") + assertThat(fileSystem.exists("/boo")).isFalse() + } + + @Test + @Throws(IOException::class) + fun testIsRecordStale() { + fileSystem.write("/boo", source(testString1)) + assertThat(fileSystem.read("/boo").readUtf8()).isEqualTo(testString1) + assertThat(fileSystem.getRecordState(TimeUnit.MINUTES, 1, "/boo")).isEqualTo(RecordState.FRESH) + assertThat(fileSystem.getRecordState(TimeUnit.MICROSECONDS, 1, "/boo")).isEqualTo(RecordState.STALE) + assertThat(fileSystem.getRecordState(TimeUnit.DAYS, 1, "/notfound")).isEqualTo(RecordState.MISSING) + } + + @Test + @Throws(IOException::class) + fun testDeleteWhileReading() { + + fileSystem.write("/boo", source(testString1)) + val source = fileSystem.read("/boo") + fileSystem.delete("/boo") + + assertThat(fileSystem.exists("/boo")).isFalse() + assertThat(source.readUtf8()).isEqualTo(testString1) + assertThat(fileSystem.exists("/boo")).isFalse() + } + + @Test + @Throws(IOException::class) + fun deleteWhileReadingThenWrite() { + + fileSystem.write("/boo", source(testString1)) + + val source1 = fileSystem.read("/boo") // open a source and hang onto it + fileSystem.delete("/boo") // now delete the file + + assertThat(fileSystem.exists("/boo")).isFalse() // exists() should say it's gone even though + // we still have a source to it + fileSystem.write("/boo", source(testString2)) // and now un-delete it by writing a new version + assertThat(fileSystem.exists("/boo")).isTrue() // exists() should say it's back + val source2 = fileSystem.read("/boo") // open another source and hang onto it + fileSystem.delete("/boo") // now delete the file *again* + + // the sources should have the correct data even though the file was deleted/re-written/deleted + assertThat(source1.readUtf8()).isEqualTo(testString1) + assertThat(source2.readUtf8()).isEqualTo(testString2) + + // now that the 2 sources have been fully read, you shouldn't be able to read it + assertThat(fileSystem.exists("/boo")).isFalse() + } + + private fun diffMe(first: String, second: String) { + try { + fileSystem.write(first, source(testString1)) + } catch (error: IOException) { + throw RuntimeException("unable to write to $first", error) + } + + try { + assertThat(fileSystem.read(second).readUtf8()).isEqualTo(testString1) + } catch (error: IOException) { + throw RuntimeException("unable to read from $second", error) + } + + try { + fileSystem.write(second, source(testString2)) + } catch (error: IOException) { + throw RuntimeException("unable to write to $second", error) + } + + try { + assertThat(fileSystem.read(first).readUtf8()).isEqualTo(testString2) + } catch (error: IOException) { + throw RuntimeException("unable to read from $first", error) + } + } + + companion object { + private const val testString1 = "aszfbW#$%#$^&*5 r7ytjdfbv!@#R$\n@!#$%2354 wtyebfsdv\n" + private const val testString2 = "#%^sdfvb#W%EtsdfbSER@#$%dsfb\nASRG \n #dsfvb \n" + + private fun source(data: String): BufferedSource = Okio.buffer(Okio.source(ByteArrayInputStream(data.toByteArray(UTF_8)))) + } +} From c32e50fe35f35de3d890eb3b253bbf62c8a0b35f Mon Sep 17 00:00:00 2001 From: Jeremy Tecson Date: Sat, 21 Jul 2018 19:56:27 +0800 Subject: [PATCH 043/498] Add links to mentioned libraries (#361) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 24371cf18..3faeeb025 100644 --- a/README.md +++ b/README.md @@ -221,7 +221,7 @@ Stores don’t care how you’re storing or retrieving your data from disk. As a **Note**: When using a Parser and a disk cache, the Parser will be called AFTER fetching from disk and not between the network and disk. This allows your persister to work on the network stream directly. -If using SQLite we recommend working with SqlBrite. If you are not using SqlBrite, an Observable can be created rather simply with `Observable.fromCallable(() -> getDBValue())` +If using SQLite we recommend working with [SqlBrite](https://github.com/square/sqlbrite). If you are not using SqlBrite, an Observable can be created rather simply with `Observable.fromCallable(() -> getDBValue())` ### Middleware - SourcePersister & FileSystem @@ -332,7 +332,7 @@ public class SampleStore extends RealStore { ```groovy compile 'com.nytimes.android:middleware-moshi3:CurrentVersion' ``` -+ **File System** Persistence Library built using OKIO Source/Sink + Middleware for streaming from Network to FileSystem ++ **File System** Persistence Library built using [Okio](https://github.com/square/okio) Source/Sink + Middleware for streaming from Network to FileSystem ```groovy compile 'com.nytimes.android:filesystem3:CurrentVersion' @@ -342,7 +342,7 @@ public class SampleStore extends RealStore { See the app for example usage of Store. Alternatively, the Wiki contains a set of recipes for common use cases + Simple Example: Retrofit + Store -+ Complex Example: BufferedSource from Retrofit (Can be OKHTTP too) + our FileSystem + our GsonSourceParser ++ Complex Example: BufferedSource from Retrofit (Can be [OkHttp](https://github.com/square/okhttp) too) + our FileSystem + our GsonSourceParser ### Talks [DroidCon Italy](https://youtu.be/TvsOsgd0--c) From c5032f6cc22ca992a98d2cbde933a39615e90e0f Mon Sep 17 00:00:00 2001 From: Jeremy Tecson Date: Sat, 21 Jul 2018 20:00:41 +0800 Subject: [PATCH 044/498] Use implementation instead of compile (#362) --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3faeeb025..a9a5f5e34 100644 --- a/README.md +++ b/README.md @@ -305,37 +305,37 @@ public class SampleStore extends RealStore { + **Cache** Cache extracted from Guava (keeps method count to a minimum) ```groovy - compile 'com.nytimes.android:cache3:CurrentVersion' + implementation 'com.nytimes.android:cache3:CurrentVersion' ``` + **Store** This contains only Store classes and has a dependecy on RxJava + the above cache. ```groovy - compile 'com.nytimes.android:store3:CurrentVersion' + implementation 'com.nytimes.android:store3:CurrentVersion' ``` + **Store-Kotlin** Store plus a couple of added Kotlin classes for more idiomatic usage. ```groovy - compile 'com.nytimes.android:store-kotlin3:CurrentVersion' + implementation 'com.nytimes.android:store-kotlin3:CurrentVersion' ``` + **Middleware** Sample Gson parsers, (feel free to create more and open PRs) ```groovy - compile 'com.nytimes.android:middleware3:CurrentVersion' + implementation 'com.nytimes.android:middleware3:CurrentVersion' ``` + **Middleware-Jackson** Sample Jackon parsers, (feel free to create more and open PRs) ```groovy - compile 'com.nytimes.android:middleware-jackson3:CurrentVersion' + implementation 'com.nytimes.android:middleware-jackson3:CurrentVersion' ``` + **Middleware-Moshi** Sample Moshi parsers, (feel free to create more and open PRs) ```groovy - compile 'com.nytimes.android:middleware-moshi3:CurrentVersion' + implementation 'com.nytimes.android:middleware-moshi3:CurrentVersion' ``` + **File System** Persistence Library built using [Okio](https://github.com/square/okio) Source/Sink + Middleware for streaming from Network to FileSystem ```groovy - compile 'com.nytimes.android:filesystem3:CurrentVersion' + implementation 'com.nytimes.android:filesystem3:CurrentVersion' ``` ### Sample Project From 28659235f8f21d36d19b8b1dafe318316657642e Mon Sep 17 00:00:00 2001 From: Jeremy Tecson Date: Sat, 21 Jul 2018 20:19:58 +0800 Subject: [PATCH 045/498] Fix typos (#360) --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a9a5f5e34..110ad24dd 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ And now for the details: ### Creating a Store -You create a Store using a builder. The only requirement is to include a `.Fetcher` that returns an Single and has a single method `fetch(key)` +You create a Store using a builder. The only requirement is to include a `.Fetcher` that returns a Single and has a single method `fetch(key)` ``` java @@ -119,12 +119,12 @@ Calls to both `fetch()` and `get()` emit one value and then call `onCompleted()` For real-time updates, you may also call `store.stream()` which returns an Observable that emits each time a new item is added to the Store. You can think of stream as an Event Bus-like feature that allows you to know when any new network hits happen for a particular Store. You can leverage the Rx operator `filter()` to only subscribe to a subset of emissions. ### Get Refreshing -There is another special way to subscribe to a Store: `getRefreshing(key)`. Get Refreshing will subscribe to `get()` which returns a single response, but unlike Get, Get Refreshing will stay subscribed. Anytime you call store.`clear(key)` anyone subscribed to `getRefreshing(key)` will resubscribe and force a new network response. +There is another special way to subscribe to a Store: `getRefreshing(key)`. Get Refreshing will subscribe to `get()` which returns a single response, but unlike Get, Get Refreshing will stay subscribed. Anytime you call `store.clear(key)` anyone subscribed to `getRefreshing(key)` will resubscribe and force a new network response. ### Inflight Debouncer -To prevent duplicate requests for the same data, Store offers an inflight debouncer. If the same request is made within a minute of a previous identical request, the same response will be returned. This is useful for situations when your app needs to make many async calls for the same data at startup or when users are obsessively pulling to refresh. As an example, The New York Times news app asynchronously calls `ConfigStore.get()` from 12 different places on startup. The first call blocks while all others wait for the data to arrive. We have seen a dramatic decrease in the app's the data usage after implementing this in flight logic. +To prevent duplicate requests for the same data, Store offers an inflight debouncer. If the same request is made within a minute of a previous identical request, the same response will be returned. This is useful for situations when your app needs to make many async calls for the same data at startup or when users are obsessively pulling to refresh. As an example, The New York Times news app asynchronously calls `ConfigStore.get()` from 12 different places on startup. The first call blocks while all others wait for the data to arrive. We have seen a dramatic decrease in the app's data usage after implementing this inflight logic. ### Adding a Parser @@ -232,7 +232,7 @@ Now back to our first example: ```java Store store = StoreBuilder.parsedWithKey() .fetcher(articleId -> api.getArticles(articleId)) - .persister(FileSystemPersister.create(FileSystemFactory.create(context.getFilesDir()),pathResolver)) + .persister(FileSystemPersister.create(FileSystemFactory.create(context.getFilesDir()), pathResolver)) .parser(GsonParserFactory.createSourceParser(gson, String.class)) .open(); ``` @@ -240,7 +240,7 @@ Store store = StoreBuilder.p As mentioned, the above builder is how we work with network operations at the New York Times. With the above setup you have: + Memory caching with Guava Cache + Disk caching with FileSystem (you can reuse the same file system implementation for all stores) -+ Parsing from a BufferedSource to a (Article in our case) with Gson ++ Parsing from a BufferedSource (to an Article in our case) with Gson + In-flight request management + Ability to get cached data or bust through your caches (`get` vs. `fetch`) + Ability to listen for any new emissions from network (stream) From 9e02b9db07c2bd0f96eabbcd3f7dbb012538fc0c Mon Sep 17 00:00:00 2001 From: Jeremy Tecson Date: Fri, 27 Jul 2018 09:58:45 +0800 Subject: [PATCH 046/498] Update README.md (#363) - Typos - Format code samples - Code block usage on types - Add direct link to RxJava and Wiki --- README.md | 203 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 107 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 110ad24dd..b44ffed3b 100644 --- a/README.md +++ b/README.md @@ -10,24 +10,26 @@ Store is a Java library for effortless, reactive data loading. + Users expect their UI experience to never be compromised (blocked) by new data loads. Whether an application is social, news, or business-to-business, users expect a seamless experience both online and offline. + International users expect minimal data downloads as many megabytes of downloaded data can quickly result in astronomical phone bills. -A Store is a class that simplifies fetching, parsing, storage, and retrieval of data in your application. A Store is similar to the Repository pattern [[https://msdn.microsoft.com/en-us/library/ff649690.aspx](https://msdn.microsoft.com/en-us/library/ff649690.aspx)] while exposing a Reactive API built with RxJava that adheres to a unidirectional data flow. +A Store is a class that simplifies fetching, parsing, storage, and retrieval of data in your application. A Store is similar to the Repository pattern [[https://msdn.microsoft.com/en-us/library/ff649690.aspx](https://msdn.microsoft.com/en-us/library/ff649690.aspx)] while exposing a Reactive API built with [RxJava](https://github.com/ReactiveX/RxJava) that adheres to a unidirectional data flow. Store provides a level of abstraction between UI elements and data operations. ### Overview -A Store is responsible for managing a particular data request. When you create an implementation of a Store, you provide it with a `Fetcher`, a function that defines how data will be fetched over network. You can also define how your Store will cache data in-memory and on-disk, as well as how to parse it. Since Store returns your data as an Observable, threading is a breeze! Once a Store is built, it handles the logic around data flow, allowing your views to use the best data source and ensuring that the newest data is always available for later offline use. Stores can be customized to work with your own implementations or use our included middleware. +A Store is responsible for managing a particular data request. When you create an implementation of a Store, you provide it with a `Fetcher`, a function that defines how data will be fetched over network. You can also define how your Store will cache data in-memory and on-disk, as well as how to parse it. Since Store returns your data as an `Observable`, threading is a breeze! Once a Store is built, it handles the logic around data flow, allowing your views to use the best data source and ensuring that the newest data is always available for later offline use. Stores can be customized to work with your own implementations or use our included middleware. Store leverages RxJava and multiple request throttling to prevent excessive calls to the network and disk cache. By utilizing Store, you eliminate the possibility of flooding your network with the same request while adding two layers of caching (memory and disk). ### How to include in your project ###### Include gradle dependency + ``` - implementation 'com.nytimes.android:store3:3.1.0' +implementation 'com.nytimes.android:store3:3.1.0' ``` + ###### Set the source & target compatibilities to `1.8` -Starting with Store 3.0, `retrolambda` is no longer used. Therefore to allow support for lambdas the Java sourceCompatibility and targetCompatibility need to be set to `1.8` +Starting with Store 3.0, `retrolambda` is no longer used. Therefore to allow support for lambdas the Java `sourceCompatibility` and `targetCompatibility` need to be set to `1.8` ``` android { @@ -41,47 +43,51 @@ android { ### Fully Configured Store Let's start by looking at what a fully configured Store looks like. We will then walk through simpler examples showing each piece: + ```java Store articleStore = StoreBuilder.parsedWithKey() - .fetcher(articleId -> api.getArticleAsBufferedSource(articleId)) //OkHttp responseBody.source() - .persister(FileSystemPersister.create(FileSystemFactory.create(context.getFilesDir()),pathResolver)) - .parser(GsonParserFactory.createSourceParser(gson, ArticleAsset.Article.class)) - .open(); - + .fetcher(articleId -> api.getArticleAsBufferedSource(articleId)) // OkHttp responseBody.source() + .persister(FileSystemPersister.create(FileSystemFactory.create(context.getFilesDir()), pathResolver)) + .parser(GsonParserFactory.createSourceParser(gson, ArticleAsset.Article.class)) + .open(); + ``` With the above setup you have: -+ In Memory Caching for rotation ++ In-memory caching for rotation + Disk caching for when users are offline + Parsing through streaming API to limit memory consumption -+ Rich API to ask for data whether you want cached/new or a stream of future data updates. ++ Rich API to ask for data whether you want cached, new or a stream of future data updates. And now for the details: ### Creating a Store -You create a Store using a builder. The only requirement is to include a `.Fetcher` that returns a Single and has a single method `fetch(key)` +You create a Store using a builder. The only requirement is to include a `Fetcher` that returns a `Single` and has a single method `fetch(key)` -``` java - Store store = StoreBuilder.<>key() - .fetcher(articleId -> api.getArticle(articleId)) //OkHttp responseBody.source() - .open(); +```java +Store store = StoreBuilder.<>key() + .fetcher(articleId -> api.getArticle(articleId)) // OkHttp responseBody.source() + .open(); ``` -Stores use generic keys as identifiers for data. A key can be any value object that properly implements toString and equals and hashCode. When your Fetcher function is called, it will be passed a particular Key value. Similarly, the key will be used as a primary identifier within caches (Make sure to have a proper hashCode!!) + +Stores use generic keys as identifiers for data. A key can be any value object that properly implements `toString()`, `equals()` and `hashCode()`. When your `Fetcher` function is called, it will be passed a particular Key value. Similarly, the key will be used as a primary identifier within caches (Make sure to have a proper `hashCode()`!!). ### Our Key implementation - Barcodes -For convenience we included our own key implementation called a BarCode. Barcode has two fields `String key` and `String type` -``` java +For convenience, we included our own key implementation called a `BarCode`. `Barcode` has two fields `String key` and `String type` + +```java BarCode barcode = new BarCode("Article", "42"); ``` -When using a Barcode as your key, you can use a StoreBuilder convenience method -``` java - Store store = StoreBuilder.barcode() - .fetcher(articleBarcode -> api.getAsset(articleBarcode.getKey(),articleBarcode.getType())) - .open(); -``` +When using a `Barcode` as your key, you can use a `StoreBuilder` convenience method + +```java +Store store = StoreBuilder.barcode() + .fetcher(articleBarcode -> api.getAsset(articleBarcode.getKey(), articleBarcode.getType())) + .open(); +``` ### Public Interface - Get, Fetch, Stream, GetRefreshing @@ -102,24 +108,25 @@ By default, 100 items will be cached in memory for 24 hours. You may pass in you ### Busting through the cache -Alternatively you can call `store.fetch(barCode)` to get an Observable that skips the memory (and optional disk cache). +Alternatively you can call `store.fetch(barCode)` to get an `Observable` that skips the memory (and optional disk cache). Fresh data call will look like: `store.fetch()` ![Simple Store Flow](https://github.com/nytm/Store/blob/feature/rx2/Images/store-2.jpg) -In the New York Times app, overnight background updates use `fetch` to make sure that calls to `store.get()` will not have to hit the network during normal usage. Another good use case for `fetch` is when a user wants to pull to refresh. +In the New York Times app, overnight background updates use `fetch()` to make sure that calls to `store.get()` will not have to hit the network during normal usage. Another good use case for `fetch()` is when a user wants to pull to refresh. Calls to both `fetch()` and `get()` emit one value and then call `onCompleted()` or throw an error. ### Stream -For real-time updates, you may also call `store.stream()` which returns an Observable that emits each time a new item is added to the Store. You can think of stream as an Event Bus-like feature that allows you to know when any new network hits happen for a particular Store. You can leverage the Rx operator `filter()` to only subscribe to a subset of emissions. +For real-time updates, you may also call `store.stream()` which returns an `Observable` that emits each time a new item is added to the Store. You can think of stream as an Event Bus-like feature that allows you to know when any new network hits happen for a particular Store. You can leverage the Rx operator `filter()` to only subscribe to a subset of emissions. + ### Get Refreshing -There is another special way to subscribe to a Store: `getRefreshing(key)`. Get Refreshing will subscribe to `get()` which returns a single response, but unlike Get, Get Refreshing will stay subscribed. Anytime you call `store.clear(key)` anyone subscribed to `getRefreshing(key)` will resubscribe and force a new network response. +There is another special way to subscribe to a Store: `getRefreshing(key)`. This method will subscribe to `get()` which returns a single response, but unlike `get()`, `getRefreshing(key)` will stay subscribed. Anytime you call `store.clear(key)` anyone subscribed to `getRefreshing(key)` will resubscribe and force a new network response. ### Inflight Debouncer @@ -135,11 +142,11 @@ Since it is rare for data to arrive from the network in the format that your vie Store store = StoreBuilder.parsedWithKey() .fetcher(articleId -> api.getArticle(articleId)) .parser(source -> { - try (InputStreamReader reader = new InputStreamReader(source.inputStream())) { - return gson.fromJson(reader, Article.class); - } catch (IOException e) { - throw new RuntimeException(e); - } + try (InputStreamReader reader = new InputStreamReader(source.inputStream())) { + return gson.fromJson(reader, Article.class); + } catch (IOException e) { + throw new RuntimeException(e); + } }) .open(); ``` @@ -149,32 +156,31 @@ Our updated data flow now looks like this: `store.get()` -> ![Simple Store Flow](https://github.com/nytm/Store/blob/feature/rx2/Images/store-3.jpg) - ### Middleware - GsonSourceParser -There are also separate middleware libraries with parsers to help in cases where your fetcher is a Reader, BufferedSource or String and your parser is Gson: +There are also separate middleware libraries with parsers to help in cases where your fetcher is a `Reader`, `BufferedSource` or `String` and your parser is Gson: - GsonReaderParser - GsonSourceParser - GsonStringParser -These can be accessed via a Factory class (GsonParserFactory). +These can be accessed via a Factory class (`GsonParserFactory`). Our example can now be rewritten as: + ```java -Store store = StoreBuilder.parsedWithKey() +Store store = StoreBuilder.parsedWithKey() .fetcher(articleId -> api.getArticle(articleId)) - .parser(GsonParserFactory.createSourceParser(gson, Article.class)) - .open(); + .parser(GsonParserFactory.createSourceParser(gson, Article.class)) + .open(); ``` -In some cases you may need to parse a top level JSONArray, in which case you can provide a TypeToken. +In some cases you may need to parse a top level JSONArray, in which case you can provide a `TypeToken`. + ```java -Store,Integer> store = StoreBuilder.>parsedWithKey() +Store, Integer> store = StoreBuilder.>parsedWithKey() .fetcher(articleId -> api.getArticles()) - .parser(GsonParserFactory.createSourceParser(gson, new TypeToken>() {})) - .open(); - - + .parser(GsonParserFactory.createSourceParser(gson, new TypeToken>() {})) + .open(); ``` Similarly we have a middleware artifact for Moshi & Jackson too! @@ -182,69 +188,68 @@ Similarly we have a middleware artifact for Moshi & Jackson too! ### Disk Caching -Stores can enable disk caching by passing a Persister into the builder. Whenever a new network request is made, the Store will first write to the disk cache and then read from the disk cache. +Stores can enable disk caching by passing a `Persister` into the builder. Whenever a new network request is made, the Store will first write to the disk cache and then read from the disk cache. Now our data flow looks like: `store.get()` -> ![Simple Store Flow](https://github.com/nytm/Store/blob/feature/rx2/Images/store-5.jpg) - - Ideally, data will be streamed from network to disk using either a BufferedSource or Reader as your network raw type (rather than String). +Ideally, data will be streamed from network to disk using either a `BufferedSource` or `Reader` as your network raw type (rather than `String`). ```java -Store store = StoreBuilder.parsedWithKey() - .fetcher(articleId -> api.getArticles()) - .persister(new Persister() { - @Override - public Maybe read(Integer key) { - if (dataIsCached) { - return Observable.fromCallable(() -> userImplementedCache.get(key)); - } else { - return Observable.empty(); - } - } - - @Override - public Single write(BarCode barCode, BufferedSource source) { - userImplementedCache.save(key, source); - return Single.just(true); - } - }) - .parser(GsonParserFactory.createSourceParser(gson, Article.class)) - .open(); +Store store = StoreBuilder.parsedWithKey() + .fetcher(articleId -> api.getArticles()) + .persister(new Persister() { + @Override + public Maybe read(Integer key) { + if (dataIsCached) { + return Observable.fromCallable(() -> userImplementedCache.get(key)); + } else { + return Observable.empty(); + } + } + + @Override + public Single write(BarCode barCode, BufferedSource source) { + userImplementedCache.save(key, source); + return Single.just(true); + } + }) + .parser(GsonParserFactory.createSourceParser(gson, Article.class)) + .open(); ``` -Stores don’t care how you’re storing or retrieving your data from disk. As a result, you can use Stores with object storage or any database (Realm, SQLite, CouchDB, Firebase etc). The only requirement is that data must be the same type when stored and retrieved as it was when received from your Fetcher. Technically there is nothing stopping you from implementing an in memory cache for the “persister” implementation and instead have two levels of in memory caching--one with inflated and one with deflated models, allowing for sharing of the “persister” cache data between stores. +Stores don’t care how you’re storing or retrieving your data from disk. As a result, you can use Stores with object storage or any database (Realm, SQLite, CouchDB, Firebase etc). The only requirement is that data must be the same type when stored and retrieved as it was when received from your `Fetcher`. Technically, there is nothing stopping you from implementing an in memory cache for the “persister” implementation and instead have two levels of in memory caching--one with inflated and one with deflated models, allowing for sharing of the “persister” cache data between stores. **Note**: When using a Parser and a disk cache, the Parser will be called AFTER fetching from disk and not between the network and disk. This allows your persister to work on the network stream directly. -If using SQLite we recommend working with [SqlBrite](https://github.com/square/sqlbrite). If you are not using SqlBrite, an Observable can be created rather simply with `Observable.fromCallable(() -> getDBValue())` +If using SQLite we recommend working with [SqlBrite](https://github.com/square/sqlbrite). If you are not using SqlBrite, an `Observable` can be created rather simply with `Observable.fromCallable(() -> getDBValue())` ### Middleware - SourcePersister & FileSystem -We've found the fastest form of persistence is streaming network responses directly to disk. As a result, we have included a separate library with a reactive FileSystem which depends on Okio BufferedSources. We have also included a FileSystemPersister which will give you disk caching and works beautifully with GsonSourceParser. When using the FileSystemPersister you must pass in a `PathResolver` which will tell the file system how to name the paths to cache entries. +We've found the fastest form of persistence is streaming network responses directly to disk. As a result, we have included a separate library with a reactive FileSystem which depends on Okio `BufferedSource`s. We have also included a `FileSystemPersister` which will give you disk caching and works beautifully with `GsonSourceParser`. When using the `FileSystemPersister` you must pass in a `PathResolver` which will tell the file system how to name the paths to cache entries. Now back to our first example: ```java Store store = StoreBuilder.parsedWithKey() - .fetcher(articleId -> api.getArticles(articleId)) - .persister(FileSystemPersister.create(FileSystemFactory.create(context.getFilesDir()), pathResolver)) - .parser(GsonParserFactory.createSourceParser(gson, String.class)) - .open(); + .fetcher(articleId -> api.getArticles(articleId)) + .persister(FileSystemPersister.create(FileSystemFactory.create(context.getFilesDir()), pathResolver)) + .parser(GsonParserFactory.createSourceParser(gson, String.class)) + .open(); ``` As mentioned, the above builder is how we work with network operations at the New York Times. With the above setup you have: + Memory caching with Guava Cache + Disk caching with FileSystem (you can reuse the same file system implementation for all stores) -+ Parsing from a BufferedSource (to an Article in our case) with Gson ++ Parsing from a BufferedSource (to an Article in our case) with Gson + In-flight request management -+ Ability to get cached data or bust through your caches (`get` vs. `fetch`) ++ Ability to get cached data or bust through your caches (`get()` vs. `fetch()`) + Ability to listen for any new emissions from network (stream) -+ Ability to be notified and resubscribed when caches are cleared (helpful for times when you need to do a post request and update another screen, such as with `getRefreshing`) ++ Ability to be notified and resubscribed when caches are cleared (helpful for times when you need to do a POST request and update another screen, such as with `getRefreshing()`) We recommend using the above builder setup for most Stores. The SourcePersister implementation has a tiny memory footprint because it will stream bytes from network to disk and then from disk to parser. The streaming nature of Stores allows us to download dozens of 1mb+ json responses without worrying about OOM on low-memory devices. As mentioned above, Stores allow us to do things like calling `configStore.get()` a dozen times asynchronously before our Main Activity finishes loading without blocking the main thread or flooding our network. @@ -257,26 +262,26 @@ store = StoreBuilder.barcode() .persister(persister) .refreshOnStale() .open(); - -``` +``` -`refreshOnStale` will backfill the disk cache anytime a record is stale. The user will still get the stale record returned to them. +`refreshOnStale()` will backfill the disk cache anytime a record is stale. The user will still get the stale record returned to them. Or alternatively: ```java - store = StoreBuilder.barcode() +store = StoreBuilder.barcode() .fetcher(fetcher) .persister(persister) .networkBeforeStale() .open(); -``` -`networkBeforeStale` - Store will try to get network source when disk data is stale. If the network source throws an error or is empty, stale disk data will be returned. +``` + +`networkBeforeStale()` - Store will try to get network source when disk data is stale. If the network source throws an error or is empty, stale disk data will be returned. ### Subclassing a Store -We can also subclass a Store implementation (RealStore): +We can also subclass a Store implementation (`RealStore`): ```java public class SampleStore extends RealStore { @@ -304,43 +309,49 @@ public class SampleStore extends RealStore { + **Cache** Cache extracted from Guava (keeps method count to a minimum) - ```groovy - implementation 'com.nytimes.android:cache3:CurrentVersion' - ``` + ```groovy + implementation 'com.nytimes.android:cache3:CurrentVersion' + ``` + + **Store** This contains only Store classes and has a dependecy on RxJava + the above cache. - ```groovy - implementation 'com.nytimes.android:store3:CurrentVersion' - ``` + ```groovy + implementation 'com.nytimes.android:store3:CurrentVersion' + ``` + + **Store-Kotlin** Store plus a couple of added Kotlin classes for more idiomatic usage. ```groovy implementation 'com.nytimes.android:store-kotlin3:CurrentVersion' ``` + + **Middleware** Sample Gson parsers, (feel free to create more and open PRs) ```groovy implementation 'com.nytimes.android:middleware3:CurrentVersion' ``` + + **Middleware-Jackson** Sample Jackon parsers, (feel free to create more and open PRs) ```groovy implementation 'com.nytimes.android:middleware-jackson3:CurrentVersion' ``` + + **Middleware-Moshi** Sample Moshi parsers, (feel free to create more and open PRs) ```groovy implementation 'com.nytimes.android:middleware-moshi3:CurrentVersion' ``` + + **File System** Persistence Library built using [Okio](https://github.com/square/okio) Source/Sink + Middleware for streaming from Network to FileSystem - ```groovy - implementation 'com.nytimes.android:filesystem3:CurrentVersion' - ``` + ```groovy + implementation 'com.nytimes.android:filesystem3:CurrentVersion' + ``` ### Sample Project -See the app for example usage of Store. Alternatively, the Wiki contains a set of recipes for common use cases +See the app for example usage of Store. Alternatively, the [Wiki](https://github.com/NYTimes/Store/wiki) contains a set of recipes for common use cases + Simple Example: Retrofit + Store + Complex Example: BufferedSource from Retrofit (Can be [OkHttp](https://github.com/square/okhttp) too) + our FileSystem + our GsonSourceParser From a3df57d02a7855d458993ee3bd9e92efb4140e58 Mon Sep 17 00:00:00 2001 From: ychescale9 Date: Fri, 26 Oct 2018 01:43:29 +1100 Subject: [PATCH 047/498] Remove Deprecated annotation for stream(V key) (#368) * Remove Deprecated annotation for stream(V key) which was meant to be undeprecated with https://github.com/NYTimes/Store/pull/252. * Fix CI - remove https://plugins.gradle.org/m2 repository applied to all projects. --- buildsystem/dependencies.gradle | 4 ---- .../com/nytimes/android/external/store3/base/impl/Store.java | 1 - 2 files changed, 5 deletions(-) diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index f10c12f52..353b29c21 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -1,10 +1,6 @@ allprojects { repositories { mavenLocal() - maven { - url 'https://plugins.gradle.org/m2/' - } - maven { url = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' } diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.java index 317dbd9b0..0570b6e03 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.java +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.java @@ -69,7 +69,6 @@ public interface Store { * Errors will be dropped * */ - @Deprecated @Nonnull Observable stream(V key); From 89279449c9cb18d99520a342a92ab643ad0cbc97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dar=C3=ADo=20Here=C3=B1=C3=BA?= Date: Tue, 30 Oct 2018 17:28:40 -0300 Subject: [PATCH 048/498] Typos on #316 & #334 (#370) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b44ffed3b..c8794cde6 100644 --- a/README.md +++ b/README.md @@ -313,7 +313,7 @@ public class SampleStore extends RealStore { implementation 'com.nytimes.android:cache3:CurrentVersion' ``` -+ **Store** This contains only Store classes and has a dependecy on RxJava + the above cache. ++ **Store** This contains only Store classes and has a dependency on RxJava + the above cache. ```groovy implementation 'com.nytimes.android:store3:CurrentVersion' @@ -331,7 +331,7 @@ public class SampleStore extends RealStore { implementation 'com.nytimes.android:middleware3:CurrentVersion' ``` -+ **Middleware-Jackson** Sample Jackon parsers, (feel free to create more and open PRs) ++ **Middleware-Jackson** Sample Jackson parsers, (feel free to create more and open PRs) ```groovy implementation 'com.nytimes.android:middleware-jackson3:CurrentVersion' From 6e6104c2bb3d28f2a24ea54633ac7b80fbb0a90e Mon Sep 17 00:00:00 2001 From: ychescale9 Date: Mon, 7 Jan 2019 22:05:05 +0800 Subject: [PATCH 049/498] Update SDK, build tools, gradle, AGP, Kotlin, and library dependencies. (#371) * Update SDK, build tools, gradle, AGP, Kotlin, and library dependencies. * Update travis config with new SDK version. --- .travis.yml | 6 ++--- app/build.gradle | 2 +- build.gradle | 14 +++++----- buildsystem/dependencies.gradle | 34 ++++++++++++------------ cache/README.md | 2 +- docs/ru/README.md | 12 ++++----- gradle/wrapper/gradle-wrapper.properties | 2 +- 7 files changed, 35 insertions(+), 37 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2d0ff2cc5..af87b12e0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,8 @@ android: components: - tools - platform-tools - - build-tools-27.0.3 - - android-27 + - build-tools-28.0.3 + - android-28 - extra-android-m2repository licenses: - 'android-sdk-license-.+' @@ -15,7 +15,7 @@ script: after_success: - gradle/deploy_snapshot.sh before_install: -- yes | sdkmanager "platforms;android-27" +- yes | sdkmanager "platforms;android-28" branches: except: - gh-pages diff --git a/app/build.gradle b/app/build.gradle index 581446576..a3f6e8aba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,7 +55,7 @@ dependencies { implementation project(':middleware') implementation project(':filesystem') implementation libraries.rxAndroid2 - compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$versions.kotlin" implementation "android.arch.persistence.room:runtime:$room_version" annotationProcessor "android.arch.persistence.room:compiler:$room_version" diff --git a/build.gradle b/build.gradle index e93762f35..84fc5e10b 100644 --- a/build.gradle +++ b/build.gradle @@ -2,9 +2,8 @@ apply from: 'buildsystem/dependencies.gradle' // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.2.41' repositories { - mavenLocal() + mavenCentral() maven { url 'https://plugins.gradle.org/m2/' } @@ -19,17 +18,16 @@ buildscript { } rootProject.ext.versions = [ - kotlin: '1.2.21' + kotlin: '1.3.0' ] dependencies { - classpath 'com.android.tools.build:gradle:3.2.0-alpha16' - classpath 'com.google.gms:google-services:3.0.0' - classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.6' + classpath 'com.android.tools.build:gradle:3.4.0-alpha02' + classpath 'com.google.gms:google-services:4.2.0' + classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.8.4' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$rootProject.ext.versions.kotlin" - classpath 'org.jetbrains.dokka:dokka-gradle-plugin:0.9.16' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'org.jetbrains.dokka:dokka-gradle-plugin:0.9.17' } } diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index 353b29c21..978b43f4a 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -11,31 +11,31 @@ allprojects { ext.versions = [ minSdk : 16, - targetSdk : 27, - compileSdk : 27, - buildTools : '27.0.3', - kotlin : '1.1.2-5', + targetSdk : 28, + compileSdk : 28, + buildTools : '28.0.3', + kotlin : '1.3.0', // UI libs. - supportLibs : '27.1.0', + supportLibs : '28.0.0', picasso : '2.5.2', - butterKnife : '7.0.1', + butterKnife : '8.8.1', // Reactive. - rxJava2 : '2.1.2', - rxJavaProGuardRules : '1.1.6.0', + rxJava2 : '2.2.3', + rxJavaProGuardRules : '1.3.0.0', rxJavaAsyncUtil : '0.21.0', - rxAndroid2 : '2.0.1', + rxAndroid2 : '2.1.0', // Others. - retrofit : '2.2.0', - dagger : '2.9', - jsr305 : '3.0.1', - okHttp : '2.7.5', - okio : '1.13.0', - gson : '2.8.1', - moshi : '1.5.0', - jackson : '2.8.8', + retrofit : '2.4.0', + dagger : '2.18', + jsr305 : '3.0.2', + okHttp : '3.11.0', + okio : '1.16.0', + gson : '2.8.5', + moshi : '1.7.0', + jackson : '2.9.7', guava : '19.0', javapoet : '1.8.0', immutables : '2.2.1', diff --git a/cache/README.md b/cache/README.md index 33ae76621..51b2b5fc5 100644 --- a/cache/README.md +++ b/cache/README.md @@ -28,7 +28,7 @@ memCache.get(key, new Callable() { https://github.com/google/guava/wiki/CachesExplained ```groovy - compile 'com.nytimes.android:cache3:3.0.0-beta' + implementation 'com.nytimes.android:cache3:3.0.0-beta' ``` ``` diff --git a/docs/ru/README.md b/docs/ru/README.md index 0cc89fef5..4e5aba4dd 100644 --- a/docs/ru/README.md +++ b/docs/ru/README.md @@ -333,32 +333,32 @@ public class SampleStore extends RealStore { + **Cache** Кэш, извлеченный из библиотеки Guava (чтобы сократить количество методов) ```groovy - compile 'com.nytimes.android:cache:CurrentVersion' + implementation 'com.nytimes.android:cache:CurrentVersion' ``` + **Store** Содержит только классы Store, зависит от RxJava и артефакта кэша ```groovy - compile 'com.nytimes.android:store:CurrentVersion' + implementation 'com.nytimes.android:store:CurrentVersion' ``` + **Middleware** Парсеры Gson (не стесняйтесь создавать новые и предлагать PR) ```groovy - compile 'com.nytimes.android:middleware:CurrentVersion' + implementation 'com.nytimes.android:middleware:CurrentVersion' ``` + **Middleware-Jackson** Парсеры Jackson (не стесняйтесь создавать новые и предлагать PR) ```groovy - compile 'com.nytimes.android:middleware-jackson:CurrentVersion' + implementation 'com.nytimes.android:middleware-jackson:CurrentVersion' ``` + **Middleware-Moshi** Парсеры Moshi (не стесняйтесь создавать новые и предлагать PR) ```groovy - compile 'com.nytimes.android:middleware-moshi:CurrentVersion' + implementation 'com.nytimes.android:middleware-moshi:CurrentVersion' ``` + **File System** библиотека, использующая Okio Source/Sink + Middleware для сохранения потока данных из сети в файловую систему ```groovy - compile 'com.nytimes.android:filesystem:CurrentVersion' + implementation 'com.nytimes.android:filesystem:CurrentVersion' ``` diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b5e6f6083..718173b4a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip From 09674f960e780e0aabf21f9cd736298194e90eca Mon Sep 17 00:00:00 2001 From: DigitalBuddha Date: Sun, 10 Feb 2019 19:23:00 -0500 Subject: [PATCH 050/498] fixing tests --- app/build.gradle | 7 +- .../com/nytimes/android/sample/SampleApp.kt | 262 +++++++------- .../nytimes/android/sample/SampleRoomStore.kt | 120 +++---- .../activity/PersistingStoreActivity.java | 149 ++++---- .../sample/activity/StoreActivity.java | 166 ++++----- .../android/sample/ClearStoreMemoryTest.kt | 63 ++++ .../nytimes/android/sample/ClearStoreTest.kt | 115 +++++++ .../android/sample/DontCacheErrorsTest.kt | 40 +++ .../nytimes/android/sample/KeyParserTest.kt | 43 +++ .../com/nytimes/android/sample/SmokeTests.kt | 217 ++++++++++++ .../android/sample/StoreIntegrationTest.java | 31 -- .../android/sample/StoreIntegrationTest.kt | 41 +++ build.gradle | 11 +- .../android/external/cache3/Cache.java | 3 +- .../android/external/fs3/FSReader.java | 60 ---- .../nytimes/android/external/fs3/FSReader.kt | 49 +++ .../android/external/fs3/FSWriter.java | 34 -- .../nytimes/android/external/fs3/FSWriter.kt | 21 ++ .../external/fs3/FileSystemPersister.java | 47 --- .../external/fs3/FileSystemPersister.kt | 43 +++ .../fs3/FileSystemPersisterFactory.java | 84 ----- .../fs3/FileSystemPersisterFactory.kt | 77 +++++ .../fs3/FileSystemRecordPersister.java | 72 ---- .../external/fs3/FileSystemRecordPersister.kt | 57 ++++ .../android/external/fs3/RecordPersister.java | 39 --- .../android/external/fs3/RecordPersister.kt | 28 ++ .../external/fs3/RecordPersisterFactory.java | 54 --- .../external/fs3/RecordPersisterFactory.kt | 49 +++ .../external/fs3/SourceAllPersister.java | 65 ---- .../external/fs3/SourceAllPersister.kt | 55 +++ .../external/fs3/SourceFileReader.java | 32 -- .../android/external/fs3/SourceFileReader.kt | 22 ++ .../external/fs3/SourceFileWriter.java | 19 -- .../android/external/fs3/SourceFileWriter.kt | 12 + .../android/external/fs3/SourcePersister.java | 58 ---- .../android/external/fs3/SourcePersister.kt | 52 +++ .../external/fs3/SourcePersisterFactory.java | 106 ------ .../external/fs3/SourcePersisterFactory.kt | 99 ++++++ .../external/fs3/FilePersisterTest.java | 2 +- .../fs3/FileSystemRecordPersisterTest.java | 2 +- .../external/fs3/RecordPersisterTest.java | 10 +- .../external/fs3/SourcePersisterTest.java | 4 +- .../store3/middleware/GsonParserFactory.java | 75 ---- .../store3/middleware/GsonParserFactory.kt | 65 ++++ .../store3/middleware/GsonReaderParser.java | 33 -- .../store3/middleware/GsonReaderParser.kt | 17 + .../store3/middleware/GsonSourceParser.java | 53 --- .../store3/middleware/GsonSourceParser.kt | 40 +++ .../store3/middleware/GsonStringParser.java | 32 -- .../store3/middleware/GsonStringParser.kt | 21 ++ .../store3/GenericParserStoreTest.java | 2 +- .../store3/GsonParserFactoryTest.java | 18 +- .../store3/GsonSourceListParserTest.java | 2 +- store/build.gradle | 24 ++ store/gradle.properties | 3 +- .../external/store3/base/AllPersister.java | 46 --- .../external/store3/base/AllPersister.kt | 38 +++ .../external/store3/base/DiskRead.java | 10 - .../android/external/store3/base/DiskRead.kt | 7 + .../external/store3/base/DiskWrite.java | 15 - .../android/external/store3/base/DiskWrite.kt | 13 + .../android/external/store3/base/Fetcher.kt | 19 ++ .../external/store3/base/InternalStore.java | 19 -- .../external/store3/base/InternalStore.kt | 13 + .../android/external/store3/base/Parser.java | 14 - .../android/external/store3/base/Parser.kt | 14 + .../external/store3/base/Persister.java | 32 -- .../android/external/store3/base/Persister.kt | 23 ++ .../store3/base/impl/CacheFactory.java | 4 +- .../store3/base/impl/MultiParser.java | 46 --- .../external/store3/base/impl/MultiParser.kt | 40 +++ .../store3/base/impl/ParsingFetcher.java | 44 --- .../store3/base/impl/ParsingFetcher.kt | 31 ++ .../store3/base/impl/RealInternalStore.java | 321 ------------------ .../store3/base/impl/RealInternalStore.kt | 287 ++++++++++++++++ .../external/store3/base/impl/RealStore.java | 153 --------- .../external/store3/base/impl/RealStore.kt | 133 ++++++++ .../store3/base/impl/RealStoreBuilder.java | 133 -------- .../store3/base/impl/RealStoreBuilder.kt | 106 ++++++ .../store3/base/impl/{Store.java => Store.kt} | 68 ++-- .../store3/base/impl/room/RealStoreRoom.java | 32 +- .../store3/base/impl/room/StoreRoom.java | 9 +- .../{Fetcher.java => room/RoomFetcher.java} | 7 +- .../external/store3/util/KeyParser.java | 11 - .../android/external/store3/util/KeyParser.kt | 11 + .../external/store3/util/NoKeyParser.java | 20 -- .../external/store3/util/NoKeyParser.kt | 13 + .../external/store3/util/NoopParserFunc.java | 17 - .../external/store3/util/NoopParserFunc.kt | 18 + .../external/store3/util/NoopPersister.java | 70 ---- .../external/store3/util/NoopPersister.kt | 66 ++++ .../external/store3/DontCacheErrorsTest.java | 62 ---- .../external/store3/ParsingFetcherTest.java | 142 ++++---- .../android/external/store3/StoreTest.java | 6 +- .../external/store3/StoreWithParserTest.java | 2 +- .../external/store3/StreamOneKeyTest.java | 6 +- .../store3/base/impl/MultiParserTest.java | 114 +++---- .../external/store3/room/StoreRoomTest.java | 2 +- .../store3/util/NoopPersisterTest.java | 8 +- 99 files changed, 2640 insertions(+), 2510 deletions(-) create mode 100644 app/src/test/java/com/nytimes/android/sample/ClearStoreMemoryTest.kt create mode 100644 app/src/test/java/com/nytimes/android/sample/ClearStoreTest.kt create mode 100644 app/src/test/java/com/nytimes/android/sample/DontCacheErrorsTest.kt create mode 100644 app/src/test/java/com/nytimes/android/sample/KeyParserTest.kt create mode 100644 app/src/test/java/com/nytimes/android/sample/SmokeTests.kt delete mode 100644 app/src/test/java/com/nytimes/android/sample/StoreIntegrationTest.java create mode 100644 app/src/test/java/com/nytimes/android/sample/StoreIntegrationTest.kt delete mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/FSReader.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/FSReader.kt delete mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/FSWriter.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/FSWriter.kt delete mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersister.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersister.kt delete mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersisterFactory.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersisterFactory.kt delete mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemRecordPersister.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemRecordPersister.kt delete mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersister.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersister.kt delete mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersisterFactory.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersisterFactory.kt delete mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/SourceAllPersister.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/SourceAllPersister.kt delete mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileReader.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileReader.kt delete mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileWriter.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileWriter.kt delete mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersister.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersister.kt delete mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersisterFactory.java create mode 100644 filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersisterFactory.kt delete mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.java create mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.kt delete mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.java create mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.kt delete mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.java create mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.kt delete mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.java create mode 100644 middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.kt delete mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/AllPersister.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/AllPersister.kt delete mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/DiskRead.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/DiskRead.kt delete mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/DiskWrite.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/DiskWrite.kt create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/Fetcher.kt delete mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/InternalStore.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/InternalStore.kt delete mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/Parser.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/Parser.kt delete mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/Persister.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/Persister.kt delete mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/impl/MultiParser.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/impl/MultiParser.kt delete mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.kt delete mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt delete mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.kt delete mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt rename store/src/main/java/com/nytimes/android/external/store3/base/impl/{Store.java => Store.kt} (58%) rename store/src/main/java/com/nytimes/android/external/store3/base/{Fetcher.java => room/RoomFetcher.java} (64%) delete mode 100644 store/src/main/java/com/nytimes/android/external/store3/util/KeyParser.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/util/KeyParser.kt delete mode 100644 store/src/main/java/com/nytimes/android/external/store3/util/NoKeyParser.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/util/NoKeyParser.kt delete mode 100644 store/src/main/java/com/nytimes/android/external/store3/util/NoopParserFunc.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/util/NoopParserFunc.kt delete mode 100644 store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.java create mode 100644 store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.kt delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest.java diff --git a/app/build.gradle b/app/build.gradle index a3f6e8aba..47b17a5fc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,7 +37,10 @@ def room_version = "1.1.0" // or, for latest rc, use "1.1.1-rc1" dependencies { testImplementation libraries.junit - + testImplementation libraries.mockito + testImplementation libraries.assertJ + testImplementation libraries.junit + testCompileOnly libraries.jsr305 implementation libraries.supportRecyclerView implementation libraries.supportAppCompat implementation libraries.supportCardView @@ -64,6 +67,8 @@ dependencies { // androidTestImplementation "android.arch.persistence.room:testing:$room_version" // optional - RxJava support for Room implementation "android.arch.persistence.room:rxjava2:$room_version" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1" + } repositories { mavenCentral() diff --git a/app/src/main/java/com/nytimes/android/sample/SampleApp.kt b/app/src/main/java/com/nytimes/android/sample/SampleApp.kt index 5f280b9e3..3431fc338 100644 --- a/app/src/main/java/com/nytimes/android/sample/SampleApp.kt +++ b/app/src/main/java/com/nytimes/android/sample/SampleApp.kt @@ -1,134 +1,136 @@ -package com.nytimes.android.sample - import android.app.Application -import android.content.Context -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.nytimes.android.external.fs3.SourcePersisterFactory -import com.nytimes.android.external.store3.base.Persister -import com.nytimes.android.external.store3.base.impl.BarCode -import com.nytimes.android.external.store3.base.impl.MemoryPolicy -import com.nytimes.android.external.store3.base.impl.Store -import com.nytimes.android.external.store3.base.impl.StoreBuilder -import com.nytimes.android.external.store3.middleware.GsonParserFactory -import com.nytimes.android.sample.data.model.GsonAdaptersModel -import com.nytimes.android.sample.data.model.RedditData -import com.nytimes.android.sample.data.remote.Api -import io.reactivex.Observable -import io.reactivex.Single -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers -import okio.BufferedSource -import retrofit2.Retrofit -import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory -import retrofit2.converter.gson.GsonConverterFactory -import java.io.IOException -import java.util.concurrent.TimeUnit +//package com.nytimes.android.sample +// +//import android.app.Application +//import android.content.Context +//import com.google.gson.Gson +//import com.google.gson.GsonBuilder +//import com.nytimes.android.external.fs3.SourcePersisterFactory +//import com.nytimes.android.external.store3.base.Persister +//import com.nytimes.android.external.store3.base.impl.BarCode +//import com.nytimes.android.external.store3.base.impl.MemoryPolicy +//import com.nytimes.android.external.store3.base.impl.Store +//import com.nytimes.android.external.store3.base.impl.StoreBuilder +//import com.nytimes.android.external.store3.middleware.GsonParserFactory +//import com.nytimes.android.sample.data.model.GsonAdaptersModel +//import com.nytimes.android.sample.data.model.RedditData +//import com.nytimes.android.sample.data.remote.Api +//import io.reactivex.Observable +//import io.reactivex.Single +//import io.reactivex.android.schedulers.AndroidSchedulers +//import io.reactivex.schedulers.Schedulers +//import okio.BufferedSource +//import retrofit2.Retrofit +//import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +//import retrofit2.converter.gson.GsonConverterFactory +//import java.io.IOException +//import java.util.concurrent.TimeUnit +// class SampleApp : Application() { - - var nonPersistedStore: Store? = null - var persistedStore: Store? =null - private var persister: Persister? =null - lateinit var sampleRoomStore:SampleRoomStore - - override fun onCreate() { - super.onCreate() - appContext = this - sampleRoomStore = SampleRoomStore(this) - initPersister(); - nonPersistedStore = provideRedditStore(); - persistedStore=providePersistedRedditStore(); - RoomSample() - } - - private fun RoomSample() { - var foo = sampleRoomStore.store.get("") - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ strings1 -> val success = strings1 != null }) { throwable -> throwable.stackTrace } - - foo = Observable.timer(15, TimeUnit.SECONDS) - .subscribe { makeFetchRequest() } - } - - private fun makeFetchRequest() { - val bar = sampleRoomStore.store.fetch("") - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ strings1 -> val success = strings1 != null }) { throwable -> throwable.stackTrace } - } - - private fun initPersister() { - try { - persister = newPersister() - } catch (exception: IOException) { - throw RuntimeException(exception) - } - - } - - /** - * Provides a Store which only retains RedditData for 10 seconds in memory. - */ - private fun provideRedditStore(): Store { - return StoreBuilder.barcode() - .fetcher { barCode -> provideRetrofit().fetchSubreddit(barCode.key, "10") } - .memoryPolicy( - MemoryPolicy - .builder() - .setExpireAfterWrite(10) - .setExpireAfterTimeUnit(TimeUnit.SECONDS) - .build() - ) - .open() - } - - /** - * Provides a Store which will persist RedditData to the cache, and use Gson to parse the JSON - * that comes back from the network into RedditData. - */ - private fun providePersistedRedditStore(): Store { - return StoreBuilder.parsedWithKey() - .fetcher({ this.fetcher(it) }) - .persister(newPersister()) - .parser(GsonParserFactory.createSourceParser(provideGson(), RedditData::class.java)) - .open() - } - - /** - * Returns a new Persister with the cache as the root. - */ - @Throws(IOException::class) - private fun newPersister(): Persister { - return SourcePersisterFactory.create(this.cacheDir) - } - - /** - * Returns a "fetcher" which will retrieve new data from the network. - */ - private fun fetcher(barCode: BarCode): Single { - return provideRetrofit().fetchSubredditForPersister(barCode.key, "10") - .map({ it.source() }) - } - - private fun provideRetrofit(): Api { - return Retrofit.Builder() - .baseUrl("http://reddit.com/") - .addConverterFactory(GsonConverterFactory.create(provideGson())) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) - .validateEagerly(BuildConfig.DEBUG) // Fail early: check Retrofit configuration at creation time in Debug build. - .build() - .create(Api::class.java) - } - - internal fun provideGson(): Gson { - return GsonBuilder() - .registerTypeAdapterFactory(GsonAdaptersModel()) - .create() - } - - companion object { - var appContext: Context? = null - } +// +// var nonPersistedStore: Store? = null +// var persistedStore: Store? =null +// private var persister: Persister? =null +//// lateinit var sampleRoomStore:SampleRoomStore +// +// override fun onCreate() { +// super.onCreate() +// appContext = this +//// sampleRoomStore = SampleRoomStore(this) +// initPersister(); +// nonPersistedStore = provideRedditStore(); +// persistedStore=providePersistedRedditStore(); +// RoomSample() +// } +// +// private fun RoomSample() { +//// var foo = sampleRoomStore.store.get("") +//// .subscribeOn(Schedulers.io()) +//// .observeOn(AndroidSchedulers.mainThread()) +//// .subscribe({ strings1 -> val success = strings1 != null }) { throwable -> throwable.stackTrace } +// +//// foo = Observable.timer(15, TimeUnit.SECONDS) +//// .subscribe { makeFetchRequest() } +// } +// +// private fun makeFetchRequest() { +//// val bar = sampleRoomStore.store.fetch("") +//// .subscribeOn(Schedulers.io()) +//// .observeOn(AndroidSchedulers.mainThread()) +//// .subscribe({ strings1 -> val success = strings1 != null }) { throwable -> throwable.stackTrace } +// } +// +// private fun initPersister() { +// try { +// persister = newPersister() +// } catch (exception: IOException) { +// throw RuntimeException(exception) +// } +// +// } +// +// /** +// * Provides a Store which only retains RedditData for 10 seconds in memory. +// */ +// private fun provideRedditStore(): Store { +// return StoreBuilder.barcode() +// .fetcher { barCode -> provideRetrofit().fetchSubreddit(barCode.key, "10") } +// .memoryPolicy( +// MemoryPolicy +// .builder() +// .setExpireAfterWrite(10) +// .setExpireAfterTimeUnit(TimeUnit.SECONDS) +// .build() +// ) +// .open() +// } +// +// /** +// * Provides a Store which will persist RedditData to the cache, and use Gson to parse the JSON +// * that comes back from the network into RedditData. +// */ +// private fun providePersistedRedditStore(): Store { +// return StoreBuilder.parsedWithKey() +// .fetcher({ this.fetcher(it) }) +// .persister(newPersister()) +// .parser(GsonParserFactory.createSourceParser(provideGson(), RedditData::class.java)) +// .open() +// } +// +// /** +// * Returns a new Persister with the cache as the root. +// */ +// @Throws(IOException::class) +// private fun newPersister(): Persister { +// return SourcePersisterFactory.create(this.cacheDir) +// } +// +// /** +// * Returns a "fetcher" which will retrieve new data from the network. +// */ +// private fun fetcher(barCode: BarCode): Single { +// return provideRetrofit().fetchSubredditForPersister(barCode.key, "10") +// .map({ it.source() }) +// } +// +// private fun provideRetrofit(): Api { +// return Retrofit.Builder() +// .baseUrl("http://reddit.com/") +// .addConverterFactory(GsonConverterFactory.create(provideGson())) +// .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) +// .validateEagerly(BuildConfig.DEBUG) // Fail early: check Retrofit configuration at creation time in Debug build. +// .build() +// .create(Api::class.java) +// } +// +// internal fun provideGson(): Gson { +// return GsonBuilder() +// .registerTypeAdapterFactory(GsonAdaptersModel()) +// .create() +// } +// +// companion object { +// var appContext: Context? = null +// } } diff --git a/app/src/main/java/com/nytimes/android/sample/SampleRoomStore.kt b/app/src/main/java/com/nytimes/android/sample/SampleRoomStore.kt index f3bf00f97..59e2018f6 100644 --- a/app/src/main/java/com/nytimes/android/sample/SampleRoomStore.kt +++ b/app/src/main/java/com/nytimes/android/sample/SampleRoomStore.kt @@ -1,60 +1,60 @@ -package com.nytimes.android.sample - -import android.arch.persistence.room.Dao -import android.arch.persistence.room.Database -import android.arch.persistence.room.Entity -import android.arch.persistence.room.Insert -import android.arch.persistence.room.PrimaryKey -import android.arch.persistence.room.Query -import android.arch.persistence.room.Room -import android.arch.persistence.room.RoomDatabase -import android.content.Context -import com.nytimes.android.external.store3.base.Fetcher -import com.nytimes.android.external.store3.base.impl.room.StoreRoom -import com.nytimes.android.external.store3.base.room.RoomPersister -import io.reactivex.Flowable -import io.reactivex.Observable -import io.reactivex.Single - -@Entity -data class User( - @PrimaryKey(autoGenerate = true) - var uid: Int = 0, - val name: String) - -@Dao -interface UserDao { - @Query("SELECT name FROM user") - fun loadAll(): Flowable> - - @Insert - fun insertAll(user: User) - -} - -@Database(entities = arrayOf(User::class), version = 1) -abstract class AppDatabase : RoomDatabase() { - abstract fun userDao(): UserDao -} - -class SampleRoomStore(context: Context){ - val db = Room.databaseBuilder(context, AppDatabase::class.java, "db").build() - - val fetcher = Fetcher { Single.just(User(name = "Mike")) } - val persister = object : RoomPersister, String> { - - override fun read(key: String): Observable> { - return db.userDao().loadAll().toObservable() - } - - override fun write(key: String, user: User) { - db.userDao().insertAll(user) - } - } - - val store = StoreRoom.from(fetcher, persister) -} - - - - +//package com.nytimes.android.sample +// +//import android.arch.persistence.room.Dao +//import android.arch.persistence.room.Database +//import android.arch.persistence.room.Entity +//import android.arch.persistence.room.Insert +//import android.arch.persistence.room.PrimaryKey +//import android.arch.persistence.room.Query +//import android.arch.persistence.room.Room +//import android.arch.persistence.room.RoomDatabase +//import android.content.Context +//import com.nytimes.android.external.store3.base.Fetcher +//import com.nytimes.android.external.store3.base.impl.room.StoreRoom +//import com.nytimes.android.external.store3.base.room.RoomPersister +//import io.reactivex.Flowable +//import io.reactivex.Observable +//import io.reactivex.Single +// +//@Entity +//data class User( +// @PrimaryKey(autoGenerate = true) +// var uid: Int = 0, +// val name: String) +// +//@Dao +//interface UserDao { +// @Query("SELECT name FROM user") +// fun loadAll(): Flowable> +// +// @Insert +// fun insertAll(user: User) +// +//} +// +//@Database(entities = arrayOf(User::class), version = 1) +//abstract class AppDatabase : RoomDatabase() { +// abstract fun userDao(): UserDao +//} +// +//class SampleRoomStore(context: Context){ +// val db = Room.databaseBuilder(context, AppDatabase::class.java, "db").build() +// +// val fetcher = Fetcher { Single.just(User(name = "Mike")) } +// val persister = object : RoomPersister, String> { +// +// override fun read(key: String): Observable> { +// return db.userDao().loadAll().toObservable() +// } +// +// override fun write(key: String, user: User) { +// db.userDao().insertAll(user) +// } +// } +// +// val store = StoreRoom.from(fetcher, persister) +//} +// +// +// +// diff --git a/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java b/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java index 8c3e18813..cb91f1261 100644 --- a/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java +++ b/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java @@ -1,94 +1,71 @@ package com.nytimes.android.sample.activity; -import android.os.Bundle; import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.util.Log; -import android.widget.Toast; - -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.sample.R; -import com.nytimes.android.sample.SampleApp; -import com.nytimes.android.sample.data.model.Children; -import com.nytimes.android.sample.data.model.Post; -import com.nytimes.android.sample.data.model.RedditData; -import com.nytimes.android.sample.reddit.PostAdapter; - -import java.util.List; - -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.schedulers.Schedulers; - -import static android.widget.Toast.makeText; public class PersistingStoreActivity extends AppCompatActivity { - private RecyclerView recyclerView; - private PostAdapter postAdapter; - private Store persistedStore; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_store); - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - - postAdapter = new PostAdapter(); - recyclerView = (RecyclerView) findViewById(R.id.postRecyclerView); - LinearLayoutManager layoutManager = new LinearLayoutManager(this); - layoutManager.setOrientation(LinearLayoutManager.VERTICAL); - recyclerView.setLayoutManager(layoutManager); - recyclerView.setAdapter(postAdapter); - } - - private void initStore() { - if (this.persistedStore == null) { - this.persistedStore = ((SampleApp) getApplicationContext()).getPersistedStore(); - } - } - - @SuppressWarnings("CheckReturnValue") - public void loadPosts() { - BarCode awwRequest = new BarCode(RedditData.class.getSimpleName(), "aww"); - - /* - First call to get(awwRequest) will use the network, then save response in the in-memory - cache. Subsequent calls will retrieve the cached version of the data. - */ - this.persistedStore - .get(awwRequest) - .flatMapObservable(this::sanitizeData) - .toList() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::showPosts, throwable -> - Log.e(StoreActivity.class.getSimpleName(), throwable.getMessage(), - throwable)); - } - - private void showPosts(List posts) { - postAdapter.setPosts(posts); - makeText(PersistingStoreActivity.this, - "Loaded " + posts.size() + " posts", - Toast.LENGTH_SHORT) - .show(); - } - - private Observable sanitizeData(RedditData redditData) { - return Observable.fromIterable(redditData.data().children()) - .map(Children::data); - } - - @Override - protected void onResume() { - super.onResume(); - initStore(); - loadPosts(); - } +// private RecyclerView recyclerView; +// private PostAdapter postAdapter; +// private Store persistedStore; +// +// @Override +// protected void onCreate(Bundle savedInstanceState) { +// super.onCreate(savedInstanceState); +// +// setContentView(R.layout.activity_store); +// setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); +// +// postAdapter = new PostAdapter(); +// recyclerView = (RecyclerView) findViewById(R.id.postRecyclerView); +// LinearLayoutManager layoutManager = new LinearLayoutManager(this); +// layoutManager.setOrientation(LinearLayoutManager.VERTICAL); +// recyclerView.setLayoutManager(layoutManager); +// recyclerView.setAdapter(postAdapter); +// } +// +// private void initStore() { +// if (this.persistedStore == null) { +// this.persistedStore = ((SampleApp) getApplicationContext()).getPersistedStore(); +// } +// } +// +// @SuppressWarnings("CheckReturnValue") +// public void loadPosts() { +// BarCode awwRequest = new BarCode(RedditData.class.getSimpleName(), "aww"); +// +// /* +// First call to get(awwRequest) will use the network, then save response in the in-memory +// cache. Subsequent calls will retrieve the cached version of the data. +// */ +// this.persistedStore +// .get(awwRequest) +// .flatMapObservable(this::sanitizeData) +// .toList() +// .subscribeOn(Schedulers.io()) +// .observeOn(AndroidSchedulers.mainThread()) +// .subscribe(this::showPosts, throwable -> +// Log.e(StoreActivity.class.getSimpleName(), throwable.getMessage(), +// throwable)); +// } +// +// private void showPosts(List posts) { +// postAdapter.setPosts(posts); +// makeText(PersistingStoreActivity.this, +// "Loaded " + posts.size() + " posts", +// Toast.LENGTH_SHORT) +// .show(); +// } +// +// private Observable sanitizeData(RedditData redditData) { +// return Observable.fromIterable(redditData.data().children()) +// .map(Children::data); +// } +// +// @Override +// protected void onResume() { +// super.onResume(); +// initStore(); +// loadPosts(); +// } } diff --git a/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java b/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java index de0703bca..f8b191e8b 100644 --- a/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java +++ b/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java @@ -1,104 +1,78 @@ package com.nytimes.android.sample.activity; -import android.os.Bundle; import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.Toolbar; -import android.util.Log; -import android.widget.Toast; - -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.sample.R; -import com.nytimes.android.sample.SampleApp; -import com.nytimes.android.sample.data.model.Children; -import com.nytimes.android.sample.data.model.Post; -import com.nytimes.android.sample.data.model.RedditData; -import com.nytimes.android.sample.reddit.PostAdapter; - -import java.util.List; - -import io.reactivex.Observable; -import io.reactivex.ObservableSource; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.annotations.NonNull; -import io.reactivex.functions.Function; -import io.reactivex.schedulers.Schedulers; - -import static android.widget.Toast.makeText; public class StoreActivity extends AppCompatActivity { - private RecyclerView recyclerView; - private PostAdapter postAdapter; - private Store nonPersistedStore; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_store); - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - postAdapter = new PostAdapter(); - recyclerView = (RecyclerView) findViewById(R.id.postRecyclerView); - LinearLayoutManager layoutManager = new LinearLayoutManager(this); - layoutManager.setOrientation(LinearLayoutManager.VERTICAL); - recyclerView.setLayoutManager(layoutManager); - recyclerView.setAdapter(postAdapter); - } - - private void initStore() { - if (this.nonPersistedStore == null) { - this.nonPersistedStore = ((SampleApp) getApplicationContext()).getNonPersistedStore(); - } - } - - @SuppressWarnings("CheckReturnValue") - public void loadPosts() { - BarCode awwRequest = new BarCode(RedditData.class.getSimpleName(), "aww"); - - /* - First call to get(awwRequest) will use the network, then save response in the in-memory - cache. Subsequent calls will retrieve the cached version of the data. - - But, since the policy of this store is to expire after 10 seconds, the cache will - only be used for subsequent requests that happen within 10 seconds of the initial request. - After that, the request will use the network. - */ - this.nonPersistedStore - .get(awwRequest) - .flatMapObservable(new Function>() { - @Override - public ObservableSource apply(@NonNull RedditData redditData) throws Exception { - return sanitizeData(redditData); - } - }) - .toList() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::showPosts, throwable -> { - Log.e(StoreActivity.class.getSimpleName(), throwable.getMessage(), throwable); - }); - } - - private void showPosts(List posts) { - postAdapter.setPosts(posts); - makeText(StoreActivity.this, - "Loaded " + posts.size() + " posts", - Toast.LENGTH_SHORT) - .show(); - } - - private Observable sanitizeData(RedditData redditData) { - return Observable.fromIterable(redditData.data().children()) - .map(Children::data); - } - - @Override - protected void onResume() { - super.onResume(); - initStore(); - loadPosts(); - } +// private RecyclerView recyclerView; +// private PostAdapter postAdapter; +// private Store nonPersistedStore; +// +// @Override +// protected void onCreate(Bundle savedInstanceState) { +// super.onCreate(savedInstanceState); +// setContentView(R.layout.activity_store); +// setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); +// postAdapter = new PostAdapter(); +// recyclerView = (RecyclerView) findViewById(R.id.postRecyclerView); +// LinearLayoutManager layoutManager = new LinearLayoutManager(this); +// layoutManager.setOrientation(LinearLayoutManager.VERTICAL); +// recyclerView.setLayoutManager(layoutManager); +// recyclerView.setAdapter(postAdapter); +// } +// +// private void initStore() { +// if (this.nonPersistedStore == null) { +// this.nonPersistedStore = ((SampleApp) getApplicationContext()).getNonPersistedStore(); +// } +// } +// +// @SuppressWarnings("CheckReturnValue") +// public void loadPosts() { +// BarCode awwRequest = new BarCode(RedditData.class.getSimpleName(), "aww"); +// +// /* +// First call to get(awwRequest) will use the network, then save response in the in-memory +// cache. Subsequent calls will retrieve the cached version of the data. +// +// But, since the policy of this store is to expire after 10 seconds, the cache will +// only be used for subsequent requests that happen within 10 seconds of the initial request. +// After that, the request will use the network. +// */ +// this.nonPersistedStore +// .get(awwRequest) +// .flatMapObservable(new Function>() { +// @Override +// public ObservableSource apply(@NonNull RedditData redditData) throws Exception { +// return sanitizeData(redditData); +// } +// }) +// .toList() +// .subscribeOn(Schedulers.io()) +// .observeOn(AndroidSchedulers.mainThread()) +// .subscribe(this::showPosts, throwable -> { +// Log.e(StoreActivity.class.getSimpleName(), throwable.getMessage(), throwable); +// }); +// } +// +// private void showPosts(List posts) { +// postAdapter.setPosts(posts); +// makeText(StoreActivity.this, +// "Loaded " + posts.size() + " posts", +// Toast.LENGTH_SHORT) +// .show(); +// } +// +// private Observable sanitizeData(RedditData redditData) { +// return Observable.fromIterable(redditData.data().children()) +// .map(Children::data); +// } +// +// @Override +// protected void onResume() { +// super.onResume(); +// initStore(); +// loadPosts(); +// } } diff --git a/app/src/test/java/com/nytimes/android/sample/ClearStoreMemoryTest.kt b/app/src/test/java/com/nytimes/android/sample/ClearStoreMemoryTest.kt new file mode 100644 index 000000000..792cb5730 --- /dev/null +++ b/app/src/test/java/com/nytimes/android/sample/ClearStoreMemoryTest.kt @@ -0,0 +1,63 @@ +package com.nytimes.android.sample + +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import java.util.concurrent.atomic.AtomicInteger + +class ClearStoreMemoryTest { + + lateinit var networkCalls: AtomicInteger + lateinit var store: Store + + @Before + fun setUp() { + networkCalls = AtomicInteger(0) + store = StoreBuilder.barcode() + .fetcher(object : Fetcher { + override suspend fun fetch(key: BarCode) = networkCalls.incrementAndGet() + + }) + .open() + } + + @Test + fun testClearSingleBarCode() { + runBlocking{ + //one request should produce one call + val barcode = BarCode("type", "key") + val result = store.get(barcode) + assertThat(networkCalls.get()).isEqualTo(1) + + store.clearMemory(barcode) + val anotherResult = store.get(barcode) + assertThat(networkCalls.get()).isEqualTo(2) + } + } + + @Test + fun testClearAllBarCodes() { + runBlocking { + + val b1 = BarCode("type1", "key1") + val b2 = BarCode("type2", "key2") + + //each request should produce one call + store.get(b1) + store.get(b2) + assertThat(networkCalls.get()).isEqualTo(2) + + store.clearMemory() + + //after everything is cleared each request should produce another 2 calls + store.get(b1) + store.get(b2) + assertThat(networkCalls.get()).isEqualTo(4) + } + } +} diff --git a/app/src/test/java/com/nytimes/android/sample/ClearStoreTest.kt b/app/src/test/java/com/nytimes/android/sample/ClearStoreTest.kt new file mode 100644 index 000000000..018df4254 --- /dev/null +++ b/app/src/test/java/com/nytimes/android/sample/ClearStoreTest.kt @@ -0,0 +1,115 @@ +//package com.nytimes.android.sample +// +//import com.nytimes.android.external.store3.base.Clearable +//import com.nytimes.android.external.store3.base.Fetcher +//import com.nytimes.android.external.store3.base.Persister +//import com.nytimes.android.external.store3.base.impl.BarCode +//import com.nytimes.android.external.store3.base.impl.Store +//import com.nytimes.android.external.store3.base.impl.StoreBuilder +//import kotlinx.coroutines.runBlocking +//import org.assertj.core.api.Assertions.assertThat +//import org.junit.Before +//import org.junit.Test +//import org.junit.runner.RunWith +//import org.mockito.runners.MockitoJUnitRunner +//import java.util.* +//import java.util.concurrent.atomic.AtomicInteger +// +// +//val barcode1 = BarCode("type1", "key1") +//val barcode2 = BarCode("type2", "key2") +// +//@RunWith(MockitoJUnitRunner::class) +//class ClearStoreTest { +// val persister = ClearingPersister() +// lateinit var networkCalls: AtomicInteger +// lateinit var store: Store +// +// @Before +// fun setUp() { +// networkCalls = AtomicInteger(0) +// store = StoreBuilder.barcode() +// .fetcher(object : Fetcher { +// override suspend fun fetch(key: BarCode): Int { +// return networkCalls.incrementAndGet() +// } +// +// }) +// .persister(persister) +// .open() +// +// } +// +// @Test +// fun testClearSingleBarCode() { +// runBlocking { +// // one request should produce one call +// val barcode = BarCode("type", "key") +// +// store.get(barcode) +// assertThat(networkCalls.toInt()).isEqualTo(1) +// +// // after clearing the memory another call should be made +// store.clear(barcode) +// store.get(barcode) +// assertThat(persister.isClear).isTrue() +// assertThat(networkCalls.toInt()).isEqualTo(2) +// } +// } +// +// +// @Test +// fun testClearAllBarCodes() { +// +// runBlocking { +// // each request should produce one call +// store.get(barcode1) +// store.get(barcode2) +// assertThat(networkCalls.toInt()).isEqualTo(2) +// +// store.clear() +// +// // after everything is cleared each request should produce another 2 calls +// store.get(barcode1) +// store.get(barcode2) +// assertThat(networkCalls.toInt()).isEqualTo(4) +// } +// +// +// } +//} +// +////everything will be mocked +//class ClearingPersister : Persister, Clearable { +// var isClear = false +// val barcode1Responses = LinkedList() +// val barcode2Responses = LinkedList() +// +// init { +// barcode1Responses.add(null) +// barcode1Responses.add(1) +// barcode1Responses.add(null) +// barcode1Responses.add(1) +// +// barcode2Responses.add(null) +// barcode2Responses.add(1) +// barcode2Responses.add(null) +// barcode2Responses.add(1) +// } +// +// override fun clear(key: BarCode) { +// isClear = true +// } +// +// override suspend fun read(key: BarCode): Int? { +// val diskValue = if (key == barcode1) barcode1Responses else barcode2Responses +// return diskValue.remove() +// } +// +// override suspend fun write(key: BarCode, raw: Int): Boolean { +// return when (raw) { +// in 1..5 -> true +// else -> throw RuntimeException("no good") +// } +// } +//} diff --git a/app/src/test/java/com/nytimes/android/sample/DontCacheErrorsTest.kt b/app/src/test/java/com/nytimes/android/sample/DontCacheErrorsTest.kt new file mode 100644 index 000000000..d65aa7c66 --- /dev/null +++ b/app/src/test/java/com/nytimes/android/sample/DontCacheErrorsTest.kt @@ -0,0 +1,40 @@ +package com.nytimes.android.sample + +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test + + +class DontCacheErrorsTest { + + internal var shouldThrow: Boolean = false + lateinit var store: Store + + @Before + fun setUp() { + store = StoreBuilder.barcode() + .fetcher(object : Fetcher { + override suspend fun fetch(key: BarCode): Int { + if (shouldThrow) { + throw RuntimeException() + } else { + return 0 + } + } + }) + .open() + } + + @Test(expected = java.lang.RuntimeException::class) + fun testStoreDoesntCacheErrors() { + runBlocking { + val barcode = BarCode("bar", "code") + shouldThrow = true + val job = store.get(barcode) + } + } +} diff --git a/app/src/test/java/com/nytimes/android/sample/KeyParserTest.kt b/app/src/test/java/com/nytimes/android/sample/KeyParserTest.kt new file mode 100644 index 000000000..ef938d1b0 --- /dev/null +++ b/app/src/test/java/com/nytimes/android/sample/KeyParserTest.kt @@ -0,0 +1,43 @@ +package com.nytimes.android.sample + +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder + +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.runners.MockitoJUnitRunner + +import io.reactivex.Single +import io.reactivex.observers.TestObserver + + +@RunWith(MockitoJUnitRunner::class) +class KeyParserTest { + private var store: Store? = null + + @Before + @Throws(Exception::class) + fun setUp() { + store = StoreBuilder.parsedWithKey() + .parser({ integer, s -> s + integer }) + .fetcher({ integer -> Single.just(NETWORK) }) + .open() + + } + + @Test + @Throws(Exception::class) + fun testStoreWithKeyParserFuncNoPersister() { + val testObservable = store!!.get(KEY).test().await() + testObservable.assertNoErrors() + .assertValues(NETWORK + KEY) + .awaitTerminalEvent() + } + + companion object { + + val NETWORK = "Network" + val KEY = 5 + } +} diff --git a/app/src/test/java/com/nytimes/android/sample/SmokeTests.kt b/app/src/test/java/com/nytimes/android/sample/SmokeTests.kt new file mode 100644 index 000000000..04fd21527 --- /dev/null +++ b/app/src/test/java/com/nytimes/android/sample/SmokeTests.kt @@ -0,0 +1,217 @@ +package com.nytimes.android.sample + +import com.nytimes.android.external.cache3.CacheBuilder +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger + +class SmokeTests { + lateinit var counter: AtomicInteger + lateinit var fetcher: Fetcher + lateinit var persister: Persister + private val barCode = BarCode("key", "value") + + @Before + fun setUp() { + counter = AtomicInteger(0) + } + + @Test + fun testSimple() { + + fetcher = object : Fetcher { + override suspend fun fetch(key: BarCode) = counter.incrementAndGet().toString() + } + + + persister = object : Persister { + override suspend fun read(key: BarCode): String? { + if (counter.get() == 0) return null + else if (counter.get() == 1) return DISK + else return "WRONG VALUE" + } + + override suspend fun write(key: BarCode, raw: String): Boolean { + return true + } + } + + val simpleStore = StoreBuilder.barcode() + .persister(persister) + .fetcher(fetcher!!) + .open() + + runBlocking { + var value = simpleStore.get(barCode) + assertThat(value).isEqualTo(DISK) + value = simpleStore.get(barCode) + assertThat(value).isEqualTo(DISK) + assertThat(counter.get()).isEqualTo(1) + } + } + + + @Test + fun testDoubleTap() { + + fetcher = object : Fetcher { + override suspend fun fetch(key: BarCode) = counter.incrementAndGet().toString() + } + + persister = object : Persister { + override suspend fun read(key: BarCode): String? { + if (counter.get() == 0) return null + else if (counter.get() == 1) return DISK + else return "WRONG VALUE" + } + + override suspend fun write(key: BarCode, raw: String): Boolean { + if (raw != "1") throw RuntimeException("Yo Dawg your inflight is broken") + return true + } + } + + val store = StoreBuilder.barcode() + .persister(persister) + .fetcher(fetcher!!) + .open() + + + runBlocking { + val value: Deferred = async { store.get(barCode) } + val value2: Deferred = async { store.get(barCode) } + val result = value.await() + val result2 = value2.await() + value2.join() + assertThat(result).isEqualTo(DISK) + assertThat(result2).isEqualTo(DISK) + assertThat(counter.get()).isEqualTo(1) + } + } + + @Test + fun testLazyDefer() { + + fetcher = object : Fetcher { + override suspend fun fetch(key: BarCode) = counter.incrementAndGet().toString() + } + + persister = object : Persister { + override suspend fun read(key: BarCode): String? { + if (counter.get() == 0) return null + else if (counter.get() == 1) return DISK + else return "WRONG VALUE" + } + + override suspend fun write(key: BarCode, raw: String): Boolean { + if (raw != "1") throw RuntimeException("Yo Dawg your inflight is broken") + return true + } + } + + val store = StoreBuilder.barcode() + .persister(persister) + .fetcher(fetcher) + .open() + + + runBlocking { + //same request same suspend function should throttle + val value: Deferred = async(start = CoroutineStart.LAZY) { store.get(barCode) } + val value2: Deferred = async(start = CoroutineStart.LAZY) { store.get(barCode) } + val throwaway: Deferred = async(start = CoroutineStart.LAZY) { store.get(BarCode("m", "n")) } + + value.start() + value2.start() + //another request + + val result = value.await() + val result2 = value2.await() + assertThat(result).isEqualTo(DISK) + assertThat(result2).isEqualTo(DISK) + assertThat(counter.get()).isEqualTo(1) + throwaway.cancel() + } + } + + @Test + fun testMutliBarcode() { + val first = BarCode("a", "a") + val second = BarCode("b", "b") + fetcher = object : Fetcher { + override suspend fun fetch(key: BarCode): String { + Thread.sleep(5000) + return counter.incrementAndGet().toString() + } + } + + persister = object : Persister { + override suspend fun read(key: BarCode): String? { + when { + counter.get() == 1 && key == first -> return "first" + counter.get() == 2 && key == second -> return "second" + else -> return null + } + } + + override suspend fun write(key: BarCode, raw: String): Boolean { + if (raw != "1" && raw != "2") throw RuntimeException("Yo Dawg your inflight is broken") + return true + } + } + + val store = StoreBuilder.barcode() + .persister(persister) + .fetcher(fetcher) + .open() + + + + runBlocking { + val value: Deferred = async(start = CoroutineStart.LAZY) { store.get(first) } + val value2: Deferred = async(start = CoroutineStart.LAZY) { store.get(second) } + + value.start() + value2.start() + //another request + + val result = value.await() + val result2 = value2.await() + assertThat(result).isEqualTo("first") + assertThat(result2).isEqualTo("second") + assertThat(counter.get()).isEqualTo(2) + } + } + + @Test + fun testEquivalence() { + val cache = CacheBuilder.newBuilder() + .maximumSize(1) + .expireAfterAccess(java.lang.Long.MAX_VALUE, TimeUnit.SECONDS) + .build() + + cache.put(barCode, MEMORY) + var value = cache.getIfPresent(barCode) + assertThat(value).isEqualTo(MEMORY) + + value = cache.getIfPresent(BarCode(barCode.type, barCode.key)) + assertThat(value).isEqualTo(MEMORY) + } + + companion object { + + private val DISK = "disk" + private val NETWORK = "fresh" + private val MEMORY = "memory" + } +} diff --git a/app/src/test/java/com/nytimes/android/sample/StoreIntegrationTest.java b/app/src/test/java/com/nytimes/android/sample/StoreIntegrationTest.java deleted file mode 100644 index 1afca4b23..000000000 --- a/app/src/test/java/com/nytimes/android/sample/StoreIntegrationTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.nytimes.android.sample; - -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; - -import org.junit.Before; -import org.junit.Test; - -import io.reactivex.Single; - -import static junit.framework.Assert.assertEquals; - -public class StoreIntegrationTest { - - private Store testStore; - - @Before - public void setUp() throws Exception { - testStore = StoreBuilder.barcode() - .fetcher(barCode -> Single.just("hello")) - .open(); - } - - @Test - public void testRepeatedGet() throws Exception { - String first = testStore.get(BarCode.empty()).blockingGet(); - assertEquals(first, "hello"); - - } -} diff --git a/app/src/test/java/com/nytimes/android/sample/StoreIntegrationTest.kt b/app/src/test/java/com/nytimes/android/sample/StoreIntegrationTest.kt new file mode 100644 index 000000000..f274ff62f --- /dev/null +++ b/app/src/test/java/com/nytimes/android/sample/StoreIntegrationTest.kt @@ -0,0 +1,41 @@ +package com.nytimes.android.sample + +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import junit.framework.Assert.assertEquals +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import java.util.concurrent.atomic.AtomicInteger + +class StoreIntegrationTest { + + lateinit var store: Store + val atomicInt = AtomicInteger(0) + @Before + @Throws(Exception::class) + fun setUp() { + store = StoreBuilder.barcode() + .fetcher(fetcher = object : Fetcher { + override suspend fun fetch(key: BarCode): String { + return atomicInt.incrementAndGet().toString() + } + }) + .open() + } + + @Test + @Throws(Exception::class) + fun testRepeatedGet() { + val barcode = BarCode.empty() + runBlocking { + val first = store.get(barcode) + val same = store.fresh(barcode) + assertEquals(first, same) + } + } + + +} diff --git a/build.gradle b/build.gradle index 84fc5e10b..7bffe489b 100644 --- a/build.gradle +++ b/build.gradle @@ -32,18 +32,15 @@ buildscript { } allprojects { - buildscript { - - } + buildscript {} repositories { - maven { - url 'https://oss.sonatype.org/content/repositories/snapshots/' - } - + maven { url 'https://oss.sonatype.org/content/repositories/snapshots/'} mavenCentral() + jcenter() } + // Workaround to prevent Gradle from stealing focus from other apps during tests run/etc. // https://gist.github.com/artem-zinnatullin/4c250e04636e25797165 tasks.withType(JavaForkOptions) { diff --git a/cache/src/main/java/com/nytimes/android/external/cache3/Cache.java b/cache/src/main/java/com/nytimes/android/external/cache3/Cache.java index 05bc9f3d0..00eafd9cf 100644 --- a/cache/src/main/java/com/nytimes/android/external/cache3/Cache.java +++ b/cache/src/main/java/com/nytimes/android/external/cache3/Cache.java @@ -6,6 +6,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; +import javax.annotation.Nonnull; import javax.annotation.Nullable; public interface Cache { @@ -36,7 +37,7 @@ public interface Cache { * * @since 11.0 */ - @Nullable + @Nonnull V get(K key, Callable valueLoader) throws ExecutionException; /** diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSReader.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSReader.java deleted file mode 100644 index 7a35f7560..000000000 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSReader.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.store3.base.DiskRead; - -import java.io.FileNotFoundException; -import java.io.IOException; - -import javax.annotation.Nonnull; - -import io.reactivex.Maybe; -import okio.BufferedSource; - -/** - * FSReader is used when persisting from file system - * PathResolver will be used in creating file system paths based on cache keys. - * Make sure to have keys containing same data resolve to same "path" - * - * @param key type - */ -public class FSReader implements DiskRead { - private static final String ERROR_MESSAGE = "resolvedKey does not resolve to a file"; - final FileSystem fileSystem; - final PathResolver pathResolver; - - public FSReader(FileSystem fileSystem, PathResolver pathResolver) { - this.fileSystem = fileSystem; - this.pathResolver = pathResolver; - } - - @Nonnull - @Override - public Maybe read(@Nonnull final T key) { - return Maybe.create(emitter -> { - String resolvedKey = pathResolver.resolve(key); - boolean exists = fileSystem.exists(resolvedKey); - - if (exists) { - BufferedSource bufferedSource = null; - try { - bufferedSource = fileSystem.read(resolvedKey); - emitter.onSuccess(bufferedSource); - emitter.onComplete(); - } catch (FileNotFoundException e) { - emitter.onError(e); - } finally { - if (bufferedSource != null) { - try { - bufferedSource.close(); - } catch (IOException e) { - e.printStackTrace(System.err); - } - } - } - } else { - emitter.onError(new FileNotFoundException(ERROR_MESSAGE + resolvedKey)); - } - }); - } -} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSReader.kt b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSReader.kt new file mode 100644 index 000000000..bcbaaca2c --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSReader.kt @@ -0,0 +1,49 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.store3.base.DiskRead + +import java.io.FileNotFoundException +import java.io.IOException + +import io.reactivex.Maybe +import okio.BufferedSource + +/** + * FSReader is used when persisting from file system + * PathResolver will be used in creating file system paths based on cache keys. + * Make sure to have keys containing same data resolve to same "path" + * + * @param key type + */ +open class FSReader(internal val fileSystem: FileSystem, internal val pathResolver: PathResolver) : DiskRead { + + suspend override fun read(key: T): BufferedSource? { + val resolvedKey = pathResolver.resolve(key) + val exists = fileSystem.exists(resolvedKey) + if (exists==true) { + var bufferedSource: BufferedSource? = null + try { + bufferedSource= fileSystem.read(resolvedKey) + return bufferedSource + } catch (e: FileNotFoundException) { + throw e + } finally { + if (bufferedSource != null) { + try { + bufferedSource.close() + } catch (e: IOException) { + e.printStackTrace(System.err) + } + } + } + } + else{ + throw FileNotFoundException(ERROR_MESSAGE + resolvedKey) + } + } + + companion object { + private val ERROR_MESSAGE = "resolvedKey does not resolve to a file" + } +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSWriter.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSWriter.java deleted file mode 100644 index 8b4eb506b..000000000 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSWriter.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.store3.base.DiskWrite; - -import javax.annotation.Nonnull; - -import io.reactivex.Single; -import okio.BufferedSource; - -/** - * FSReader is used when persisting to file system - * PathResolver will be used in creating file system paths based on cache keys. - * Make sure to have keys containing same data resolve to same "path" - * @param key type - */ -public class FSWriter implements DiskWrite { - final FileSystem fileSystem; - final PathResolver pathResolver; - - public FSWriter(FileSystem fileSystem, PathResolver pathResolver) { - this.fileSystem = fileSystem; - this.pathResolver = pathResolver; - } - - @Nonnull - @Override - public Single write(@Nonnull final T key, @Nonnull final BufferedSource data) { - return Single.fromCallable(() -> { - fileSystem.write(pathResolver.resolve(key), data); - return true; - }); - } -} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSWriter.kt b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSWriter.kt new file mode 100644 index 000000000..90c83496e --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSWriter.kt @@ -0,0 +1,21 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.store3.base.DiskWrite +import okio.BufferedSource + +/** + * FSReader is used when persisting to file system + * PathResolver will be used in creating file system paths based on cache keys. + * Make sure to have keys containing same data resolve to same "path" + * @param key type + */ +open class FSWriter(internal val fileSystem: FileSystem, internal val pathResolver: PathResolver) : + DiskWrite { + + override suspend fun write(key: T, data: BufferedSource): Boolean { + fileSystem.write(pathResolver.resolve(key), data) + return true + + } +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersister.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersister.java deleted file mode 100644 index 61aca8757..000000000 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersister.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.store3.base.Persister; - -import javax.annotation.Nonnull; - -import io.reactivex.Maybe; -import io.reactivex.Single; -import okio.BufferedSource; - -/** - * FileSystemPersister is used when persisting to/from file system - * PathResolver will be used in creating file system paths based on cache keys. - * Make sure to have keys containing same data resolve to same "path" - * @param key type - */ -public final class FileSystemPersister implements Persister { - private final FSReader fileReader; - private final FSWriter fileWriter; - - private FileSystemPersister(FileSystem fileSystem, PathResolver pathResolver) { - fileReader = new FSReader<>(fileSystem, pathResolver); - fileWriter = new FSWriter<>(fileSystem, pathResolver); - } - - @Nonnull - public static Persister create(FileSystem fileSystem, - PathResolver pathResolver) { - if (fileSystem == null) { - throw new IllegalArgumentException("root file cannot be null."); - } - return new FileSystemPersister<>(fileSystem, pathResolver); - } - - @Nonnull - @Override - public Maybe read(@Nonnull final T key) { - return fileReader.read(key); - } - - @Nonnull - @Override - public Single write(@Nonnull final T key, @Nonnull final BufferedSource data) { - return fileWriter.write(key, data); - } -} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersister.kt b/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersister.kt new file mode 100644 index 000000000..acc6fccef --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersister.kt @@ -0,0 +1,43 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.store3.base.Persister + +import io.reactivex.Maybe +import io.reactivex.Single +import okio.BufferedSource + +/** + * FileSystemPersister is used when persisting to/from file system + * PathResolver will be used in creating file system paths based on cache keys. + * Make sure to have keys containing same data resolve to same "path" + * @param key type + */ +class FileSystemPersister private constructor(fileSystem: FileSystem, pathResolver: PathResolver) : Persister { + private val fileReader: FSReader + private val fileWriter: FSWriter + + init { + fileReader = FSReader(fileSystem, pathResolver) + fileWriter = FSWriter(fileSystem, pathResolver) + } + + override suspend fun read(key: T): BufferedSource? { + return fileReader.read(key) + } + + override suspend fun write(key: T, data: BufferedSource): Boolean { + return fileWriter.write(key, data) + } + + companion object { + + fun create(fileSystem: FileSystem?, + pathResolver: PathResolver): Persister { + if (fileSystem == null) { + throw IllegalArgumentException("root file cannot be null.") + } + return FileSystemPersister(fileSystem, pathResolver) + } + } +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersisterFactory.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersisterFactory.java deleted file mode 100644 index 02324a9bf..000000000 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersisterFactory.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.fs3.filesystem.FileSystemFactory; -import com.nytimes.android.external.store3.base.Persister; - -import java.io.File; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nonnull; - -import okio.BufferedSource; - -public final class FileSystemPersisterFactory { - - private FileSystemPersisterFactory() { - } - - /** - * Returns a new {@link BufferedSource} persister with the provided file as the root of the - * persistence {@link FileSystem}. - * - * @throws IOException - */ - @Nonnull - public static Persister create(@Nonnull File root, - PathResolver pathResolver) throws IOException { - if (root == null) { - throw new IllegalArgumentException("root file cannot be null."); - } - return FileSystemPersister.create(FileSystemFactory.create(root), pathResolver); - } - - /** - * Returns a new {@link BufferedSource} persister with the provided fileSystem as the root of the - * persistence {@link FileSystem}. - * - * @throws IOException - */ - @Nonnull - public static Persister create(@Nonnull FileSystem fileSystem, - PathResolver pathResolver) throws IOException { - if (fileSystem == null) { - throw new IllegalArgumentException("root file cannot be null."); - } - return FileSystemPersister.create(fileSystem, pathResolver); - } - - /** - * Returns a new {@link BufferedSource} persister with the provided file as the root of the - * persistence {@link FileSystem}. - * - * @throws IOException - */ - @Nonnull - public static Persister create(@Nonnull File root, - PathResolver pathResolver, - long expirationDuration, - @Nonnull TimeUnit expirationUnit) - throws IOException { - if (root == null) { - throw new IllegalArgumentException("root file cannot be null."); - } - return FileSystemRecordPersister.create(FileSystemFactory.create(root), pathResolver, - expirationDuration, expirationUnit); - } - - /** - * Returns a new {@link BufferedSource} persister with the provided fileSystem as the root of the - * persistence {@link FileSystem}. - **/ - @Nonnull - public static Persister create(@Nonnull FileSystem fileSystem, - PathResolver pathResolver, - long expirationDuration, - @Nonnull TimeUnit expirationUnit) { - if (fileSystem == null) { - throw new IllegalArgumentException("fileSystem cannot be null."); - } - return FileSystemRecordPersister.create(fileSystem, pathResolver, expirationDuration, - expirationUnit); - } -} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersisterFactory.kt b/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersisterFactory.kt new file mode 100644 index 000000000..ac843c161 --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemPersisterFactory.kt @@ -0,0 +1,77 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.fs3.filesystem.FileSystemFactory +import com.nytimes.android.external.store3.base.Persister + +import java.io.File +import java.io.IOException +import java.util.concurrent.TimeUnit + +import okio.BufferedSource + +object FileSystemPersisterFactory { + + /** + * Returns a new [BufferedSource] persister with the provided file as the root of the + * persistence [FileSystem]. + * + * @throws IOException + */ + @Throws(IOException::class) + fun create(root: File, + pathResolver: PathResolver): Persister { + if (root == null) { + throw IllegalArgumentException("root file cannot be null.") + } + return FileSystemPersister.create(FileSystemFactory.create(root), pathResolver) + } + + /** + * Returns a new [BufferedSource] persister with the provided fileSystem as the root of the + * persistence [FileSystem]. + * + * @throws IOException + */ + @Throws(IOException::class) + fun create(fileSystem: FileSystem, + pathResolver: PathResolver): Persister { + if (fileSystem == null) { + throw IllegalArgumentException("root file cannot be null.") + } + return FileSystemPersister.create(fileSystem, pathResolver) + } + + /** + * Returns a new [BufferedSource] persister with the provided file as the root of the + * persistence [FileSystem]. + * + * @throws IOException + */ + @Throws(IOException::class) + fun create(root: File, + pathResolver: PathResolver, + expirationDuration: Long, + expirationUnit: TimeUnit): Persister { + if (root == null) { + throw IllegalArgumentException("root file cannot be null.") + } + return FileSystemRecordPersister.create(FileSystemFactory.create(root), pathResolver, + expirationDuration, expirationUnit) + } + + /** + * Returns a new [BufferedSource] persister with the provided fileSystem as the root of the + * persistence [FileSystem]. + */ + fun create(fileSystem: FileSystem, + pathResolver: PathResolver, + expirationDuration: Long, + expirationUnit: TimeUnit): Persister { + if (fileSystem == null) { + throw IllegalArgumentException("fileSystem cannot be null.") + } + return FileSystemRecordPersister.create(fileSystem, pathResolver, expirationDuration, + expirationUnit) + } +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemRecordPersister.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemRecordPersister.java deleted file mode 100644 index 48d8cb7fe..000000000 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemRecordPersister.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.RecordProvider; -import com.nytimes.android.external.store3.base.RecordState; - -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nonnull; - -import io.reactivex.Maybe; -import io.reactivex.Single; -import okio.BufferedSource; - -/** - * FileSystemRecordPersister is used when persisting to/from file system while being stale aware - * PathResolver will be used in creating file system paths based on cache keys. - * Make sure to have keys containing same data resolve to same "path" - * - * @param key type - */ -public final class FileSystemRecordPersister implements Persister, RecordProvider { - private final FSReader fileReader; - private final FSWriter fileWriter; - private final FileSystem fileSystem; - private final PathResolver pathResolver; - private final long expirationDuration; - @Nonnull - private final TimeUnit expirationUnit; - - private FileSystemRecordPersister(FileSystem fileSystem, PathResolver pathResolver, - long expirationDuration, - @Nonnull TimeUnit expirationUnit) { - this.fileSystem = fileSystem; - this.pathResolver = pathResolver; - this.expirationDuration = expirationDuration; - this.expirationUnit = expirationUnit; - fileReader = new FSReader<>(fileSystem, pathResolver); - fileWriter = new FSWriter<>(fileSystem, pathResolver); - } - - @Nonnull - public static FileSystemRecordPersister create(FileSystem fileSystem, - PathResolver pathResolver, - long expirationDuration, - @Nonnull TimeUnit expirationUnit) { - if (fileSystem == null) { - throw new IllegalArgumentException("root file cannot be null."); - } - return new FileSystemRecordPersister<>(fileSystem, pathResolver, - expirationDuration, expirationUnit); - } - - @Nonnull - @Override - public RecordState getRecordState(@Nonnull Key key) { - return fileSystem.getRecordState(expirationUnit, expirationDuration, pathResolver.resolve(key)); - } - - @Nonnull - @Override - public Maybe read(@Nonnull Key key) { - return fileReader.read(key); - } - - @Nonnull - @Override - public Single write(@Nonnull Key key, @Nonnull BufferedSource bufferedSource) { - return fileWriter.write(key, bufferedSource); - } -} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemRecordPersister.kt b/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemRecordPersister.kt new file mode 100644 index 000000000..048ac51c0 --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/FileSystemRecordPersister.kt @@ -0,0 +1,57 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.RecordProvider +import com.nytimes.android.external.store3.base.RecordState + +import java.util.concurrent.TimeUnit + +import io.reactivex.Maybe +import io.reactivex.Single +import okio.BufferedSource + +/** + * FileSystemRecordPersister is used when persisting to/from file system while being stale aware + * PathResolver will be used in creating file system paths based on cache keys. + * Make sure to have keys containing same data resolve to same "path" + * + * @param key type + */ +class FileSystemRecordPersister private constructor(private val fileSystem: FileSystem, private val pathResolver: PathResolver, + private val expirationDuration: Long, + private val expirationUnit: TimeUnit) : Persister, RecordProvider { + private val fileReader: FSReader + private val fileWriter: FSWriter + + init { + fileReader = FSReader(fileSystem, pathResolver) + fileWriter = FSWriter(fileSystem, pathResolver) + } + + override fun getRecordState(key: Key): RecordState { + return fileSystem.getRecordState(expirationUnit, expirationDuration, pathResolver.resolve(key)) + } + + override suspend fun read(key: Key): BufferedSource? { + return fileReader.read(key) + } + + override suspend fun write(key: Key, bufferedSource: BufferedSource): Boolean { + return fileWriter.write(key, bufferedSource) + } + + companion object { + + fun create(fileSystem: FileSystem?, + pathResolver: PathResolver, + expirationDuration: Long, + expirationUnit: TimeUnit): FileSystemRecordPersister { + if (fileSystem == null) { + throw IllegalArgumentException("root file cannot be null.") + } + return FileSystemRecordPersister(fileSystem, pathResolver, + expirationDuration, expirationUnit) + } + } +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersister.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersister.java deleted file mode 100644 index a318d3448..000000000 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersister.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.store3.base.RecordProvider; -import com.nytimes.android.external.store3.base.RecordState; -import com.nytimes.android.external.store3.base.impl.BarCode; - -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nonnull; -import javax.inject.Inject; - -public class RecordPersister extends SourcePersister implements RecordProvider { - - @Nonnull - private final TimeUnit expirationUnit; - private final long expirationDuration; - - @Inject - public RecordPersister(FileSystem fileSystem, - long expirationDuration, - @Nonnull TimeUnit expirationUnit) { - super(fileSystem); - this.expirationDuration = expirationDuration; - this.expirationUnit = expirationUnit; - } - - public static RecordPersister create(FileSystem fileSystem, - long expirationDuration, - @Nonnull TimeUnit expirationUnit) { - return new RecordPersister(fileSystem, expirationDuration, expirationUnit); - } - - @Nonnull - @Override - public RecordState getRecordState(@Nonnull BarCode barCode) { - return sourceFileReader.getRecordState(barCode, expirationUnit, expirationDuration); - } -} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersister.kt b/filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersister.kt new file mode 100644 index 000000000..bbbe62b37 --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersister.kt @@ -0,0 +1,28 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.store3.base.RecordProvider +import com.nytimes.android.external.store3.base.RecordState +import com.nytimes.android.external.store3.base.impl.BarCode + +import java.util.concurrent.TimeUnit +import javax.inject.Inject + +class RecordPersister @Inject +constructor(fileSystem: FileSystem, + private val expirationDuration: Long, + private val expirationUnit: TimeUnit) : SourcePersister(fileSystem), RecordProvider { + + override fun getRecordState(barCode: BarCode): RecordState { + return sourceFileReader.getRecordState(barCode, expirationUnit, expirationDuration) + } + + companion object { + + fun create(fileSystem: FileSystem, + expirationDuration: Long, + expirationUnit: TimeUnit): RecordPersister { + return RecordPersister(fileSystem, expirationDuration, expirationUnit) + } + } +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersisterFactory.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersisterFactory.java deleted file mode 100644 index 5fca92a66..000000000 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersisterFactory.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.fs3.filesystem.FileSystemFactory; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; - -import java.io.File; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nonnull; - -import okio.BufferedSource; - -/** - * Factory for {@link RecordPersister} - */ - -public final class RecordPersisterFactory { - private RecordPersisterFactory() { - } - - /** - * Returns a new {@link BufferedSource} persister with the provided file as the root of the - * persistence {@link FileSystem}. - * - * @throws IOException - */ - @Nonnull - public static Persister create(@Nonnull File root, - long expirationDuration, - @Nonnull TimeUnit expirationUnit) throws IOException { - if (root == null) { - throw new IllegalArgumentException("root file cannot be null."); - } - return new RecordPersister(FileSystemFactory.create(root), expirationDuration, expirationUnit); - } - - /** - * Returns a new {@link BufferedSource} persister with the provided fileSystem as the root of the - * persistence {@link FileSystem}. - **/ - @Nonnull - public static Persister create(@Nonnull FileSystem fileSystem, - long expirationDuration, - @Nonnull TimeUnit expirationUnit) { - if (fileSystem == null) { - throw new IllegalArgumentException("fileSystem cannot be null."); - } - return new RecordPersister(fileSystem, expirationDuration, expirationUnit); - } - -} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersisterFactory.kt b/filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersisterFactory.kt new file mode 100644 index 000000000..ef035ad8d --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/RecordPersisterFactory.kt @@ -0,0 +1,49 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.fs3.filesystem.FileSystemFactory +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode + +import java.io.File +import java.io.IOException +import java.util.concurrent.TimeUnit + +import okio.BufferedSource + +/** + * Factory for [RecordPersister] + */ + +object RecordPersisterFactory { + + /** + * Returns a new [BufferedSource] persister with the provided file as the root of the + * persistence [FileSystem]. + * + * @throws IOException + */ + @Throws(IOException::class) + fun create(root: File, + expirationDuration: Long, + expirationUnit: TimeUnit): Persister { + if (root == null) { + throw IllegalArgumentException("root file cannot be null.") + } + return RecordPersister(FileSystemFactory.create(root), expirationDuration, expirationUnit) + } + + /** + * Returns a new [BufferedSource] persister with the provided fileSystem as the root of the + * persistence [FileSystem]. + */ + fun create(fileSystem: FileSystem, + expirationDuration: Long, + expirationUnit: TimeUnit): Persister { + if (fileSystem == null) { + throw IllegalArgumentException("fileSystem cannot be null.") + } + return RecordPersister(fileSystem, expirationDuration, expirationUnit) + } + +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceAllPersister.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceAllPersister.java deleted file mode 100644 index 2c55fc834..000000000 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceAllPersister.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.nytimes.android.external.fs3; - - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.store3.base.AllPersister; -import com.nytimes.android.external.store3.base.impl.BarCode; - -import java.io.FileNotFoundException; - -import javax.annotation.Nonnull; -import javax.inject.Inject; - -import io.reactivex.Maybe; -import io.reactivex.Observable; -import io.reactivex.Single; -import okio.BufferedSource; - -public class SourceAllPersister implements AllPersister { - - @Nonnull - final FSAllReader sourceFileAllReader; - @Nonnull - final FSAllEraser sourceFileAllEraser; - - @Nonnull - final FSReader sourceFileReader; - @Nonnull - final FSWriter sourceFileWriter; - - @Inject - public SourceAllPersister(FileSystem fileSystem) { - sourceFileAllReader = new FSAllReader(fileSystem); - sourceFileAllEraser = new FSAllEraser(fileSystem); - sourceFileReader = new FSReader<>(fileSystem, new BarCodeReadAllPathResolver()); - sourceFileWriter = new FSWriter<>(fileSystem, new BarCodeReadAllPathResolver()); - } - - @Nonnull - @Override - public Observable readAll(@Nonnull final String path) throws FileNotFoundException { - return sourceFileAllReader.readAll(path); - } - - @Nonnull - @Override - public Observable deleteAll(@Nonnull final String path) { - return sourceFileAllEraser.deleteAll(path); - } - - @Nonnull - @Override - public Maybe read(@Nonnull BarCode barCode) { - return sourceFileReader.read(barCode); - } - - @Nonnull - @Override - public Single write(@Nonnull BarCode barCode, @Nonnull BufferedSource data) { - return sourceFileWriter.write(barCode, data); - } - - public static SourceAllPersister create(FileSystem fileSystem) { - return new SourceAllPersister(fileSystem); - } -} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceAllPersister.kt b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceAllPersister.kt new file mode 100644 index 000000000..5bf04706c --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceAllPersister.kt @@ -0,0 +1,55 @@ +package com.nytimes.android.external.fs3 + + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.store3.base.AllPersister +import com.nytimes.android.external.store3.base.impl.BarCode + +import java.io.FileNotFoundException +import javax.inject.Inject + +import io.reactivex.Maybe +import io.reactivex.Observable +import io.reactivex.Single +import okio.BufferedSource + +class SourceAllPersister @Inject +constructor(fileSystem: FileSystem) : AllPersister { + + internal val sourceFileAllReader: FSAllReader + internal val sourceFileAllEraser: FSAllEraser + + internal val sourceFileReader: FSReader + internal val sourceFileWriter: FSWriter + + init { + sourceFileAllReader = FSAllReader(fileSystem) + sourceFileAllEraser = FSAllEraser(fileSystem) + sourceFileReader = FSReader(fileSystem, BarCodeReadAllPathResolver()) + sourceFileWriter = FSWriter(fileSystem, BarCodeReadAllPathResolver()) + } + + @Throws(FileNotFoundException::class) + override fun readAll(path: String): Observable { + return sourceFileAllReader.readAll(path) + } + + override fun deleteAll(path: String): Observable { + return sourceFileAllEraser.deleteAll(path) + } + + override suspend fun read(barCode: BarCode): BufferedSource? { + return sourceFileReader.read(barCode) + } + + override suspend fun write(barCode: BarCode, data: BufferedSource): Boolean { + return sourceFileWriter.write(barCode, data) + } + + companion object { + + fun create(fileSystem: FileSystem): SourceAllPersister { + return SourceAllPersister(fileSystem) + } + } +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileReader.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileReader.java deleted file mode 100644 index 0952a95e8..000000000 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileReader.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.store3.base.DiskRead; -import com.nytimes.android.external.store3.base.RecordState; -import com.nytimes.android.external.store3.base.impl.BarCode; - -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nonnull; - -import okio.BufferedSource; - -import static com.nytimes.android.external.fs3.SourcePersister.pathForBarcode; - -public class SourceFileReader extends FSReader implements DiskRead { - - public SourceFileReader(FileSystem fileSystem) { - this(fileSystem, new BarCodePathResolver()); - } - - public SourceFileReader(FileSystem fileSystem, PathResolver pathResolver) { - super(fileSystem, pathResolver); - } - - @Nonnull - public RecordState getRecordState(@Nonnull BarCode barCode, - @Nonnull TimeUnit expirationUnit, - long expirationDuration) { - return fileSystem.getRecordState(expirationUnit, expirationDuration, pathForBarcode(barCode)); - } -} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileReader.kt b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileReader.kt new file mode 100644 index 000000000..13d132db1 --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileReader.kt @@ -0,0 +1,22 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.store3.base.DiskRead +import com.nytimes.android.external.store3.base.RecordState +import com.nytimes.android.external.store3.base.impl.BarCode + +import java.util.concurrent.TimeUnit + +import okio.BufferedSource + +import com.nytimes.android.external.fs3.SourcePersister.pathForBarcode + +class SourceFileReader @JvmOverloads constructor(fileSystem: FileSystem, pathResolver: PathResolver = BarCodePathResolver()) + : FSReader(fileSystem, pathResolver), DiskRead { + + fun getRecordState(barCode: BarCode, + expirationUnit: TimeUnit, + expirationDuration: Long): RecordState { + return fileSystem.getRecordState(expirationUnit, expirationDuration, pathForBarcode(barCode)) + } +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileWriter.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileWriter.java deleted file mode 100644 index 3a225a70f..000000000 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileWriter.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.store3.base.DiskWrite; -import com.nytimes.android.external.store3.base.impl.BarCode; - -import okio.BufferedSource; - -public class SourceFileWriter extends FSWriter implements DiskWrite { - - public SourceFileWriter(FileSystem fileSystem) { - this(fileSystem, new BarCodePathResolver()); - } - - public SourceFileWriter(FileSystem fileSystem, PathResolver pathResolver) { - super(fileSystem, pathResolver); - } - -} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileWriter.kt b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileWriter.kt new file mode 100644 index 000000000..c17c86513 --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileWriter.kt @@ -0,0 +1,12 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.store3.base.DiskWrite +import com.nytimes.android.external.store3.base.impl.BarCode + +import okio.BufferedSource + +class SourceFileWriter @JvmOverloads +constructor(fileSystem: FileSystem, pathResolver: +PathResolver = BarCodePathResolver()) : + FSWriter(fileSystem, pathResolver), DiskWrite diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersister.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersister.java deleted file mode 100644 index 789f752c0..000000000 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersister.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.nytimes.android.external.fs3; - - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; - -import javax.annotation.Nonnull; -import javax.inject.Inject; - -import io.reactivex.Maybe; -import io.reactivex.Single; -import okio.BufferedSource; - -/** - * Persister to be used when storing something to persister from a BufferedSource - * example usage: - * ParsingStoreBuilder.builder() - * .fetcher(fetcher) - * .persister(new SourcePersister(fileSystem)) - * .parser(new GsonSourceParser<>(gson, BookResults.class)) - * .open(); - */ -public class SourcePersister implements Persister { - - @Nonnull - final SourceFileReader sourceFileReader; - @Nonnull - final SourceFileWriter sourceFileWriter; - - @Inject - public SourcePersister(FileSystem fileSystem) { - sourceFileReader = new SourceFileReader(fileSystem); - sourceFileWriter = new SourceFileWriter(fileSystem); - } - - public static SourcePersister create(FileSystem fileSystem) { - return new SourcePersister(fileSystem); - } - - @Nonnull - static String pathForBarcode(@Nonnull BarCode barCode) { - return barCode.getType() + barCode.getKey(); - } - - @Nonnull - @Override - public Maybe read(@Nonnull final BarCode barCode) { - return sourceFileReader.read(barCode); - } - - @Nonnull - @Override - public Single write(@Nonnull final BarCode barCode, @Nonnull final BufferedSource data) { - return sourceFileWriter.write(barCode, data); - } - -} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersister.kt b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersister.kt new file mode 100644 index 000000000..79fad454a --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersister.kt @@ -0,0 +1,52 @@ +package com.nytimes.android.external.fs3 + + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import javax.inject.Inject + +import io.reactivex.Maybe +import io.reactivex.Single +import okio.BufferedSource + +/** + * Persister to be used when storing something to persister from a BufferedSource + * example usage: + * ParsingStoreBuilder., BookResults>builder() + * .fetcher(fetcher) + * .persister(new SourcePersister(fileSystem)) + * .parser(new GsonSourceParser<>(gson, BookResults.class)) + * .open(); + */ +open class SourcePersister @Inject +constructor(fileSystem: FileSystem) : Persister { + + internal val sourceFileReader: SourceFileReader + internal val sourceFileWriter: SourceFileWriter + + init { + sourceFileReader = SourceFileReader(fileSystem) + sourceFileWriter = SourceFileWriter(fileSystem) + } + + override suspend fun read(barCode: BarCode): BufferedSource? { + return sourceFileReader.read(barCode) + } + + override suspend fun write(barCode: BarCode, data: BufferedSource): Boolean { + return sourceFileWriter.write(barCode, data) + } + + companion object { + + fun create(fileSystem: FileSystem): SourcePersister { + return SourcePersister(fileSystem) + } + + internal fun pathForBarcode(barCode: BarCode): String { + return barCode.type + barCode.key + } + } + +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersisterFactory.java b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersisterFactory.java deleted file mode 100644 index 03a2aecf5..000000000 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersisterFactory.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.nytimes.android.external.fs3; - -import com.nytimes.android.external.fs3.filesystem.FileSystem; -import com.nytimes.android.external.fs3.filesystem.FileSystemFactory; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; - -import java.io.File; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -import javax.annotation.Nonnull; - -import okio.BufferedSource; - -/** - * Factory for {@link SourcePersister} - */ - -public final class SourcePersisterFactory { - private SourcePersisterFactory() { - } - - - /** - * Returns a new {@link BufferedSource} persister with the provided file as the root of the - * persistence {@link FileSystem}. - * - * @throws IOException - */ - @Nonnull - public static Persister create(@Nonnull File root, - long expirationDuration, - @Nonnull TimeUnit expirationUnit) throws IOException { - if (root == null) { - throw new IllegalArgumentException("root file cannot be null."); - } - return RecordPersister.create(FileSystemFactory.create(root), expirationDuration, expirationUnit); - } - - /** - * Returns a new {@link BufferedSource} persister with the provided fileSystem as the root of the - * persistence {@link FileSystem}. - **/ - @Nonnull - public static Persister create(@Nonnull FileSystem fileSystem, - long expirationDuration, - @Nonnull TimeUnit expirationUnit) { - if (fileSystem == null) { - throw new IllegalArgumentException("fileSystem cannot be null."); - } - return RecordPersister.create(fileSystem, expirationDuration, expirationUnit); - } - - /** - * Returns a new {@link BufferedSource} persister with the provided file as the root of the - * persistence {@link FileSystem}. - * - * @throws IOException - */ - @Nonnull - public static Persister create(@Nonnull File root) throws IOException { - if (root == null) { - throw new IllegalArgumentException("root file cannot be null."); - } - return SourcePersister.create(FileSystemFactory.create(root)); - } - - /** - * Returns a new {@link BufferedSource} persister with the provided fileSystem as the root of the - * persistence {@link FileSystem}. - **/ - @Nonnull - public static Persister create(@Nonnull FileSystem fileSystem) { - if (fileSystem == null) { - throw new IllegalArgumentException("fileSystem cannot be null."); - } - return SourcePersister.create(fileSystem); - } - - /** - * Returns a new {@link BufferedSource} persister with the provided file as the root of the - * persistence {@link FileSystem}. - * - * @throws IOException - */ - @Nonnull - public static Persister createAll(@Nonnull File root) throws IOException { - if (root == null) { - throw new IllegalArgumentException("root file cannot be null."); - } - return SourceAllPersister.create(FileSystemFactory.create(root)); - } - - /** - * Returns a new {@link BufferedSource} persister with the provided fileSystem as the root of the - * persistence {@link FileSystem}. - **/ - @Nonnull - public static Persister createAll(@Nonnull FileSystem fileSystem) { - if (fileSystem == null) { - throw new IllegalArgumentException("fileSystem cannot be null."); - } - return SourceAllPersister.create(fileSystem); - } -} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersisterFactory.kt b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersisterFactory.kt new file mode 100644 index 000000000..24f4f148e --- /dev/null +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourcePersisterFactory.kt @@ -0,0 +1,99 @@ +package com.nytimes.android.external.fs3 + +import com.nytimes.android.external.fs3.filesystem.FileSystem +import com.nytimes.android.external.fs3.filesystem.FileSystemFactory +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode + +import java.io.File +import java.io.IOException +import java.util.concurrent.TimeUnit + +import okio.BufferedSource + +/** + * Factory for [SourcePersister] + */ + +object SourcePersisterFactory { + + + /** + * Returns a new [BufferedSource] persister with the provided file as the root of the + * persistence [FileSystem]. + * + * @throws IOException + */ + @Throws(IOException::class) + fun create(root: File, + expirationDuration: Long, + expirationUnit: TimeUnit): Persister { + if (root == null) { + throw IllegalArgumentException("root file cannot be null.") + } + return RecordPersister.create(FileSystemFactory.create(root), expirationDuration, expirationUnit) + } + + /** + * Returns a new [BufferedSource] persister with the provided fileSystem as the root of the + * persistence [FileSystem]. + */ + fun create(fileSystem: FileSystem, + expirationDuration: Long, + expirationUnit: TimeUnit): Persister { + if (fileSystem == null) { + throw IllegalArgumentException("fileSystem cannot be null.") + } + return RecordPersister.create(fileSystem, expirationDuration, expirationUnit) + } + + /** + * Returns a new [BufferedSource] persister with the provided file as the root of the + * persistence [FileSystem]. + * + * @throws IOException + */ + @Throws(IOException::class) + fun create(root: File): Persister { + if (root == null) { + throw IllegalArgumentException("root file cannot be null.") + } + return SourcePersister.create(FileSystemFactory.create(root)) + } + + /** + * Returns a new [BufferedSource] persister with the provided fileSystem as the root of the + * persistence [FileSystem]. + */ + fun create(fileSystem: FileSystem): Persister { + if (fileSystem == null) { + throw IllegalArgumentException("fileSystem cannot be null.") + } + return SourcePersister.create(fileSystem) + } + + /** + * Returns a new [BufferedSource] persister with the provided file as the root of the + * persistence [FileSystem]. + * + * @throws IOException + */ + @Throws(IOException::class) + fun createAll(root: File): Persister { + if (root == null) { + throw IllegalArgumentException("root file cannot be null.") + } + return SourceAllPersister.create(FileSystemFactory.create(root)) + } + + /** + * Returns a new [BufferedSource] persister with the provided fileSystem as the root of the + * persistence [FileSystem]. + */ + fun createAll(fileSystem: FileSystem): Persister { + if (fileSystem == null) { + throw IllegalArgumentException("fileSystem cannot be null.") + } + return SourceAllPersister.create(fileSystem) + } +} diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/FilePersisterTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/FilePersisterTest.java index 770a1576a..2d5919089 100644 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/FilePersisterTest.java +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/FilePersisterTest.java @@ -33,7 +33,7 @@ public class FilePersisterTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - fileSystemPersister = FileSystemPersister.create(fileSystem, new BarCodePathResolver()); + fileSystemPersister = FileSystemPersister.Companion.create(fileSystem, new BarCodePathResolver()); } @Test diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/FileSystemRecordPersisterTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/FileSystemRecordPersisterTest.java index 9a2086702..132f1d88d 100644 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/FileSystemRecordPersisterTest.java +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/FileSystemRecordPersisterTest.java @@ -34,7 +34,7 @@ public class FileSystemRecordPersisterTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - fileSystemPersister = FileSystemRecordPersister.create(fileSystem, + fileSystemPersister = FileSystemRecordPersister.Companion.create(fileSystem, new BarCodePathResolver(), 1, TimeUnit.DAYS); } diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/RecordPersisterTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/RecordPersisterTest.java index 4807097da..0b48bb151 100644 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/RecordPersisterTest.java +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/RecordPersisterTest.java @@ -46,7 +46,7 @@ public void readExists() throws FileNotFoundException { @Test public void freshTest() { - when(fileSystem.getRecordState(TimeUnit.DAYS, 1L, SourcePersister.pathForBarcode(simple))) + when(fileSystem.getRecordState(TimeUnit.DAYS, 1L, SourcePersister.Companion.pathForBarcode(simple))) .thenReturn(RecordState.FRESH); assertThat(sourcePersister.getRecordState(simple)).isEqualTo(RecordState.FRESH); @@ -54,7 +54,7 @@ public void freshTest() { @Test public void staleTest() { - when(fileSystem.getRecordState(TimeUnit.DAYS, 1L, SourcePersister.pathForBarcode(simple))) + when(fileSystem.getRecordState(TimeUnit.DAYS, 1L, SourcePersister.Companion.pathForBarcode(simple))) .thenReturn(RecordState.STALE); assertThat(sourcePersister.getRecordState(simple)).isEqualTo(RecordState.STALE); @@ -62,7 +62,7 @@ public void staleTest() { @Test public void missingTest() { - when(fileSystem.getRecordState(TimeUnit.DAYS, 1L, SourcePersister.pathForBarcode(simple))) + when(fileSystem.getRecordState(TimeUnit.DAYS, 1L, SourcePersister.Companion.pathForBarcode(simple))) .thenReturn(RecordState.MISSING); assertThat(sourcePersister.getRecordState(simple)).isEqualTo(RecordState.MISSING); @@ -71,7 +71,7 @@ public void missingTest() { @Test @SuppressWarnings("CheckReturnValue") public void readDoesNotExist() throws FileNotFoundException { - when(fileSystem.exists(SourcePersister.pathForBarcode(simple))) + when(fileSystem.exists(SourcePersister.Companion.pathForBarcode(simple))) .thenReturn(false); sourcePersister.read(simple).test().assertError(FileNotFoundException.class); @@ -84,6 +84,6 @@ public void write() throws IOException { @Test public void pathForBarcode() { - assertThat(SourcePersister.pathForBarcode(simple)).isEqualTo("typekey"); + assertThat(SourcePersister.Companion.pathForBarcode(simple)).isEqualTo("typekey"); } } diff --git a/filesystem/src/test/java/com/nytimes/android/external/fs3/SourcePersisterTest.java b/filesystem/src/test/java/com/nytimes/android/external/fs3/SourcePersisterTest.java index b3940bc7f..a3531c0f6 100644 --- a/filesystem/src/test/java/com/nytimes/android/external/fs3/SourcePersisterTest.java +++ b/filesystem/src/test/java/com/nytimes/android/external/fs3/SourcePersisterTest.java @@ -50,7 +50,7 @@ public void readExists() throws FileNotFoundException { @Test @SuppressWarnings("CheckReturnValue") public void readDoesNotExist() throws FileNotFoundException { - when(fileSystem.exists(SourcePersister.pathForBarcode(simple))) + when(fileSystem.exists(SourcePersister.Companion.pathForBarcode(simple))) .thenReturn(false); sourcePersister.read(simple).test().assertError(FileNotFoundException.class); @@ -63,6 +63,6 @@ public void write() throws IOException { @Test public void pathForBarcode() { - assertThat(SourcePersister.pathForBarcode(simple)).isEqualTo("typekey"); + assertThat(SourcePersister.Companion.pathForBarcode(simple)).isEqualTo("typekey"); } } diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.java b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.java deleted file mode 100644 index e40e2cdcc..000000000 --- a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.nytimes.android.external.store3.middleware; - - -import com.google.gson.Gson; -import com.nytimes.android.external.store3.base.Parser; - -import java.io.Reader; -import java.lang.reflect.Type; - -import javax.annotation.Nonnull; - -import okio.BufferedSource; - -/** - * Factory which returns various Gson {@link Parser} implementations. - */ -public final class GsonParserFactory { - private GsonParserFactory() { - } - - /** - * Returns a new Parser which parses from {@link Reader} to the specified type, using - * a new default configured {@link Gson} instance. - */ - @Nonnull - public static Parser createReaderParser(@Nonnull Type type) { - return createReaderParser(new Gson(), type); - } - - /** - * Returns a new Parser which parses from {@link Reader} to the specified type, using - * the provided {@link Gson} instance. - */ - @Nonnull - public static Parser createReaderParser(@Nonnull Gson gson, @Nonnull Type type) { - return new GsonReaderParser<>(gson, type); - } - - /** - * Returns a new Parser which parses from {@link Reader} to the specified type, using - * a new default configured {@link Gson} instance. - */ - @Nonnull - public static Parser createSourceParser(@Nonnull Type type) { - return createSourceParser(new Gson(), type); - } - - /** - * Returns a new Parser which parses from {@link BufferedSource} to the specified type, using - * the provided {@link Gson} instance. - */ - @Nonnull - public static Parser createSourceParser(@Nonnull Gson gson, @Nonnull Type type) { - return new GsonSourceParser<>(gson, type); - } - - /** - * Returns a new Parser which parses from a String to the specified type, using - * a new default {@link Gson} instance. - */ - @Nonnull - public static Parser createStringParser(@Nonnull Class type) { - return createStringParser(new Gson(), type); - } - - /** - * Returns a new Parser which parses from a String to the specified type, using - * the provided {@link Gson} instance. - */ - @Nonnull - public static Parser createStringParser(@Nonnull Gson gson, @Nonnull Type type) { - return new GsonStringParser<>(gson, type); - } - -} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.kt b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.kt new file mode 100644 index 000000000..56cdcf0e2 --- /dev/null +++ b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonParserFactory.kt @@ -0,0 +1,65 @@ +package com.nytimes.android.external.store3.middleware + + +import com.google.gson.Gson +import com.nytimes.android.external.store3.base.Parser + +import java.io.Reader +import java.lang.reflect.Type + +import okio.BufferedSource + +/** + * Factory which returns various Gson [Parser] implementations. + */ +object GsonParserFactory { + + /** + * Returns a new Parser which parses from [Reader] to the specified type, using + * a new default configured [Gson] instance. + */ + fun createReaderParser(type: Type): Parser { + return createReaderParser(Gson(), type) + } + + /** + * Returns a new Parser which parses from [Reader] to the specified type, using + * the provided [Gson] instance. + */ + fun createReaderParser(gson: Gson, type: Type): Parser { + return GsonReaderParser(gson, type) + } + + /** + * Returns a new Parser which parses from [Reader] to the specified type, using + * a new default configured [Gson] instance. + */ + fun createSourceParser(type: Type): Parser { + return createSourceParser(Gson(), type) + } + + /** + * Returns a new Parser which parses from [BufferedSource] to the specified type, using + * the provided [Gson] instance. + */ + fun createSourceParser(gson: Gson, type: Type): Parser { + return GsonSourceParser(gson, type) + } + + /** + * Returns a new Parser which parses from a String to the specified type, using + * a new default [Gson] instance. + */ + fun createStringParser(type: Class): Parser { + return createStringParser(Gson(), type) + } + + /** + * Returns a new Parser which parses from a String to the specified type, using + * the provided [Gson] instance. + */ + fun createStringParser(gson: Gson, type: Type): Parser { + return GsonStringParser(gson, type) + } + +} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.java b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.java deleted file mode 100644 index 345e58b72..000000000 --- a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.nytimes.android.external.store3.middleware; - -import com.google.gson.Gson; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.util.ParserException; - -import java.io.Reader; -import java.lang.reflect.Type; - -import javax.inject.Inject; - -import io.reactivex.annotations.NonNull; - -import static com.nytimes.android.external.cache3.Preconditions.checkNotNull; - -public class GsonReaderParser implements Parser { - - private final Gson gson; - private final Type type; - - @Inject - public GsonReaderParser(Gson gson, Type type) { - checkNotNull(gson, "Gson can't be null"); - checkNotNull(type, "Type can't be null"); - this.gson = gson; - this.type = type; - } - - @Override - public Parsed apply(@NonNull Reader reader) throws ParserException { - return gson.fromJson(reader, type); - } -} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.kt b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.kt new file mode 100644 index 000000000..f34b556a3 --- /dev/null +++ b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonReaderParser.kt @@ -0,0 +1,17 @@ +package com.nytimes.android.external.store3.middleware + +import com.google.gson.Gson +import com.nytimes.android.external.store3.base.Parser +import com.nytimes.android.external.store3.util.ParserException +import java.io.Reader +import java.lang.reflect.Type +import javax.inject.Inject + +class GsonReaderParser @Inject +constructor(private val gson: Gson, private val type: Type) : Parser { + + @Throws(ParserException::class) + override + suspend fun apply(raw: Reader): Parsed = gson.fromJson(raw, type) + +} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.java b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.java deleted file mode 100644 index 527a6ada2..000000000 --- a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.nytimes.android.external.store3.middleware; - - -import com.google.gson.Gson; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.util.ParserException; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.reflect.Type; -import java.nio.charset.Charset; - -import javax.inject.Inject; - -import io.reactivex.annotations.NonNull; -import okio.BufferedSource; - -import static com.nytimes.android.external.cache3.Preconditions.checkNotNull; - - -/** - * Parser to be used when going from a BufferedSource to any Parsed Type - * example usage: - * ParsingStoreBuilder.builder() - * .fetcher(fetcher) - * .persister(SourcePersisterFactory.create(getApplicationContext().getCacheDir())) - * .parser(GsonParserFactory.createSourceParser(new Gson(),BookResult.class) - * .open(); - */ - - -public class GsonSourceParser implements Parser { - - private final Gson gson; - private final Type type; - - @Inject - public GsonSourceParser(Gson gson, Type type) { - checkNotNull(gson, "Gson can't be null"); - checkNotNull(type, "Type can't be null"); - this.gson = gson; - this.type = type; - } - - @Override - public Parsed apply(@NonNull BufferedSource bufferedSource) throws ParserException { - try (InputStreamReader reader = new InputStreamReader(bufferedSource.inputStream(), Charset.forName("UTF-8"))) { - return gson.fromJson(reader, type); - } catch (IOException e) { - throw new ParserException(e.getMessage(), e); - } - } -} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.kt b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.kt new file mode 100644 index 000000000..ce8b4068d --- /dev/null +++ b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonSourceParser.kt @@ -0,0 +1,40 @@ +package com.nytimes.android.external.store3.middleware + + +import com.google.gson.Gson +import com.nytimes.android.external.store3.base.Parser +import com.nytimes.android.external.store3.util.ParserException +import io.reactivex.annotations.NonNull +import okio.BufferedSource +import java.io.IOException +import java.io.InputStreamReader +import java.lang.reflect.Type +import java.nio.charset.Charset +import javax.inject.Inject + + +/** + * Parser to be used when going from a BufferedSource to any Parsed Type + * example usage: + * ParsingStoreBuilder., BookResults>builder() + * .fetcher(fetcher) + * .persister(SourcePersisterFactory.create(getApplicationContext().getCacheDir())) + * .parser(GsonParserFactory.createSourceParser(new Gson(),BookResult.class) + * .open(); + */ + + +class GsonSourceParser @Inject +constructor(private val gson: Gson, private val type: Type) : Parser { + + @Throws(ParserException::class) + override + suspend fun apply(@NonNull raw: BufferedSource): Parsed { + try { + InputStreamReader(raw.inputStream(), Charset.forName("UTF-8")).use { reader -> return gson.fromJson(reader, type) } + } catch (e: IOException) { + throw ParserException(e.message, e) + } + + } +} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.java b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.java deleted file mode 100644 index 9fde01dfc..000000000 --- a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.nytimes.android.external.store3.middleware; - -import com.google.gson.Gson; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.util.ParserException; - -import java.lang.reflect.Type; - -import javax.inject.Inject; - -import io.reactivex.annotations.NonNull; - -import static com.nytimes.android.external.cache3.Preconditions.checkNotNull; - -public class GsonStringParser implements Parser { - - private final Gson gson; - private final Type type; - - @Inject - public GsonStringParser(Gson gson, Type parsedClass) { - checkNotNull(gson, "Gson can't be null"); - checkNotNull(parsedClass, "Type can't be null"); - this.gson = gson; - this.type = parsedClass; - } - - @Override - public Parsed apply(@NonNull String s) throws ParserException { - return gson.fromJson(s, type); - } -} diff --git a/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.kt b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.kt new file mode 100644 index 000000000..f1e100d27 --- /dev/null +++ b/middleware/src/main/java/com/nytimes/android/external/store3/middleware/GsonStringParser.kt @@ -0,0 +1,21 @@ +package com.nytimes.android.external.store3.middleware + +import com.google.gson.Gson +import com.nytimes.android.external.store3.base.Parser +import com.nytimes.android.external.store3.util.ParserException + +import java.lang.reflect.Type + +import javax.inject.Inject + +import io.reactivex.annotations.NonNull + +class GsonStringParser @Inject +constructor(private val gson: Gson, private val type: Type) : Parser { + + @Throws(ParserException::class) + override + suspend fun apply(@NonNull raw: String): Parsed { + return gson.fromJson(raw, type) + } +} diff --git a/middleware/src/test/java/com/nytimes/android/external/store3/GenericParserStoreTest.java b/middleware/src/test/java/com/nytimes/android/external/store3/GenericParserStoreTest.java index 069416e8b..bbd17dd76 100644 --- a/middleware/src/test/java/com/nytimes/android/external/store3/GenericParserStoreTest.java +++ b/middleware/src/test/java/com/nytimes/android/external/store3/GenericParserStoreTest.java @@ -42,7 +42,7 @@ private static BufferedSource source(String data) { public void testSimple() { MockitoAnnotations.initMocks(this); - Parser parser = GsonParserFactory.createSourceParser(new Gson(), Foo.class); + Parser parser = GsonParserFactory.INSTANCE.createSourceParser(new Gson(), Foo.class); Store simpleStore = StoreBuilder.parsedWithKey() .persister(persister) diff --git a/middleware/src/test/java/com/nytimes/android/external/store3/GsonParserFactoryTest.java b/middleware/src/test/java/com/nytimes/android/external/store3/GsonParserFactoryTest.java index 60aae06f0..17b6b87c4 100644 --- a/middleware/src/test/java/com/nytimes/android/external/store3/GsonParserFactoryTest.java +++ b/middleware/src/test/java/com/nytimes/android/external/store3/GsonParserFactoryTest.java @@ -28,44 +28,44 @@ public void setUp() throws Exception { @Test public void shouldCreateParsersProperly() { - GsonParserFactory.createReaderParser(gson, type); - GsonParserFactory.createSourceParser(gson, type); - GsonParserFactory.createStringParser(gson, type); + GsonParserFactory.INSTANCE.createReaderParser(gson, type); + GsonParserFactory.INSTANCE.createSourceParser(gson, type); + GsonParserFactory.INSTANCE.createStringParser(gson, type); } @Test public void shouldThrowExceptionWhenCreatingReaderWithNullType() { expectedException.expect(NullPointerException.class); - GsonParserFactory.createReaderParser(gson, null); + GsonParserFactory.INSTANCE.createReaderParser(gson, null); } @Test public void shouldThrowExceptionWhenCreatingReaderWithNullGson() { expectedException.expect(NullPointerException.class); - GsonParserFactory.createReaderParser(null, type); + GsonParserFactory.INSTANCE.createReaderParser(null, type); } @Test public void shouldThrowExceptionWhenCreatingSourceWithNullType() { expectedException.expect(NullPointerException.class); - GsonParserFactory.createSourceParser(gson, null); + GsonParserFactory.INSTANCE.createSourceParser(gson, null); } @Test public void shouldThrowExceptionWhenCreatingSourceWithNullGson() { expectedException.expect(NullPointerException.class); - GsonParserFactory.createSourceParser(null, type); + GsonParserFactory.INSTANCE.createSourceParser(null, type); } @Test public void shouldThrowExceptionWhenCreatingStringWithNullType() { expectedException.expect(NullPointerException.class); - GsonParserFactory.createStringParser(gson, null); + GsonParserFactory.INSTANCE.createStringParser(gson, null); } @Test public void shouldThrowExceptionWhenCreatingStringWithNullGson() { expectedException.expect(NullPointerException.class); - GsonParserFactory.createStringParser(null, type); + GsonParserFactory.INSTANCE.createStringParser(null, type); } } diff --git a/middleware/src/test/java/com/nytimes/android/external/store3/GsonSourceListParserTest.java b/middleware/src/test/java/com/nytimes/android/external/store3/GsonSourceListParserTest.java index 62e9a44a5..f23fa7c46 100644 --- a/middleware/src/test/java/com/nytimes/android/external/store3/GsonSourceListParserTest.java +++ b/middleware/src/test/java/com/nytimes/android/external/store3/GsonSourceListParserTest.java @@ -46,7 +46,7 @@ public void testSimple() { MockitoAnnotations.initMocks(this); Parser> parser = - GsonParserFactory.createSourceParser(new Gson(), new TypeToken>() { + GsonParserFactory.INSTANCE.createSourceParser(new Gson(), new TypeToken>() { }.getType()); diff --git a/store/build.gradle b/store/build.gradle index e40f96362..bc1423dc0 100644 --- a/store/build.gradle +++ b/store/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'java' +apply plugin: 'kotlin' group = GROUP version = VERSION_NAME @@ -6,6 +7,8 @@ version = VERSION_NAME dependencies { implementation project(path: ':cache') implementation libraries.rxJava2 + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1" + apiElements libraries.rxJava2 compileOnly libraries.jsr305 @@ -13,16 +16,37 @@ dependencies { testImplementation libraries.assertJ testImplementation libraries.junit testCompileOnly libraries.jsr305 + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" } buildscript { + ext.kotlin_version = '1.3.0' tasks.withType(JavaCompile) { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } } apply from: rootProject.file("gradle/maven-push.gradle") apply from: rootProject.file("gradle/checkstyle.gradle") apply from: rootProject.file("gradle/pmd.gradle") targetCompatibility = 1.8 sourceCompatibility = 1.8 +repositories { + mavenCentral() +} +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} diff --git a/store/gradle.properties b/store/gradle.properties index edef291d3..aa6b53e5c 100644 --- a/store/gradle.properties +++ b/store/gradle.properties @@ -1,4 +1,5 @@ POM_NAME=com.nytimes.android POM_ARTIFACT_ID=store3 POM_PACKAGING=aar -android.enableAapt2=false \ No newline at end of file +android.enableAapt2=false +kotlin.coroutines=enable diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/AllPersister.java b/store/src/main/java/com/nytimes/android/external/store3/base/AllPersister.java deleted file mode 100644 index fe1394fe5..000000000 --- a/store/src/main/java/com/nytimes/android/external/store3/base/AllPersister.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.nytimes.android.external.store3.base; - - -import java.io.FileNotFoundException; - -import javax.annotation.Nonnull; - -import io.reactivex.Maybe; -import io.reactivex.Observable; -import io.reactivex.Single; - - -public interface AllPersister extends Persister, DiskAllRead, DiskAllErase { - /** - * @param path to use to get data from persister - * If data is not available implementer needs to - * throw an exception - */ - @Override - @Nonnull - Observable readAll(@Nonnull final String path) throws FileNotFoundException; - - /** - * @param path to delete all the data in the the path. - */ - @Override - @Nonnull - Observable deleteAll(@Nonnull final String path); - - /** - * @param key to use to get data from persister - * If data is not available implementer needs to - * throw an exception - */ - @Override - @Nonnull - Maybe read(@Nonnull final Key key); - - /** - * @param key to use to store data to persister - * @param raw raw string to be stored - */ - @Override - @Nonnull - Single write(@Nonnull final Key key, @Nonnull final Raw raw); -} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/AllPersister.kt b/store/src/main/java/com/nytimes/android/external/store3/base/AllPersister.kt new file mode 100644 index 000000000..87e9e60c8 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/AllPersister.kt @@ -0,0 +1,38 @@ +package com.nytimes.android.external.store3.base + + +import java.io.FileNotFoundException + +import io.reactivex.Maybe +import io.reactivex.Observable +import io.reactivex.Single + + +interface AllPersister : Persister, DiskAllRead, DiskAllErase { + /** + * @param path to use to get data from persister + * If data is not available implementer needs to + * throw an exception + */ + @Throws(FileNotFoundException::class) + override fun readAll(path: String): Observable + + /** + * @param path to delete all the data in the the path. + */ + override fun deleteAll(path: String): Observable + + /** + * @param key to use to get data from persister + * If data is not available implementer needs to + * throw an exception + */ + // @Override + override suspend fun read(key: Key): Raw? + + /** + * @param key to use to store data to persister + * @param raw raw string to be stored + */ + override suspend fun write(key: Key, raw: Raw): Boolean +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/DiskRead.java b/store/src/main/java/com/nytimes/android/external/store3/base/DiskRead.java deleted file mode 100644 index 1133545c7..000000000 --- a/store/src/main/java/com/nytimes/android/external/store3/base/DiskRead.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.nytimes.android.external.store3.base; - -import javax.annotation.Nonnull; - -import io.reactivex.Maybe; - -public interface DiskRead { - @Nonnull - Maybe read(@Nonnull Key key); -} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/DiskRead.kt b/store/src/main/java/com/nytimes/android/external/store3/base/DiskRead.kt new file mode 100644 index 000000000..2482ce358 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/DiskRead.kt @@ -0,0 +1,7 @@ +package com.nytimes.android.external.store3.base + +import io.reactivex.Maybe + +interface DiskRead { + suspend fun read(key: Key): Raw? +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/DiskWrite.java b/store/src/main/java/com/nytimes/android/external/store3/base/DiskWrite.java deleted file mode 100644 index c74ef19d4..000000000 --- a/store/src/main/java/com/nytimes/android/external/store3/base/DiskWrite.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.nytimes.android.external.store3.base; - -import javax.annotation.Nonnull; - -import io.reactivex.Single; - -public interface DiskWrite { - /** - * @param key to use to get data from persister - * If data is not available implementer needs to - * either return Observable.empty or throw an exception - */ - @Nonnull - Single write(@Nonnull Key key, @Nonnull Raw raw); -} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/DiskWrite.kt b/store/src/main/java/com/nytimes/android/external/store3/base/DiskWrite.kt new file mode 100644 index 000000000..3d5df9a9a --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/DiskWrite.kt @@ -0,0 +1,13 @@ +package com.nytimes.android.external.store3.base + +import com.sun.org.apache.xpath.internal.operations.Bool +import io.reactivex.Single + +interface DiskWrite { + /** + * @param key to use to get data from persister + * If data is not available implementer needs to + * either return Observable.empty or throw an exception + */ + suspend fun write(key: Key, raw: Raw): Boolean +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/Fetcher.kt b/store/src/main/java/com/nytimes/android/external/store3/base/Fetcher.kt new file mode 100644 index 000000000..4c5f2c368 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/Fetcher.kt @@ -0,0 +1,19 @@ +package com.nytimes.android.external.store3.base + +import io.reactivex.Observable +import io.reactivex.Single + + +/** + * Interface for fetching new data for a Store + * + * @param data type before parsing + */ +interface Fetcher { + + /** + * @param key Container with Key and Type used as a request param + * @return Observable that emits [Raw] data + */ + suspend fun fetch(key: Key):Raw +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/InternalStore.java b/store/src/main/java/com/nytimes/android/external/store3/base/InternalStore.java deleted file mode 100644 index c66a94a51..000000000 --- a/store/src/main/java/com/nytimes/android/external/store3/base/InternalStore.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.nytimes.android.external.store3.base; - -import com.nytimes.android.external.store3.base.impl.Store; - -import javax.annotation.Nonnull; - -import io.reactivex.Maybe; - -/** -2 * this interface allows us to mark a {@link Store} as "internal", exposing methods for retrieving data - * directly from memory or from disk. - */ -public interface InternalStore extends Store { - @Nonnull - Maybe memory(@Nonnull final Key key); - - @Nonnull - Maybe disk(@Nonnull final Key key); -} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/InternalStore.kt b/store/src/main/java/com/nytimes/android/external/store3/base/InternalStore.kt new file mode 100644 index 000000000..3d2b11893 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/InternalStore.kt @@ -0,0 +1,13 @@ +package com.nytimes.android.external.store3.base + +import com.nytimes.android.external.store3.base.impl.Store + +/** + * 2 * this interface allows us to mark a [Store] as "internal", exposing methods for retrieving data + * directly from memory or from disk. + */ +interface InternalStore : Store { +// suspend fun memory(key: Key): Parsed? + + suspend fun disk(key: Key): Parsed? +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/Parser.java b/store/src/main/java/com/nytimes/android/external/store3/base/Parser.java deleted file mode 100644 index dbd3ca79d..000000000 --- a/store/src/main/java/com/nytimes/android/external/store3/base/Parser.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.nytimes.android.external.store3.base; - -import com.nytimes.android.external.store3.util.ParserException; - -import io.reactivex.annotations.NonNull; -import io.reactivex.functions.Function; - -//just a marker interface allowing for a reimplementation of how the parser is implemented -public interface Parser extends Function { - - @Override - Parsed apply(@NonNull Raw raw) throws ParserException; - -} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/Parser.kt b/store/src/main/java/com/nytimes/android/external/store3/base/Parser.kt new file mode 100644 index 000000000..2a7f2f694 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/Parser.kt @@ -0,0 +1,14 @@ +package com.nytimes.android.external.store3.base + +import com.nytimes.android.external.store3.util.ParserException + +import io.reactivex.annotations.NonNull +import io.reactivex.functions.Function + +//just a marker interface allowing for a reimplementation of how the parser is implemented +interface Parser { + + @Throws(ParserException::class) + suspend fun apply(@NonNull raw: Raw): Parsed + +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/Persister.java b/store/src/main/java/com/nytimes/android/external/store3/base/Persister.java deleted file mode 100644 index e404b9914..000000000 --- a/store/src/main/java/com/nytimes/android/external/store3/base/Persister.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.nytimes.android.external.store3.base; - -import javax.annotation.Nonnull; - -import io.reactivex.Maybe; -import io.reactivex.Single; - -/** - * Interface for fetching data from persister - * when implementing also think about implementing PathResolver to ease in creating primary keys - * - * @param data type before parsing - */ -public interface Persister extends DiskRead, DiskWrite, BasePersister { - - /** - * @param key to use to get data from persister - * If data is not available implementer needs to - * either return Observable.empty or throw an exception - */ - @Override - @Nonnull - Maybe read(@Nonnull final Key key); - - /** - * @param key to use to store data to persister - * @param raw raw string to be stored - */ - @Override - @Nonnull - Single write(@Nonnull final Key key, @Nonnull final Raw raw); -} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/Persister.kt b/store/src/main/java/com/nytimes/android/external/store3/base/Persister.kt new file mode 100644 index 000000000..18228f679 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/Persister.kt @@ -0,0 +1,23 @@ +package com.nytimes.android.external.store3.base + +/** + * Interface for fetching data from persister + * when implementing also think about implementing PathResolver to ease in creating primary keys + * + * @param data type before parsing + */ +interface Persister : DiskRead, DiskWrite, BasePersister { + + /** + * @param key to use to get data from persister + * If data is not available implementer needs to + * either return Observable.empty or throw an exception + */ + override suspend fun read(key: Key): Raw? + + /** + * @param key to use to store data to persister + * @param raw raw string to be stored + */ + override suspend fun write(key: Key, raw: Raw): Boolean +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/CacheFactory.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/CacheFactory.java index 0abbc980e..d692c9984 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/CacheFactory.java +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/CacheFactory.java @@ -14,11 +14,11 @@ private CacheFactory() { } - static Cache> createCache(MemoryPolicy memoryPolicy) { + static Cache createCache(MemoryPolicy memoryPolicy) { return createBaseCache(memoryPolicy); } - static Cache> createInflighter(MemoryPolicy memoryPolicy) { + static Cache createInflighter(MemoryPolicy memoryPolicy) { return createBaseInFlighter(memoryPolicy); } diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/MultiParser.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/MultiParser.java deleted file mode 100644 index 7d17898d2..000000000 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/MultiParser.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.nytimes.android.external.store3.base.impl; - -import com.nytimes.android.external.store3.util.KeyParser; -import com.nytimes.android.external.store3.util.ParserException; - -import java.util.ArrayList; -import java.util.List; - -import io.reactivex.annotations.NonNull; - -import static com.nytimes.android.external.cache3.Preconditions.checkArgument; -import static com.nytimes.android.external.cache3.Preconditions.checkNotNull; - -public class MultiParser implements KeyParser { - - private final List parsers = new ArrayList<>(); - - public MultiParser(List parsers) { - checkNotNull(parsers, "Parsers can't be null."); - checkArgument(!parsers.isEmpty(), "Parsers can't be empty."); - for (KeyParser parser : parsers) { - checkNotNull(parser, "Parser can't be null."); - } - this.parsers.addAll(parsers); - } - - private ParserException createParserException() { - return new ParserException("One of the provided parsers has a wrong typing. " + - "Make sure that parsers are passed in a correct order and the fromTypes match each other."); - } - - @Override - @NonNull - @SuppressWarnings("unchecked") - public Parsed apply(@NonNull Key key, @NonNull Raw raw) throws ParserException { - Object parsed = raw; - for (KeyParser parser : parsers) { - try { - parsed = parser.apply(key, parsed); - } catch (ClassCastException exception) { - throw createParserException(); - } - } - return (Parsed) parsed; - } -} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/MultiParser.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/MultiParser.kt new file mode 100644 index 000000000..7af611683 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/MultiParser.kt @@ -0,0 +1,40 @@ +//package com.nytimes.android.external.store3.base.impl +// +//import com.nytimes.android.external.store3.util.KeyParser +//import com.nytimes.android.external.store3.util.ParserException +// +//import java.util.ArrayList +// +//import io.reactivex.annotations.NonNull +// +//import com.nytimes.android.external.cache3.Preconditions.checkArgument +//import com.nytimes.android.external.cache3.Preconditions.checkNotNull +// +//class MultiParser(parsers: List>) : KeyParser { +// +// private val parsers = ArrayList>() +// +// init { +// this.parsers.addAll(parsers) +// } +// +// private fun createParserException(): ParserException { +// return ParserException("One of the provided parsers has a wrong typing. " + "Make sure that parsers are passed in a correct order and the fromTypes match each other.") +// } +// +// @NonNull +// @Throws(ParserException::class) +// override +// suspend fun apply(@NonNull key: Key, @NonNull raw: Raw): Parsed { +// var parsed: Any = raw!! +// for (parser in parsers) { +// try { +// parsed = parser.apply(key, parsed)!! +// } catch (exception: ClassCastException) { +// throw createParserException() +// } +// +// } +// return parsed as Parsed +// } +//} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.java deleted file mode 100644 index 3cc9c7657..000000000 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.nytimes.android.external.store3.base.impl; - -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Parser; - -import javax.annotation.Nonnull; - -import io.reactivex.Single; - - -/** - * Parsing fetcher that takes parser of Raw type and fetcher of raw type returning parsed instance. - * Created on 10/20/17. - */ -public class ParsingFetcher implements Fetcher { - - private final Fetcher rawFetcher; - private final Parser parser; - - /** - * Creates instance of ParsingFetcher - * - * @param rawFetcher fetches raw data by key - * @param parsedParser parses raw data to the instance of 'Parsed' - */ - public ParsingFetcher(@Nonnull Fetcher rawFetcher, @Nonnull Parser parsedParser) { - this.rawFetcher = rawFetcher; - this.parser = parsedParser; - } - - /** - * Creates ParsingFetcher for raw data type Fetcher and Raw data Parser. - */ - public static final ParsingFetcher from( - @Nonnull Fetcher fetcher, @Nonnull Parser parser) { - return new ParsingFetcher(fetcher, parser); - } - - @Nonnull - @Override - public Single fetch(@Nonnull Key key) { - return rawFetcher.fetch(key).map(parser); - } -} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.kt new file mode 100644 index 000000000..e372226c1 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.kt @@ -0,0 +1,31 @@ +package com.nytimes.android.external.store3.base.impl + +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Parser + +import io.reactivex.Single + + +/** + * Parsing fetcher that takes parser of Raw type and fetcher of raw type returning parsed instance. + * Created on 10/20/17. + */ +class ParsingFetcher +(private val rawFetcher: Fetcher, + private val parser: Parser) : Fetcher { + + override suspend fun fetch(key: Key): Parsed { + return rawFetcher.fetch(key).let { parser.apply(it) } + } + + companion object { + + /** + * Creates ParsingFetcher for raw data type Fetcher and Raw data Parser. + */ + fun from( + fetcher: Fetcher, parser: Parser): ParsingFetcher { + return ParsingFetcher(fetcher, parser) + } + } +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.java deleted file mode 100644 index b5332b7da..000000000 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.java +++ /dev/null @@ -1,321 +0,0 @@ -package com.nytimes.android.external.store3.base.impl; - -import com.nytimes.android.external.cache3.Cache; -import com.nytimes.android.external.store.util.Result; -import com.nytimes.android.external.store3.annotations.Experimental; -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.InternalStore; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.util.KeyParser; -import java.util.AbstractMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ExecutionException; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import io.reactivex.Maybe; -import io.reactivex.Observable; -import io.reactivex.Single; -import io.reactivex.subjects.PublishSubject; - -/** - * Store to be used for loading an object from different data sources - * - * @param data type before parsing, usually a String, Reader or BufferedSource - * @param data type after parsing - *

- * Example usage: @link - */ -final class RealInternalStore implements InternalStore { - Cache> inFlightRequests; - Cache> memCache; - StalePolicy stalePolicy; - Persister persister; - KeyParser parser; - - private final PublishSubject refreshSubject = PublishSubject.create(); - private Fetcher fetcher; - private PublishSubject> subject; - - RealInternalStore(Fetcher fetcher, - Persister persister, - KeyParser parser, - StalePolicy stalePolicy) { - this(fetcher, persister, parser, null, stalePolicy); - } - - RealInternalStore(Fetcher fetcher, - Persister persister, - KeyParser parser, - MemoryPolicy memoryPolicy, - StalePolicy stalePolicy) { - - this.fetcher = fetcher; - this.persister = persister; - this.parser = parser; - this.stalePolicy = stalePolicy; - - this.memCache = CacheFactory.createCache(memoryPolicy); - this.inFlightRequests = CacheFactory.createInflighter(memoryPolicy); - - subject = PublishSubject.create(); - } - - /** - * @param key - * @return an observable from the first data source that is available - */ - @Nonnull - @Override - public Single get(@Nonnull final Key key) { - return lazyCache(key) - .switchIfEmpty(fetch(key).toMaybe()) - .toSingle(); - } - - @Nonnull - @Override - public Single> getWithResult(@Nonnull Key key) { - return lazyCacheWithResult(key) - .switchIfEmpty(fetchWithResult(key).toMaybe()) - .toSingle(); - } - - @Override - @Nonnull - @Experimental - public Observable getRefreshing(@Nonnull final Key key) { - return get(key) - .toObservable() - .compose(StoreUtil.repeatWhenSubjectEmits(refreshSubject, key)); - } - - - /** - * @return data from memory - */ - private Maybe lazyCache(@Nonnull final Key key) { - return Maybe - .defer(() -> cache(key)) - .onErrorResumeNext(Maybe.empty()); - } - - Maybe cache(@Nonnull final Key key) { - try { - return memCache.get(key, () -> disk(key)); - } catch (ExecutionException e) { - return Maybe.empty(); - } - } - - /** - * @return data from memory - */ - private Maybe> lazyCacheWithResult(@Nonnull final Key key) { - return Maybe - .defer(() -> cacheWithResult(key)) - .onErrorResumeNext(Maybe.>empty()); - } - - Maybe> cacheWithResult(@Nonnull final Key key) { - try { - Maybe maybeResult = memCache.get(key, () -> disk(key)); - return maybeResult == null ? Maybe.>empty() : maybeResult.map(Result::createFromCache); - } catch (ExecutionException e) { - return Maybe.empty(); - } - } - - - @Nonnull - @Override - public Maybe memory(@Nonnull Key key) { - Maybe cachedValue = memCache.getIfPresent(key); - return cachedValue == null ? Maybe.empty() : cachedValue; - } - - /** - * Fetch data from persister and update memory after. If an error occurs, emit an empty observable - * so that the concat call in {@link #get(Key)} moves on to {@link #fetch(Key)} - * - * @param key - * @return - */ - @Nonnull - @Override - public Maybe disk(@Nonnull final Key key) { - if (StoreUtil.shouldReturnNetworkBeforeStale(persister, stalePolicy, key)) { - return Maybe.empty(); - } - - return readDisk(key); - } - - Maybe readDisk(@Nonnull final Key key) { - return persister().read(key) - .onErrorResumeNext(Maybe.empty()) - .map(raw -> parser.apply(key, raw)) - .doOnSuccess(parsed -> { - updateMemory(key, parsed); - if (stalePolicy == StalePolicy.REFRESH_ON_STALE - && StoreUtil.persisterIsStale(key, persister)) { - backfillCache(key); - } - }).cache(); - } - - @SuppressWarnings("CheckReturnValue") - void backfillCache(@Nonnull Key key) { - fetch(key).subscribe(parsed -> { - // do Nothing we are just backfilling cache - }, throwable -> { - // do nothing as we are just backfilling cache - }); - } - - - /** - * Will check to see if there exists an in flight observable and return it before - * going to network - * - * @return data from fetch and store it in memory and persister - */ - @Nonnull - @Override - public Single fetch(@Nonnull final Key key) { - return Single.defer(() -> fetchAndPersist(key)); - } - - @Nonnull - @Override - public Single> fetchWithResult(@Nonnull Key key) { - return fetch(key).map(Result::createFromNetwork); - } - - /** - * There should only be one fetch request in flight at any give time. - *

- * Return cached request in the form of a Behavior Subject which will emit to its subscribers - * the last value it gets. Subject/Observable is cached in a {@link ConcurrentMap} to maintain - * thread safety. - * - * @param key resource identifier - * @return observable that emits a {@link Parsed} value - */ - @Nullable - Single fetchAndPersist(@Nonnull final Key key) { - try { - return inFlightRequests.get(key, () -> response(key)); - } catch (ExecutionException e) { - return Single.error(e); - } - } - - @Nonnull - Single response(@Nonnull final Key key) { - return fetcher() - .fetch(key) - .flatMap(raw -> persister() - .write(key, raw) - .flatMap(aBoolean -> readDisk(key).toSingle())) - .onErrorResumeNext(throwable -> { - if (stalePolicy == StalePolicy.NETWORK_BEFORE_STALE) { - return readDisk(key) - .switchIfEmpty(Maybe.error(throwable)) - .toSingle(); - } - return Single.error(throwable); - }) - .doOnSuccess(data -> notifySubscribers(data, key)) - .doAfterTerminate(() -> inFlightRequests.invalidate(key)) - .cache(); - } - - void notifySubscribers(Parsed data, Key key) { - subject.onNext(new AbstractMap.SimpleEntry<>(key, data)); - } - - /** - * Get data stream for Subjects with the argument id - * - * @return - */ - @Nonnull - @Override - public Observable stream(@Nonnull Key key) { - return subject - .hide() - .startWith(get(key).toObservable(). - map(data -> new AbstractMap.SimpleEntry<>(key, data))) - .filter(simpleEntry -> simpleEntry.getKey().equals(key)) - .map(AbstractMap.SimpleEntry::getValue); - } - - @Nonnull - @Override - public Observable stream() { - return subject.hide().map(AbstractMap.SimpleEntry::getValue); - } - - - /** - * Only update memory after persister has been successfully updated - * - * @param key - * @param data - */ - void updateMemory(@Nonnull final Key key, final Parsed data) { - memCache.put(key, Maybe.just(data)); - } - - @Override - @Deprecated - public void clearMemory() { - clear(); - } - - /** - * Clear memory by id - * - * @param key of data to clear - */ - @Override - @Deprecated - public void clearMemory(@Nonnull final Key key) { - clear(key); - } - - - @Override - public void clear() { - for (Key cachedKey : memCache.asMap().keySet()) { - clear(cachedKey); - } - } - - @Override - public void clear(@Nonnull Key key) { - inFlightRequests.invalidate(key); - memCache.invalidate(key); - StoreUtil.clearPersister(persister(), key); - notifyRefresh(key); - } - - private void notifyRefresh(@Nonnull Key key) { - refreshSubject.onNext(key); - } - - /** - * @return DiskDAO that stores and stores data - */ - Persister persister() { - return persister; - } - - /** - * - */ - Fetcher fetcher() { - return fetcher; - } -} - diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt new file mode 100644 index 000000000..084517ee5 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt @@ -0,0 +1,287 @@ +package com.nytimes.android.external.store3.base.impl + +import com.nytimes.android.external.cache3.Cache +import com.nytimes.android.external.store.util.Result +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.InternalStore +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.util.KeyParser +import io.reactivex.Observable +import io.reactivex.subjects.PublishSubject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.async +import java.util.* +import java.util.concurrent.ConcurrentMap + +/** + * Store to be used for loading an object from different data sources + * + * @param data type before parsing, usually a String, Reader or BufferedSource + * @param data type after parsing + * + * + * Example usage: @link + */ +internal class RealInternalStore(private val fetcher: Fetcher, + private val persister: Persister, + private val parser: KeyParser, + memoryPolicy: MemoryPolicy?, + private val stalePolicy: StalePolicy) : + Fetcher by fetcher, + Persister by persister, + KeyParser by parser, + InternalStore { + val inFlightRequests: Cache> + var memCache: Cache> + private val inFlightScope = CoroutineScope(SupervisorJob()) + private val memoryScope = CoroutineScope(SupervisorJob()) + private val refreshSubject = PublishSubject.create() + private val subject: PublishSubject> + + constructor(fetcher: Fetcher, + persister: Persister, + parser: KeyParser, + stalePolicy: StalePolicy) : this(fetcher, persister, parser, null, stalePolicy) { + } + + init { + this.memCache = CacheFactory.createCache(memoryPolicy) + this.inFlightRequests = CacheFactory.createInflighter(memoryPolicy) + subject = PublishSubject.create>() + } + + /** + * @param key + * @return an observable from the first data source that is available + */ + override suspend fun get(key: Key): Parsed { + return memCache.get(key) { + memoryScope.async { + return@async disk(key)?:fresh(key) + } + }.await() + } + + +// @Experimental +// override fun getRefreshing(key: Key): Observable { +// return get(key) +// .toObservable() +// .compose(StoreUtil.repeatWhenSubjectEmits(refreshSubject, key)) +// } + + +// /** +// * @return data from memory +// */ +// private fun lazyCache(key: Key): Maybe { +// return Maybe +// .defer { cache(key) } +// .onErrorResumeNext(Maybe.empty()) +// } + +// fun cache(key: Key): Parsed? { +// try { +// return memCache.get(key) { memoryScope.async { disk(key) } }.await() +// } catch (e: ExecutionException) { +// return Maybe.empty() +// } +// +// } + +// /** +// * @return data from memory +// */ +// private fun lazyCacheWithResult(key: Key): Maybe> { +// return Maybe +// .defer { cacheWithResult(key) } +// .onErrorResumeNext(Maybe.empty()) +// } +// +// fun cacheWithResult(key: Key): Maybe> { +// try { +// val maybeResult = memCache.get(key) { disk(key) } +// return if (maybeResult == null) Maybe.empty() else maybeResult.map { Result.createFromCache(it) } +// } catch (e: ExecutionException) { +// return Maybe.empty() +// } +// +// } + + +// suspend override fun memory(key: Key): Parsed? { +// val cachedValue = memCache.getIfPresent(key) +// return cachedValue +// } + + /** + * Fetch data from persister and update memory after. If an error occurs, emit an empty observable + * so that the concat call in [.get] moves on to [.fresh] + * + * @param key + * @return + */ + override suspend fun disk(key: Key): Parsed? { + return if (StoreUtil.shouldReturnNetworkBeforeStale(persister, stalePolicy, key)) { + null + } else readDisk(key) + + } + +// fun readDisk(key: Key): Maybe { +// return read(key) +// .onErrorResumeNext(Maybe.empty()) +// .map { raw -> apply(key, raw) } +// .doOnSuccess { parsed -> +// updateMemory(key, parsed) +// if (stalePolicy == StalePolicy.REFRESH_ON_STALE && StoreUtil.persisterIsStale(key, persister)) { +// backfillCache(key) +// } +// }.cache() +// } + + + suspend fun readDisk(key: Key): Parsed? { + return try { + val diskValue: Parsed? = read(key) + ?.let { apply(key, it) } +// ?.also { updateMemory(key, it) } //TODO MIKE: check whether we need to update the cache on the way back + if (stalePolicy == StalePolicy.REFRESH_ON_STALE) { + backfillCache(key) + } + diskValue; + } catch (e: Exception) { + //store fetching acts as a fallthrough, + // if we error on disk fetching we should return no data rather than throwing the error + null + } + } + + suspend fun backfillCache(key: Key) { + fresh(key) + } + + + /** + * Will check to see if there exists an in flight observable and return it before + * going to network + * + * @return data from fresh and store it in memory and persister + */ + override suspend fun fresh(key: Key): Parsed { + return fetchAndPersist(key) + } + + override suspend fun freshWithResult(key: Key): Result { + return fresh(key).let { Result.createFromNetwork(it) } + } + + /** + * There should only be one fresh request in flight at any give time. + * + * + * Return cached request in the form of a Behavior Subject which will emit to its subscribers + * the last value it gets. Subject/Observable is cached in a [ConcurrentMap] to maintain + * thread safety. + * + * @param key resource identifier + * @return observable that emits a [Parsed] value + */ + suspend fun fetchAndPersist(key: Key): Parsed = + inFlightRequests + .get(key) { inFlightScope.async { response(key) } } + .await() + + + suspend fun response(key: Key): Parsed { + return try { + val fetchedValue = fetch(key) + + write(key, fetchedValue) + return readDisk(key)!! + } catch (e: Exception) { + handleNetworkError(key, e) + } + + + } + + suspend fun handleNetworkError(key: Key, throwable: Throwable): Parsed { + if (stalePolicy == StalePolicy.NETWORK_BEFORE_STALE) { + val diskValue = readDisk(key) + if (diskValue != null) + return diskValue else throw throwable + } + throw throwable + } + + fun notifySubscribers(data: Parsed, key: Key) { + subject.onNext(AbstractMap.SimpleEntry(key, data)) + } + + /** + * Get data stream for Subjects with the argument id + * + * @return + */ + override fun stream(key: Key): Observable { + TODO("not yet implemented") +// return subject +// .hide() +// .startWith(get(key).toObservable().map> { data -> AbstractMap.SimpleEntry(key, data) }) +// .filter { simpleEntry -> simpleEntry.key == key } +// .map({ it.value }) + } + + override fun stream(): Observable { + return subject.hide().map({ it.value }) + } + + +// /** +// * Only update memory after persister has been successfully updated +// * +// * @param key +// * @param data +// */ +// fun updateMemory(key: Key, data: Parsed) { +// memCache.put(key, Maybe.just(data)) +// } + + @Deprecated("") + override fun clearMemory() { + clear() + } + + /** + * Clear memory by id + * + * @param key of data to clear + */ + @Deprecated("") + override fun clearMemory(key: Key) { + clear(key) + } + + + override fun clear() { + for (cachedKey in memCache.asMap().keys) { + clear(cachedKey) + } + } + + override fun clear(key: Key) { + inFlightRequests.invalidate(key) + memCache.invalidate(key) + StoreUtil.clearPersister(persister, key) + notifyRefresh(key) + } + + private fun notifyRefresh(key: Key) { + refreshSubject.onNext(key) + } + +} + diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.java deleted file mode 100644 index 5fab322f6..000000000 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.nytimes.android.external.store3.base.impl; - -import com.nytimes.android.external.store.util.Result; -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.InternalStore; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.util.KeyParser; -import com.nytimes.android.external.store3.util.NoKeyParser; -import com.nytimes.android.external.store3.util.NoopParserFunc; -import com.nytimes.android.external.store3.util.NoopPersister; - -import javax.annotation.Nonnull; - -import io.reactivex.Maybe; -import io.reactivex.Observable; -import io.reactivex.Single; - -public class RealStore implements Store { - - private final InternalStore internalStore; - - public RealStore(InternalStore internalStore) { - this.internalStore = internalStore; - } - - public RealStore(Fetcher fetcher) { - final Parser noOpFunc = new NoopParserFunc<>(); - internalStore = new RealInternalStore<>(fetcher, NoopPersister.create(), - new NoKeyParser(noOpFunc), StalePolicy.UNSPECIFIED); - } - - public RealStore(Fetcher fetcher, - Persister persister) { - final Parser noOpFunc = new NoopParserFunc<>(); - internalStore = new RealInternalStore<>(fetcher, - persister, - new NoKeyParser(noOpFunc), - StalePolicy.UNSPECIFIED); - } - - public RealStore(Fetcher fetcher, - Persister persister, - final Parser parser) { - internalStore = new RealInternalStore<>(fetcher, - persister, - new NoKeyParser(parser), - StalePolicy.UNSPECIFIED); - } - - - public RealStore(Fetcher fetcher, - Persister persister, - Parser parser, - MemoryPolicy memoryPolicy, - StalePolicy policy) { - internalStore = new RealInternalStore<>(fetcher, persister, - new NoKeyParser(parser), memoryPolicy, policy); - } - - public RealStore(Fetcher fetcher, - Persister persister, - KeyParser parser, - MemoryPolicy memoryPolicy, - StalePolicy policy) { - internalStore = new RealInternalStore<>(fetcher, persister, - parser, memoryPolicy, policy); - } - - - @Nonnull - @Override - public Single get(@Nonnull final Key key) { - return internalStore.get(key); - } - - @Nonnull - @Override - public Single> getWithResult(@Nonnull Key key) { - return internalStore.getWithResult(key); - } - - @Override - public Observable getRefreshing(@Nonnull Key key) { - return internalStore.getRefreshing(key); - } - - - /** - * Will check to see if there exists an in flight observable and return it before - * going to network - * - * @return data from fetch and store it in memory and persister - */ - @Nonnull - @Override - public Single fetch(@Nonnull final Key key) { - return internalStore.fetch(key); - } - - @Nonnull - @Override - public Single> fetchWithResult(@Nonnull Key key) { - return internalStore.fetchWithResult(key); - } - - @Nonnull - @Override - public Observable stream() { - return internalStore.stream(); - } - - @Nonnull - @Override - public Observable stream(Key key) { - return internalStore.stream(key); - } - - @Override - public void clearMemory() { - internalStore.clearMemory(); - } - - /** - * Clear memory by id - * - * @param key of data to clear - */ - @Override - public void clearMemory(@Nonnull final Key key) { - internalStore.clearMemory(key); - } - - @Override - public void clear() { - internalStore.clear(); - } - - @Override - public void clear(@Nonnull Key key) { - internalStore.clear(key); - } - - protected Maybe memory(@Nonnull Key key) { - return internalStore.memory(key); - } - - @Nonnull - protected Maybe disk(@Nonnull Key key) { - return internalStore.disk(key); - } - -} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.kt new file mode 100644 index 000000000..dec386b1b --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.kt @@ -0,0 +1,133 @@ +package com.nytimes.android.external.store3.base.impl + +import com.nytimes.android.external.store.util.Result +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.InternalStore +import com.nytimes.android.external.store3.base.Parser +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.util.KeyParser +import com.nytimes.android.external.store3.util.NoKeyParser +import com.nytimes.android.external.store3.util.NoopParserFunc +import com.nytimes.android.external.store3.util.NoopPersister + +import io.reactivex.Maybe +import io.reactivex.Observable +import io.reactivex.Single + +open class RealStore : Store { + + private val internalStore: InternalStore + + constructor(internalStore: InternalStore) { + this.internalStore = internalStore + } + + constructor(fetcher: Fetcher) { + val noOpFunc = NoopParserFunc() + internalStore = RealInternalStore(fetcher, NoopPersister.create(), + NoKeyParser(noOpFunc), StalePolicy.UNSPECIFIED) + } + + constructor(fetcher: Fetcher, + persister: Persister) { + val noOpFunc = NoopParserFunc() + internalStore = RealInternalStore(fetcher, + persister, + NoKeyParser(noOpFunc), + StalePolicy.UNSPECIFIED) + } + + constructor(fetcher: Fetcher, + persister: Persister, + parser: Parser) { + internalStore = RealInternalStore(fetcher, + persister, + NoKeyParser(parser), + StalePolicy.UNSPECIFIED) + } + + + constructor(fetcher: Fetcher, + persister: Persister, + parser: Parser, + memoryPolicy: MemoryPolicy, + policy: StalePolicy) { + internalStore = RealInternalStore(fetcher, persister, + NoKeyParser(parser), memoryPolicy, policy) + } + + constructor(fetcher: Fetcher, + persister: Persister, + parser: KeyParser, + memoryPolicy: MemoryPolicy, + policy: StalePolicy) { + internalStore = RealInternalStore(fetcher, persister, + parser, memoryPolicy, policy) + } + + + suspend override fun get(key: Key): Parsed { + return internalStore.get(key) + } + + fun getWithResult(key: Key): Single> { +TODO("not implemented") + } + + fun getRefreshing(key: Key): Observable { + TODO("not implemented") + } + + + /** + * Will check to see if there exists an in flight observable and return it before + * going to network + * + * @return data from fresh and store it in memory and persister + */ + suspend override fun fresh(key: Key): Parsed { + return internalStore.fresh(key) + } + + suspend override fun freshWithResult(key: Key): Result { + TODO("not implemented") + } + + override fun stream(): Observable { + return internalStore.stream() + } + + override fun stream(key: Key): Observable { + return internalStore.stream(key) + } + + override fun clearMemory() { + internalStore.clearMemory() + } + + /** + * Clear memory by id + * + * @param key of data to clear + */ + override fun clearMemory(key: Key) { + internalStore.clearMemory(key) + } + + override fun clear() { + internalStore.clear() + } + + override fun clear(key: Key) { + internalStore.clear(key) + } + +// protected fun memory(key: Key): Maybe { +// return internalStore.memory(key) +// } + + protected suspend fun disk(key: Key): Parsed? { + return internalStore.disk(key) + } + +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.java deleted file mode 100644 index b6be9890a..000000000 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.nytimes.android.external.store3.base.impl; - - -import com.nytimes.android.external.store3.base.DiskRead; -import com.nytimes.android.external.store3.base.DiskWrite; -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.util.KeyParser; -import com.nytimes.android.external.store3.util.NoKeyParser; -import com.nytimes.android.external.store3.util.NoopParserFunc; -import com.nytimes.android.external.store3.util.NoopPersister; - -import java.util.ArrayList; -import java.util.List; - -import javax.annotation.Nonnull; - -import io.reactivex.Maybe; -import io.reactivex.Single; - - -/** - * Builder where there parser is used. - */ -public class RealStoreBuilder { - private final List parsers = new ArrayList<>(); - private Persister persister; - private Fetcher fetcher; - private MemoryPolicy memoryPolicy; - - @SuppressWarnings("PMD.UnusedPrivateField") //remove when it is implemented... - private StalePolicy stalePolicy = StalePolicy.UNSPECIFIED; - - @Nonnull - public static RealStoreBuilder builder() { - return new RealStoreBuilder<>(); - } - - @Nonnull - public RealStoreBuilder fetcher(final @Nonnull Fetcher fetcher) { - this.fetcher = fetcher; - return this; - } - - @Nonnull - public RealStoreBuilder persister(final @Nonnull Persister persister) { - this.persister = persister; - return this; - } - - @Nonnull - public RealStoreBuilder persister(final @Nonnull DiskRead diskRead, - final @Nonnull DiskWrite diskWrite) { - persister = new Persister() { - @Nonnull - @Override - public Maybe read(@Nonnull Key key) { - return diskRead.read(key); - } - - @Nonnull - @Override - public Single write(@Nonnull Key key, @Nonnull Raw raw) { - return diskWrite.write(key, raw); - } - }; - return this; - } - - @Nonnull - public RealStoreBuilder parser(final @Nonnull Parser parser) { - this.parsers.clear(); - this.parsers.add(new NoKeyParser<>(parser)); - return this; - } - - @Nonnull - public RealStoreBuilder parser(final @Nonnull KeyParser parser) { - this.parsers.clear(); - this.parsers.add(parser); - return this; - } - - @Nonnull - @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") - public RealStoreBuilder parsers(final @Nonnull List parsers) { - this.parsers.clear(); - for (Parser parser : parsers) { - this.parsers.add(new NoKeyParser<>(parser)); - } - return this; - } - - @Nonnull - public RealStoreBuilder memoryPolicy(MemoryPolicy memoryPolicy) { - this.memoryPolicy = memoryPolicy; - return this; - } - - //Store will backfill the disk cache anytime a record is stale - //User will still get the stale record returned to them - public RealStoreBuilder refreshOnStale() { - stalePolicy = StalePolicy.REFRESH_ON_STALE; - return this; - } - - //Store will try to get network source when disk data is stale - //if network source throws error or is empty, stale disk data will be returned - @Nonnull - public RealStoreBuilder networkBeforeStale() { - stalePolicy = StalePolicy.NETWORK_BEFORE_STALE; - return this; - } - - @Nonnull - public Store open() { - if (persister == null) { - persister = NoopPersister.create(memoryPolicy); - } - - if (parsers.isEmpty()) { - parser(new NoopParserFunc()); - } - - KeyParser multiParser = new MultiParser<>(parsers); - - RealInternalStore realInternalStore - = new RealInternalStore<>(fetcher, persister, multiParser, memoryPolicy, stalePolicy); - - return new RealStore<>(realInternalStore); - } -} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt new file mode 100644 index 000000000..6f56c0557 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt @@ -0,0 +1,106 @@ +package com.nytimes.android.external.store3.base.impl + + +import com.nytimes.android.external.store3.base.* +import com.nytimes.android.external.store3.util.KeyParser +import com.nytimes.android.external.store3.util.NoKeyParser +import com.nytimes.android.external.store3.util.NoopParserFunc +import com.nytimes.android.external.store3.util.NoopPersister + + +/** + * Builder where there parser is used. + */ +class RealStoreBuilder { + private var parser: KeyParser? = null + private var persister: Persister? = null + private var fetcher: Fetcher? = null + private var memoryPolicy: MemoryPolicy? = null + + private//remove when it is implemented... + var stalePolicy = StalePolicy.UNSPECIFIED + + fun fetcher(fetcher: Fetcher): RealStoreBuilder { + this.fetcher = fetcher + return this + } + + fun persister(persister: Persister): RealStoreBuilder { + this.persister = persister + return this + } + + fun persister(diskRead: DiskRead, + diskWrite: DiskWrite): RealStoreBuilder { + persister = object : Persister { + override suspend fun read(key: Key): Raw? = + diskRead.read(key) + + override suspend fun write(key: Key, raw: Raw): Boolean = + diskWrite.write(key, raw) + } + return this + } + + fun parser(parser: Parser): RealStoreBuilder { + this.parser = NoKeyParser(parser) + return this + } + + fun parser(parser: KeyParser): RealStoreBuilder { + this.parser = parser + + return this + } + + fun parsers(parsers: List>): RealStoreBuilder { + TODO("not implemented") +// this.parsers.clear() +// for (parser in parsers) { +// this.parsers.add(NoKeyParser(parser)) +// } +// return this + } + + fun memoryPolicy(memoryPolicy: MemoryPolicy): RealStoreBuilder { + this.memoryPolicy = memoryPolicy + return this + } + + //Store will backfill the disk cache anytime a record is stale + //User will still get the stale record returned to them + fun refreshOnStale(): RealStoreBuilder { + stalePolicy = StalePolicy.REFRESH_ON_STALE + return this + } + + //Store will try to get network source when disk data is stale + //if network source throws error or is empty, stale disk data will be returned + fun networkBeforeStale(): RealStoreBuilder { + stalePolicy = StalePolicy.NETWORK_BEFORE_STALE + return this + } + + fun open(): Store { + if (persister == null) { + persister = NoopPersister.create(memoryPolicy) + } + + if (parser==null) { + parser(NoopParserFunc()) + } + +// val multiParser = MultiParser(parsers) + + val realInternalStore: InternalStore = RealInternalStore(fetcher!!, persister!!, parser!!, memoryPolicy, stalePolicy) + + return RealStore(realInternalStore) + } + + companion object { + + fun builder(): RealStoreBuilder { + return RealStoreBuilder() + } + } +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.kt similarity index 58% rename from store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.java rename to store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.kt index 0570b6e03..023fb5d48 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.java +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.kt @@ -1,98 +1,92 @@ -package com.nytimes.android.external.store3.base.impl; +package com.nytimes.android.external.store3.base.impl -import com.nytimes.android.external.store.util.Result; -import com.nytimes.android.external.store3.annotations.Experimental; -import javax.annotation.Nonnull; -import io.reactivex.Observable; -import io.reactivex.Single; +import com.nytimes.android.external.store.util.Result +import com.nytimes.android.external.store3.annotations.Experimental +import io.reactivex.Observable +import io.reactivex.Single /** - * a {@link StoreBuilder StoreBuilder} + * a [StoreBuilder] * will return an instance of a store - *

- * A {@link Store Store} can - * {@link Store#get(V) Store.get() } cached data or - * force a call to {@link Store#fetch(V) Store.fetch() } + * + * + * A [Store] can + * [Store.get() ][Store.get] cached data or + * force a call to [Store.fresh() ][Store.fresh] * (skipping cache) */ -public interface Store { +interface Store { /** * Return an Observable of T for request Barcode * Data will be returned from oldest non expired source * Sources are Memory Cache, Disk Cache, Inflight, Network Response */ - @Nonnull - Single get(@Nonnull V key); + suspend fun get(key: V): T /** - * Return an Observable of {@link Result} for request Barcode + * Return an Observable of [Result] for request Barcode * Data will be returned from oldest non expired source * Sources are Memory Cache, Disk Cache, Inflight, Network Response - */ - @Nonnull - Single> getWithResult(@Nonnull V key); + */ +// suspend fun getWithResult(key: V): Result /** * Calls store.get(), additionally will repeat anytime store.clear(barcode) is called * WARNING: getRefreshing(barcode) is an endless observable, be careful when combining * with operators that expect an OnComplete event */ - @Experimental - Observable getRefreshing(@Nonnull final V key); +// @Experimental +// fun getRefreshing(key: V): Observable /** * Return an Observable of T for requested Barcode skipping Memory & Disk Cache */ - @Nonnull - Single fetch(@Nonnull V key); + suspend fun fresh(key: V): T /** - * Return an Observable of {@link Result} for requested Barcode skipping Memory & Disk Cache - */ - @Nonnull - Single> fetchWithResult(@Nonnull V key); + * Return an Observable of [Result] for requested Barcode skipping Memory & Disk Cache + */ + suspend fun freshWithResult(key: V): Result /** * @return an Observable that emits "fresh" new response from the store that hit the fetcher * WARNING: stream is an endless observable, be careful when combining * with operators that expect an OnComplete event */ - @Nonnull - Observable stream(); + fun stream(): Observable /** - * Similar to {@link Store#get(V) Store.get() } + * Similar to [Store.get() ][Store.get] * Rather than returning a single response, * Stream will stay subscribed for future emissions to the Store * Errors will be dropped * */ - @Nonnull - Observable stream(V key); + fun stream(key: V): Observable /** * Clear the memory cache of all entries */ - @Deprecated - void clearMemory(); + @Deprecated("") + fun clearMemory() /** * Purge a particular entry from memory cache. */ - @Deprecated - void clearMemory(@Nonnull V key); + @Deprecated("") + fun clearMemory(key: V) /** * purges all entries from memory and disk cache * Persister will only be cleared if they implements Clearable */ - void clear(); + fun clear() /** * Purge a particular entry from memory and disk cache. * Persister will only be cleared if they implements Clearable */ - void clear(@Nonnull V key); + fun clear(key: V) } diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/room/RealStoreRoom.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/room/RealStoreRoom.java index 8c93cd2b2..7f499d055 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/room/RealStoreRoom.java +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/room/RealStoreRoom.java @@ -2,11 +2,11 @@ import com.nytimes.android.external.cache3.Cache; import com.nytimes.android.external.store3.annotations.Experimental; -import com.nytimes.android.external.store3.base.Fetcher; import com.nytimes.android.external.store3.base.impl.CacheFactory; import com.nytimes.android.external.store3.base.impl.MemoryPolicy; import com.nytimes.android.external.store3.base.impl.StalePolicy; import com.nytimes.android.external.store3.base.impl.StoreUtil; +import com.nytimes.android.external.store3.base.room.RoomFetcher; import com.nytimes.android.external.store3.base.room.RoomPersister; import java.util.Collection; @@ -27,25 +27,25 @@ */ @Experimental class RealStoreRoom extends StoreRoom { - private final Fetcher fetcher; + private final RoomFetcher fetcher; private final RoomPersister persister; private final Cache> memCache; private final StalePolicy stalePolicy; private final Cache> inFlightRequests; - RealStoreRoom(Fetcher fetcher, - RoomPersister persister) { + RealStoreRoom(RoomFetcher fetcher, + RoomPersister persister) { this(fetcher, persister, null, StalePolicy.UNSPECIFIED); } - RealStoreRoom(Fetcher fetcher, - RoomPersister persister, - StalePolicy stalePolicy) { + RealStoreRoom(RoomFetcher fetcher, + RoomPersister persister, + StalePolicy stalePolicy) { this(fetcher, persister, null, stalePolicy); } - RealStoreRoom(Fetcher fetcher, + RealStoreRoom(RoomFetcher fetcher, RoomPersister persister, MemoryPolicy memoryPolicy, StalePolicy stalePolicy) { @@ -129,7 +129,7 @@ void backfillCache(@Nonnull Key key) { * Will check to see if there exists an in flight observable and return it before * going to network * - * @return data from fetch and store it in memory and persister + * @return data from fresh and store it in memory and persister */ @Nonnull @Override @@ -139,7 +139,7 @@ public Observable fetch(@Nonnull final Key key) { /** - * There should only be one fetch request in flight at any give time. + * There should only be one fresh request in flight at any give time. *

* Return cached request in the form of a Behavior Subject which will emit to its subscribers * the last value it gets. Subject/Observable is cached in a {@link ConcurrentMap} to maintain @@ -149,7 +149,7 @@ public Observable fetch(@Nonnull final Key key) { * @return observable that emits a {@link Parsed} value */ @Nullable - Observable fetchAndPersist(@Nonnull final Key key) { + private Observable fetchAndPersist(@Nonnull final Key key) { try { return inFlightRequests.get(key, () -> response(key)); } catch (ExecutionException e) { @@ -158,11 +158,13 @@ Observable fetchAndPersist(@Nonnull final Key key) { } @Nonnull - Observable response(@Nonnull final Key key) { + private Observable response(@Nonnull final Key key) { return fetcher() .fetch(key) - .doOnSuccess(it -> persister().write(key, it)) - .flatMapObservable(it -> readDisk(key)) + .concatMapEagerDelayError(it -> { + persister().write(key, it); + return readDisk(key); + },true) .onErrorResumeNext(throwable -> { if (stalePolicy == StalePolicy.NETWORK_BEFORE_STALE) { return readDisk(key).switchIfEmpty(Observable.error(throwable)); @@ -212,7 +214,7 @@ RoomPersister persister() { /** * */ - Fetcher fetcher() { + RoomFetcher fetcher() { return fetcher; } diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/room/StoreRoom.java b/store/src/main/java/com/nytimes/android/external/store3/base/impl/room/StoreRoom.java index 4c28017a3..f90db8fe1 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/room/StoreRoom.java +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/room/StoreRoom.java @@ -5,6 +5,7 @@ import com.nytimes.android.external.store3.base.impl.MemoryPolicy; import com.nytimes.android.external.store3.base.impl.StalePolicy; import com.nytimes.android.external.store3.base.impl.StoreBuilder; +import com.nytimes.android.external.store3.base.room.RoomFetcher; import com.nytimes.android.external.store3.base.room.RoomPersister; import javax.annotation.Nonnull; @@ -17,7 +18,7 @@ *

* A {@link StoreRoom Store} can * {@link StoreRoom#get(V) Store.get() } cached data or - * force a call to {@link StoreRoom#fetch(V) Store.fetch() } + * force a call to {@link StoreRoom#fetch(V) Store.fresh() } * (skipping cache) */ @Experimental @@ -51,19 +52,19 @@ public abstract class StoreRoom { public static StoreRoom from - (Fetcher fetcher, RoomPersister persister) { + (RoomFetcher fetcher, RoomPersister persister) { return new RealStoreRoom<>(fetcher, persister); } public static StoreRoom from( - Fetcher fetcher, + RoomFetcher fetcher, RoomPersister persister, StalePolicy policy) { return new RealStoreRoom<>(fetcher, persister, policy); } public static StoreRoom from - (Fetcher fetcher, RoomPersister persister, + (RoomFetcher fetcher, RoomPersister persister, StalePolicy stalePolicy, MemoryPolicy memoryPolicy) { return new RealStoreRoom<>(fetcher, persister, memoryPolicy, stalePolicy); } diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/Fetcher.java b/store/src/main/java/com/nytimes/android/external/store3/base/room/RoomFetcher.java similarity index 64% rename from store/src/main/java/com/nytimes/android/external/store3/base/Fetcher.java rename to store/src/main/java/com/nytimes/android/external/store3/base/room/RoomFetcher.java index 058966bf0..a8a013e29 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/Fetcher.java +++ b/store/src/main/java/com/nytimes/android/external/store3/base/room/RoomFetcher.java @@ -1,7 +1,8 @@ -package com.nytimes.android.external.store3.base; +package com.nytimes.android.external.store3.base.room; import javax.annotation.Nonnull; +import io.reactivex.Observable; import io.reactivex.Single; @@ -10,12 +11,12 @@ * * @param data type before parsing */ -public interface Fetcher { +public interface RoomFetcher { /** * @param key Container with Key and Type used as a request param * @return Observable that emits {@link Raw} data */ @Nonnull - Single fetch(@Nonnull Key key); + Observable fetch(@Nonnull Key key); } diff --git a/store/src/main/java/com/nytimes/android/external/store3/util/KeyParser.java b/store/src/main/java/com/nytimes/android/external/store3/util/KeyParser.java deleted file mode 100644 index fe1e26f39..000000000 --- a/store/src/main/java/com/nytimes/android/external/store3/util/KeyParser.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.nytimes.android.external.store3.util; - -import io.reactivex.annotations.NonNull; -import io.reactivex.functions.BiFunction; - -public interface KeyParser extends BiFunction { - - @Override - Parsed apply(@NonNull Key key, @NonNull Raw raw) throws ParserException; - -} diff --git a/store/src/main/java/com/nytimes/android/external/store3/util/KeyParser.kt b/store/src/main/java/com/nytimes/android/external/store3/util/KeyParser.kt new file mode 100644 index 000000000..477233ebe --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/util/KeyParser.kt @@ -0,0 +1,11 @@ +package com.nytimes.android.external.store3.util + +import io.reactivex.annotations.NonNull +import io.reactivex.functions.BiFunction + +interface KeyParser { + + @Throws(ParserException::class) + suspend fun apply(@NonNull key: Key, @NonNull raw: Raw): Parsed + +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/util/NoKeyParser.java b/store/src/main/java/com/nytimes/android/external/store3/util/NoKeyParser.java deleted file mode 100644 index db41ef9b5..000000000 --- a/store/src/main/java/com/nytimes/android/external/store3/util/NoKeyParser.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.nytimes.android.external.store3.util; - -import com.nytimes.android.external.store3.base.Parser; - -import javax.annotation.Nonnull; - -import io.reactivex.annotations.NonNull; - -public class NoKeyParser implements KeyParser { - private final Parser parser; - - public NoKeyParser(@Nonnull Parser parser) { - this.parser = parser; - } - - @Override - public Parsed apply(@NonNull Key key, @NonNull Raw raw) throws ParserException { - return parser.apply(raw); - } -} diff --git a/store/src/main/java/com/nytimes/android/external/store3/util/NoKeyParser.kt b/store/src/main/java/com/nytimes/android/external/store3/util/NoKeyParser.kt new file mode 100644 index 000000000..496237aa7 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/util/NoKeyParser.kt @@ -0,0 +1,13 @@ +package com.nytimes.android.external.store3.util + +import com.nytimes.android.external.store3.base.Parser + +import io.reactivex.annotations.NonNull + +class NoKeyParser(private val parser: Parser) : KeyParser { + + @Throws(ParserException::class) + override suspend fun apply(@NonNull key: Key, @NonNull raw: Raw): Parsed { + return parser.apply(raw) + } +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/util/NoopParserFunc.java b/store/src/main/java/com/nytimes/android/external/store3/util/NoopParserFunc.java deleted file mode 100644 index 9a87270f4..000000000 --- a/store/src/main/java/com/nytimes/android/external/store3/util/NoopParserFunc.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.nytimes.android.external.store3.util; - -import com.nytimes.android.external.store3.base.Parser; - -import io.reactivex.annotations.NonNull; - -/** - * Pass-through parser for stores that parse externally - */ -public class NoopParserFunc implements Parser { - - @Override - public Parsed apply(@NonNull Raw raw) throws ParserException { - //noinspection unchecked - return (Parsed) raw; - } -} diff --git a/store/src/main/java/com/nytimes/android/external/store3/util/NoopParserFunc.kt b/store/src/main/java/com/nytimes/android/external/store3/util/NoopParserFunc.kt new file mode 100644 index 000000000..876be4985 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/util/NoopParserFunc.kt @@ -0,0 +1,18 @@ +package com.nytimes.android.external.store3.util + +import com.nytimes.android.external.store3.base.Parser + +import io.reactivex.annotations.NonNull + +/** + * Pass-through parser for stores that parse externally + */ +class NoopParserFunc : Parser { + + @Throws(ParserException::class) + override + suspend fun apply(raw: Raw): Parsed { + + return raw as Parsed + } +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.java b/store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.java deleted file mode 100644 index b889430e3..000000000 --- a/store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.nytimes.android.external.store3.util; - -import com.nytimes.android.external.cache3.Cache; -import com.nytimes.android.external.cache3.CacheBuilder; -import com.nytimes.android.external.store3.base.Clearable; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.MemoryPolicy; - -import java.util.concurrent.TimeUnit; -import javax.annotation.Nonnull; - -import io.reactivex.Maybe; -import io.reactivex.Single; - -/** - * Pass-through diskdao for stores that don't want to use persister - */ -public class NoopPersister implements Persister, Clearable { - protected final Cache> networkResponses; - - NoopPersister(MemoryPolicy memoryPolicy) { - if (memoryPolicy.hasAccessPolicy()) { - networkResponses = CacheBuilder.newBuilder() - .expireAfterAccess(memoryPolicy.getExpireAfterAccess(), memoryPolicy.getExpireAfterTimeUnit()) - .build(); - - } else if (memoryPolicy.hasWritePolicy()) { - networkResponses = CacheBuilder.newBuilder() - .expireAfterWrite(memoryPolicy.getExpireAfterWrite(), memoryPolicy.getExpireAfterTimeUnit()) - .build(); - } else { - throw new IllegalArgumentException("No expiry policy set on memory-policy."); - } - } - - public static NoopPersister create() { - return NoopPersister.create(null); - } - - public static NoopPersister create(MemoryPolicy memoryPolicy) { - if (memoryPolicy == null) { - MemoryPolicy defaultPolicy = MemoryPolicy - .builder() - .setExpireAfterWrite(24) - .setExpireAfterTimeUnit(TimeUnit.HOURS) - .build(); - return new NoopPersister<>(defaultPolicy); - } - return new NoopPersister<>(memoryPolicy); - } - - @Nonnull - @Override - public Maybe read(@Nonnull Key key) { - Maybe cachedValue = networkResponses.getIfPresent(key); - return cachedValue == null ? Maybe.empty() : cachedValue; - } - - @Nonnull - @Override - public Single write(@Nonnull Key key, @Nonnull Raw raw) { - networkResponses.put(key, Maybe.just(raw)); - return Single.just(true); - } - - @Override - public void clear(@Nonnull Key key) { - networkResponses.invalidate(key); - } -} diff --git a/store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.kt b/store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.kt new file mode 100644 index 000000000..10ee76100 --- /dev/null +++ b/store/src/main/java/com/nytimes/android/external/store3/util/NoopPersister.kt @@ -0,0 +1,66 @@ +package com.nytimes.android.external.store3.util + +import com.nytimes.android.external.cache3.Cache +import com.nytimes.android.external.cache3.CacheBuilder +import com.nytimes.android.external.store3.base.Clearable +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.MemoryPolicy +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.async +import java.util.concurrent.TimeUnit + +/** + * Pass-through diskdao for stores that don't want to use persister + */ +class NoopPersister internal constructor(memoryPolicy: MemoryPolicy) : Persister, Clearable { + val networkResponses: Cache> + private val memoryScope = CoroutineScope(SupervisorJob()) + + init { + if (memoryPolicy.hasAccessPolicy()) { + networkResponses = CacheBuilder.newBuilder() + .expireAfterAccess(memoryPolicy.expireAfterAccess, memoryPolicy.expireAfterTimeUnit) + .build() + + } else if (memoryPolicy.hasWritePolicy()) { + networkResponses = CacheBuilder.newBuilder() + .expireAfterWrite(memoryPolicy.expireAfterWrite, memoryPolicy.expireAfterTimeUnit) + .build() + } else { + throw IllegalArgumentException("No expiry policy set on memory-policy.") + } + } + + override suspend fun read(key: Key): Raw? = networkResponses.getIfPresent(key)?.await() + + + override suspend fun write(key: Key, raw: Raw): Boolean { + networkResponses.put(key, memoryScope.async { raw }) + return true + } + + override fun clear(key: Key) { + networkResponses.invalidate(key) + } + + companion object { + + fun create(): NoopPersister { + return NoopPersister.create(null) + } + + fun create(memoryPolicy: MemoryPolicy?): NoopPersister { + if (memoryPolicy == null) { + val defaultPolicy = MemoryPolicy + .builder() + .setExpireAfterWrite(24) + .setExpireAfterTimeUnit(TimeUnit.HOURS) + .build() + return NoopPersister(defaultPolicy) + } + return NoopPersister(memoryPolicy) + } + } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest.java b/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest.java deleted file mode 100644 index 9134d23d2..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; - -import org.junit.Before; -import org.junit.Test; - -import io.reactivex.Single; - - -public class DontCacheErrorsTest { - - boolean shouldThrow; - private Store store; - - @Before - public void setUp() { - store = StoreBuilder.barcode() - .fetcher(barCode -> Single.fromCallable(() -> { - if (shouldThrow) { - throw new RuntimeException(); - } else { - return 0; - } - })) - .open(); - } - - @Test - public void testStoreDoesntCacheErrors() throws InterruptedException { - BarCode barcode = new BarCode("bar", "code"); - - shouldThrow = true; - store.get(barcode).test() - .assertTerminated() - .assertError(Exception.class) - .awaitTerminalEvent(); - - shouldThrow = false; - store.get(barcode).test() - .assertNoErrors() - .awaitTerminalEvent(); - } - - @Test - public void testStoreDoesntCacheErrorsWithResult() throws InterruptedException { - BarCode barcode = new BarCode("bar", "code"); - - shouldThrow = true; - store.getWithResult(barcode).test() - .assertTerminated() - .assertError(Exception.class) - .awaitTerminalEvent(); - - shouldThrow = false; - store.get(barcode).test() - .assertNoErrors() - .awaitTerminalEvent(); - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/ParsingFetcherTest.java b/store/src/test/java/com/nytimes/android/external/store3/ParsingFetcherTest.java index 909e753c5..d7f6c9910 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/ParsingFetcherTest.java +++ b/store/src/test/java/com/nytimes/android/external/store3/ParsingFetcherTest.java @@ -1,71 +1,71 @@ -package com.nytimes.android.external.store3; - -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.ParsingFetcher; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import io.reactivex.Maybe; -import io.reactivex.Single; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class ParsingFetcherTest { - - static final String RAW_DATA = "Test data."; - static final String PARSED = "DATA PARSED"; - - @Mock - Fetcher fetcher; - @Mock - Parser parser; - @Mock - Persister persister; - private final BarCode barCode = new BarCode("key", "value"); - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testPersistFetcher() { - - Store simpleStore = StoreBuilder.barcode() - .fetcher(ParsingFetcher.from(fetcher, parser)) - .persister(persister) - .open(); - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(RAW_DATA)); - - when(parser.apply(RAW_DATA)) - .thenReturn(PARSED); - - when(persister.read(barCode)) - .thenReturn(Maybe.just(PARSED)); - - when(persister.write(barCode, PARSED)) - .thenReturn(Single.just(true)); - - String value = simpleStore.fetch(barCode).blockingGet(); - - assertThat(value).isEqualTo(PARSED); - - verify(fetcher, times(1)).fetch(barCode); - verify(parser, times(1)).apply(RAW_DATA); - - verify(persister, times(1)).write(barCode, PARSED); - } -} +//package com.nytimes.android.external.store3; +// +//import com.nytimes.android.external.store3.base.Fetcher; +//import com.nytimes.android.external.store3.base.Parser; +//import com.nytimes.android.external.store3.base.Persister; +//import com.nytimes.android.external.store3.base.impl.BarCode; +//import com.nytimes.android.external.store3.base.impl.ParsingFetcher; +//import com.nytimes.android.external.store3.base.impl.Store; +//import com.nytimes.android.external.store3.base.impl.StoreBuilder; +// +//import org.junit.Before; +//import org.junit.Test; +//import org.mockito.Mock; +//import org.mockito.MockitoAnnotations; +// +//import io.reactivex.Maybe; +//import io.reactivex.Single; +// +//import static org.assertj.core.api.Assertions.assertThat; +//import static org.mockito.Mockito.times; +//import static org.mockito.Mockito.verify; +//import static org.mockito.Mockito.when; +// +//public class ParsingFetcherTest { +// +// static final String RAW_DATA = "Test data."; +// static final String PARSED = "DATA PARSED"; +// +// @Mock +// Fetcher fetcher; +// @Mock +// Parser parser; +// @Mock +// Persister persister; +// private final BarCode barCode = new BarCode("key", "value"); +// +// @Before +// public void setUp() { +// MockitoAnnotations.initMocks(this); +// } +// +// @Test +// public void testPersistFetcher() { +// +// Store simpleStore = StoreBuilder.barcode() +// .fetcher(ParsingFetcher.Companion.from(fetcher, parser)) +// .persister(persister) +// .open(); +// +// when(fetcher.fetch(barCode)) +// .thenReturn(Single.just(RAW_DATA)); +// +// when(parser.apply(RAW_DATA)) +// .thenReturn(PARSED); +// +// when(persister.read(barCode)) +// .thenReturn(Maybe.just(PARSED)); +// +// when(persister.write(barCode, PARSED)) +// .thenReturn(Single.just(true)); +// +// String value = simpleStore.fresh(barCode).blockingGet(); +// +// assertThat(value).isEqualTo(PARSED); +// +// verify(fetcher, times(1)).fetch(barCode); +// verify(parser, times(1)).apply(RAW_DATA); +// +// verify(persister, times(1)).write(barCode, PARSED); +// } +//} diff --git a/store/src/test/java/com/nytimes/android/external/store3/StoreTest.java b/store/src/test/java/com/nytimes/android/external/store3/StoreTest.java index 4026088c9..5e085503c 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/StoreTest.java +++ b/store/src/test/java/com/nytimes/android/external/store3/StoreTest.java @@ -28,7 +28,7 @@ public class StoreTest { private static final String DISK = "disk"; - private static final String NETWORK = "fetch"; + private static final String NETWORK = "fresh"; private static final String MEMORY = "memory"; final AtomicInteger counter = new AtomicInteger(0); @Mock @@ -224,7 +224,7 @@ public void testSubclassWithResult() { @Test public void testNoopAndDefault() { - Persister persister = spy(NoopPersister.create()); + Persister persister = spy(NoopPersister.Companion.create()); RealStore simpleStore = new SampleStore(fetcher, persister); @@ -249,7 +249,7 @@ public void testNoopAndDefault() { @Test public void testNoopAndDefaultWithResult() { - Persister persister = spy(NoopPersister.create()); + Persister persister = spy(NoopPersister.Companion.create()); RealStore simpleStore = new SampleStore(fetcher, persister); diff --git a/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.java b/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.java index 21a81a1a3..4abb495af 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.java +++ b/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.java @@ -21,7 +21,7 @@ public class StoreWithParserTest { private static final String DISK = "persister"; - private static final String NETWORK = "fetch"; + private static final String NETWORK = "fresh"; @Mock Fetcher fetcher; @Mock diff --git a/store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.java b/store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.java index 51bee2d90..e0299e63e 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.java +++ b/store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.java @@ -63,12 +63,12 @@ public void setUp() { public void testStream() { TestObserver streamObservable = store.stream(barCode).test(); //first time we subscribe to stream it will fail getting from memory & disk and instead - //fetch from network, write to disk and notifiy subscribers + //fresh from network, write to disk and notifiy subscribers streamObservable.assertValueCount(1); store.clear(); - //fetch should notify subscribers again - store.fetch(barCode).test().awaitCount(1); + //fresh should notify subscribers again + store.fresh(barCode).test().awaitCount(1); streamObservable.assertValues(TEST_ITEM, TEST_ITEM2); //get for another barcode should not trigger a stream for barcode1 diff --git a/store/src/test/java/com/nytimes/android/external/store3/base/impl/MultiParserTest.java b/store/src/test/java/com/nytimes/android/external/store3/base/impl/MultiParserTest.java index e73174d4f..906b7767b 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/base/impl/MultiParserTest.java +++ b/store/src/test/java/com/nytimes/android/external/store3/base/impl/MultiParserTest.java @@ -1,57 +1,57 @@ -package com.nytimes.android.external.store3.base.impl; - -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.util.KeyParser; -import com.nytimes.android.external.store3.util.NoKeyParser; -import com.nytimes.android.external.store3.util.ParserException; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -import static org.junit.Assert.assertNotNull; - -public class MultiParserTest { - - private static final Parser PARSER_1 = String::valueOf; - - private static final Parser PARSER_2 = value -> new BarCode(value, "KEY"); - - private static final Parser PARSER_3 = barCode -> UUID.randomUUID(); - - @Rule - public ExpectedException expectedException = ExpectedException.none(); - - @Test - public void shouldParseChainProperly() { - List parsersChain = new ArrayList<>(); - parsersChain.add(new NoKeyParser<>(PARSER_1)); - parsersChain.add(new NoKeyParser<>(PARSER_2)); - parsersChain.add(new NoKeyParser<>(PARSER_3)); - - KeyParser parser = new MultiParser<>(parsersChain); - UUID parsed = parser.apply(new Object(), 100); - - assertNotNull(parsed); - } - - @Test - public void shouldFailIfOneOfParsersIsInvalid() { - expectedException.expect(ParserException.class); - - List parsersChain = new ArrayList<>(); - parsersChain.add(new NoKeyParser<>(PARSER_1)); - parsersChain.add(new NoKeyParser<>(PARSER_3)); - parsersChain.add(new NoKeyParser<>(PARSER_2)); - - KeyParser parser = new MultiParser<>(parsersChain); - UUID parsed = parser.apply(new Object(), 100); - - assertNotNull(parsed); - } - -} +//package com.nytimes.android.external.store3.base.impl; +// +//import com.nytimes.android.external.store3.base.Parser; +//import com.nytimes.android.external.store3.util.KeyParser; +//import com.nytimes.android.external.store3.util.NoKeyParser; +//import com.nytimes.android.external.store3.util.ParserException; +// +//import org.junit.Rule; +//import org.junit.Test; +//import org.junit.rules.ExpectedException; +// +//import java.util.ArrayList; +//import java.util.List; +//import java.util.UUID; +// +//import static org.junit.Assert.assertNotNull; +// +//public class MultiParserTest { +// +// private static final Parser PARSER_1 = String::valueOf; +// +// private static final Parser PARSER_2 = value -> new BarCode(value, "KEY"); +// +// private static final Parser PARSER_3 = barCode -> UUID.randomUUID(); +// +// @Rule +// public ExpectedException expectedException = ExpectedException.none(); +// +// @Test +// public void shouldParseChainProperly() { +// List parsersChain = new ArrayList<>(); +// parsersChain.add(new NoKeyParser<>(PARSER_1)); +// parsersChain.add(new NoKeyParser<>(PARSER_2)); +// parsersChain.add(new NoKeyParser<>(PARSER_3)); +// +// KeyParser parser = new MultiParser<>(parsersChain); +// UUID parsed = parser.apply(new Object(), 100); +// +// assertNotNull(parsed); +// } +// +// @Test +// public void shouldFailIfOneOfParsersIsInvalid() { +// expectedException.expect(ParserException.class); +// +// List parsersChain = new ArrayList<>(); +// parsersChain.add(new NoKeyParser<>(PARSER_1)); +// parsersChain.add(new NoKeyParser<>(PARSER_3)); +// parsersChain.add(new NoKeyParser<>(PARSER_2)); +// +// KeyParser parser = new MultiParser<>(parsersChain); +// UUID parsed = parser.apply(new Object(), 100); +// +// assertNotNull(parsed); +// } +// +//} diff --git a/store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.java b/store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.java index 3f2e6e701..cf53e76e3 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.java +++ b/store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.java @@ -24,7 +24,7 @@ public class StoreRoomTest { private static final String DISK = "disk"; - private static final String NETWORK = "fetch"; + private static final String NETWORK = "fresh"; final AtomicInteger counter = new AtomicInteger(0); @Mock Fetcher fetcher; diff --git a/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.java b/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.java index 659cf22f6..a7fab548a 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.java +++ b/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.java @@ -17,7 +17,7 @@ public class NoopPersisterTest { @Test public void writeReadTest() { BarCode barCode = new BarCode("key", "value"); - NoopPersister persister = NoopPersister.create(); + NoopPersister persister = NoopPersister.Companion.create(); boolean success = persister.write(barCode, "foo").blockingGet(); assertThat(success).isTrue(); String rawValue = persister.read(barCode).blockingGet(); @@ -41,19 +41,19 @@ public void testReadingOfMemoryPolicies() { .setExpireAfterWrite(1) .setExpireAfterTimeUnit(TimeUnit.HOURS) .build(); - NoopPersister.create(expireAfterWritePolicy); + NoopPersister.Companion.create(expireAfterWritePolicy); MemoryPolicy expireAfterAccessPolicy = MemoryPolicy.builder() .setExpireAfterAccess(1) .setExpireAfterTimeUnit(TimeUnit.HOURS) .build(); - NoopPersister.create(expireAfterAccessPolicy); + NoopPersister.Companion.create(expireAfterAccessPolicy); exception.expect(IllegalArgumentException.class); exception.expectMessage("No expiry policy set"); MemoryPolicy incompletePolicy = MemoryPolicy.builder() .setExpireAfterTimeUnit(TimeUnit.HOURS) .build(); - NoopPersister.create(incompletePolicy); + NoopPersister.Companion.create(incompletePolicy); } } From 9432e1b4e8991088b6ec6109c35edc94e4defa41 Mon Sep 17 00:00:00 2001 From: DigitalBuddha Date: Sun, 10 Feb 2019 20:23:15 -0500 Subject: [PATCH 051/498] fixing tests --- app/build.gradle | 4 + .../android/sample/PersistingStoreActivity.kt | 42 +++++---- .../com/nytimes/android/sample/SampleApp.kt | 31 ++++--- .../activity/PersistingStoreActivity.java | 0 .../sample/activity/StoreActivity.java | 0 .../nytimes/android/sample/data/remote/Api.kt | 7 +- .../nytimes/android/sample/KeyParserTest.kt | 86 +++++++++---------- .../nytimes/android/sample/NoNetworkTest.kt | 43 ++++++++++ .../nytimes/android/external/fs3/FSReader.kt | 15 ++-- .../android/external/fs3/SourceFileReader.kt | 2 +- .../middleware/moshi/MoshiSourceParser.kt | 5 +- .../middleware/moshi/MoshiStringParser.kt | 4 +- store/build.gradle | 18 ++-- 13 files changed, 158 insertions(+), 99 deletions(-) delete mode 100644 app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java delete mode 100644 app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java create mode 100644 app/src/test/java/com/nytimes/android/sample/NoNetworkTest.kt diff --git a/app/build.gradle b/app/build.gradle index 864f2f5c6..5e9816dc1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -71,6 +71,10 @@ dependencies { // optional - RxJava support for Room implementation "android.arch.persistence.room:rxjava2:$room_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.1" + implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.0" + + } repositories { diff --git a/app/src/main/java/com/nytimes/android/sample/PersistingStoreActivity.kt b/app/src/main/java/com/nytimes/android/sample/PersistingStoreActivity.kt index ae333eb69..87200d87a 100644 --- a/app/src/main/java/com/nytimes/android/sample/PersistingStoreActivity.kt +++ b/app/src/main/java/com/nytimes/android/sample/PersistingStoreActivity.kt @@ -4,24 +4,29 @@ import android.os.Bundle import android.support.v7.app.AppCompatActivity import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.Toolbar -import android.util.Log import android.view.View import android.widget.Toast import android.widget.Toast.makeText import com.nytimes.android.external.store3.base.impl.BarCode import com.nytimes.android.external.store3.base.impl.Store -import com.nytimes.android.sample.R.id.postRecyclerView +import com.nytimes.android.sample.data.model.Children import com.nytimes.android.sample.data.model.Post import com.nytimes.android.sample.data.model.RedditData import com.nytimes.android.sample.reddit.PostAdapter import com.squareup.moshi.Moshi -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers import kotlinx.android.synthetic.main.activity_store.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext -class PersistingStoreActivity : AppCompatActivity() { +class PersistingStoreActivity : AppCompatActivity(), CoroutineScope { + lateinit var job: Job + + override val coroutineContext: CoroutineContext + get() = job + Dispatchers.Main lateinit var postAdapter: PostAdapter lateinit var persistedStore: Store @@ -29,6 +34,7 @@ class PersistingStoreActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + job = Job() setContentView(R.layout.activity_store) setSupportActionBar(findViewById(R.id.toolbar) as Toolbar) @@ -49,13 +55,12 @@ class PersistingStoreActivity : AppCompatActivity() { First call to get(awwRequest) will use the network, then save response in the in-memory cache. Subsequent calls will retrieve the cached version of the data. */ - this.persistedStore - .get(awwRequest) - .flatMapObservable { sanitizeData(it) } - .toList() - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ showPosts(it) }, {it -> Log.e(PersistingStoreActivity::class.java.simpleName, it.message, it)}) + launch { + showPosts( + sanitizeData( + persistedStore.get(awwRequest))) + } + } private fun showPosts(posts: List) { @@ -66,13 +71,16 @@ class PersistingStoreActivity : AppCompatActivity() { .show() } - private fun sanitizeData(redditData: RedditData): Observable { - return Observable.fromIterable(redditData.data.children) - .map({ it.data }) - } + fun sanitizeData(redditData: RedditData): List = + redditData.data.children.map(Children::data) override fun onResume() { super.onResume() loadPosts() } + + override fun onDestroy() { + super.onDestroy() + job.cancel() // all children coroutines gets destroyed automatically + } } diff --git a/app/src/main/java/com/nytimes/android/sample/SampleApp.kt b/app/src/main/java/com/nytimes/android/sample/SampleApp.kt index a3b1a86bf..2555ddcc3 100644 --- a/app/src/main/java/com/nytimes/android/sample/SampleApp.kt +++ b/app/src/main/java/com/nytimes/android/sample/SampleApp.kt @@ -2,7 +2,9 @@ package com.nytimes.android.sample import android.app.Application import android.content.Context +import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory import com.nytimes.android.external.fs3.SourcePersisterFactory +import com.nytimes.android.external.store3.base.Fetcher import com.nytimes.android.external.store3.base.Persister import com.nytimes.android.external.store3.base.impl.BarCode import com.nytimes.android.external.store3.base.impl.MemoryPolicy @@ -12,10 +14,11 @@ import com.nytimes.android.external.store3.middleware.moshi.MoshiParserFactory import com.nytimes.android.sample.data.model.RedditData import com.nytimes.android.sample.data.remote.Api import com.squareup.moshi.Moshi -import io.reactivex.Single +import kotlinx.coroutines.Deferred +import okhttp3.ResponseBody import okio.BufferedSource +import okio.Okio.source import retrofit2.Retrofit -import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.moshi.MoshiConverterFactory import java.io.IOException import java.util.concurrent.TimeUnit @@ -26,12 +29,12 @@ class SampleApp : Application() { lateinit var persistedStore: Store val moshi = Moshi.Builder().build() lateinit var persister: Persister - lateinit var sampleRoomStore:SampleRoomStore +// lateinit var sampleRoomStore:SampleRoomStore override fun onCreate() { super.onCreate() appContext = this - sampleRoomStore = SampleRoomStore(this) +// sampleRoomStore = SampleRoomStore(this) initPersister(); nonPersistedStore = provideRedditStore(); persistedStore = providePersistedRedditStore(); @@ -69,7 +72,11 @@ class SampleApp : Application() { */ private fun provideRedditStore(): Store { return StoreBuilder.barcode() - .fetcher { barCode -> provideRetrofit().fetchSubreddit(barCode.key, "10") } + .fetcher(object : Fetcher { + override suspend fun fetch(key: BarCode): RedditData { + return provideRetrofit().fetchSubreddit(key.key, "10").await() + } + }) .memoryPolicy( MemoryPolicy .builder() @@ -86,7 +93,11 @@ class SampleApp : Application() { */ private fun providePersistedRedditStore(): Store { return StoreBuilder.parsedWithKey() - .fetcher({ this.fetcher(it) }) + .fetcher(object : Fetcher { + override suspend fun fetch(key: BarCode): BufferedSource { + return fetcher(key).await().source() + } + }) .persister(newPersister()) .parser(MoshiParserFactory.createSourceParser(moshi, RedditData::class.java)) .open() @@ -103,16 +114,16 @@ class SampleApp : Application() { /** * Returns a "fetcher" which will retrieve new data from the network. */ - private fun fetcher(barCode: BarCode): Single { + private suspend fun fetcher(barCode: BarCode): Deferred { return provideRetrofit().fetchSubredditForPersister(barCode.key, "10") - .map({ it.source() }) + } private fun provideRetrofit(): Api { return Retrofit.Builder() - .baseUrl("http://reddit.com/") + .baseUrl("https://reddit.com/") .addConverterFactory(MoshiConverterFactory.create(moshi)) - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .addCallAdapterFactory(CoroutineCallAdapterFactory()) .validateEagerly(BuildConfig.DEBUG) // Fail early: check Retrofit configuration at creation time in Debug build. .build() .create(Api::class.java) diff --git a/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java b/app/src/main/java/com/nytimes/android/sample/activity/PersistingStoreActivity.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java b/app/src/main/java/com/nytimes/android/sample/activity/StoreActivity.java deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/src/main/java/com/nytimes/android/sample/data/remote/Api.kt b/app/src/main/java/com/nytimes/android/sample/data/remote/Api.kt index 15c01e77c..03d6c91ae 100644 --- a/app/src/main/java/com/nytimes/android/sample/data/remote/Api.kt +++ b/app/src/main/java/com/nytimes/android/sample/data/remote/Api.kt @@ -1,8 +1,7 @@ package com.nytimes.android.sample.data.remote import com.nytimes.android.sample.data.model.RedditData - -import io.reactivex.Single +import kotlinx.coroutines.Deferred import okhttp3.ResponseBody import retrofit2.http.GET import retrofit2.http.Path @@ -12,9 +11,9 @@ interface Api { @GET("r/{subredditName}/new/.json") fun fetchSubreddit(@Path("subredditName") subredditName: String, - @Query("limit") limit: String): Single + @Query("limit") limit: String): Deferred @GET("r/{subredditName}/new/.json") fun fetchSubredditForPersister(@Path("subredditName") subredditName: String, - @Query("limit") limit: String): Single + @Query("limit") limit: String): Deferred } diff --git a/app/src/test/java/com/nytimes/android/sample/KeyParserTest.kt b/app/src/test/java/com/nytimes/android/sample/KeyParserTest.kt index ef938d1b0..d5ddaef1d 100644 --- a/app/src/test/java/com/nytimes/android/sample/KeyParserTest.kt +++ b/app/src/test/java/com/nytimes/android/sample/KeyParserTest.kt @@ -1,43 +1,43 @@ -package com.nytimes.android.sample - -import com.nytimes.android.external.store3.base.impl.Store -import com.nytimes.android.external.store3.base.impl.StoreBuilder - -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.runners.MockitoJUnitRunner - -import io.reactivex.Single -import io.reactivex.observers.TestObserver - - -@RunWith(MockitoJUnitRunner::class) -class KeyParserTest { - private var store: Store? = null - - @Before - @Throws(Exception::class) - fun setUp() { - store = StoreBuilder.parsedWithKey() - .parser({ integer, s -> s + integer }) - .fetcher({ integer -> Single.just(NETWORK) }) - .open() - - } - - @Test - @Throws(Exception::class) - fun testStoreWithKeyParserFuncNoPersister() { - val testObservable = store!!.get(KEY).test().await() - testObservable.assertNoErrors() - .assertValues(NETWORK + KEY) - .awaitTerminalEvent() - } - - companion object { - - val NETWORK = "Network" - val KEY = 5 - } -} +//package com.nytimes.android.sample +// +//import com.nytimes.android.external.store3.base.impl.Store +//import com.nytimes.android.external.store3.base.impl.StoreBuilder +// +//import org.junit.Before +//import org.junit.Test +//import org.junit.runner.RunWith +//import org.mockito.runners.MockitoJUnitRunner +// +//import io.reactivex.Single +//import io.reactivex.observers.TestObserver +// +// +//@RunWith(MockitoJUnitRunner::class) +//class KeyParserTest { +// private var store: Store? = null +// +// @Before +// @Throws(Exception::class) +// fun setUp() { +// store = StoreBuilder.parsedWithKey() +// .parser({ integer, s -> s + integer }) +// .fetcher({ integer -> Single.just(NETWORK) }) +// .open() +// +// } +// +// @Test +// @Throws(Exception::class) +// fun testStoreWithKeyParserFuncNoPersister() { +// val testObservable = store!!.get(KEY).test().await() +// testObservable.assertNoErrors() +// .assertValues(NETWORK + KEY) +// .awaitTerminalEvent() +// } +// +// companion object { +// +// val NETWORK = "Network" +// val KEY = 5 +// } +//} diff --git a/app/src/test/java/com/nytimes/android/sample/NoNetworkTest.kt b/app/src/test/java/com/nytimes/android/sample/NoNetworkTest.kt new file mode 100644 index 000000000..5e654c71f --- /dev/null +++ b/app/src/test/java/com/nytimes/android/sample/NoNetworkTest.kt @@ -0,0 +1,43 @@ +package com.nytimes.android.sample + +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder + +import org.junit.Before +import org.junit.Test + +import io.reactivex.Single +import kotlinx.coroutines.runBlocking + +class NoNetworkTest { + private lateinit var store: Store + + @Before + fun setUp() { + store = StoreBuilder.barcode() + .fetcher(object :Fetcher{ + override suspend fun fetch(key: BarCode): Any { + throw EXCEPTION + + } + }) + .open() + + } + + @Test(expected = java.lang.Exception::class) + @Throws(Exception::class) + fun testNoNetwork() { + runBlocking { + store.get(BarCode("test", "test")) + } + + + } + + companion object { + private val EXCEPTION = RuntimeException() + } +} diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSReader.kt b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSReader.kt index bcbaaca2c..f29143bcf 100644 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/FSReader.kt +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/FSReader.kt @@ -29,13 +29,14 @@ open class FSReader(internal val fileSystem: FileSystem, internal val pathRes } catch (e: FileNotFoundException) { throw e } finally { - if (bufferedSource != null) { - try { - bufferedSource.close() - } catch (e: IOException) { - e.printStackTrace(System.err) - } - } + //TODO MIKE: figure out why this was here +// if (bufferedSource != null) { +// try { +// bufferedSource.close() +// } catch (e: IOException) { +// e.printStackTrace(System.err) +// } +// } } } else{ diff --git a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileReader.kt b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileReader.kt index 13d132db1..a90802b85 100644 --- a/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileReader.kt +++ b/filesystem/src/main/java/com/nytimes/android/external/fs3/SourceFileReader.kt @@ -1,5 +1,6 @@ package com.nytimes.android.external.fs3 +import com.nytimes.android.external.fs3.SourcePersister.Companion.pathForBarcode import com.nytimes.android.external.fs3.filesystem.FileSystem import com.nytimes.android.external.store3.base.DiskRead import com.nytimes.android.external.store3.base.RecordState @@ -9,7 +10,6 @@ import java.util.concurrent.TimeUnit import okio.BufferedSource -import com.nytimes.android.external.fs3.SourcePersister.pathForBarcode class SourceFileReader @JvmOverloads constructor(fileSystem: FileSystem, pathResolver: PathResolver = BarCodePathResolver()) : FSReader(fileSystem, pathResolver), DiskRead { diff --git a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParser.kt b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParser.kt index 618e09fda..21ec03481 100644 --- a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParser.kt +++ b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiSourceParser.kt @@ -22,12 +22,11 @@ constructor(moshi: Moshi, type: Type) : Parser { } @Throws(ParserException::class) - override fun apply(@NonNull bufferedSource: BufferedSource): Parsed? { + override suspend fun apply(@NonNull bufferedSource: BufferedSource): Parsed { try { - return jsonAdapter.fromJson(bufferedSource) + return jsonAdapter.fromJson(bufferedSource)!! } catch (e: IOException) { throw ParserException(e.message, e) } - } } diff --git a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParser.kt b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParser.kt index a5d105038..f3d063996 100644 --- a/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParser.kt +++ b/middleware-moshi/src/main/java/com/nytimes/android/external/store3/middleware/moshi/MoshiStringParser.kt @@ -21,9 +21,9 @@ constructor(moshi: Moshi, type: Type) : Parser { } @Throws(ParserException::class) - override fun apply(@NonNull s: String): Parsed? { + override suspend fun apply(@NonNull s: String): Parsed { try { - return jsonAdapter.fromJson(s) + return jsonAdapter.fromJson(s)!! } catch (e: IOException) { throw ParserException(e.message, e) } diff --git a/store/build.gradle b/store/build.gradle index 6a78c8c2b..a0eef28e0 100644 --- a/store/build.gradle +++ b/store/build.gradle @@ -3,6 +3,12 @@ buildscript { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } } plugins { @@ -31,18 +37,6 @@ dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" } -buildscript { - tasks.withType(JavaCompile) { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - repositories { - mavenCentral() - } - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} apply from: rootProject.file("gradle/maven-push.gradle") apply from: rootProject.file("gradle/checkstyle.gradle") apply from: rootProject.file("gradle/pmd.gradle") From 7335157455dbd6d904789a71e12798e47738471e Mon Sep 17 00:00:00 2001 From: fabioCollini Date: Mon, 11 Feb 2019 15:15:08 -0500 Subject: [PATCH 052/498] Fix some tests --- .../com/nytimes/android/sample/SampleApp.kt | 14 +- build.gradle | 4 +- buildsystem/dependencies.gradle | 4 +- store/build.gradle | 1 + .../android/external/store3/base/Fetcher.kt | 5 +- .../store3/base/impl/ParsingFetcher.kt | 6 +- .../store3/base/impl/RealStoreBuilder.kt | 11 +- .../external/store3/ClearStoreMemoryTest.kt | 25 +- .../android/external/store3/ClearStoreTest.kt | 73 ++--- .../external/store3/ClearingPersister.java | 29 -- .../external/store3/ClearingPersister.kt | 19 ++ .../external/store3/DontCacheErrorsTest1.kt | 66 ++-- .../external/store3/GetRefreshingTest.java | 98 ------ .../external/store3/GetRefreshingTest.kt | 76 +++++ .../android/external/store3/KeyParserTest.kt | 21 +- .../android/external/store3/NoNetworkTest.kt | 34 +- .../external/store3/SampleParsingStore.java | 17 - .../external/store3/SampleParsingStore.kt | 13 + .../external/store3/SequentialTest.java | 67 ---- .../android/external/store3/SequentialTest.kt | 53 ++++ .../android/external/store3/StoreTest.java | 290 ------------------ .../android/external/store3/StoreTest.kt | 276 +++++++++++++++++ .../external/store3/StoreWithParserTest.java | 149 --------- .../external/store3/StoreWithParserTest.kt | 152 +++++++++ .../android/external/store3/StreamTest.java | 70 ----- .../android/external/store3/StreamTest.kt | 56 ++++ .../store3/room/ClearStoreRoomTest.java | 112 ------- .../store3/room/ClearStoreRoomTest.kt | 90 ++++++ .../external/store3/room/StoreRoomTest.java | 98 ------ .../external/store3/room/StoreRoomTest.kt | 86 ++++++ .../store3/util/NoopPersisterTest.java | 59 ---- .../external/store3/util/NoopPersisterTest.kt | 59 ++++ 32 files changed, 1007 insertions(+), 1126 deletions(-) delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.kt delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/GetRefreshingTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/GetRefreshingTest.kt delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/SampleParsingStore.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/SampleParsingStore.kt delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/SequentialTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/SequentialTest.kt delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/StoreTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/StoreTest.kt delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.kt delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/StreamTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/StreamTest.kt delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.kt delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.kt delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.kt diff --git a/app/src/main/java/com/nytimes/android/sample/SampleApp.kt b/app/src/main/java/com/nytimes/android/sample/SampleApp.kt index 2555ddcc3..d24f291b6 100644 --- a/app/src/main/java/com/nytimes/android/sample/SampleApp.kt +++ b/app/src/main/java/com/nytimes/android/sample/SampleApp.kt @@ -4,7 +4,6 @@ import android.app.Application import android.content.Context import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory import com.nytimes.android.external.fs3.SourcePersisterFactory -import com.nytimes.android.external.store3.base.Fetcher import com.nytimes.android.external.store3.base.Persister import com.nytimes.android.external.store3.base.impl.BarCode import com.nytimes.android.external.store3.base.impl.MemoryPolicy @@ -17,7 +16,6 @@ import com.squareup.moshi.Moshi import kotlinx.coroutines.Deferred import okhttp3.ResponseBody import okio.BufferedSource -import okio.Okio.source import retrofit2.Retrofit import retrofit2.converter.moshi.MoshiConverterFactory import java.io.IOException @@ -72,11 +70,7 @@ class SampleApp : Application() { */ private fun provideRedditStore(): Store { return StoreBuilder.barcode() - .fetcher(object : Fetcher { - override suspend fun fetch(key: BarCode): RedditData { - return provideRetrofit().fetchSubreddit(key.key, "10").await() - } - }) + .fetcher { key -> provideRetrofit().fetchSubreddit(key.key, "10").await() } .memoryPolicy( MemoryPolicy .builder() @@ -93,11 +87,7 @@ class SampleApp : Application() { */ private fun providePersistedRedditStore(): Store { return StoreBuilder.parsedWithKey() - .fetcher(object : Fetcher { - override suspend fun fetch(key: BarCode): BufferedSource { - return fetcher(key).await().source() - } - }) + .fetcher { key -> fetcher(key).await().source() } .persister(newPersister()) .parser(MoshiParserFactory.createSourceParser(moshi, RedditData::class.java)) .open() diff --git a/build.gradle b/build.gradle index 36ccf4179..bef6f7cd6 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ apply from: 'buildsystem/dependencies.gradle' // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.0' + ext.kotlin_version = '1.3.21' repositories { mavenCentral() maven { @@ -23,7 +23,7 @@ buildscript { ] dependencies { - classpath 'com.android.tools.build:gradle:3.2.0-beta04' + classpath 'com.android.tools.build:gradle:3.3.1' classpath 'com.google.gms:google-services:3.0.0' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.6' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11' diff --git a/buildsystem/dependencies.gradle b/buildsystem/dependencies.gradle index 903958bb0..d8fa2f315 100644 --- a/buildsystem/dependencies.gradle +++ b/buildsystem/dependencies.gradle @@ -54,7 +54,8 @@ ext.versions = [ // Testing. junit : '4.12', assertJ : '1.7.1', - mockito : '1.9.5', + mockito : '2.24.0', + mockitoKotlin : '2.1.0', robolectric : '3.1.2', supportTestRunner : '0.4.1', espresso : '2.2.1', @@ -120,6 +121,7 @@ ext.libraries = [ junit : "junit:junit:$versions.junit", assertJ : "org.assertj:assertj-core:$versions.assertJ", mockito : "org.mockito:mockito-core:$versions.mockito", + mockitoKotlin : "com.nhaarman.mockitokotlin2:mockito-kotlin:$versions.mockitoKotlin", robolectric : "org.robolectric:robolectric:$versions.robolectric", robolectricMultiDex : "org.robolectric:shadows-multidex:$versions.robolectric", supportTestRunner : "com.android.support.test:runner:$versions.supportTestRunner", diff --git a/store/build.gradle b/store/build.gradle index a0eef28e0..eed56dd47 100644 --- a/store/build.gradle +++ b/store/build.gradle @@ -31,6 +31,7 @@ dependencies { compileOnly libraries.jsr305 testImplementation libraries.mockito + testImplementation libraries.mockitoKotlin testImplementation libraries.assertJ testImplementation libraries.junit testCompileOnly libraries.jsr305 diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/Fetcher.kt b/store/src/main/java/com/nytimes/android/external/store3/base/Fetcher.kt index 4c5f2c368..80610817e 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/Fetcher.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/base/Fetcher.kt @@ -1,8 +1,5 @@ package com.nytimes.android.external.store3.base -import io.reactivex.Observable -import io.reactivex.Single - /** * Interface for fetching new data for a Store @@ -15,5 +12,5 @@ interface Fetcher { * @param key Container with Key and Type used as a request param * @return Observable that emits [Raw] data */ - suspend fun fetch(key: Key):Raw + suspend fun fetch(key: Key): Raw } diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.kt index e372226c1..eb9901d9d 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.kt @@ -3,8 +3,6 @@ package com.nytimes.android.external.store3.base.impl import com.nytimes.android.external.store3.base.Fetcher import com.nytimes.android.external.store3.base.Parser -import io.reactivex.Single - /** * Parsing fetcher that takes parser of Raw type and fetcher of raw type returning parsed instance. @@ -12,9 +10,9 @@ import io.reactivex.Single */ class ParsingFetcher (private val rawFetcher: Fetcher, - private val parser: Parser) : Fetcher { + private val parser: Parser) { - override suspend fun fetch(key: Key): Parsed { + suspend fun fetch(key: Key): Parsed { return rawFetcher.fetch(key).let { parser.apply(it) } } diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt index 6f56c0557..5230ff90e 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt @@ -25,6 +25,15 @@ class RealStoreBuilder { return this } + fun fetcher(fetcher: suspend (Key) -> Raw): RealStoreBuilder { + this.fetcher = object : Fetcher { + override suspend fun fetch(key: Key): Raw { + return fetcher(key) + } + } + return this + } + fun persister(persister: Persister): RealStoreBuilder { this.persister = persister return this @@ -86,7 +95,7 @@ class RealStoreBuilder { persister = NoopPersister.create(memoryPolicy) } - if (parser==null) { + if (parser == null) { parser(NoopParserFunc()) } diff --git a/store/src/test/java/com/nytimes/android/external/store3/ClearStoreMemoryTest.kt b/store/src/test/java/com/nytimes/android/external/store3/ClearStoreMemoryTest.kt index 988440955..729533cbe 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/ClearStoreMemoryTest.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/ClearStoreMemoryTest.kt @@ -3,14 +3,11 @@ package com.nytimes.android.external.store3 import com.nytimes.android.external.store3.base.impl.BarCode import com.nytimes.android.external.store3.base.impl.Store import com.nytimes.android.external.store3.base.impl.StoreBuilder - +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test -import io.reactivex.Single - -import org.assertj.core.api.Assertions.assertThat - class ClearStoreMemoryTest { private var networkCalls = 0 @@ -20,38 +17,38 @@ class ClearStoreMemoryTest { fun setUp() { networkCalls = 0 store = StoreBuilder.barcode() - .fetcher { barCode -> Single.fromCallable { networkCalls++ } } + .fetcher { networkCalls++ } .open() } @Test - fun testClearSingleBarCode() { + fun testClearSingleBarCode() = runBlocking { //one request should produce one call val barcode = BarCode("type", "key") - store.get(barcode).test().awaitTerminalEvent() + store.get(barcode) assertThat(networkCalls).isEqualTo(1) // after clearing the memory another call should be made store.clearMemory(barcode) - store.get(barcode).test().awaitTerminalEvent() + store.get(barcode) assertThat(networkCalls).isEqualTo(2) } @Test - fun testClearAllBarCodes() { + fun testClearAllBarCodes() = runBlocking { val b1 = BarCode("type1", "key1") val b2 = BarCode("type2", "key2") //each request should produce one call - store.get(b1).test().awaitTerminalEvent() - store.get(b2).test().awaitTerminalEvent() + store.get(b1) + store.get(b2) assertThat(networkCalls).isEqualTo(2) store.clearMemory() //after everything is cleared each request should produce another 2 calls - store.get(b1).test().awaitTerminalEvent() - store.get(b2).test().awaitTerminalEvent() + store.get(b1) + store.get(b2) assertThat(networkCalls).isEqualTo(4) } } diff --git a/store/src/test/java/com/nytimes/android/external/store3/ClearStoreTest.kt b/store/src/test/java/com/nytimes/android/external/store3/ClearStoreTest.kt index dd0533089..5501f7103 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/ClearStoreTest.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/ClearStoreTest.kt @@ -1,24 +1,19 @@ package com.nytimes.android.external.store3 +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever import com.nytimes.android.external.store3.base.impl.BarCode import com.nytimes.android.external.store3.base.impl.Store import com.nytimes.android.external.store3.base.impl.StoreBuilder -import io.reactivex.Maybe -import io.reactivex.Single +import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify -import org.mockito.runners.MockitoJUnitRunner import java.util.concurrent.atomic.AtomicInteger -@RunWith(MockitoJUnitRunner::class) class ClearStoreTest { - @Mock - lateinit var persister: ClearingPersister + val persister: ClearingPersister = mock() private lateinit var networkCalls: AtomicInteger private lateinit var store: Store @@ -26,66 +21,66 @@ class ClearStoreTest { fun setUp() { networkCalls = AtomicInteger(0) store = StoreBuilder.barcode() - .fetcher { barCode -> Single.fromCallable { networkCalls.incrementAndGet() } } + .fetcher { networkCalls.incrementAndGet() } .persister(persister) .open() } @Test - fun testClearSingleBarCode() { + fun testClearSingleBarCode() = runBlocking { // one request should produce one call val barcode = BarCode("type", "key") - `when`(persister.read(barcode)) - .thenReturn(Maybe.empty()) //read from disk on get - .thenReturn(Maybe.just(1)) //read from disk after fetching from network - .thenReturn(Maybe.empty()) //read from disk after clearing - .thenReturn(Maybe.just(1)) //read from disk after making additional network call - `when`(persister.write(barcode, 1)).thenReturn(Single.just(true)) - `when`(persister.write(barcode, 2)).thenReturn(Single.just(true)) + whenever(persister.read(barcode)) + .thenReturn(null) //read from disk on get + .thenReturn(1) //read from disk after fetching from network + .thenReturn(null) //read from disk after clearing + .thenReturn(1) //read from disk after making additional network call + whenever(persister.write(barcode, 1)).thenReturn(true) + whenever(persister.write(barcode, 2)).thenReturn(true) - store.get(barcode).test().awaitTerminalEvent() + store.get(barcode) assertThat(networkCalls.toInt()).isEqualTo(1) // after clearing the memory another call should be made store.clear(barcode) - store.get(barcode).test().awaitTerminalEvent() + store.get(barcode) verify(persister).clear(barcode) assertThat(networkCalls.toInt()).isEqualTo(2) } @Test - fun testClearAllBarCodes() { + fun testClearAllBarCodes() = runBlocking { val barcode1 = BarCode("type1", "key1") val barcode2 = BarCode("type2", "key2") - `when`(persister.read(barcode1)) - .thenReturn(Maybe.empty()) //read from disk - .thenReturn(Maybe.just(1)) //read from disk after fetching from network - .thenReturn(Maybe.empty()) //read from disk after clearing disk cache - .thenReturn(Maybe.just(1)) //read from disk after making additional network call - `when`(persister.write(barcode1, 1)).thenReturn(Single.just(true)) - `when`(persister.write(barcode1, 2)).thenReturn(Single.just(true)) + whenever(persister.read(barcode1)) + .thenReturn(null) //read from disk + .thenReturn(1) //read from disk after fetching from network + .thenReturn(null) //read from disk after clearing disk cache + .thenReturn(1) //read from disk after making additional network call + whenever(persister.write(barcode1, 1)).thenReturn(true) + whenever(persister.write(barcode1, 2)).thenReturn(true) - `when`(persister.read(barcode2)) - .thenReturn(Maybe.empty()) //read from disk - .thenReturn(Maybe.just(1)) //read from disk after fetching from network - .thenReturn(Maybe.empty()) //read from disk after clearing disk cache - .thenReturn(Maybe.just(1)) //read from disk after making additional network call + whenever(persister.read(barcode2)) + .thenReturn(null) //read from disk + .thenReturn(1) //read from disk after fetching from network + .thenReturn(null) //read from disk after clearing disk cache + .thenReturn(1) //read from disk after making additional network call - `when`(persister.write(barcode2, 1)).thenReturn(Single.just(true)) - `when`(persister.write(barcode2, 2)).thenReturn(Single.just(true)) + whenever(persister.write(barcode2, 1)).thenReturn(true) + whenever(persister.write(barcode2, 2)).thenReturn(true) // each request should produce one call - store.get(barcode1).test().awaitTerminalEvent() - store.get(barcode2).test().awaitTerminalEvent() + store.get(barcode1) + store.get(barcode2) assertThat(networkCalls.toInt()).isEqualTo(2) store.clear() // after everything is cleared each request should produce another 2 calls - store.get(barcode1).test().awaitTerminalEvent() - store.get(barcode2).test().awaitTerminalEvent() + store.get(barcode1) + store.get(barcode2) assertThat(networkCalls.toInt()).isEqualTo(4) } } diff --git a/store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.java b/store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.java deleted file mode 100644 index 8a884a729..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.nytimes.android.external.store3.base.Clearable; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; - -import javax.annotation.Nonnull; - -import io.reactivex.Maybe; -import io.reactivex.Single; - -public class ClearingPersister implements Persister, Clearable { - @Override - public void clear(@Nonnull BarCode key) { - throw new RuntimeException(); - } - - @Nonnull - @Override - public Maybe read(@Nonnull BarCode barCode) { - throw new RuntimeException(); - } - - @Nonnull - @Override - public Single write(@Nonnull BarCode barCode, @Nonnull Integer integer) { - throw new RuntimeException(); - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.kt b/store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.kt new file mode 100644 index 000000000..a6d74c76f --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.kt @@ -0,0 +1,19 @@ +package com.nytimes.android.external.store3 + +import com.nytimes.android.external.store3.base.Clearable +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode + +class ClearingPersister : Persister, Clearable { + override suspend fun read(key: BarCode): Int? { + throw RuntimeException() + } + + override suspend fun write(key: BarCode, raw: Int): Boolean { + throw RuntimeException() + } + + override fun clear(key: BarCode) { + throw RuntimeException() + } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt b/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt index bd4e74d70..5bcc7db86 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt @@ -3,12 +3,11 @@ package com.nytimes.android.external.store3 import com.nytimes.android.external.store3.base.impl.BarCode import com.nytimes.android.external.store3.base.impl.Store import com.nytimes.android.external.store3.base.impl.StoreBuilder - +import junit.framework.Assert.fail +import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Test -import io.reactivex.Single - class DontCacheErrorsTest { @@ -18,49 +17,46 @@ class DontCacheErrorsTest { @Before fun setUp() { store = StoreBuilder.barcode() - .fetcher { barCode -> - Single.fromCallable { - if (shouldThrow) { - throw RuntimeException() - } else { - 0 - } + .fetcher { + if (shouldThrow) { + throw RuntimeException() + } else { + 0 } } .open() } @Test - @Throws(InterruptedException::class) - fun testStoreDoesntCacheErrors() { + fun testStoreDoesntCacheErrors() = runBlocking { val barcode = BarCode("bar", "code") shouldThrow = true - store.get(barcode).test() - .assertTerminated() - .assertError(Exception::class.java) - .awaitTerminalEvent() - - shouldThrow = false - store.get(barcode).test() - .assertNoErrors() - .awaitTerminalEvent() - } - @Test - @Throws(InterruptedException::class) - fun testStoreDoesntCacheErrorsWithResult() { - val barcode = BarCode("bar", "code") - - shouldThrow = true - store.getWithResult(barcode).test() - .assertTerminated() - .assertError(Exception::class.java) - .awaitTerminalEvent() + try { + store.get(barcode) + fail() + } catch (e: Exception) { + } shouldThrow = false - store.get(barcode).test() - .assertNoErrors() - .awaitTerminalEvent() + store.get(barcode) } + +// TODO storeWithResult test +// @Test +// fun testStoreDoesntCacheErrorsWithResult() = runBlocking { +// val barcode = BarCode("bar", "code") +// +// shouldThrow = true +// store.getWithResult(barcode).test() +// .assertTerminated() +// .assertError(Exception::class.java) +// .awaitTerminalEvent() +// +// shouldThrow = false +// store.get(barcode).test() +// .assertNoErrors() +// .awaitTerminalEvent() +// } } diff --git a/store/src/test/java/com/nytimes/android/external/store3/GetRefreshingTest.java b/store/src/test/java/com/nytimes/android/external/store3/GetRefreshingTest.java deleted file mode 100644 index 94d788f54..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/GetRefreshingTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import java.util.concurrent.atomic.AtomicInteger; - -import io.reactivex.Maybe; -import io.reactivex.Single; -import io.reactivex.observers.TestObserver; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class GetRefreshingTest { - @Mock - ClearingPersister persister; - AtomicInteger networkCalls; - private Store store; - - @Before - public void setUp() { - networkCalls = new AtomicInteger(0); - store = StoreBuilder.barcode() - .fetcher(barCode -> Single.fromCallable(() -> networkCalls.incrementAndGet())) - .persister(persister) - .open(); - } - - @Test - public void testRefreshOnClear() { - BarCode barcode = new BarCode("type", "key"); - when(persister.read(barcode)) - .thenReturn(Maybe.empty()) //read from disk - .thenReturn(Maybe.just(1)) //read from disk after fetching from network - .thenReturn(Maybe.empty()) //read from disk after clearing disk cache - .thenReturn(Maybe.just(1)); //read from disk after making additional network call - when(persister.write(barcode, 1)).thenReturn(Single.just(true)); - when(persister.write(barcode, 2)).thenReturn(Single.just(true)); - - - TestObserver refreshingObservable = store.getRefreshing(barcode).test(); - refreshingObservable.assertValueCount(1); - assertThat(networkCalls.intValue()).isEqualTo(1); - //clearing the store should produce another network call - store.clear(barcode); - refreshingObservable.assertValueCount(2); - assertThat(networkCalls.intValue()).isEqualTo(2); - - store.get(barcode).test().awaitTerminalEvent(); - refreshingObservable.assertValueCount(2); - assertThat(networkCalls.intValue()).isEqualTo(2); - } - - @Test - public void testRefreshOnClearAll() { - BarCode barcode1 = new BarCode("type", "key"); - BarCode barcode2 = new BarCode("type", "key2"); - - when(persister.read(barcode1)) - .thenReturn(Maybe.empty()) //read from disk - .thenReturn(Maybe.just(1)) //read from disk after fetching from network - .thenReturn(Maybe.empty()) //read from disk after clearing disk cache - .thenReturn(Maybe.just(1)); //read from disk after making additional network call - when(persister.write(barcode1, 1)).thenReturn(Single.just(true)); - when(persister.write(barcode1, 2)).thenReturn(Single.just(true)); - - when(persister.read(barcode2)) - .thenReturn(Maybe.empty()) //read from disk - .thenReturn(Maybe.just(1)) //read from disk after fetching from network - .thenReturn(Maybe.empty()) //read from disk after clearing disk cache - .thenReturn(Maybe.just(1)); //read from disk after making additional network call - - when(persister.write(barcode2, 1)).thenReturn(Single.just(true)); - when(persister.write(barcode2, 2)).thenReturn(Single.just(true)); - - TestObserver testObservable1 = store.getRefreshing(barcode1).test(); - TestObserver testObservable2 = store.getRefreshing(barcode2).test(); - testObservable1.assertValueCount(1); - testObservable2.assertValueCount(1); - - assertThat(networkCalls.intValue()).isEqualTo(2); - - store.clear(); - assertThat(networkCalls.intValue()).isEqualTo(4); - - - } - -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/GetRefreshingTest.kt b/store/src/test/java/com/nytimes/android/external/store3/GetRefreshingTest.kt new file mode 100644 index 000000000..ec806e979 --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/GetRefreshingTest.kt @@ -0,0 +1,76 @@ +package com.nytimes.android.external.store3 + +import com.nhaarman.mockitokotlin2.mock +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import java.util.concurrent.atomic.AtomicInteger + +class GetRefreshingTest { + val persister: ClearingPersister = mock() + val networkCalls = AtomicInteger(0) + private val store: Store = StoreBuilder.barcode() + .fetcher { networkCalls.incrementAndGet() } + .persister(persister) + .open() + +// @Test +// fun testRefreshOnClear() = runBlocking { +// val barcode = BarCode("type", "key") +// `when`(persister.read(barcode)) +// .thenReturn(Maybe.empty()) //read from disk +// .thenReturn(Maybe.just(1)) //read from disk after fetching from network +// .thenReturn(Maybe.empty()) //read from disk after clearing disk cache +// .thenReturn(Maybe.just(1)) //read from disk after making additional network call +// `when`(persister.write(barcode, 1)).thenReturn(Single.just(true)) +// `when`(persister.write(barcode, 2)).thenReturn(Single.just(true)) +// +// +// val refreshingObservable = store.getRefreshing(barcode).test() +// refreshingObservable.assertValueCount(1) +// assertThat(networkCalls.toInt()).isEqualTo(1) +// //clearing the store should produce another network call +// store.clear(barcode) +// refreshingObservable.assertValueCount(2) +// assertThat(networkCalls.toInt()).isEqualTo(2) +// +// store.get(barcode).test().awaitTerminalEvent() +// refreshingObservable.assertValueCount(2) +// assertThat(networkCalls.toInt()).isEqualTo(2) +// } +// +// @Test +// fun testRefreshOnClearAll() { +// val barcode1 = BarCode("type", "key") +// val barcode2 = BarCode("type", "key2") +// +// `when`(persister.read(barcode1)) +// .thenReturn(Maybe.empty()) //read from disk +// .thenReturn(Maybe.just(1)) //read from disk after fetching from network +// .thenReturn(Maybe.empty()) //read from disk after clearing disk cache +// .thenReturn(Maybe.just(1)) //read from disk after making additional network call +// `when`(persister.write(barcode1, 1)).thenReturn(Single.just(true)) +// `when`(persister.write(barcode1, 2)).thenReturn(Single.just(true)) +// +// `when`(persister.read(barcode2)) +// .thenReturn(Maybe.empty()) //read from disk +// .thenReturn(Maybe.just(1)) //read from disk after fetching from network +// .thenReturn(Maybe.empty()) //read from disk after clearing disk cache +// .thenReturn(Maybe.just(1)) //read from disk after making additional network call +// +// `when`(persister.write(barcode2, 1)).thenReturn(Single.just(true)) +// `when`(persister.write(barcode2, 2)).thenReturn(Single.just(true)) +// +// val testObservable1 = store.getRefreshing(barcode1).test() +// val testObservable2 = store.getRefreshing(barcode2).test() +// testObservable1.assertValueCount(1) +// testObservable2.assertValueCount(1) +// +// assertThat(networkCalls.toInt()).isEqualTo(2) +// +// store.clear() +// assertThat(networkCalls.toInt()).isEqualTo(4) +// +// +// } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.kt b/store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.kt index 620b5a22f..899dd9f18 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.kt @@ -2,7 +2,9 @@ package com.nytimes.android.external.store3 import com.nytimes.android.external.store3.base.impl.Store import com.nytimes.android.external.store3.base.impl.StoreBuilder -import io.reactivex.Single +import com.nytimes.android.external.store3.util.KeyParser +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -17,23 +19,24 @@ class KeyParserTest { @Throws(Exception::class) fun setUp() { store = StoreBuilder.parsedWithKey() - .parser { integer, s -> s + integer } - .fetcher { integer -> Single.just(NETWORK) } + .parser(object : KeyParser { + override suspend fun apply(key: Int, raw: String): String { + return raw + key + } + }) + .fetcher { NETWORK } .open() } @Test @Throws(Exception::class) - fun testStoreWithKeyParserFuncNoPersister() { - val testObservable = store.get(KEY).test().await() - testObservable.assertNoErrors() - .assertValues(NETWORK + KEY) - .awaitTerminalEvent() + fun testStoreWithKeyParserFuncNoPersister() = runBlocking { + assertThat(store.get(KEY)).isEqualTo(NETWORK + KEY) } companion object { private const val NETWORK = "Network" - val KEY = 5 + const val KEY = 5 } } diff --git a/store/src/test/java/com/nytimes/android/external/store3/NoNetworkTest.kt b/store/src/test/java/com/nytimes/android/external/store3/NoNetworkTest.kt index fdea5a156..e2d621f87 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/NoNetworkTest.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/NoNetworkTest.kt @@ -3,37 +3,39 @@ package com.nytimes.android.external.store3 import com.nytimes.android.external.store3.base.impl.BarCode import com.nytimes.android.external.store3.base.impl.Store import com.nytimes.android.external.store3.base.impl.StoreBuilder - +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.fail import org.junit.Before import org.junit.Test -import io.reactivex.Single - class NoNetworkTest { private lateinit var store: Store @Before fun setUp() { store = StoreBuilder.barcode() - .fetcher { barcode -> Single.error(EXCEPTION) } + .fetcher { throw EXCEPTION } .open() } @Test - @Throws(Exception::class) - fun testNoNetwork() { - store.get(BarCode("test", "test")) - .test() - .assertError(EXCEPTION) + fun testNoNetwork() = runBlocking { + try { + store.get(BarCode("test", "test")) + fail("Exception not thrown") + } catch (e: Exception) { + assertThat(e).isEqualTo(EXCEPTION) + } } - @Test - @Throws(Exception::class) - fun testNoNetworkWithResult() { - store.getWithResult(BarCode("test", "test")) - .test() - .assertError(EXCEPTION) - } +// TODO getWithResult test +// @Test +// fun testNoNetworkWithResult() = runBlocking { +// store.getWithResult(BarCode("test", "test")) +// .test() +// .assertError(EXCEPTION) +// } companion object { private val EXCEPTION = RuntimeException() diff --git a/store/src/test/java/com/nytimes/android/external/store3/SampleParsingStore.java b/store/src/test/java/com/nytimes/android/external/store3/SampleParsingStore.java deleted file mode 100644 index 996f998d9..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/SampleParsingStore.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.RealStore; - - -public class SampleParsingStore extends RealStore { - - public SampleParsingStore(Fetcher fetcher, - Persister persister, - Parser parser) { - super(fetcher, persister, parser); - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/SampleParsingStore.kt b/store/src/test/java/com/nytimes/android/external/store3/SampleParsingStore.kt new file mode 100644 index 000000000..e9d092241 --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/SampleParsingStore.kt @@ -0,0 +1,13 @@ +package com.nytimes.android.external.store3 + +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Parser +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.RealStore + + +class SampleParsingStore(fetcher: Fetcher, + persister: Persister, + parser: Parser +) : RealStore(fetcher, persister, parser) diff --git a/store/src/test/java/com/nytimes/android/external/store3/SequentialTest.java b/store/src/test/java/com/nytimes/android/external/store3/SequentialTest.java deleted file mode 100644 index 1e8b66d2c..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/SequentialTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.nytimes.android.external.store.util.Result; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; -import org.junit.Before; -import org.junit.Test; -import io.reactivex.Single; - -import static org.assertj.core.api.Assertions.assertThat; - -public class SequentialTest { - - int networkCalls = 0; - private Store store; - - @Before - public void setUp() { - networkCalls = 0; - store = StoreBuilder.barcode() - .fetcher(barcode -> Single.fromCallable(() -> networkCalls++)) - .open(); - } - - @Test - public void sequentially() { - BarCode b = new BarCode("one", "two"); - store.get(b).test().awaitTerminalEvent(); - store.get(b).test().awaitTerminalEvent(); - - assertThat(networkCalls).isEqualTo(1); - } - - @Test - public void sequentiallyWithResult() { - BarCode b = new BarCode("one", "two"); - store.getWithResult(b).test().awaitTerminalEvent(); - store.getWithResult(b).test().awaitTerminalEvent(); - - assertThat(networkCalls).isEqualTo(1); - } - - @Test - public void parallel() { - BarCode b = new BarCode("one", "two"); - Single first = store.get(b); - Single second = store.get(b); - - first.test().awaitTerminalEvent(); - second.test().awaitTerminalEvent(); - - assertThat(networkCalls).isEqualTo(1); - } - - @Test - public void parallelWithResult() { - BarCode b = new BarCode("one", "two"); - Single> first = store.getWithResult(b); - Single> second = store.getWithResult(b); - - first.test().awaitTerminalEvent(); - second.test().awaitTerminalEvent(); - - assertThat(networkCalls).isEqualTo(1); - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/SequentialTest.kt b/store/src/test/java/com/nytimes/android/external/store3/SequentialTest.kt new file mode 100644 index 000000000..ea3aca3ba --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/SequentialTest.kt @@ -0,0 +1,53 @@ +package com.nytimes.android.external.store3 + +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test + +class SequentialTest { + + var networkCalls = 0 + private var store = StoreBuilder.barcode() + .fetcher { networkCalls++ } + .open() + + @Test + fun sequentially() = runBlocking { + val b = BarCode("one", "two") + store.get(b) + store.get(b) + + assertThat(networkCalls).isEqualTo(1) + } + +// @Test +// fun sequentiallyWithResult() = runBlocking { +// val b = BarCode("one", "two") +// store.getWithResult(b) +// store.getWithResult(b) +// +// assertThat(networkCalls).isEqualTo(1) +// } + + @Test + fun parallel() = runBlocking { + val b = BarCode("one", "two") + val deferred = async { store.get(b) } + store.get(b) + deferred.await() + + assertThat(networkCalls).isEqualTo(1) + } + +// @Test +// fun parallelWithResult() = runBlocking { +// val b = BarCode("one", "two") +// val first = store.getWithResult(b) +// val second = store.getWithResult(b) +// +// assertThat(networkCalls).isEqualTo(1) +// } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/StoreTest.java b/store/src/test/java/com/nytimes/android/external/store3/StoreTest.java deleted file mode 100644 index 5e085503c..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/StoreTest.java +++ /dev/null @@ -1,290 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.nytimes.android.external.cache3.Cache; -import com.nytimes.android.external.cache3.CacheBuilder; -import com.nytimes.android.external.store.util.Result; -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.RealStore; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; -import com.nytimes.android.external.store3.util.NoopPersister; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import io.reactivex.Maybe; -import io.reactivex.Single; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class StoreTest { - - private static final String DISK = "disk"; - private static final String NETWORK = "fresh"; - private static final String MEMORY = "memory"; - final AtomicInteger counter = new AtomicInteger(0); - @Mock - Fetcher fetcher; - @Mock - Persister persister; - private final BarCode barCode = new BarCode("key", "value"); - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testSimple() { - - Store simpleStore = StoreBuilder.barcode() - .persister(persister) - .fetcher(fetcher) - .open(); - - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)); - - when(persister.write(barCode, NETWORK)) - .thenReturn(Single.just(true)); - - String value = simpleStore.get(barCode).blockingGet(); - - assertThat(value).isEqualTo(DISK); - value = simpleStore.get(barCode).blockingGet(); - assertThat(value).isEqualTo(DISK); - verify(fetcher, times(1)).fetch(barCode); - } - - @Test - public void testSimpleWithResult() { - - Store simpleStore = StoreBuilder.barcode() - .persister(persister) - .fetcher(fetcher) - .open(); - - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)); - - when(persister.write(barCode, NETWORK)) - .thenReturn(Single.just(true)); - - Result result = simpleStore.getWithResult(barCode).blockingGet(); - - assertThat(result.source()).isEqualTo(Result.Source.NETWORK); - assertThat(result.value()).isEqualTo(DISK); - - result = simpleStore.getWithResult(barCode).blockingGet(); - assertThat(result.source()).isEqualTo(Result.Source.CACHE); - assertThat(result.value()).isEqualTo(DISK); - verify(fetcher, times(1)).fetch(barCode); - } - - - @Test - public void testDoubleTap() { - - Store simpleStore = StoreBuilder.barcode() - .persister(persister) - .fetcher(fetcher) - .open(); - - Single networkSingle = - Single.create(emitter -> { - if (counter.incrementAndGet() == 1) { - emitter.onSuccess(NETWORK); - } else { - emitter.onError(new RuntimeException("Yo Dawg your inflight is broken")); - } - }); - - - when(fetcher.fetch(barCode)) - .thenReturn(networkSingle); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)); - - when(persister.write(barCode, NETWORK)) - .thenReturn(Single.just(true)); - - - String response = simpleStore.get(barCode) - .zipWith(simpleStore.get(barCode), (s, s2) -> "hello") - .blockingGet(); - assertThat(response).isEqualTo("hello"); - verify(fetcher, times(1)).fetch(barCode); - } - - @Test - public void testDoubleTapWithResult() { - - Store simpleStore = StoreBuilder.barcode() - .persister(persister) - .fetcher(fetcher) - .open(); - - Single networkSingle = - Single.create(emitter -> { - if (counter.incrementAndGet() == 1) { - emitter.onSuccess(NETWORK); - } else { - emitter.onError(new RuntimeException("Yo Dawg your inflight is broken")); - } - }); - - - when(fetcher.fetch(barCode)) - .thenReturn(networkSingle); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)); - - when(persister.write(barCode, NETWORK)) - .thenReturn(Single.just(true)); - - - Result response = simpleStore.getWithResult(barCode) - .zipWith(simpleStore.getWithResult(barCode), (s, s2) -> Result.createFromNetwork("hello")) - .blockingGet(); - - assertThat(response.source()).isEqualTo(Result.Source.NETWORK); - assertThat(response.value()).isEqualTo("hello"); - verify(fetcher, times(1)).fetch(barCode); - } - - - @Test - public void testSubclass() { - - RealStore simpleStore = new SampleStore(fetcher, persister); - simpleStore.clear(); - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)); - when(persister.write(barCode, NETWORK)).thenReturn(Single.just(true)); - - String value = simpleStore.get(barCode).blockingGet(); - assertThat(value).isEqualTo(DISK); - value = simpleStore.get(barCode).blockingGet(); - assertThat(value).isEqualTo(DISK); - verify(fetcher, times(1)).fetch(barCode); - } - - @Test - public void testSubclassWithResult() { - - RealStore simpleStore = new SampleStore(fetcher, persister); - simpleStore.clear(); - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)); - when(persister.write(barCode, NETWORK)).thenReturn(Single.just(true)); - - Result result = simpleStore.getWithResult(barCode).blockingGet(); - - assertThat(result.source()).isEqualTo(Result.Source.NETWORK); - assertThat(result.value()).isEqualTo(DISK); - - result = simpleStore.getWithResult(barCode).blockingGet(); - assertThat(result.source()).isEqualTo(Result.Source.CACHE); - assertThat(result.value()).isEqualTo(DISK); - verify(fetcher, times(1)).fetch(barCode); - } - - @Test - public void testNoopAndDefault() { - - Persister persister = spy(NoopPersister.Companion.create()); - RealStore simpleStore = new SampleStore(fetcher, persister); - - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)); - - String value = simpleStore.get(barCode).blockingGet(); - verify(fetcher, times(1)).fetch(barCode); - verify(persister, times(1)).write(barCode, NETWORK); - verify(persister, times(2)).read(barCode); - assertThat(value).isEqualTo(NETWORK); - - - value = simpleStore.get(barCode).blockingGet(); - verify(persister, times(2)).read(barCode); - verify(persister, times(1)).write(barCode, NETWORK); - verify(fetcher, times(1)).fetch(barCode); - - assertThat(value).isEqualTo(NETWORK); - } - - @Test - public void testNoopAndDefaultWithResult() { - - Persister persister = spy(NoopPersister.Companion.create()); - RealStore simpleStore = new SampleStore(fetcher, persister); - - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)); - - Result value = simpleStore.getWithResult(barCode).blockingGet(); - verify(fetcher, times(1)).fetch(barCode); - verify(persister, times(1)).write(barCode, NETWORK); - verify(persister, times(2)).read(barCode); - assertThat(value.source()).isEqualTo(Result.Source.NETWORK); - assertThat(value.value()).isEqualTo(NETWORK); - - - value = simpleStore.getWithResult(barCode).blockingGet(); - verify(persister, times(2)).read(barCode); - verify(persister, times(1)).write(barCode, NETWORK); - verify(fetcher, times(1)).fetch(barCode); - - assertThat(value.source()).isEqualTo(Result.Source.CACHE); - assertThat(value.value()).isEqualTo(NETWORK); - } - - @Test - public void testEquivalence() { - Cache cache = CacheBuilder.newBuilder() - .maximumSize(1) - .expireAfterAccess(Long.MAX_VALUE, TimeUnit.SECONDS) - .build(); - - cache.put(barCode, MEMORY); - String value = cache.getIfPresent(barCode); - assertThat(value).isEqualTo(MEMORY); - - value = cache.getIfPresent(new BarCode(barCode.getType(), barCode.getKey())); - assertThat(value).isEqualTo(MEMORY); - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/StoreTest.kt b/store/src/test/java/com/nytimes/android/external/store3/StoreTest.kt new file mode 100644 index 000000000..2cfde446b --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/StoreTest.kt @@ -0,0 +1,276 @@ +package com.nytimes.android.external.store3 + +import com.nhaarman.mockitokotlin2.mock +import com.nytimes.android.external.cache3.CacheBuilder +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import com.nytimes.android.external.store3.util.NoopPersister +import io.reactivex.Maybe +import io.reactivex.Single +import kotlinx.coroutines.async +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.mockito.Mockito.* +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger + +class StoreTest { + val counter = AtomicInteger(0) + val fetcher: Fetcher = mock() + var persister: Persister = mock() + private val barCode = BarCode("key", "value") + + @Test + fun testSimple() = runBlocking { + + val simpleStore = StoreBuilder.barcode() + .persister(persister) + .fetcher(fetcher) + .open() + + + `when`(fetcher.fetch(barCode)) + .thenReturn(Single.just(NETWORK)) + + `when`(persister.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(Maybe.just(DISK)) + + `when`(persister.write(barCode, NETWORK)) + .thenReturn(Single.just(true)) + + var value = simpleStore.get(barCode) + + assertThat(value).isEqualTo(DISK) + value = simpleStore.get(barCode) + assertThat(value).isEqualTo(DISK) + verify>(fetcher, times(1)).fetch(barCode) + } + +// @Test +// fun testSimpleWithResult() = runBlocking { +// +// val simpleStore = StoreBuilder.barcode() +// .persister(persister) +// .fetcher(fetcher) +// .open() +// +// +// `when`(fetcher.fetch(barCode)) +// .thenReturn(Single.just(NETWORK)) +// +// `when`(persister.read(barCode)) +// .thenReturn(Maybe.empty()) +// .thenReturn(Maybe.just(DISK)) +// +// `when`(persister.write(barCode, NETWORK)) +// .thenReturn(Single.just(true)) +// +// var result = simpleStore.getWithResult(barCode) +// +// assertThat(result.source()).isEqualTo(Result.Source.NETWORK) +// assertThat(result.value()).isEqualTo(DISK) +// +// result = simpleStore.getWithResult(barCode) +// assertThat(result.source()).isEqualTo(Result.Source.CACHE) +// assertThat(result.value()).isEqualTo(DISK) +// verify>(fetcher, times(1)).fetch(barCode) +// } + + + @Test + fun testDoubleTap() = runBlocking { + + val simpleStore = StoreBuilder.barcode() + .persister(persister) + .fetcher(fetcher) + .open() + + val networkSingle = Single.create { emitter -> + if (counter.incrementAndGet() == 1) { + emitter.onSuccess(NETWORK) + } else { + emitter.onError(RuntimeException("Yo Dawg your inflight is broken")) + } + } + + + `when`(fetcher.fetch(barCode)) + .thenReturn(networkSingle) + + `when`(persister.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(Maybe.just(DISK)) + + `when`(persister.write(barCode, NETWORK)) + .thenReturn(Single.just(true)) + + + val deferred = async { simpleStore.get(barCode) } + simpleStore.get(barCode) + deferred.await() + + verify(fetcher, times(1)).fetch(barCode) + } + +// @Test +// fun testDoubleTapWithResult() = runBlocking { +// +// val simpleStore = StoreBuilder.barcode() +// .persister(persister) +// .fetcher(fetcher) +// .open() +// +// val networkSingle = Single.create { emitter -> +// if (counter.incrementAndGet() == 1) { +// emitter.onSuccess(NETWORK) +// } else { +// emitter.onError(RuntimeException("Yo Dawg your inflight is broken")) +// } +// } +// +// +// `when`(fetcher.fetch(barCode)) +// .thenReturn(networkSingle) +// +// `when`(persister.read(barCode)) +// .thenReturn(Maybe.empty()) +// .thenReturn(Maybe.just(DISK)) +// +// `when`(persister.write(barCode, NETWORK)) +// .thenReturn(Single.just(true)) +// +// +// val response = simpleStore.getWithResult(barCode) +// .zipWith(simpleStore.getWithResult(barCode), { s, s2 -> Result.createFromNetwork("hello") }) +// +// +// assertThat(response.source()).isEqualTo(Result.Source.NETWORK) +// assertThat(response.value()).isEqualTo("hello") +// verify>(fetcher, times(1)).fetch(barCode) +// } + + + @Test + fun testSubclass() = runBlocking { + + val simpleStore = SampleStore(fetcher, persister) + simpleStore.clear() + + `when`(fetcher.fetch(barCode)) + .thenReturn(Single.just(NETWORK)) + + `when`(persister.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(Maybe.just(DISK)) + `when`(persister.write(barCode, NETWORK)).thenReturn(Single.just(true)) + + var value = simpleStore.get(barCode) + assertThat(value).isEqualTo(DISK) + value = simpleStore.get(barCode) + assertThat(value).isEqualTo(DISK) + verify>(fetcher, times(1)).fetch(barCode) + } + +// @Test +// fun testSubclassWithResult() = runBlocking { +// +// val simpleStore = SampleStore(fetcher, persister) +// simpleStore.clear() +// +// `when`(fetcher.fetch(barCode)) +// .thenReturn(Single.just(NETWORK)) +// +// `when`(persister.read(barCode)) +// .thenReturn(Maybe.empty()) +// .thenReturn(Maybe.just(DISK)) +// `when`(persister.write(barCode, NETWORK)).thenReturn(Single.just(true)) +// +// var result = simpleStore.getWithResult(barCode) +// +// assertThat(result.source()).isEqualTo(Result.Source.NETWORK) +// assertThat(result.value()).isEqualTo(DISK) +// +// result = simpleStore.getWithResult(barCode) +// assertThat(result.source()).isEqualTo(Result.Source.CACHE) +// assertThat(result.value()).isEqualTo(DISK) +// verify>(fetcher, times(1)).fetch(barCode) +// } + + @Test + fun testNoopAndDefault() = runBlocking { + + val persister = spy(NoopPersister.create()) + val simpleStore = SampleStore(fetcher, persister) + + + `when`(fetcher.fetch(barCode)) + .thenReturn(Single.just(NETWORK)) + + var value = simpleStore.get(barCode) + verify>(fetcher, times(1)).fetch(barCode) + verify>(persister, times(1)).write(barCode, NETWORK) + verify>(persister, times(2)).read(barCode) + assertThat(value).isEqualTo(NETWORK) + + + value = simpleStore.get(barCode) + verify>(persister, times(2)).read(barCode) + verify>(persister, times(1)).write(barCode, NETWORK) + verify>(fetcher, times(1)).fetch(barCode) + + assertThat(value).isEqualTo(NETWORK) + } + +// @Test +// fun testNoopAndDefaultWithResult() = runBlocking { +// +// val persister = spy(NoopPersister.create()) +// val simpleStore = SampleStore(fetcher, persister) +// +// +// `when`(fetcher.fetch(barCode)) +// .thenReturn(Single.just(NETWORK)) +// +// var value = simpleStore.getWithResult(barCode) +// verify>(fetcher, times(1)).fetch(barCode) +// verify>(persister, times(1)).write(barCode, NETWORK) +// verify>(persister, times(2)).read(barCode) +// assertThat(value.source()).isEqualTo(Result.Source.NETWORK) +// assertThat(value.value()).isEqualTo(NETWORK) +// +// +// value = simpleStore.getWithResult(barCode) +// verify>(persister, times(2)).read(barCode) +// verify>(persister, times(1)).write(barCode, NETWORK) +// verify>(fetcher, times(1)).fetch(barCode) +// +// assertThat(value.source()).isEqualTo(Result.Source.CACHE) +// assertThat(value.value()).isEqualTo(NETWORK) +// } + + @Test + fun testEquivalence() = runBlocking { + val cache = CacheBuilder.newBuilder() + .maximumSize(1) + .expireAfterAccess(java.lang.Long.MAX_VALUE, TimeUnit.SECONDS) + .build() + + cache.put(barCode, MEMORY) + var value = cache.getIfPresent(barCode) + assertThat(value).isEqualTo(MEMORY) + + value = cache.getIfPresent(BarCode(barCode.type, barCode.key)) + assertThat(value).isEqualTo(MEMORY) + } + + companion object { + + private val DISK = "disk" + private val NETWORK = "fresh" + private val MEMORY = "memory" + } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.java b/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.java deleted file mode 100644 index 4abb495af..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.nytimes.android.external.store.util.Result; -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Parser; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.ParsingStoreBuilder; -import com.nytimes.android.external.store3.base.impl.Store; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import io.reactivex.Maybe; -import io.reactivex.Single; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class StoreWithParserTest { - - private static final String DISK = "persister"; - private static final String NETWORK = "fresh"; - @Mock - Fetcher fetcher; - @Mock - Persister persister; - @Mock - Parser parser; - - private final BarCode barCode = new BarCode("key", "value"); - - @Test - public void testSimple() throws Exception { - MockitoAnnotations.initMocks(this); - - - Store simpleStore = ParsingStoreBuilder.builder() - .persister(persister) - .fetcher(fetcher) - .parser(parser) - .open(); - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)); - - when(persister.write(barCode, NETWORK)) - .thenReturn(Single.just(true)); - - when(parser.apply(DISK)).thenReturn(barCode.getKey()); - - String value = simpleStore.get(barCode).blockingGet(); - assertThat(value).isEqualTo(barCode.getKey()); - value = simpleStore.get(barCode).blockingGet(); - assertThat(value).isEqualTo(barCode.getKey()); - verify(fetcher, times(1)).fetch(barCode); - } - - @Test - public void testSimpleWithResult() throws Exception { - MockitoAnnotations.initMocks(this); - - - Store simpleStore = ParsingStoreBuilder.builder() - .persister(persister) - .fetcher(fetcher) - .parser(parser) - .open(); - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)); - - when(persister.write(barCode, NETWORK)) - .thenReturn(Single.just(true)); - - when(parser.apply(DISK)).thenReturn(barCode.getKey()); - - Result result = simpleStore.getWithResult(barCode).blockingGet(); - assertThat(result.source()).isEqualTo(Result.Source.NETWORK); - assertThat(result.value()).isEqualTo(barCode.getKey()); - - result = simpleStore.getWithResult(barCode).blockingGet(); - assertThat(result.source()).isEqualTo(Result.Source.CACHE); - assertThat(result.value()).isEqualTo(barCode.getKey()); - verify(fetcher, times(1)).fetch(barCode); - } - - @Test - public void testSubclass() throws Exception { - MockitoAnnotations.initMocks(this); - - Store simpleStore = new SampleParsingStore(fetcher, persister, parser); - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)); - - when(persister.write(barCode, NETWORK)) - .thenReturn(Single.just(true)); - - when(parser.apply(DISK)).thenReturn(barCode.getKey()); - - String value = simpleStore.get(barCode).blockingGet(); - assertThat(value).isEqualTo(barCode.getKey()); - value = simpleStore.get(barCode).blockingGet(); - assertThat(value).isEqualTo(barCode.getKey()); - verify(fetcher, times(1)).fetch(barCode); - } - - @Test - public void testSubclassWithResult() throws Exception { - MockitoAnnotations.initMocks(this); - - Store simpleStore = new SampleParsingStore(fetcher, persister, parser); - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)); - - when(persister.write(barCode, NETWORK)) - .thenReturn(Single.just(true)); - - when(parser.apply(DISK)).thenReturn(barCode.getKey()); - - Result result = simpleStore.getWithResult(barCode).blockingGet(); - assertThat(result.source()).isEqualTo(Result.Source.NETWORK); - assertThat(result.value()).isEqualTo(barCode.getKey()); - - result = simpleStore.getWithResult(barCode).blockingGet(); - assertThat(result.source()).isEqualTo(Result.Source.CACHE); - assertThat(result.value()).isEqualTo(barCode.getKey()); - verify(fetcher, times(1)).fetch(barCode); - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.kt b/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.kt new file mode 100644 index 000000000..ba0f6a5a3 --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.kt @@ -0,0 +1,152 @@ +package com.nytimes.android.external.store3 + +import com.nytimes.android.external.store.util.Result +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Parser +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.ParsingStoreBuilder +import io.reactivex.Maybe +import io.reactivex.Single +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.* +import org.mockito.MockitoAnnotations + +class StoreWithParserTest { + @Mock + internal var fetcher: Fetcher? = null + @Mock + internal var persister: Persister? = null + @Mock + internal var parser: Parser? = null + + private val barCode = BarCode("key", "value") + + @Test + @Throws(Exception::class) + fun testSimple() { + MockitoAnnotations.initMocks(this) + + + val simpleStore = ParsingStoreBuilder.builder() + .persister(persister!!) + .fetcher(fetcher!!) + .parser(parser!!) + .open() + + `when`(fetcher!!.fetch(barCode)) + .thenReturn(Single.just(NETWORK)) + + `when`(persister!!.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(Maybe.just(DISK)) + + `when`(persister!!.write(barCode, NETWORK)) + .thenReturn(Single.just(true)) + + `when`(parser!!.apply(DISK)).thenReturn(barCode.key) + + var value = simpleStore.get(barCode).blockingGet() + assertThat(value).isEqualTo(barCode.key) + value = simpleStore.get(barCode).blockingGet() + assertThat(value).isEqualTo(barCode.key) + verify>(fetcher, times(1)).fetch(barCode) + } + + @Test + @Throws(Exception::class) + fun testSimpleWithResult() { + MockitoAnnotations.initMocks(this) + + + val simpleStore = ParsingStoreBuilder.builder() + .persister(persister!!) + .fetcher(fetcher!!) + .parser(parser!!) + .open() + + `when`(fetcher!!.fetch(barCode)) + .thenReturn(Single.just(NETWORK)) + + `when`(persister!!.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(Maybe.just(DISK)) + + `when`(persister!!.write(barCode, NETWORK)) + .thenReturn(Single.just(true)) + + `when`(parser!!.apply(DISK)).thenReturn(barCode.key) + + var result = simpleStore.getWithResult(barCode).blockingGet() + assertThat(result.source()).isEqualTo(Result.Source.NETWORK) + assertThat(result.value()).isEqualTo(barCode.key) + + result = simpleStore.getWithResult(barCode).blockingGet() + assertThat(result.source()).isEqualTo(Result.Source.CACHE) + assertThat(result.value()).isEqualTo(barCode.key) + verify>(fetcher, times(1)).fetch(barCode) + } + + @Test + @Throws(Exception::class) + fun testSubclass() { + MockitoAnnotations.initMocks(this) + + val simpleStore = SampleParsingStore(fetcher!!, persister!!, parser!!) + + `when`(fetcher!!.fetch(barCode)) + .thenReturn(Single.just(NETWORK)) + + `when`(persister!!.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(Maybe.just(DISK)) + + `when`(persister!!.write(barCode, NETWORK)) + .thenReturn(Single.just(true)) + + `when`(parser!!.apply(DISK)).thenReturn(barCode.key) + + var value = simpleStore.get(barCode).blockingGet() + assertThat(value).isEqualTo(barCode.key) + value = simpleStore.get(barCode).blockingGet() + assertThat(value).isEqualTo(barCode.key) + verify>(fetcher, times(1)).fetch(barCode) + } + + @Test + @Throws(Exception::class) + fun testSubclassWithResult() { + MockitoAnnotations.initMocks(this) + + val simpleStore = SampleParsingStore(fetcher!!, persister!!, parser!!) + + `when`(fetcher!!.fetch(barCode)) + .thenReturn(Single.just(NETWORK)) + + `when`(persister!!.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(Maybe.just(DISK)) + + `when`(persister!!.write(barCode, NETWORK)) + .thenReturn(Single.just(true)) + + `when`(parser!!.apply(DISK)).thenReturn(barCode.key) + + var result = simpleStore.getWithResult(barCode).blockingGet() + assertThat(result.source()).isEqualTo(Result.Source.NETWORK) + assertThat(result.value()).isEqualTo(barCode.key) + + result = simpleStore.getWithResult(barCode).blockingGet() + assertThat(result.source()).isEqualTo(Result.Source.CACHE) + assertThat(result.value()).isEqualTo(barCode.key) + verify>(fetcher, times(1)).fetch(barCode) + } + + companion object { + + private val DISK = "persister" + private val NETWORK = "fresh" + } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/StreamTest.java b/store/src/test/java/com/nytimes/android/external/store3/StreamTest.java deleted file mode 100644 index 2e8dd5de8..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/StreamTest.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.runners.MockitoJUnitRunner; - -import io.reactivex.Maybe; -import io.reactivex.Single; -import io.reactivex.observers.TestObserver; - -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class StreamTest { - - private static final String TEST_ITEM = "test"; - - @Mock - Fetcher fetcher; - @Mock - Persister persister; - - private final BarCode barCode = new BarCode("key", "value"); - - private Store store; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - store = StoreBuilder.barcode() - .persister(persister) - .fetcher(fetcher) - .open(); - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(TEST_ITEM)); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(TEST_ITEM)); - - when(persister.write(barCode, TEST_ITEM)) - .thenReturn(Single.just(true)); - } - - @Test - public void testStream() { - TestObserver streamObservable = store.stream().test(); - streamObservable.assertValueCount(0); - store.get(barCode).subscribe(); - streamObservable.assertValueCount(1); - } - - @Test - public void testStreamEmitsOnlyFreshData() { - store.get(barCode).subscribe(); - TestObserver streamObservable = store.stream().test(); - streamObservable.assertValueCount(0); - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/StreamTest.kt b/store/src/test/java/com/nytimes/android/external/store3/StreamTest.kt new file mode 100644 index 000000000..41257f649 --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/StreamTest.kt @@ -0,0 +1,56 @@ +package com.nytimes.android.external.store3 + +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test + +class StreamTest { + + val fetcher: Fetcher = mock() + val persister: Persister = mock() + + private val barCode = BarCode("key", "value") + + private val store = StoreBuilder.barcode() + .persister(persister) + .fetcher(fetcher) + .open() + + @Before + fun setUp() = runBlocking { + whenever(fetcher.fetch(barCode)).thenReturn(TEST_ITEM) + + whenever(persister.read(barCode)) + .thenReturn(null) + .thenReturn(TEST_ITEM) + + whenever(persister.write(barCode, TEST_ITEM)) + .thenReturn(true) + } + + @Test + fun testStream() = runBlocking { + val streamObservable = store.stream().test() + streamObservable.assertValueCount(0) + store.get(barCode) + streamObservable.assertValueCount(1) + } + + @Test + fun testStreamEmitsOnlyFreshData() = runBlocking { + store.get(barCode) + val streamObservable = store.stream().test() + streamObservable.assertValueCount(0) + } + + companion object { + + private val TEST_ITEM = "test" + } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.java b/store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.java deleted file mode 100644 index e398845e9..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.nytimes.android.external.store3.room; - -import com.nytimes.android.external.store3.base.Clearable; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.StalePolicy; -import com.nytimes.android.external.store3.base.impl.room.StoreRoom; -import com.nytimes.android.external.store3.base.room.RoomPersister; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; - -import java.util.concurrent.atomic.AtomicInteger; - -import javax.annotation.Nonnull; - -import io.reactivex.Observable; -import io.reactivex.Single; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class ClearStoreRoomTest { - @Mock - RoomClearingPersister persister; - private AtomicInteger networkCalls; - private StoreRoom store; - - @Before - public void setUp() { - networkCalls = new AtomicInteger(0); - store = StoreRoom.from( - barCode -> Single.fromCallable(() -> networkCalls.incrementAndGet()), - persister, - StalePolicy.UNSPECIFIED); - } - - @Test - public void testClearSingleBarCode() { - // one request should produce one call - BarCode barcode = new BarCode("type", "key"); - - when(persister.read(barcode)) - .thenReturn(Observable.empty()) //read from disk on get - .thenReturn(Observable.just(1)) //read from disk after fetching from network - .thenReturn(Observable.empty()) //read from disk after clearing - .thenReturn(Observable.just(1)); //read from disk after making additional network call - - store.get(barcode).test().awaitTerminalEvent(); - assertThat(networkCalls.intValue()).isEqualTo(1); - - // after clearing the memory another call should be made - store.clear(barcode); - store.get(barcode).test().awaitTerminalEvent(); - verify(persister).clear(barcode); - assertThat(networkCalls.intValue()).isEqualTo(2); - } - - @Test - public void testClearAllBarCodes() { - BarCode barcode1 = new BarCode("type1", "key1"); - BarCode barcode2 = new BarCode("type2", "key2"); - - when(persister.read(barcode1)) - .thenReturn(Observable.empty()) //read from disk - .thenReturn(Observable.just(1)) //read from disk after fetching from network - .thenReturn(Observable.empty()) //read from disk after clearing disk cache - .thenReturn(Observable.just(1)); //read from disk after making additional network call - - when(persister.read(barcode2)) - .thenReturn(Observable.empty()) //read from disk - .thenReturn(Observable.just(1)) //read from disk after fetching from network - .thenReturn(Observable.empty()) //read from disk after clearing disk cache - .thenReturn(Observable.just(1)); //read from disk after making additional network call - - - // each request should produce one call - store.get(barcode1).test().awaitTerminalEvent(); - store.get(barcode2).test().awaitTerminalEvent(); - assertThat(networkCalls.intValue()).isEqualTo(2); - - store.clear(); - - // after everything is cleared each request should produce another 2 calls - store.get(barcode1).test().awaitTerminalEvent(); - store.get(barcode2).test().awaitTerminalEvent(); - assertThat(networkCalls.intValue()).isEqualTo(4); - } - - //everything will be mocked - static class RoomClearingPersister implements RoomPersister, Clearable { - @Override - public void clear(@Nonnull BarCode key) { - throw new RuntimeException(); - } - - @Nonnull - @Override - public Observable read(@Nonnull BarCode barCode) { - throw new RuntimeException(); - } - - @Override - public void write(@Nonnull BarCode barCode, @Nonnull Integer integer) { - //noop - } - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.kt b/store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.kt new file mode 100644 index 000000000..c44b776ce --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.kt @@ -0,0 +1,90 @@ +package com.nytimes.android.external.store3.room + +import com.nytimes.android.external.store3.base.Clearable +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.StalePolicy +import com.nytimes.android.external.store3.base.impl.room.StoreRoom +import com.nytimes.android.external.store3.base.room.RoomPersister +import io.reactivex.Observable +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify +import java.util.concurrent.atomic.AtomicInteger + +class ClearStoreRoomTest { + @Mock + internal var persister: RoomClearingPersister? = null + private val networkCalls: AtomicInteger = AtomicInteger(0) + private var store = StoreRoom.from({ Observable.fromCallable { networkCalls.incrementAndGet() } }, + persister, + StalePolicy.UNSPECIFIED) + + @Test + fun testClearSingleBarCode() { + // one request should produce one call + val barcode = BarCode("type", "key") + + `when`(persister!!.read(barcode)) + .thenReturn(Observable.empty()) //read from disk on get + .thenReturn(Observable.just(1)) //read from disk after fetching from network + .thenReturn(Observable.empty()) //read from disk after clearing + .thenReturn(Observable.just(1)) //read from disk after making additional network call + + store!!.get(barcode).test().awaitTerminalEvent() + assertThat(networkCalls.toInt()).isEqualTo(1) + + // after clearing the memory another call should be made + store!!.clear(barcode) + store!!.get(barcode).test().awaitTerminalEvent() + verify(persister).clear(barcode) + assertThat(networkCalls.toInt()).isEqualTo(2) + } + + @Test + fun testClearAllBarCodes() { + val barcode1 = BarCode("type1", "key1") + val barcode2 = BarCode("type2", "key2") + + `when`(persister!!.read(barcode1)) + .thenReturn(Observable.empty()) //read from disk + .thenReturn(Observable.just(1)) //read from disk after fetching from network + .thenReturn(Observable.empty()) //read from disk after clearing disk cache + .thenReturn(Observable.just(1)) //read from disk after making additional network call + + `when`(persister!!.read(barcode2)) + .thenReturn(Observable.empty()) //read from disk + .thenReturn(Observable.just(1)) //read from disk after fetching from network + .thenReturn(Observable.empty()) //read from disk after clearing disk cache + .thenReturn(Observable.just(1)) //read from disk after making additional network call + + + // each request should produce one call + store!!.get(barcode1).test().awaitTerminalEvent() + store!!.get(barcode2).test().awaitTerminalEvent() + assertThat(networkCalls.toInt()).isEqualTo(2) + + store!!.clear() + + // after everything is cleared each request should produce another 2 calls + store!!.get(barcode1).test().awaitTerminalEvent() + store!!.get(barcode2).test().awaitTerminalEvent() + assertThat(networkCalls.toInt()).isEqualTo(4) + } + + //everything will be mocked + internal class RoomClearingPersister : RoomPersister, Clearable { + override fun clear(key: BarCode) { + throw RuntimeException() + } + + override fun read(barCode: BarCode): Observable { + throw RuntimeException() + } + + override fun write(barCode: BarCode, integer: Int) { + //noop + } + } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.java b/store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.java deleted file mode 100644 index cf53e76e3..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.nytimes.android.external.store3.room; - -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.StalePolicy; -import com.nytimes.android.external.store3.base.impl.room.StoreRoom; -import com.nytimes.android.external.store3.base.room.RoomPersister; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.concurrent.atomic.AtomicInteger; - -import io.reactivex.Observable; -import io.reactivex.Single; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class StoreRoomTest { - - private static final String DISK = "disk"; - private static final String NETWORK = "fresh"; - final AtomicInteger counter = new AtomicInteger(0); - @Mock - Fetcher fetcher; - @Mock - RoomPersister persister; - private final BarCode barCode = new BarCode("key", "value"); - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testSimple() { - - StoreRoom simpleStore = StoreRoom.from( - fetcher, - persister, - StalePolicy.UNSPECIFIED - ); - - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)); - - when(persister.read(barCode)) - .thenReturn(Observable.empty()) - .thenReturn(Observable.just(DISK)); - - - String value = simpleStore.get(barCode).blockingFirst(); - - assertThat(value).isEqualTo(DISK); - value = simpleStore.get(barCode).blockingFirst(); - assertThat(value).isEqualTo(DISK); - verify(fetcher, times(1)).fetch(barCode); - } - - - @Test - public void testDoubleTap() { - StoreRoom simpleStore = StoreRoom.from( - fetcher, - persister, - StalePolicy.UNSPECIFIED - ); - - Single networkSingle = - Single.create(emitter -> { - if (counter.incrementAndGet() == 1) { - emitter.onSuccess(NETWORK); - } else { - emitter.onError(new RuntimeException("Yo Dawg your inflight is broken")); - } - }); - - when(fetcher.fetch(barCode)) - .thenReturn(networkSingle); - - when(persister.read(barCode)) - .thenReturn(Observable.empty()) - .thenReturn(Observable.just(DISK)); - - - String response = simpleStore.get(barCode) - .zipWith(simpleStore.get(barCode), (s, s2) -> "hello") - .blockingFirst(); - assertThat(response).isEqualTo("hello"); - verify(fetcher, times(1)).fetch(barCode); - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.kt b/store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.kt new file mode 100644 index 000000000..4aa8687dc --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/room/StoreRoomTest.kt @@ -0,0 +1,86 @@ +package com.nytimes.android.external.store3.room + +import com.nhaarman.mockitokotlin2.mock +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.StalePolicy +import com.nytimes.android.external.store3.base.impl.room.StoreRoom +import com.nytimes.android.external.store3.base.room.RoomFetcher +import com.nytimes.android.external.store3.base.room.RoomPersister +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.functions.BiFunction +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.mockito.Mockito.* +import java.util.concurrent.atomic.AtomicInteger + +class StoreRoomTest { + val counter = AtomicInteger(0) + val fetcher: RoomFetcher = mock() + val persister: RoomPersister = mock() + private val barCode = BarCode("key", "value") + + @Test + fun testSimple() { + + val simpleStore = StoreRoom.from( + fetcher, + persister, + StalePolicy.UNSPECIFIED + ) + + + `when`(fetcher.fetch(barCode)) + .thenReturn(Observable.just(NETWORK)) + + `when`(persister.read(barCode)) + .thenReturn(Observable.empty()) + .thenReturn(Observable.just(DISK)) + + + var value = simpleStore.get(barCode).blockingFirst() + + assertThat(value).isEqualTo(DISK) + value = simpleStore.get(barCode).blockingFirst() + assertThat(value).isEqualTo(DISK) + verify(fetcher, times(1)).fetch(barCode) + } + + + @Test + fun testDoubleTap() { + val simpleStore = StoreRoom.from( + fetcher, + persister, + StalePolicy.UNSPECIFIED + ) + + val networkSingle = Single.create { emitter -> + if (counter.incrementAndGet() == 1) { + emitter.onSuccess(NETWORK) + } else { + emitter.onError(RuntimeException("Yo Dawg your inflight is broken")) + } + } + + `when`(fetcher.fetch(barCode)) + .thenReturn(networkSingle.toObservable()) + + `when`(persister.read(barCode)) + .thenReturn(Observable.empty()) + .thenReturn(Observable.just(DISK)) + + + val response = simpleStore.get(barCode) + .zipWith(simpleStore.get(barCode), BiFunction { s, s2 -> "hello" }) + .blockingFirst() + assertThat(response).isEqualTo("hello") + verify(fetcher, times(1)).fetch(barCode) + } + + companion object { + + private val DISK = "disk" + private val NETWORK = "fresh" + } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.java b/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.java deleted file mode 100644 index a7fab548a..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.nytimes.android.external.store3.util; - -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.MemoryPolicy; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import static org.assertj.core.api.Assertions.assertThat; -import java.util.concurrent.TimeUnit; - -public class NoopPersisterTest { - - @Rule public ExpectedException exception = ExpectedException.none(); - - @Test - public void writeReadTest() { - BarCode barCode = new BarCode("key", "value"); - NoopPersister persister = NoopPersister.Companion.create(); - boolean success = persister.write(barCode, "foo").blockingGet(); - assertThat(success).isTrue(); - String rawValue = persister.read(barCode).blockingGet(); - assertThat(rawValue).isEqualTo("foo"); - } - - @Test - public void noopParserFuncTest() { - NoopParserFunc noopParserFunc = new NoopParserFunc<>(); - String input = "foo"; - String output = noopParserFunc.apply(input); - assertThat(input).isEqualTo(output); - //intended object ref comparison - assertThat(input).isSameAs(output); - } - - // https://github.com/NYTimes/Store/issues/312 - @Test - public void testReadingOfMemoryPolicies() { - MemoryPolicy expireAfterWritePolicy = MemoryPolicy.builder() - .setExpireAfterWrite(1) - .setExpireAfterTimeUnit(TimeUnit.HOURS) - .build(); - NoopPersister.Companion.create(expireAfterWritePolicy); - - MemoryPolicy expireAfterAccessPolicy = MemoryPolicy.builder() - .setExpireAfterAccess(1) - .setExpireAfterTimeUnit(TimeUnit.HOURS) - .build(); - NoopPersister.Companion.create(expireAfterAccessPolicy); - - exception.expect(IllegalArgumentException.class); - exception.expectMessage("No expiry policy set"); - MemoryPolicy incompletePolicy = MemoryPolicy.builder() - .setExpireAfterTimeUnit(TimeUnit.HOURS) - .build(); - NoopPersister.Companion.create(incompletePolicy); - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.kt b/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.kt new file mode 100644 index 000000000..489a7e171 --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.kt @@ -0,0 +1,59 @@ +package com.nytimes.android.external.store3.util + +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.MemoryPolicy +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.rules.ExpectedException +import java.util.concurrent.TimeUnit + +class NoopPersisterTest { + + @Rule + var exception = ExpectedException.none() + + @Test + fun writeReadTest() = runBlocking { + val barCode = BarCode("key", "value") + val persister = NoopPersister.create() + val success = persister.write(barCode, "foo") + assertThat(success).isTrue() + val rawValue = persister.read(barCode)!! + assertThat(rawValue).isEqualTo("foo") + } + + @Test + fun noopParserFuncTest() = runBlocking { + val noopParserFunc = NoopParserFunc() + val input = "foo" + val output = noopParserFunc.apply(input) + assertThat(input).isEqualTo(output) + //intended object ref comparison + assertThat(input).isSameAs(output) + } + + // https://github.com/NYTimes/Store/issues/312 + @Test + fun testReadingOfMemoryPolicies() = runBlocking { + val expireAfterWritePolicy = MemoryPolicy.builder() + .setExpireAfterWrite(1) + .setExpireAfterTimeUnit(TimeUnit.HOURS) + .build() + NoopPersister.create(expireAfterWritePolicy) + + val expireAfterAccessPolicy = MemoryPolicy.builder() + .setExpireAfterAccess(1) + .setExpireAfterTimeUnit(TimeUnit.HOURS) + .build() + NoopPersister.create(expireAfterAccessPolicy) + + exception.expect(IllegalArgumentException::class.java) + exception.expectMessage("No expiry policy set") + val incompletePolicy = MemoryPolicy.builder() + .setExpireAfterTimeUnit(TimeUnit.HOURS) + .build() + NoopPersister.create(incompletePolicy) + } +} From 9b01921283afcf9c5c86e3b0888d77d496345f68 Mon Sep 17 00:00:00 2001 From: fabioCollini Date: Mon, 11 Feb 2019 15:34:40 -0500 Subject: [PATCH 053/498] Restored interface --- .../android/external/store3/base/impl/ParsingFetcher.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.kt index eb9901d9d..2cc741a8a 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/ParsingFetcher.kt @@ -10,9 +10,9 @@ import com.nytimes.android.external.store3.base.Parser */ class ParsingFetcher (private val rawFetcher: Fetcher, - private val parser: Parser) { + private val parser: Parser) : Fetcher { - suspend fun fetch(key: Key): Parsed { + override suspend fun fetch(key: Key): Parsed { return rawFetcher.fetch(key).let { parser.apply(it) } } From 4577142552156c67742af51fad7c35f0fbf84991 Mon Sep 17 00:00:00 2001 From: fabioCollini Date: Mon, 11 Feb 2019 16:00:38 -0500 Subject: [PATCH 054/498] Fix compilation errors in tests --- .../external/store3/base/impl/RealStore.kt | 16 +- .../store3/base/impl/RealStoreBuilder.kt | 9 + .../external/store3/StoreBuilderTest.java | 71 -------- .../external/store3/StoreBuilderTest.kt | 47 +++++ .../external/store3/StoreWithParserTest.kt | 171 ++++++++---------- .../external/store3/StreamOneKeyTest.java | 85 --------- .../external/store3/StreamOneKeyTest.kt | 75 ++++++++ 7 files changed, 217 insertions(+), 257 deletions(-) delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/StoreBuilderTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/StoreBuilderTest.kt delete mode 100644 store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.java create mode 100644 store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.kt diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.kt index dec386b1b..e3100dc9f 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.kt @@ -9,8 +9,6 @@ import com.nytimes.android.external.store3.util.KeyParser import com.nytimes.android.external.store3.util.NoKeyParser import com.nytimes.android.external.store3.util.NoopParserFunc import com.nytimes.android.external.store3.util.NoopPersister - -import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Single @@ -37,12 +35,12 @@ open class RealStore : Store { StalePolicy.UNSPECIFIED) } - constructor(fetcher: Fetcher, - persister: Persister, - parser: Parser) { - internalStore = RealInternalStore(fetcher, - persister, - NoKeyParser(parser), + constructor(fetcher: Fetcher<*, Key>, + persister: Persister<*, Key>, + parser: Parser<*, Parsed>) { + internalStore = RealInternalStore(fetcher as Fetcher, + persister as Persister, + NoKeyParser(parser as Parser), StalePolicy.UNSPECIFIED) } @@ -71,7 +69,7 @@ open class RealStore : Store { } fun getWithResult(key: Key): Single> { -TODO("not implemented") + TODO("not implemented") } fun getRefreshing(key: Key): Observable { diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt index 5230ff90e..14807bed0 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt @@ -56,6 +56,15 @@ class RealStoreBuilder { return this } + fun parser(parser: suspend (Raw) -> Parsed): RealStoreBuilder { + this.parser = NoKeyParser(object : Parser { + override suspend fun apply(raw: Raw): Parsed { + return parser(raw) + } + }) + return this + } + fun parser(parser: KeyParser): RealStoreBuilder { this.parser = parser diff --git a/store/src/test/java/com/nytimes/android/external/store3/StoreBuilderTest.java b/store/src/test/java/com/nytimes/android/external/store3/StoreBuilderTest.java deleted file mode 100644 index 70d854a68..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/StoreBuilderTest.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.nytimes.android.external.store3; - - -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; - -import org.junit.Test; - -import java.util.Date; - -import javax.annotation.Nonnull; - -import io.reactivex.Maybe; -import io.reactivex.Single; - -import static org.assertj.core.api.Assertions.assertThat; - -public class StoreBuilderTest { - - public static final Date DATE = new Date(); - - @Test - public void testBuildersBuildWithCorrectTypes() { - //test is checking whether types are correct in builders - Store store = StoreBuilder.parsedWithKey() - .fetcher(key -> Single.just(String.valueOf(key))) - .persister(new Persister() { - @Nonnull - @Override - public Maybe read(@Nonnull Integer key) { - return Maybe.just(String.valueOf(key)); - } - - @Nonnull - @Override - public Single write(@Nonnull Integer key, @Nonnull String s) { - return Single.just(true); - } - }) - .parser(s -> DATE) - .open(); - - - Store barCodeStore = StoreBuilder.barcode().fetcher(new Fetcher() { - @Nonnull - @Override - public Single fetch(@Nonnull BarCode barCode) { - return Single.just(DATE); - } - }).open(); - - - Store keyStore = StoreBuilder.key() - .fetcher(new Fetcher() { - @Nonnull - @Override - public Single fetch(@Nonnull Integer key) { - return Single.just(DATE); - } - }) - .open(); - Date result = store.get(5).blockingGet(); - result = barCodeStore.get(new BarCode("test", "5")).blockingGet(); - result = keyStore.get(5).blockingGet(); - assertThat(result).isNotNull(); - - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/StoreBuilderTest.kt b/store/src/test/java/com/nytimes/android/external/store3/StoreBuilderTest.kt new file mode 100644 index 000000000..b1aa6a408 --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/StoreBuilderTest.kt @@ -0,0 +1,47 @@ +package com.nytimes.android.external.store3 + + +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import java.util.* + +class StoreBuilderTest { + + @Test + fun testBuildersBuildWithCorrectTypes() = runBlocking { + //test is checking whether types are correct in builders + val store = StoreBuilder.parsedWithKey() + .fetcher { key -> key.toString() } + .persister(object : Persister { + override suspend fun read(key: Int): String? { + return key.toString() + } + + override suspend fun write(key: Int, raw: String) = true + }) + .parser { DATE } + .open() + + + val barCodeStore = StoreBuilder.barcode().fetcher { DATE }.open() + + + val keyStore = StoreBuilder.key() + .fetcher { DATE } + .open() + var result = store.get(5) + result = barCodeStore.get(BarCode("test", "5")) + result = keyStore.get(5) + assertThat(result).isNotNull() + + } + + companion object { + + val DATE = Date() + } +} diff --git a/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.kt b/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.kt index ba0f6a5a3..5b3f88a02 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.kt @@ -1,6 +1,6 @@ package com.nytimes.android.external.store3 -import com.nytimes.android.external.store.util.Result +import com.nhaarman.mockitokotlin2.mock import com.nytimes.android.external.store3.base.Fetcher import com.nytimes.android.external.store3.base.Parser import com.nytimes.android.external.store3.base.Persister @@ -8,141 +8,128 @@ import com.nytimes.android.external.store3.base.impl.BarCode import com.nytimes.android.external.store3.base.impl.ParsingStoreBuilder import io.reactivex.Maybe import io.reactivex.Single +import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import org.mockito.Mock import org.mockito.Mockito.* import org.mockito.MockitoAnnotations class StoreWithParserTest { - @Mock - internal var fetcher: Fetcher? = null - @Mock - internal var persister: Persister? = null - @Mock - internal var parser: Parser? = null + val fetcher: Fetcher = mock() + val persister: Persister = mock() + val parser: Parser = mock() private val barCode = BarCode("key", "value") @Test - @Throws(Exception::class) - fun testSimple() { - MockitoAnnotations.initMocks(this) - - + fun testSimple() = runBlocking { val simpleStore = ParsingStoreBuilder.builder() - .persister(persister!!) - .fetcher(fetcher!!) - .parser(parser!!) + .persister(persister) + .fetcher(fetcher) + .parser(parser) .open() - `when`(fetcher!!.fetch(barCode)) + `when`(fetcher.fetch(barCode)) .thenReturn(Single.just(NETWORK)) - `when`(persister!!.read(barCode)) + `when`(persister.read(barCode)) .thenReturn(Maybe.empty()) .thenReturn(Maybe.just(DISK)) - `when`(persister!!.write(barCode, NETWORK)) + `when`(persister.write(barCode, NETWORK)) .thenReturn(Single.just(true)) - `when`(parser!!.apply(DISK)).thenReturn(barCode.key) + `when`(parser.apply(DISK)).thenReturn(barCode.key) - var value = simpleStore.get(barCode).blockingGet() + var value = simpleStore.get(barCode) assertThat(value).isEqualTo(barCode.key) - value = simpleStore.get(barCode).blockingGet() + value = simpleStore.get(barCode) assertThat(value).isEqualTo(barCode.key) verify>(fetcher, times(1)).fetch(barCode) } - @Test - @Throws(Exception::class) - fun testSimpleWithResult() { - MockitoAnnotations.initMocks(this) - - - val simpleStore = ParsingStoreBuilder.builder() - .persister(persister!!) - .fetcher(fetcher!!) - .parser(parser!!) - .open() - - `when`(fetcher!!.fetch(barCode)) - .thenReturn(Single.just(NETWORK)) - - `when`(persister!!.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)) - - `when`(persister!!.write(barCode, NETWORK)) - .thenReturn(Single.just(true)) - - `when`(parser!!.apply(DISK)).thenReturn(barCode.key) - - var result = simpleStore.getWithResult(barCode).blockingGet() - assertThat(result.source()).isEqualTo(Result.Source.NETWORK) - assertThat(result.value()).isEqualTo(barCode.key) - - result = simpleStore.getWithResult(barCode).blockingGet() - assertThat(result.source()).isEqualTo(Result.Source.CACHE) - assertThat(result.value()).isEqualTo(barCode.key) - verify>(fetcher, times(1)).fetch(barCode) - } +// @Test +// fun testSimpleWithResult() = runBlocking { +// val simpleStore = ParsingStoreBuilder.builder() +// .persister(persister) +// .fetcher(fetcher) +// .parser(parser) +// .open() +// +// `when`(fetcher.fetch(barCode)) +// .thenReturn(Single.just(NETWORK)) +// +// `when`(persister.read(barCode)) +// .thenReturn(Maybe.empty()) +// .thenReturn(Maybe.just(DISK)) +// +// `when`(persister.write(barCode, NETWORK)) +// .thenReturn(Single.just(true)) +// +// `when`(parser.apply(DISK)).thenReturn(barCode.key) +// +// var result = simpleStore.getWithResult(barCode) +// assertThat(result.source()).isEqualTo(Result.Source.NETWORK) +// assertThat(result.value()).isEqualTo(barCode.key) +// +// result = simpleStore.getWithResult(barCode) +// assertThat(result.source()).isEqualTo(Result.Source.CACHE) +// assertThat(result.value()).isEqualTo(barCode.key) +// verify>(fetcher, times(1)).fetch(barCode) +// } @Test - @Throws(Exception::class) - fun testSubclass() { + fun testSubclass() = runBlocking { MockitoAnnotations.initMocks(this) - val simpleStore = SampleParsingStore(fetcher!!, persister!!, parser!!) + val simpleStore = SampleParsingStore(fetcher, persister, parser) - `when`(fetcher!!.fetch(barCode)) + `when`(fetcher.fetch(barCode)) .thenReturn(Single.just(NETWORK)) - `when`(persister!!.read(barCode)) + `when`(persister.read(barCode)) .thenReturn(Maybe.empty()) .thenReturn(Maybe.just(DISK)) - `when`(persister!!.write(barCode, NETWORK)) + `when`(persister.write(barCode, NETWORK)) .thenReturn(Single.just(true)) - `when`(parser!!.apply(DISK)).thenReturn(barCode.key) + `when`(parser.apply(DISK)).thenReturn(barCode.key) - var value = simpleStore.get(barCode).blockingGet() + var value = simpleStore.get(barCode) assertThat(value).isEqualTo(barCode.key) - value = simpleStore.get(barCode).blockingGet() + value = simpleStore.get(barCode) assertThat(value).isEqualTo(barCode.key) verify>(fetcher, times(1)).fetch(barCode) } - @Test - @Throws(Exception::class) - fun testSubclassWithResult() { - MockitoAnnotations.initMocks(this) - - val simpleStore = SampleParsingStore(fetcher!!, persister!!, parser!!) - - `when`(fetcher!!.fetch(barCode)) - .thenReturn(Single.just(NETWORK)) - - `when`(persister!!.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)) - - `when`(persister!!.write(barCode, NETWORK)) - .thenReturn(Single.just(true)) - - `when`(parser!!.apply(DISK)).thenReturn(barCode.key) - - var result = simpleStore.getWithResult(barCode).blockingGet() - assertThat(result.source()).isEqualTo(Result.Source.NETWORK) - assertThat(result.value()).isEqualTo(barCode.key) - - result = simpleStore.getWithResult(barCode).blockingGet() - assertThat(result.source()).isEqualTo(Result.Source.CACHE) - assertThat(result.value()).isEqualTo(barCode.key) - verify>(fetcher, times(1)).fetch(barCode) - } +// @Test +// fun testSubclassWithResult() = runBlocking { +// MockitoAnnotations.initMocks(this) +// +// val simpleStore = SampleParsingStore(fetcher, persister, parser) +// +// `when`(fetcher.fetch(barCode)) +// .thenReturn(Single.just(NETWORK)) +// +// `when`(persister.read(barCode)) +// .thenReturn(Maybe.empty()) +// .thenReturn(Maybe.just(DISK)) +// +// `when`(persister.write(barCode, NETWORK)) +// .thenReturn(Single.just(true)) +// +// `when`(parser.apply(DISK)).thenReturn(barCode.key) +// +// var result = simpleStore.getWithResult(barCode) +// assertThat(result.source()).isEqualTo(Result.Source.NETWORK) +// assertThat(result.value()).isEqualTo(barCode.key) +// +// result = simpleStore.getWithResult(barCode) +// assertThat(result.source()).isEqualTo(Result.Source.CACHE) +// assertThat(result.value()).isEqualTo(barCode.key) +// verify>(fetcher, times(1)).fetch(barCode) +// } companion object { diff --git a/store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.java b/store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.java deleted file mode 100644 index e0299e63e..000000000 --- a/store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.nytimes.android.external.store3; - -import com.nytimes.android.external.store3.base.Fetcher; -import com.nytimes.android.external.store3.base.Persister; -import com.nytimes.android.external.store3.base.impl.BarCode; -import com.nytimes.android.external.store3.base.impl.Store; -import com.nytimes.android.external.store3.base.impl.StoreBuilder; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.runners.MockitoJUnitRunner; - -import io.reactivex.Maybe; -import io.reactivex.Single; -import io.reactivex.observers.TestObserver; - -import static org.mockito.Mockito.when; - -@RunWith(MockitoJUnitRunner.class) -public class StreamOneKeyTest { - - private static final String TEST_ITEM = "test"; - private static final String TEST_ITEM2 = "test2"; - - @Mock - Fetcher fetcher; - @Mock - Persister persister; - - private final BarCode barCode = new BarCode("key", "value"); - private final BarCode barCode2 = new BarCode("key2", "value2"); - - private Store store; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - store = StoreBuilder.barcode() - .persister(persister) - .fetcher(fetcher) - .open(); - - when(fetcher.fetch(barCode)) - .thenReturn(Single.just(TEST_ITEM)) - .thenReturn(Single.just(TEST_ITEM2)); - - when(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(TEST_ITEM)) - .thenReturn(Maybe.just(TEST_ITEM2)); - - when(persister.write(barCode, TEST_ITEM)) - .thenReturn(Single.just(true)); - when(persister.write(barCode, TEST_ITEM2)) - .thenReturn(Single.just(true)); - } - - @Test - public void testStream() { - TestObserver streamObservable = store.stream(barCode).test(); - //first time we subscribe to stream it will fail getting from memory & disk and instead - //fresh from network, write to disk and notifiy subscribers - streamObservable.assertValueCount(1); - - store.clear(); - //fresh should notify subscribers again - store.fresh(barCode).test().awaitCount(1); - streamObservable.assertValues(TEST_ITEM, TEST_ITEM2); - - //get for another barcode should not trigger a stream for barcode1 - when(fetcher.fetch(barCode2)) - .thenReturn(Single.just(TEST_ITEM)); - when(persister.read(barCode2)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(TEST_ITEM)); - when(persister.write(barCode2, TEST_ITEM)) - .thenReturn(Single.just(true)); - store.get(barCode2).test().awaitCount(1); - streamObservable.assertValueCount(2); - } -} diff --git a/store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.kt b/store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.kt new file mode 100644 index 000000000..9e525c761 --- /dev/null +++ b/store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.kt @@ -0,0 +1,75 @@ +package com.nytimes.android.external.store3 + +import com.nhaarman.mockitokotlin2.mock +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import io.reactivex.Maybe +import io.reactivex.Single +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import org.mockito.Mockito.`when` + +class StreamOneKeyTest { + + val fetcher: Fetcher = mock() + val persister: Persister = mock() + + private val barCode = BarCode("key", "value") + private val barCode2 = BarCode("key2", "value2") + + private val store: Store = StoreBuilder.barcode() + .persister(persister) + .fetcher(fetcher) + .open() + + @Before + fun setUp() = runBlocking { + `when`(fetcher.fetch(barCode)) + .thenReturn(Single.just(TEST_ITEM)) + .thenReturn(Single.just(TEST_ITEM2)) + + `when`(persister.read(barCode)) + .thenReturn(Maybe.empty()) + .thenReturn(Maybe.just(TEST_ITEM)) + .thenReturn(Maybe.just(TEST_ITEM2)) + + `when`(persister.write(barCode, TEST_ITEM)) + .thenReturn(Single.just(true)) + `when`(persister.write(barCode, TEST_ITEM2)) + .thenReturn(Single.just(true)) + } + + @Test + fun testStream() = runBlocking { + val streamObservable = store.stream(barCode).test() + //first time we subscribe to stream it will fail getting from memory & disk and instead + //fresh from network, write to disk and notifiy subscribers + streamObservable.assertValueCount(1) + + store.clear() + //fresh should notify subscribers again + store.fresh(barCode) + streamObservable.assertValues(TEST_ITEM, TEST_ITEM2) + + //get for another barcode should not trigger a stream for barcode1 + `when`(fetcher.fetch(barCode2)) + .thenReturn(Single.just(TEST_ITEM)) + `when`(persister.read(barCode2)) + .thenReturn(Maybe.empty()) + .thenReturn(Maybe.just(TEST_ITEM)) + `when`(persister.write(barCode2, TEST_ITEM)) + .thenReturn(Single.just(true)) + store.get(barCode2) + streamObservable.assertValueCount(2) + } + + companion object { + + private val TEST_ITEM = "test" + private val TEST_ITEM2 = "test2" + } +} From 1a791c4c15e3a96ebdfb32cb2bd2486ddaac7095 Mon Sep 17 00:00:00 2001 From: fabioCollini Date: Mon, 11 Feb 2019 16:10:16 -0500 Subject: [PATCH 055/498] Fix some tests --- .../external/store3/ClearingPersister.kt | 2 +- .../external/store3/DontCacheErrorsTest1.kt | 3 +- .../android/external/store3/StoreTest.kt | 99 ++++++++++--------- .../store3/room/ClearStoreRoomTest.kt | 31 +++--- .../external/store3/util/NoopPersisterTest.kt | 2 +- 5 files changed, 69 insertions(+), 68 deletions(-) diff --git a/store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.kt b/store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.kt index a6d74c76f..7fefb3cb3 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/ClearingPersister.kt @@ -4,7 +4,7 @@ import com.nytimes.android.external.store3.base.Clearable import com.nytimes.android.external.store3.base.Persister import com.nytimes.android.external.store3.base.impl.BarCode -class ClearingPersister : Persister, Clearable { +open class ClearingPersister : Persister, Clearable { override suspend fun read(key: BarCode): Int? { throw RuntimeException() } diff --git a/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt b/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt index 5bcc7db86..07bd4423b 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt @@ -36,7 +36,8 @@ class DontCacheErrorsTest { try { store.get(barcode) fail() - } catch (e: Exception) { + } catch (e: RuntimeException) { + e.printStackTrace() } shouldThrow = false diff --git a/store/src/test/java/com/nytimes/android/external/store3/StoreTest.kt b/store/src/test/java/com/nytimes/android/external/store3/StoreTest.kt index 2cfde446b..5cdf08052 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/StoreTest.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/StoreTest.kt @@ -1,13 +1,13 @@ package com.nytimes.android.external.store3 import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever import com.nytimes.android.external.cache3.CacheBuilder import com.nytimes.android.external.store3.base.Fetcher import com.nytimes.android.external.store3.base.Persister import com.nytimes.android.external.store3.base.impl.BarCode import com.nytimes.android.external.store3.base.impl.StoreBuilder import com.nytimes.android.external.store3.util.NoopPersister -import io.reactivex.Maybe import io.reactivex.Single import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking @@ -32,15 +32,15 @@ class StoreTest { .open() - `when`(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)) + whenever(fetcher.fetch(barCode)) + .thenReturn(NETWORK) - `when`(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)) + whenever(persister.read(barCode)) + .thenReturn(null) + .thenReturn(DISK) - `when`(persister.write(barCode, NETWORK)) - .thenReturn(Single.just(true)) + whenever(persister.write(barCode, NETWORK)) + .thenReturn(true) var value = simpleStore.get(barCode) @@ -59,15 +59,15 @@ class StoreTest { // .open() // // -// `when`(fetcher.fetch(barCode)) -// .thenReturn(Single.just(NETWORK)) +// whenever(fetcher.fetch(barCode)) +// .thenReturn(NETWORK) // -// `when`(persister.read(barCode)) -// .thenReturn(Maybe.empty()) -// .thenReturn(Maybe.just(DISK)) +// whenever(persister.read(barCode)) +// .thenReturn(null) +// .thenReturn(DISK) // -// `when`(persister.write(barCode, NETWORK)) -// .thenReturn(Single.just(true)) +// whenever(persister.write(barCode, NETWORK)) +// .thenReturn(true) // // var result = simpleStore.getWithResult(barCode) // @@ -90,23 +90,24 @@ class StoreTest { .open() val networkSingle = Single.create { emitter -> - if (counter.incrementAndGet() == 1) { - emitter.onSuccess(NETWORK) - } else { - emitter.onError(RuntimeException("Yo Dawg your inflight is broken")) - } } - `when`(fetcher.fetch(barCode)) - .thenReturn(networkSingle) + whenever(fetcher.fetch(barCode)) + .thenAnswer { + if (counter.incrementAndGet() == 1) { + NETWORK + } else { + throw RuntimeException("Yo Dawg your inflight is broken") + } + } - `when`(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)) + whenever(persister.read(barCode)) + .thenReturn(null) + .thenReturn(DISK) - `when`(persister.write(barCode, NETWORK)) - .thenReturn(Single.just(true)) + whenever(persister.write(barCode, NETWORK)) + .thenReturn(true) val deferred = async { simpleStore.get(barCode) } @@ -133,15 +134,15 @@ class StoreTest { // } // // -// `when`(fetcher.fetch(barCode)) +// whenever(fetcher.fetch(barCode)) // .thenReturn(networkSingle) // -// `when`(persister.read(barCode)) -// .thenReturn(Maybe.empty()) -// .thenReturn(Maybe.just(DISK)) +// whenever(persister.read(barCode)) +// .thenReturn(null) +// .thenReturn(DISK) // -// `when`(persister.write(barCode, NETWORK)) -// .thenReturn(Single.just(true)) +// whenever(persister.write(barCode, NETWORK)) +// .thenReturn(true) // // // val response = simpleStore.getWithResult(barCode) @@ -160,13 +161,13 @@ class StoreTest { val simpleStore = SampleStore(fetcher, persister) simpleStore.clear() - `when`(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)) + whenever(fetcher.fetch(barCode)) + .thenReturn(NETWORK) - `when`(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)) - `when`(persister.write(barCode, NETWORK)).thenReturn(Single.just(true)) + whenever(persister.read(barCode)) + .thenReturn(null) + .thenReturn(DISK) + whenever(persister.write(barCode, NETWORK)).thenReturn(true) var value = simpleStore.get(barCode) assertThat(value).isEqualTo(DISK) @@ -181,13 +182,13 @@ class StoreTest { // val simpleStore = SampleStore(fetcher, persister) // simpleStore.clear() // -// `when`(fetcher.fetch(barCode)) -// .thenReturn(Single.just(NETWORK)) +// whenever(fetcher.fetch(barCode)) +// .thenReturn(NETWORK) // -// `when`(persister.read(barCode)) -// .thenReturn(Maybe.empty()) -// .thenReturn(Maybe.just(DISK)) -// `when`(persister.write(barCode, NETWORK)).thenReturn(Single.just(true)) +// whenever(persister.read(barCode)) +// .thenReturn(null) +// .thenReturn(DISK) +// whenever(persister.write(barCode, NETWORK)).thenReturn(true) // // var result = simpleStore.getWithResult(barCode) // @@ -207,8 +208,8 @@ class StoreTest { val simpleStore = SampleStore(fetcher, persister) - `when`(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)) + whenever(fetcher.fetch(barCode)) + .thenReturn(NETWORK) var value = simpleStore.get(barCode) verify>(fetcher, times(1)).fetch(barCode) @@ -232,8 +233,8 @@ class StoreTest { // val simpleStore = SampleStore(fetcher, persister) // // -// `when`(fetcher.fetch(barCode)) -// .thenReturn(Single.just(NETWORK)) +// whenever(fetcher.fetch(barCode)) +// .thenReturn(NETWORK) // // var value = simpleStore.getWithResult(barCode) // verify>(fetcher, times(1)).fetch(barCode) diff --git a/store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.kt b/store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.kt index c44b776ce..413a56fa9 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/room/ClearStoreRoomTest.kt @@ -1,5 +1,6 @@ package com.nytimes.android.external.store3.room +import com.nhaarman.mockitokotlin2.mock import com.nytimes.android.external.store3.base.Clearable import com.nytimes.android.external.store3.base.impl.BarCode import com.nytimes.android.external.store3.base.impl.StalePolicy @@ -8,16 +9,14 @@ import com.nytimes.android.external.store3.base.room.RoomPersister import io.reactivex.Observable import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.verify import java.util.concurrent.atomic.AtomicInteger class ClearStoreRoomTest { - @Mock - internal var persister: RoomClearingPersister? = null + private val persister: RoomClearingPersister = mock() private val networkCalls: AtomicInteger = AtomicInteger(0) - private var store = StoreRoom.from({ Observable.fromCallable { networkCalls.incrementAndGet() } }, + private val store = StoreRoom.from({ Observable.fromCallable { networkCalls.incrementAndGet() } }, persister, StalePolicy.UNSPECIFIED) @@ -26,18 +25,18 @@ class ClearStoreRoomTest { // one request should produce one call val barcode = BarCode("type", "key") - `when`(persister!!.read(barcode)) + `when`(persister.read(barcode)) .thenReturn(Observable.empty()) //read from disk on get .thenReturn(Observable.just(1)) //read from disk after fetching from network .thenReturn(Observable.empty()) //read from disk after clearing .thenReturn(Observable.just(1)) //read from disk after making additional network call - store!!.get(barcode).test().awaitTerminalEvent() + store.get(barcode).test().awaitTerminalEvent() assertThat(networkCalls.toInt()).isEqualTo(1) // after clearing the memory another call should be made - store!!.clear(barcode) - store!!.get(barcode).test().awaitTerminalEvent() + store.clear(barcode) + store.get(barcode).test().awaitTerminalEvent() verify(persister).clear(barcode) assertThat(networkCalls.toInt()).isEqualTo(2) } @@ -47,13 +46,13 @@ class ClearStoreRoomTest { val barcode1 = BarCode("type1", "key1") val barcode2 = BarCode("type2", "key2") - `when`(persister!!.read(barcode1)) + `when`(persister.read(barcode1)) .thenReturn(Observable.empty()) //read from disk .thenReturn(Observable.just(1)) //read from disk after fetching from network .thenReturn(Observable.empty()) //read from disk after clearing disk cache .thenReturn(Observable.just(1)) //read from disk after making additional network call - `when`(persister!!.read(barcode2)) + `when`(persister.read(barcode2)) .thenReturn(Observable.empty()) //read from disk .thenReturn(Observable.just(1)) //read from disk after fetching from network .thenReturn(Observable.empty()) //read from disk after clearing disk cache @@ -61,20 +60,20 @@ class ClearStoreRoomTest { // each request should produce one call - store!!.get(barcode1).test().awaitTerminalEvent() - store!!.get(barcode2).test().awaitTerminalEvent() + store.get(barcode1).test().awaitTerminalEvent() + store.get(barcode2).test().awaitTerminalEvent() assertThat(networkCalls.toInt()).isEqualTo(2) - store!!.clear() + store.clear() // after everything is cleared each request should produce another 2 calls - store!!.get(barcode1).test().awaitTerminalEvent() - store!!.get(barcode2).test().awaitTerminalEvent() + store.get(barcode1).test().awaitTerminalEvent() + store.get(barcode2).test().awaitTerminalEvent() assertThat(networkCalls.toInt()).isEqualTo(4) } //everything will be mocked - internal class RoomClearingPersister : RoomPersister, Clearable { + internal open class RoomClearingPersister : RoomPersister, Clearable { override fun clear(key: BarCode) { throw RuntimeException() } diff --git a/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.kt b/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.kt index 489a7e171..e726a6fe6 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/util/NoopPersisterTest.kt @@ -11,7 +11,7 @@ import java.util.concurrent.TimeUnit class NoopPersisterTest { - @Rule + @get:Rule var exception = ExpectedException.none() @Test From 9420c03078089f03c94e5df8b44ecc1fc50b916d Mon Sep 17 00:00:00 2001 From: fabioCollini Date: Mon, 11 Feb 2019 17:40:53 -0500 Subject: [PATCH 056/498] Restored MultiParser --- .../external/store3/base/impl/MultiParser.kt | 66 +++++++----------- .../store3/base/impl/RealStoreBuilder.kt | 69 ++++++++----------- .../android/external/store3/util/KeyParser.kt | 3 +- .../external/store3/util/NoKeyParser.kt | 2 +- 4 files changed, 56 insertions(+), 84 deletions(-) diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/MultiParser.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/MultiParser.kt index 7af611683..e41d3c2ef 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/MultiParser.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/MultiParser.kt @@ -1,40 +1,26 @@ -//package com.nytimes.android.external.store3.base.impl -// -//import com.nytimes.android.external.store3.util.KeyParser -//import com.nytimes.android.external.store3.util.ParserException -// -//import java.util.ArrayList -// -//import io.reactivex.annotations.NonNull -// -//import com.nytimes.android.external.cache3.Preconditions.checkArgument -//import com.nytimes.android.external.cache3.Preconditions.checkNotNull -// -//class MultiParser(parsers: List>) : KeyParser { -// -// private val parsers = ArrayList>() -// -// init { -// this.parsers.addAll(parsers) -// } -// -// private fun createParserException(): ParserException { -// return ParserException("One of the provided parsers has a wrong typing. " + "Make sure that parsers are passed in a correct order and the fromTypes match each other.") -// } -// -// @NonNull -// @Throws(ParserException::class) -// override -// suspend fun apply(@NonNull key: Key, @NonNull raw: Raw): Parsed { -// var parsed: Any = raw!! -// for (parser in parsers) { -// try { -// parsed = parser.apply(key, parsed)!! -// } catch (exception: ClassCastException) { -// throw createParserException() -// } -// -// } -// return parsed as Parsed -// } -//} +package com.nytimes.android.external.store3.base.impl + +import com.nytimes.android.external.store3.util.KeyParser +import com.nytimes.android.external.store3.util.ParserException +import io.reactivex.annotations.NonNull + +class MultiParser(private val parsers: List>) : KeyParser { + + private fun createParserException(): ParserException { + return ParserException("One of the provided parsers has a wrong typing. " + + "Make sure that parsers are passed in a correct order and the fromTypes match each other.") + } + + @NonNull + override suspend fun apply(@NonNull key: Key, @NonNull raw: Raw): Parsed { + var parsed: Any = raw!! + for (parser in parsers) { + try { + parsed = parser.apply(key, parsed)!! + } catch (exception: ClassCastException) { + throw createParserException() + } + } + return parsed as Parsed + } +} diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt index 14807bed0..adf79761e 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStoreBuilder.kt @@ -6,13 +6,14 @@ import com.nytimes.android.external.store3.util.KeyParser import com.nytimes.android.external.store3.util.NoKeyParser import com.nytimes.android.external.store3.util.NoopParserFunc import com.nytimes.android.external.store3.util.NoopPersister +import java.util.* /** * Builder where there parser is used. */ class RealStoreBuilder { - private var parser: KeyParser? = null + private val parsers = ArrayList>() private var persister: Persister? = null private var fetcher: Fetcher? = null private var memoryPolicy: MemoryPolicy? = null @@ -20,27 +21,24 @@ class RealStoreBuilder { private//remove when it is implemented... var stalePolicy = StalePolicy.UNSPECIFIED - fun fetcher(fetcher: Fetcher): RealStoreBuilder { + fun fetcher(fetcher: Fetcher): RealStoreBuilder = apply { this.fetcher = fetcher - return this } - fun fetcher(fetcher: suspend (Key) -> Raw): RealStoreBuilder { + fun fetcher(fetcher: suspend (Key) -> Raw): RealStoreBuilder = apply { this.fetcher = object : Fetcher { override suspend fun fetch(key: Key): Raw { return fetcher(key) } } - return this } - fun persister(persister: Persister): RealStoreBuilder { + fun persister(persister: Persister): RealStoreBuilder = apply { this.persister = persister - return this } fun persister(diskRead: DiskRead, - diskWrite: DiskWrite): RealStoreBuilder { + diskWrite: DiskWrite): RealStoreBuilder = apply { persister = object : Persister { override suspend fun read(key: Key): Raw? = diskRead.read(key) @@ -48,55 +46,44 @@ class RealStoreBuilder { override suspend fun write(key: Key, raw: Raw): Boolean = diskWrite.write(key, raw) } - return this } - fun parser(parser: Parser): RealStoreBuilder { - this.parser = NoKeyParser(parser) - return this + fun parser(parser: Parser): RealStoreBuilder = apply { + this.parsers.clear() + this.parsers.add(NoKeyParser(parser as Parser)) } - fun parser(parser: suspend (Raw) -> Parsed): RealStoreBuilder { - this.parser = NoKeyParser(object : Parser { - override suspend fun apply(raw: Raw): Parsed { - return parser(raw) - } - }) - return this - } - - fun parser(parser: KeyParser): RealStoreBuilder { - this.parser = parser + fun parser(parser: suspend (Raw) -> Parsed): RealStoreBuilder = + parser(NoKeyParser(object : Parser { + override suspend fun apply(raw: Raw): Parsed { + return parser(raw) + } + })) - return this + fun parser(parser: KeyParser): RealStoreBuilder = apply { + this.parsers.clear() + this.parsers.add(parser as KeyParser) } - fun parsers(parsers: List>): RealStoreBuilder { - TODO("not implemented") -// this.parsers.clear() -// for (parser in parsers) { -// this.parsers.add(NoKeyParser(parser)) -// } -// return this + fun parsers(parsers: List>): RealStoreBuilder = apply { + this.parsers.clear() + this.parsers.addAll(parsers.map { NoKeyParser(it as Parser) }) } - fun memoryPolicy(memoryPolicy: MemoryPolicy): RealStoreBuilder { + fun memoryPolicy(memoryPolicy: MemoryPolicy): RealStoreBuilder = apply { this.memoryPolicy = memoryPolicy - return this } //Store will backfill the disk cache anytime a record is stale //User will still get the stale record returned to them - fun refreshOnStale(): RealStoreBuilder { + fun refreshOnStale(): RealStoreBuilder = apply { stalePolicy = StalePolicy.REFRESH_ON_STALE - return this } //Store will try to get network source when disk data is stale //if network source throws error or is empty, stale disk data will be returned - fun networkBeforeStale(): RealStoreBuilder { + fun networkBeforeStale(): RealStoreBuilder = apply { stalePolicy = StalePolicy.NETWORK_BEFORE_STALE - return this } fun open(): Store { @@ -104,15 +91,15 @@ class RealStoreBuilder { persister = NoopPersister.create(memoryPolicy) } - if (parser == null) { + if (parsers.isEmpty()) { parser(NoopParserFunc()) } -// val multiParser = MultiParser(parsers) + val multiParser = MultiParser(parsers) - val realInternalStore: InternalStore = RealInternalStore(fetcher!!, persister!!, parser!!, memoryPolicy, stalePolicy) + val realInternalStore: RealInternalStore = RealInternalStore(fetcher!!, persister!!, multiParser, memoryPolicy, stalePolicy) - return RealStore(realInternalStore) + return RealStore(realInternalStore) } companion object { diff --git a/store/src/main/java/com/nytimes/android/external/store3/util/KeyParser.kt b/store/src/main/java/com/nytimes/android/external/store3/util/KeyParser.kt index 477233ebe..5909e6018 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/util/KeyParser.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/util/KeyParser.kt @@ -1,9 +1,8 @@ package com.nytimes.android.external.store3.util import io.reactivex.annotations.NonNull -import io.reactivex.functions.BiFunction -interface KeyParser { +interface KeyParser { @Throws(ParserException::class) suspend fun apply(@NonNull key: Key, @NonNull raw: Raw): Parsed diff --git a/store/src/main/java/com/nytimes/android/external/store3/util/NoKeyParser.kt b/store/src/main/java/com/nytimes/android/external/store3/util/NoKeyParser.kt index 496237aa7..37548401d 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/util/NoKeyParser.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/util/NoKeyParser.kt @@ -4,7 +4,7 @@ import com.nytimes.android.external.store3.base.Parser import io.reactivex.annotations.NonNull -class NoKeyParser(private val parser: Parser) : KeyParser { +class NoKeyParser(private val parser: Parser) : KeyParser { @Throws(ParserException::class) override suspend fun apply(@NonNull key: Key, @NonNull raw: Raw): Parsed { From 4efddf1ac0954acaa3815d2ea9feac81e5d4486c Mon Sep 17 00:00:00 2001 From: Mike Nakhimovich Date: Tue, 12 Feb 2019 11:40:39 -0500 Subject: [PATCH 057/498] WIP --- .../android/sample/PersistingStoreActivity.kt | 4 + .../com/nytimes/android/sample/SampleApp.kt | 2 +- .../nytimes/android/sample/ClearStoreTest.kt | 230 ++++----- .../nytimes/android/sample/KeyParserTest.kt | 86 ++-- .../com/nytimes/android/sample/SmokeTests.kt | 6 +- .../com/nytimes/android/sample/StreamTest.kt | 75 +++ build.gradle | 14 +- gradle/wrapper/gradle-wrapper.properties | 4 +- .../store3/base/impl/RealInternalStore.kt | 450 ++++++++---------- .../external/store3/base/impl/RealStore.kt | 9 +- .../external/store3/base/impl/Store.kt | 8 +- .../android/external/store3/KeyParserTest.kt | 14 +- 12 files changed, 455 insertions(+), 447 deletions(-) create mode 100644 app/src/test/java/com/nytimes/android/sample/StreamTest.kt diff --git a/app/src/main/java/com/nytimes/android/sample/PersistingStoreActivity.kt b/app/src/main/java/com/nytimes/android/sample/PersistingStoreActivity.kt index 87200d87a..90a1c5e87 100644 --- a/app/src/main/java/com/nytimes/android/sample/PersistingStoreActivity.kt +++ b/app/src/main/java/com/nytimes/android/sample/PersistingStoreActivity.kt @@ -19,6 +19,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlin.coroutines.CoroutineContext @@ -63,6 +64,9 @@ class PersistingStoreActivity : AppCompatActivity(), CoroutineScope { } + suspend fun Store.gett(key: BarCode) = + withContext(Dispatchers.IO) { get(key) } + private fun showPosts(posts: List) { postAdapter.setPosts(posts) makeText(this@PersistingStoreActivity, diff --git a/app/src/main/java/com/nytimes/android/sample/SampleApp.kt b/app/src/main/java/com/nytimes/android/sample/SampleApp.kt index d24f291b6..c31cbcb56 100644 --- a/app/src/main/java/com/nytimes/android/sample/SampleApp.kt +++ b/app/src/main/java/com/nytimes/android/sample/SampleApp.kt @@ -104,7 +104,7 @@ class SampleApp : Application() { /** * Returns a "fetcher" which will retrieve new data from the network. */ - private suspend fun fetcher(barCode: BarCode): Deferred { + private fun fetcher(barCode: BarCode): Deferred { return provideRetrofit().fetchSubredditForPersister(barCode.key, "10") } diff --git a/app/src/test/java/com/nytimes/android/sample/ClearStoreTest.kt b/app/src/test/java/com/nytimes/android/sample/ClearStoreTest.kt index 018df4254..fb67e2cfe 100644 --- a/app/src/test/java/com/nytimes/android/sample/ClearStoreTest.kt +++ b/app/src/test/java/com/nytimes/android/sample/ClearStoreTest.kt @@ -1,115 +1,115 @@ -//package com.nytimes.android.sample -// -//import com.nytimes.android.external.store3.base.Clearable -//import com.nytimes.android.external.store3.base.Fetcher -//import com.nytimes.android.external.store3.base.Persister -//import com.nytimes.android.external.store3.base.impl.BarCode -//import com.nytimes.android.external.store3.base.impl.Store -//import com.nytimes.android.external.store3.base.impl.StoreBuilder -//import kotlinx.coroutines.runBlocking -//import org.assertj.core.api.Assertions.assertThat -//import org.junit.Before -//import org.junit.Test -//import org.junit.runner.RunWith -//import org.mockito.runners.MockitoJUnitRunner -//import java.util.* -//import java.util.concurrent.atomic.AtomicInteger -// -// -//val barcode1 = BarCode("type1", "key1") -//val barcode2 = BarCode("type2", "key2") -// -//@RunWith(MockitoJUnitRunner::class) -//class ClearStoreTest { -// val persister = ClearingPersister() -// lateinit var networkCalls: AtomicInteger -// lateinit var store: Store -// -// @Before -// fun setUp() { -// networkCalls = AtomicInteger(0) -// store = StoreBuilder.barcode() -// .fetcher(object : Fetcher { -// override suspend fun fetch(key: BarCode): Int { -// return networkCalls.incrementAndGet() -// } -// -// }) -// .persister(persister) -// .open() -// -// } -// -// @Test -// fun testClearSingleBarCode() { -// runBlocking { -// // one request should produce one call -// val barcode = BarCode("type", "key") -// -// store.get(barcode) -// assertThat(networkCalls.toInt()).isEqualTo(1) -// -// // after clearing the memory another call should be made -// store.clear(barcode) -// store.get(barcode) -// assertThat(persister.isClear).isTrue() -// assertThat(networkCalls.toInt()).isEqualTo(2) -// } -// } -// -// -// @Test -// fun testClearAllBarCodes() { -// -// runBlocking { -// // each request should produce one call -// store.get(barcode1) -// store.get(barcode2) -// assertThat(networkCalls.toInt()).isEqualTo(2) -// -// store.clear() -// -// // after everything is cleared each request should produce another 2 calls -// store.get(barcode1) -// store.get(barcode2) -// assertThat(networkCalls.toInt()).isEqualTo(4) -// } -// -// -// } -//} -// -////everything will be mocked -//class ClearingPersister : Persister, Clearable { -// var isClear = false -// val barcode1Responses = LinkedList() -// val barcode2Responses = LinkedList() -// -// init { -// barcode1Responses.add(null) -// barcode1Responses.add(1) -// barcode1Responses.add(null) -// barcode1Responses.add(1) -// -// barcode2Responses.add(null) -// barcode2Responses.add(1) -// barcode2Responses.add(null) -// barcode2Responses.add(1) -// } -// -// override fun clear(key: BarCode) { -// isClear = true -// } -// -// override suspend fun read(key: BarCode): Int? { -// val diskValue = if (key == barcode1) barcode1Responses else barcode2Responses -// return diskValue.remove() -// } -// -// override suspend fun write(key: BarCode, raw: Int): Boolean { -// return when (raw) { -// in 1..5 -> true -// else -> throw RuntimeException("no good") -// } -// } -//} +package com.nytimes.android.sample + +import com.nytimes.android.external.store3.base.Clearable +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Persister +import com.nytimes.android.external.store3.base.impl.BarCode +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.runners.MockitoJUnitRunner +import java.util.* +import java.util.concurrent.atomic.AtomicInteger + + +val barcode1 = BarCode("type1", "key1") +val barcode2 = BarCode("type2", "key2") + +@RunWith(MockitoJUnitRunner::class) +class ClearStoreTest { + val persister = ClearingPersister() + lateinit var networkCalls: AtomicInteger + lateinit var store: Store + + @Before + fun setUp() { + networkCalls = AtomicInteger(0) + store = StoreBuilder.barcode() + .fetcher(object : Fetcher { + override suspend fun fetch(key: BarCode): Int { + return networkCalls.incrementAndGet() + } + + }) + .persister(persister) + .open() + + } + + @Test + fun testClearSingleBarCode() { + runBlocking { + // one request should produce one call + val barcode = BarCode("type", "key") + + store.get(barcode) + assertThat(networkCalls.toInt()).isEqualTo(1) + + // after clearing the memory another call should be made + store.clear(barcode) + store.get(barcode) + assertThat(persister.isClear).isTrue() + assertThat(networkCalls.toInt()).isEqualTo(2) + } + } + + + @Test + fun testClearAllBarCodes() { + + runBlocking { + // each request should produce one call + val result = store.get(barcode1) + store.get(barcode2) + assertThat(networkCalls.toInt()).isEqualTo(2) + + store.clear() + + // after everything is cleared each request should produce another 2 calls + store.get(barcode1) + store.get(barcode2) + assertThat(networkCalls.toInt()).isEqualTo(4) + } + + + } +} + +//everything will be mocked +class ClearingPersister : Persister, Clearable { + var isClear = false + val barcode1Responses = LinkedList() + val barcode2Responses = LinkedList() + + init { + barcode1Responses.add(null) + barcode1Responses.add(1) + barcode1Responses.add(null) + barcode1Responses.add(1) + + barcode2Responses.add(null) + barcode2Responses.add(1) + barcode2Responses.add(null) + barcode2Responses.add(1) + } + + override fun clear(key: BarCode) { + isClear = true + } + + override suspend fun read(key: BarCode): Int? { + val diskValue = if (key == barcode1) barcode1Responses else barcode2Responses + return diskValue.remove() + } + + override suspend fun write(key: BarCode, raw: Int): Boolean { + return when (raw) { + in 1..5 -> true + else -> throw RuntimeException("no good") + } + } +} diff --git a/app/src/test/java/com/nytimes/android/sample/KeyParserTest.kt b/app/src/test/java/com/nytimes/android/sample/KeyParserTest.kt index d5ddaef1d..2c589de53 100644 --- a/app/src/test/java/com/nytimes/android/sample/KeyParserTest.kt +++ b/app/src/test/java/com/nytimes/android/sample/KeyParserTest.kt @@ -1,43 +1,43 @@ -//package com.nytimes.android.sample -// -//import com.nytimes.android.external.store3.base.impl.Store -//import com.nytimes.android.external.store3.base.impl.StoreBuilder -// -//import org.junit.Before -//import org.junit.Test -//import org.junit.runner.RunWith -//import org.mockito.runners.MockitoJUnitRunner -// -//import io.reactivex.Single -//import io.reactivex.observers.TestObserver -// -// -//@RunWith(MockitoJUnitRunner::class) -//class KeyParserTest { -// private var store: Store? = null -// -// @Before -// @Throws(Exception::class) -// fun setUp() { -// store = StoreBuilder.parsedWithKey() -// .parser({ integer, s -> s + integer }) -// .fetcher({ integer -> Single.just(NETWORK) }) -// .open() -// -// } -// -// @Test -// @Throws(Exception::class) -// fun testStoreWithKeyParserFuncNoPersister() { -// val testObservable = store!!.get(KEY).test().await() -// testObservable.assertNoErrors() -// .assertValues(NETWORK + KEY) -// .awaitTerminalEvent() -// } -// -// companion object { -// -// val NETWORK = "Network" -// val KEY = 5 -// } -//} +package com.nytimes.android.sample + +import com.nytimes.android.external.store3.base.Fetcher +import com.nytimes.android.external.store3.base.Parser +import com.nytimes.android.external.store3.base.impl.Store +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.runners.MockitoJUnitRunner + +@RunWith(MockitoJUnitRunner::class) +class KeyParserTest { + lateinit var store: Store + + @Before + @Throws(Exception::class) + fun setUp() { + store = StoreBuilder.parsedWithKey() + .parser(object : Parser { + override suspend fun apply(raw: String): String = raw + KEY + }) + .fetcher(object : Fetcher { + override suspend fun fetch(key: Int): String = NETWORK + }) + .open() + } + + @Test + fun testStoreWithKeyParserFuncNoPersister() { + runBlocking { + assertThat(store.get(KEY)).isEqualTo(NETWORK + KEY) + } + } + + companion object { + + val NETWORK = "Network" + val KEY = 5 + } +} diff --git a/app/src/test/java/com/nytimes/android/sample/SmokeTests.kt b/app/src/test/java/com/nytimes/android/sample/SmokeTests.kt index 04fd21527..ffd9d40b3 100644 --- a/app/src/test/java/com/nytimes/android/sample/SmokeTests.kt +++ b/app/src/test/java/com/nytimes/android/sample/SmokeTests.kt @@ -158,8 +158,8 @@ class SmokeTests { persister = object : Persister { override suspend fun read(key: BarCode): String? { when { - counter.get() == 1 && key == first -> return "first" - counter.get() == 2 && key == second -> return "second" + counter.get() >= 1 && key == first -> return "first" + counter.get() >= 2 && key == second -> return "second" else -> return null } } @@ -210,7 +210,7 @@ class SmokeTests { companion object { - private val DISK = "disk" + val DISK = "disk" private val NETWORK = "fresh" private val MEMORY = "memory" } diff --git a/app/src/test/java/com/nytimes/android/sample/StreamTest.kt b/app/src/test/java/com/nytimes/android/sample/StreamTest.kt new file mode 100644 index 000000000..10d51d9d1 --- /dev/null +++ b/app/src/test/java/com/nytimes/android/sample/StreamTest.kt @@ -0,0 +1,75 @@ +//package com.nytimes.android.sample +// +//import com.nytimes.android.external.store3.base.Fetcher +//import com.nytimes.android.external.store3.base.Persister +//import com.nytimes.android.external.store3.base.impl.BarCode +//import com.nytimes.android.external.store3.base.impl.Store +//import com.nytimes.android.external.store3.base.impl.StoreBuilder +//import org.junit.Before +//import org.junit.Test +//import org.junit.runner.RunWith +//import org.mockito.runners.MockitoJUnitRunner +//import java.util.concurrent.atomic.AtomicInteger +// +//@RunWith(MockitoJUnitRunner::class) +//class StreamTest { +// +// lateinit var counter: AtomicInteger +// lateinit var fetcher: Fetcher +// lateinit var persister: Persister +// private val barCode = BarCode("key", "value") +// lateinit var store: Store +// @Before +// fun setUp() { +// fetcher = object : Fetcher { +// override suspend fun fetch(key: BarCode) = counter.incrementAndGet().toString() +// } +// +// +// persister = object : Persister { +// override suspend fun read(key: BarCode): String? { +// when { +// counter.get() == 0 -> return null +// counter.get() == 1 -> return SmokeTests.DISK +// else -> return "WRONG VALUE" +// } +// } +// +// override suspend fun write( +// key: BarCode, +// raw: String +// ): Boolean { +// return true +// } +// } +// +// val store = StoreBuilder.barcode() +// .persister(persister) +// .fetcher(fetcher) +// .open() +// } +// +// @Test +// fun testStream() { +// val streamObservable = store.stream() +// .test() +// streamObservable.assertValueCount(0) +// store!!.get(barCode) +// .subscribe() +// streamObservable.assertValueCount(1) +// } +// +// @Test +// fun testStreamEmitsOnlyFreshData() { +// store!!.get(barCode) +// .subscribe() +// val streamObservable = store!!.stream() +// .test() +// streamObservable.assertValueCount(0) +// } +// +// companion object { +// +// private val TEST_ITEM = "test" +// } +//} diff --git a/build.gradle b/build.gradle index bef6f7cd6..f561e1524 100644 --- a/build.gradle +++ b/build.gradle @@ -19,14 +19,14 @@ buildscript { } rootProject.ext.versions = [ - kotlin: '1.3.0' + kotlin: '1.3.21' ] dependencies { - classpath 'com.android.tools.build:gradle:3.3.1' + classpath 'com.android.tools.build:gradle:3.5.0-alpha03' classpath 'com.google.gms:google-services:3.0.0' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.6' - classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11' +// classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$rootProject.ext.versions.kotlin" classpath 'org.jetbrains.dokka:dokka-gradle-plugin:0.9.17' } @@ -81,11 +81,11 @@ subprojects { project.android.dexOptions.preDexLibraries = rootProject.ext.preDexLibs } } - project.plugins.apply('net.ltgt.errorprone') +// project.plugins.apply('net.ltgt.errorprone') - configurations.errorprone { - resolutionStrategy.force 'com.google.errorprone:error_prone_core:2.0.15' - } +// configurations.errorprone { +// resolutionStrategy.force 'com.google.errorprone:error_prone_core:2.0.15' +// } } task gitHooksInit (type:Exec) { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 718173b4a..3d954946c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu May 24 12:01:07 EDT 2018 +#Mon Feb 11 14:11:03 EST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt index 084517ee5..5960d1162 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt @@ -1,18 +1,22 @@ package com.nytimes.android.external.store3.base.impl import com.nytimes.android.external.cache3.Cache -import com.nytimes.android.external.store.util.Result import com.nytimes.android.external.store3.base.Fetcher import com.nytimes.android.external.store3.base.InternalStore import com.nytimes.android.external.store3.base.Persister import com.nytimes.android.external.store3.util.KeyParser -import io.reactivex.Observable import io.reactivex.subjects.PublishSubject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async -import java.util.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.filter +import kotlinx.coroutines.channels.map +import kotlinx.coroutines.withContext +import java.util.AbstractMap import java.util.concurrent.ConcurrentMap /** @@ -24,264 +28,188 @@ import java.util.concurrent.ConcurrentMap * * Example usage: @link */ -internal class RealInternalStore(private val fetcher: Fetcher, - private val persister: Persister, - private val parser: KeyParser, - memoryPolicy: MemoryPolicy?, - private val stalePolicy: StalePolicy) : - Fetcher by fetcher, - Persister by persister, - KeyParser by parser, - InternalStore { - val inFlightRequests: Cache> - var memCache: Cache> - private val inFlightScope = CoroutineScope(SupervisorJob()) - private val memoryScope = CoroutineScope(SupervisorJob()) - private val refreshSubject = PublishSubject.create() - private val subject: PublishSubject> - - constructor(fetcher: Fetcher, - persister: Persister, - parser: KeyParser, - stalePolicy: StalePolicy) : this(fetcher, persister, parser, null, stalePolicy) { - } - - init { - this.memCache = CacheFactory.createCache(memoryPolicy) - this.inFlightRequests = CacheFactory.createInflighter(memoryPolicy) - subject = PublishSubject.create>() - } - - /** - * @param key - * @return an observable from the first data source that is available - */ - override suspend fun get(key: Key): Parsed { - return memCache.get(key) { - memoryScope.async { - return@async disk(key)?:fresh(key) - } - }.await() - } - - -// @Experimental -// override fun getRefreshing(key: Key): Observable { -// return get(key) -// .toObservable() -// .compose(StoreUtil.repeatWhenSubjectEmits(refreshSubject, key)) -// } - - -// /** -// * @return data from memory -// */ -// private fun lazyCache(key: Key): Maybe { -// return Maybe -// .defer { cache(key) } -// .onErrorResumeNext(Maybe.empty()) -// } - -// fun cache(key: Key): Parsed? { -// try { -// return memCache.get(key) { memoryScope.async { disk(key) } }.await() -// } catch (e: ExecutionException) { -// return Maybe.empty() -// } -// -// } - -// /** -// * @return data from memory -// */ -// private fun lazyCacheWithResult(key: Key): Maybe> { -// return Maybe -// .defer { cacheWithResult(key) } -// .onErrorResumeNext(Maybe.empty()) -// } -// -// fun cacheWithResult(key: Key): Maybe> { -// try { -// val maybeResult = memCache.get(key) { disk(key) } -// return if (maybeResult == null) Maybe.empty() else maybeResult.map { Result.createFromCache(it) } -// } catch (e: ExecutionException) { -// return Maybe.empty() -// } -// -// } - - -// suspend override fun memory(key: Key): Parsed? { -// val cachedValue = memCache.getIfPresent(key) -// return cachedValue -// } - - /** - * Fetch data from persister and update memory after. If an error occurs, emit an empty observable - * so that the concat call in [.get] moves on to [.fresh] - * - * @param key - * @return - */ - override suspend fun disk(key: Key): Parsed? { - return if (StoreUtil.shouldReturnNetworkBeforeStale(persister, stalePolicy, key)) { - null - } else readDisk(key) - - } - -// fun readDisk(key: Key): Maybe { -// return read(key) -// .onErrorResumeNext(Maybe.empty()) -// .map { raw -> apply(key, raw) } -// .doOnSuccess { parsed -> -// updateMemory(key, parsed) -// if (stalePolicy == StalePolicy.REFRESH_ON_STALE && StoreUtil.persisterIsStale(key, persister)) { -// backfillCache(key) -// } -// }.cache() -// } - - - suspend fun readDisk(key: Key): Parsed? { - return try { - val diskValue: Parsed? = read(key) - ?.let { apply(key, it) } -// ?.also { updateMemory(key, it) } //TODO MIKE: check whether we need to update the cache on the way back - if (stalePolicy == StalePolicy.REFRESH_ON_STALE) { - backfillCache(key) - } - diskValue; - } catch (e: Exception) { - //store fetching acts as a fallthrough, - // if we error on disk fetching we should return no data rather than throwing the error - null - } - } - - suspend fun backfillCache(key: Key) { - fresh(key) - } - - - /** - * Will check to see if there exists an in flight observable and return it before - * going to network - * - * @return data from fresh and store it in memory and persister - */ - override suspend fun fresh(key: Key): Parsed { - return fetchAndPersist(key) - } - - override suspend fun freshWithResult(key: Key): Result { - return fresh(key).let { Result.createFromNetwork(it) } - } - - /** - * There should only be one fresh request in flight at any give time. - * - * - * Return cached request in the form of a Behavior Subject which will emit to its subscribers - * the last value it gets. Subject/Observable is cached in a [ConcurrentMap] to maintain - * thread safety. - * - * @param key resource identifier - * @return observable that emits a [Parsed] value - */ - suspend fun fetchAndPersist(key: Key): Parsed = - inFlightRequests - .get(key) { inFlightScope.async { response(key) } } - .await() - - - suspend fun response(key: Key): Parsed { - return try { - val fetchedValue = fetch(key) - - write(key, fetchedValue) - return readDisk(key)!! - } catch (e: Exception) { - handleNetworkError(key, e) - } - - - } - - suspend fun handleNetworkError(key: Key, throwable: Throwable): Parsed { - if (stalePolicy == StalePolicy.NETWORK_BEFORE_STALE) { - val diskValue = readDisk(key) - if (diskValue != null) - return diskValue else throw throwable - } - throw throwable - } - - fun notifySubscribers(data: Parsed, key: Key) { - subject.onNext(AbstractMap.SimpleEntry(key, data)) - } - - /** - * Get data stream for Subjects with the argument id - * - * @return - */ - override fun stream(key: Key): Observable { - TODO("not yet implemented") -// return subject -// .hide() -// .startWith(get(key).toObservable().map> { data -> AbstractMap.SimpleEntry(key, data) }) -// .filter { simpleEntry -> simpleEntry.key == key } -// .map({ it.value }) - } - - override fun stream(): Observable { - return subject.hide().map({ it.value }) - } - - -// /** -// * Only update memory after persister has been successfully updated -// * -// * @param key -// * @param data -// */ -// fun updateMemory(key: Key, data: Parsed) { -// memCache.put(key, Maybe.just(data)) -// } - - @Deprecated("") - override fun clearMemory() { - clear() - } - - /** - * Clear memory by id - * - * @param key of data to clear - */ - @Deprecated("") - override fun clearMemory(key: Key) { - clear(key) - } - - - override fun clear() { - for (cachedKey in memCache.asMap().keys) { - clear(cachedKey) +internal class RealInternalStore( + private val fetcher: Fetcher, + private val persister: Persister, + private val parser: KeyParser, + memoryPolicy: MemoryPolicy?, + private val stalePolicy: StalePolicy +) : + Fetcher by fetcher, + Persister by persister, + KeyParser by parser, + InternalStore { + val inFlightRequests: Cache> + var memCache: Cache> + private val inFlightScope = CoroutineScope(SupervisorJob()) + private val memoryScope = CoroutineScope(SupervisorJob()) + private val refreshSubject = PublishSubject.create() + private val subject: Channel> + + constructor( + fetcher: Fetcher, + persister: Persister, + parser: KeyParser, + stalePolicy: StalePolicy + ) : this(fetcher, persister, parser, null, stalePolicy) { + } + + init { + this.memCache = CacheFactory.createCache(memoryPolicy) + this.inFlightRequests = CacheFactory.createInflighter(memoryPolicy) + subject = Channel() + } + + /** + * @param key + * @return an observable from the first data source that is available + */ + override suspend fun get(key: Key): Parsed = + withContext(Dispatchers.IO) { + memCache.get(key) { + memoryScope.async { + return@async disk(key) ?: fresh(key) } - } - - override fun clear(key: Key) { - inFlightRequests.invalidate(key) - memCache.invalidate(key) - StoreUtil.clearPersister(persister, key) - notifyRefresh(key) - } - - private fun notifyRefresh(key: Key) { - refreshSubject.onNext(key) - } - + } + .await() + } + + + + /** + * Fetch data from persister and update memory after. If an error occurs, emit an empty observable + * so that the concat call in [.get] moves on to [.fresh] + * + * @param key + * @return + */ + override suspend fun disk(key: Key): Parsed? { + return if (StoreUtil.shouldReturnNetworkBeforeStale(persister, stalePolicy, key)) { + null + } else readDisk(key) + + } + + suspend fun readDisk(key: Key): Parsed? { + return try { + val diskValue: Parsed? = read(key) + ?.let { apply(key, it) } + if (stalePolicy == StalePolicy.REFRESH_ON_STALE) { + backfillCache(key) + } + diskValue; + } catch (e: Exception) { + //store fetching acts as a fallthrough, + // if we error on disk fetching we should return no data rather than throwing the error + null + } + } + + private fun updateMemory( + key: Key, + it: Parsed + ) { + memCache.put(key, memoryScope.async { it }) + } + + suspend fun backfillCache(key: Key) { + fresh(key) + } + + /** + * Will check to see if there exists an in flight observable and return it before + * going to network + * + * @return data from fresh and store it in memory and persister + */ + override suspend fun fresh(key: Key): Parsed = + withContext(Dispatchers.IO) { fetchAndPersist(key).also { updateMemory(key, it) } } + + /** + * There should only be one fresh request in flight at any give time. + * + * + * Return cached request in the form of a Behavior Subject which will emit to its subscribers + * the last value it gets. Subject/Observable is cached in a [ConcurrentMap] to maintain + * thread safety. + * + * @param key resource identifier + * @return observable that emits a [Parsed] value + */ + suspend fun fetchAndPersist(key: Key): Parsed = + inFlightRequests + .get(key) { inFlightScope.async { response(key) } } + .await() + + suspend fun response(key: Key): Parsed { + return try { + val fetchedValue = fetch(key) + write(key, fetchedValue) + val diskValue = readDisk(key)!! +// notifySubscribers(diskValue, key) + return diskValue + } catch (e: Exception) { + handleNetworkError(key, e) + } + } + + suspend fun handleNetworkError( + key: Key, + throwable: Throwable + ): Parsed { + if (stalePolicy == StalePolicy.NETWORK_BEFORE_STALE) { + val diskValue = readDisk(key) + if (diskValue != null) + return diskValue else throw throwable + } + throw throwable + } + + suspend fun notifySubscribers( + data: Parsed, + key: Key + ) { + subject.send(AbstractMap.SimpleEntry(key, data)) + } + + //STREAM NO longer calls get + override fun stream(key: Key): ReceiveChannel = + subject.filter { it.key == key }.map { it.value } + + override fun stream(): ReceiveChannel { + return subject.map { it.value } + } + + @Deprecated("") + override fun clearMemory() { + clear() + } + + /** + * Clear memory by id + * + * @param key of data to clear + */ + @Deprecated("") + override fun clearMemory(key: Key) { + clear(key) + } + + override fun clear() { + for (cachedKey in memCache.asMap().keys) { + clear(cachedKey) + } + } + + override fun clear(key: Key) { + inFlightRequests.invalidate(key) + memCache.invalidate(key) + StoreUtil.clearPersister(persister, key) + notifyRefresh(key) + } + + private fun notifyRefresh(key: Key) { + refreshSubject.onNext(key) + } } diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.kt index dec386b1b..26078ca3f 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealStore.kt @@ -13,6 +13,7 @@ import com.nytimes.android.external.store3.util.NoopPersister import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Single +import kotlinx.coroutines.channels.ReceiveChannel open class RealStore : Store { @@ -89,15 +90,13 @@ TODO("not implemented") return internalStore.fresh(key) } - suspend override fun freshWithResult(key: Key): Result { - TODO("not implemented") - } - override fun stream(): Observable { + + override fun stream(): ReceiveChannel { return internalStore.stream() } - override fun stream(key: Key): Observable { + override fun stream(key: Key): ReceiveChannel { return internalStore.stream(key) } diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.kt index 023fb5d48..d0fcc68b6 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.kt @@ -4,6 +4,8 @@ import com.nytimes.android.external.store.util.Result import com.nytimes.android.external.store3.annotations.Experimental import io.reactivex.Observable import io.reactivex.Single +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel /** * a [StoreBuilder] @@ -48,14 +50,14 @@ interface Store { /** * Return an Observable of [Result] for requested Barcode skipping Memory & Disk Cache */ - suspend fun freshWithResult(key: V): Result +// suspend fun freshWithResult(key: V): Result /** * @return an Observable that emits "fresh" new response from the store that hit the fetcher * WARNING: stream is an endless observable, be careful when combining * with operators that expect an OnComplete event */ - fun stream(): Observable + fun stream(): ReceiveChannel /** * Similar to [Store.get() ][Store.get] @@ -64,7 +66,7 @@ interface Store { * Errors will be dropped * */ - fun stream(key: V): Observable + fun stream(key: V): ReceiveChannel /** * Clear the memory cache of all entries diff --git a/store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.kt b/store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.kt index 899dd9f18..0df2ba1da 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/KeyParserTest.kt @@ -19,13 +19,13 @@ class KeyParserTest { @Throws(Exception::class) fun setUp() { store = StoreBuilder.parsedWithKey() - .parser(object : KeyParser { - override suspend fun apply(key: Int, raw: String): String { - return raw + key - } - }) - .fetcher { NETWORK } - .open() + .parser(object : KeyParser { + override suspend fun apply(key: Int, raw: String): String { + return raw + key + } + }) + .fetcher { NETWORK } + .open() } @Test From 45f2cbdbd6e6975c82e36692ee6ee5a84c01e98f Mon Sep 17 00:00:00 2001 From: fabioCollini Date: Wed, 20 Feb 2019 23:23:35 -0500 Subject: [PATCH 058/498] Replaced Channel with BroadcastChannel --- build.gradle | 2 +- .../store3/base/impl/RealInternalStore.kt | 48 +++++++++-------- .../external/store3/StreamOneKeyTest.kt | 51 +++++++++---------- .../android/external/store3/StreamTest.kt | 11 ++-- 4 files changed, 54 insertions(+), 58 deletions(-) diff --git a/build.gradle b/build.gradle index f561e1524..0fcef25d6 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ buildscript { ] dependencies { - classpath 'com.android.tools.build:gradle:3.5.0-alpha03' + classpath 'com.android.tools.build:gradle:3.5.0-alpha04' classpath 'com.google.gms:google-services:3.0.0' classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.5.6' // classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.11' diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt index 5960d1162..712b46589 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt @@ -6,17 +6,12 @@ import com.nytimes.android.external.store3.base.InternalStore import com.nytimes.android.external.store3.base.Persister import com.nytimes.android.external.store3.util.KeyParser import io.reactivex.subjects.PublishSubject -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.async -import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.BroadcastChannel +import kotlinx.coroutines.channels.Channel.Factory.CONFLATED import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.filter import kotlinx.coroutines.channels.map -import kotlinx.coroutines.withContext -import java.util.AbstractMap import java.util.concurrent.ConcurrentMap /** @@ -39,26 +34,23 @@ internal class RealInternalStore( Persister by persister, KeyParser by parser, InternalStore { - val inFlightRequests: Cache> - var memCache: Cache> + private val inFlightRequests: Cache> = CacheFactory.createInflighter(memoryPolicy) + var memCache: Cache> = CacheFactory.createCache(memoryPolicy) private val inFlightScope = CoroutineScope(SupervisorJob()) private val memoryScope = CoroutineScope(SupervisorJob()) private val refreshSubject = PublishSubject.create() - private val subject: Channel> + private val subject = BroadcastChannel?>(CONFLATED).apply { + //a conflated channel always maintains the last element, the stream method ignore this element. + //Here we add an empty element that will be ignored later + offer(null) + } constructor( fetcher: Fetcher, persister: Persister, parser: KeyParser, stalePolicy: StalePolicy - ) : this(fetcher, persister, parser, null, stalePolicy) { - } - - init { - this.memCache = CacheFactory.createCache(memoryPolicy) - this.inFlightRequests = CacheFactory.createInflighter(memoryPolicy) - subject = Channel() - } + ) : this(fetcher, persister, parser, null, stalePolicy) /** * @param key @@ -68,7 +60,7 @@ internal class RealInternalStore( withContext(Dispatchers.IO) { memCache.get(key) { memoryScope.async { - return@async disk(key) ?: fresh(key) + disk(key) ?: fresh(key) } } .await() @@ -97,7 +89,7 @@ internal class RealInternalStore( if (stalePolicy == StalePolicy.REFRESH_ON_STALE) { backfillCache(key) } - diskValue; + diskValue } catch (e: Exception) { //store fetching acts as a fallthrough, // if we error on disk fetching we should return no data rather than throwing the error @@ -146,7 +138,7 @@ internal class RealInternalStore( val fetchedValue = fetch(key) write(key, fetchedValue) val diskValue = readDisk(key)!! -// notifySubscribers(diskValue, key) + notifySubscribers(diskValue, key) return diskValue } catch (e: Exception) { handleNetworkError(key, e) @@ -169,17 +161,23 @@ internal class RealInternalStore( data: Parsed, key: Key ) { - subject.send(AbstractMap.SimpleEntry(key, data)) + subject.send(key to data) } //STREAM NO longer calls get override fun stream(key: Key): ReceiveChannel = - subject.filter { it.key == key }.map { it.value } + streamSubscription().filter { it.first == key }.map { (_, value) -> value } override fun stream(): ReceiveChannel { - return subject.map { it.value } + return streamSubscription().map { (_, value) -> value } } + private fun streamSubscription() = + subject.openSubscription() + //ignore first element so only new elements are returned + .apply { poll() } + .map { it!! } + @Deprecated("") override fun clearMemory() { clear() diff --git a/store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.kt b/store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.kt index 9e525c761..6d1a6c888 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/StreamOneKeyTest.kt @@ -1,17 +1,16 @@ package com.nytimes.android.external.store3 import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever import com.nytimes.android.external.store3.base.Fetcher import com.nytimes.android.external.store3.base.Persister import com.nytimes.android.external.store3.base.impl.BarCode import com.nytimes.android.external.store3.base.impl.Store import com.nytimes.android.external.store3.base.impl.StoreBuilder -import io.reactivex.Maybe -import io.reactivex.Single import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test -import org.mockito.Mockito.`when` class StreamOneKeyTest { @@ -28,43 +27,41 @@ class StreamOneKeyTest { @Before fun setUp() = runBlocking { - `when`(fetcher.fetch(barCode)) - .thenReturn(Single.just(TEST_ITEM)) - .thenReturn(Single.just(TEST_ITEM2)) + whenever(fetcher.fetch(barCode)) + .thenReturn(TEST_ITEM) + .thenReturn(TEST_ITEM2) - `when`(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(TEST_ITEM)) - .thenReturn(Maybe.just(TEST_ITEM2)) + whenever(persister.read(barCode)) + .thenReturn(TEST_ITEM) + .thenReturn(TEST_ITEM2) - `when`(persister.write(barCode, TEST_ITEM)) - .thenReturn(Single.just(true)) - `when`(persister.write(barCode, TEST_ITEM2)) - .thenReturn(Single.just(true)) + whenever(persister.write(barCode, TEST_ITEM)) + .thenReturn(true) + whenever(persister.write(barCode, TEST_ITEM2)) + .thenReturn(true) } @Test fun testStream() = runBlocking { - val streamObservable = store.stream(barCode).test() - //first time we subscribe to stream it will fail getting from memory & disk and instead - //fresh from network, write to disk and notifiy subscribers - streamObservable.assertValueCount(1) + val streamObservable = store.stream(barCode) + //stream doesn't invoke get anymore so when we call it the channel is empty + assertThat(streamObservable.isEmpty).isTrue() store.clear() //fresh should notify subscribers again store.fresh(barCode) - streamObservable.assertValues(TEST_ITEM, TEST_ITEM2) + assertThat(streamObservable.poll()).isEqualTo(TEST_ITEM) +// assertThat(streamObservable.poll()).isEqualTo(TEST_ITEM2) //get for another barcode should not trigger a stream for barcode1 - `when`(fetcher.fetch(barCode2)) - .thenReturn(Single.just(TEST_ITEM)) - `when`(persister.read(barCode2)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(TEST_ITEM)) - `when`(persister.write(barCode2, TEST_ITEM)) - .thenReturn(Single.just(true)) + whenever(fetcher.fetch(barCode2)) + .thenReturn(TEST_ITEM) + whenever(persister.read(barCode2)) + .thenReturn(TEST_ITEM) + whenever(persister.write(barCode2, TEST_ITEM)) + .thenReturn(true) store.get(barCode2) - streamObservable.assertValueCount(2) + assertThat(streamObservable.isEmpty).isTrue() } companion object { diff --git a/store/src/test/java/com/nytimes/android/external/store3/StreamTest.kt b/store/src/test/java/com/nytimes/android/external/store3/StreamTest.kt index 41257f649..37d6ecc6f 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/StreamTest.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/StreamTest.kt @@ -7,6 +7,7 @@ import com.nytimes.android.external.store3.base.Persister import com.nytimes.android.external.store3.base.impl.BarCode import com.nytimes.android.external.store3.base.impl.StoreBuilder import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test @@ -36,17 +37,17 @@ class StreamTest { @Test fun testStream() = runBlocking { - val streamObservable = store.stream().test() - streamObservable.assertValueCount(0) + val streamObservable = store.stream() + assertThat(streamObservable.isEmpty).isTrue() store.get(barCode) - streamObservable.assertValueCount(1) + assertThat(streamObservable.isEmpty).isFalse() } @Test fun testStreamEmitsOnlyFreshData() = runBlocking { store.get(barCode) - val streamObservable = store.stream().test() - streamObservable.assertValueCount(0) + val streamObservable = store.stream() + assertThat(streamObservable.isEmpty).isTrue() } companion object { From 6ab7f7c4196abd9493c0aed1d5f38c15b848b5c9 Mon Sep 17 00:00:00 2001 From: fabioCollini Date: Wed, 20 Feb 2019 23:38:49 -0500 Subject: [PATCH 059/498] Fix some tests --- .../store3/base/impl/RealInternalStore.kt | 2 + .../external/store3/DontCacheErrorsTest1.kt | 23 ++---- .../external/store3/StoreWithParserTest.kt | 78 +++++++++---------- 3 files changed, 49 insertions(+), 54 deletions(-) diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt index 712b46589..0bfb280d3 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt @@ -142,6 +142,8 @@ internal class RealInternalStore( return diskValue } catch (e: Exception) { handleNetworkError(key, e) + } finally { + inFlightRequests.invalidate(key) } } diff --git a/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt b/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt index 07bd4423b..a80511032 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/DontCacheErrorsTest1.kt @@ -1,31 +1,24 @@ package com.nytimes.android.external.store3 import com.nytimes.android.external.store3.base.impl.BarCode -import com.nytimes.android.external.store3.base.impl.Store import com.nytimes.android.external.store3.base.impl.StoreBuilder import junit.framework.Assert.fail import kotlinx.coroutines.runBlocking -import org.junit.Before import org.junit.Test class DontCacheErrorsTest { private var shouldThrow: Boolean = false - private lateinit var store: Store - - @Before - fun setUp() { - store = StoreBuilder.barcode() - .fetcher { - if (shouldThrow) { - throw RuntimeException() - } else { - 0 - } + private val store = StoreBuilder.barcode() + .fetcher { + if (shouldThrow) { + throw RuntimeException() + } else { + 0 } - .open() - } + } + .open() @Test fun testStoreDoesntCacheErrors() = runBlocking { diff --git a/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.kt b/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.kt index 5b3f88a02..8f8a09523 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/StoreWithParserTest.kt @@ -1,17 +1,17 @@ package com.nytimes.android.external.store3 import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.whenever import com.nytimes.android.external.store3.base.Fetcher import com.nytimes.android.external.store3.base.Parser import com.nytimes.android.external.store3.base.Persister import com.nytimes.android.external.store3.base.impl.BarCode import com.nytimes.android.external.store3.base.impl.ParsingStoreBuilder -import io.reactivex.Maybe -import io.reactivex.Single import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat import org.junit.Test -import org.mockito.Mockito.* +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations class StoreWithParserTest { @@ -29,23 +29,23 @@ class StoreWithParserTest { .parser(parser) .open() - `when`(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)) + whenever(fetcher.fetch(barCode)) + .thenReturn(NETWORK) - `when`(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)) + whenever(persister.read(barCode)) + .thenReturn(null) + .thenReturn(DISK) - `when`(persister.write(barCode, NETWORK)) - .thenReturn(Single.just(true)) + whenever(persister.write(barCode, NETWORK)) + .thenReturn(true) - `when`(parser.apply(DISK)).thenReturn(barCode.key) + whenever(parser.apply(DISK)).thenReturn(barCode.key) var value = simpleStore.get(barCode) assertThat(value).isEqualTo(barCode.key) value = simpleStore.get(barCode) assertThat(value).isEqualTo(barCode.key) - verify>(fetcher, times(1)).fetch(barCode) + verify(fetcher, times(1)).fetch(barCode) } // @Test @@ -56,17 +56,17 @@ class StoreWithParserTest { // .parser(parser) // .open() // -// `when`(fetcher.fetch(barCode)) -// .thenReturn(Single.just(NETWORK)) +// whenever(fetcher.fetch(barCode)) +// .thenReturn(NETWORK) // -// `when`(persister.read(barCode)) -// .thenReturn(Maybe.empty()) -// .thenReturn(Maybe.just(DISK)) +// whenever(persister.read(barCode)) +// .thenReturn(null) +// .thenReturn(DISK) // -// `when`(persister.write(barCode, NETWORK)) -// .thenReturn(Single.just(true)) +// whenever(persister.write(barCode, NETWORK)) +// .thenReturn(true) // -// `when`(parser.apply(DISK)).thenReturn(barCode.key) +// whenever(parser.apply(DISK)).thenReturn(barCode.key) // // var result = simpleStore.getWithResult(barCode) // assertThat(result.source()).isEqualTo(Result.Source.NETWORK) @@ -75,7 +75,7 @@ class StoreWithParserTest { // result = simpleStore.getWithResult(barCode) // assertThat(result.source()).isEqualTo(Result.Source.CACHE) // assertThat(result.value()).isEqualTo(barCode.key) -// verify>(fetcher, times(1)).fetch(barCode) +// verify(fetcher, times(1)).fetch(barCode) // } @Test @@ -84,23 +84,23 @@ class StoreWithParserTest { val simpleStore = SampleParsingStore(fetcher, persister, parser) - `when`(fetcher.fetch(barCode)) - .thenReturn(Single.just(NETWORK)) + whenever(fetcher.fetch(barCode)) + .thenReturn(NETWORK) - `when`(persister.read(barCode)) - .thenReturn(Maybe.empty()) - .thenReturn(Maybe.just(DISK)) + whenever(persister.read(barCode)) + .thenReturn(null) + .thenReturn(DISK) - `when`(persister.write(barCode, NETWORK)) - .thenReturn(Single.just(true)) + whenever(persister.write(barCode, NETWORK)) + .thenReturn(true) - `when`(parser.apply(DISK)).thenReturn(barCode.key) + whenever(parser.apply(DISK)).thenReturn(barCode.key) var value = simpleStore.get(barCode) assertThat(value).isEqualTo(barCode.key) value = simpleStore.get(barCode) assertThat(value).isEqualTo(barCode.key) - verify>(fetcher, times(1)).fetch(barCode) + verify(fetcher, times(1)).fetch(barCode) } // @Test @@ -109,17 +109,17 @@ class StoreWithParserTest { // // val simpleStore = SampleParsingStore(fetcher, persister, parser) // -// `when`(fetcher.fetch(barCode)) -// .thenReturn(Single.just(NETWORK)) +// whenever(fetcher.fetch(barCode)) +// .thenReturn(NETWORK) // -// `when`(persister.read(barCode)) -// .thenReturn(Maybe.empty()) -// .thenReturn(Maybe.just(DISK)) +// whenever(persister.read(barCode)) +// .thenReturn(null) +// .thenReturn(DISK) // -// `when`(persister.write(barCode, NETWORK)) -// .thenReturn(Single.just(true)) +// whenever(persister.write(barCode, NETWORK)) +// .thenReturn(true) // -// `when`(parser.apply(DISK)).thenReturn(barCode.key) +// whenever(parser.apply(DISK)).thenReturn(barCode.key) // // var result = simpleStore.getWithResult(barCode) // assertThat(result.source()).isEqualTo(Result.Source.NETWORK) @@ -128,7 +128,7 @@ class StoreWithParserTest { // result = simpleStore.getWithResult(barCode) // assertThat(result.source()).isEqualTo(Result.Source.CACHE) // assertThat(result.value()).isEqualTo(barCode.key) -// verify>(fetcher, times(1)).fetch(barCode) +// verify(fetcher, times(1)).fetch(barCode) // } companion object { From 2b32e6b947aa4ff5dbf995dd607221d843084aac Mon Sep 17 00:00:00 2001 From: fabioCollini Date: Fri, 22 Feb 2019 20:49:43 -0500 Subject: [PATCH 060/498] Don't cache errors --- .../store3/base/impl/RealInternalStore.kt | 15 ++++++++++----- .../android/external/store3/base/impl/Store.kt | 5 +---- .../nytimes/android/external/store3/StoreTest.kt | 2 ++ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt index 0bfb280d3..d09d05369 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/RealInternalStore.kt @@ -58,12 +58,17 @@ internal class RealInternalStore( */ override suspend fun get(key: Key): Parsed = withContext(Dispatchers.IO) { - memCache.get(key) { - memoryScope.async { - disk(key) ?: fresh(key) - } + try { + memCache.get(key) { + memoryScope.async { + disk(key) ?: fresh(key) + } + } + .await() + } catch (e: Exception) { + // should we remove the key from the cache here ? + disk(key) ?: fresh(key) } - .await() } diff --git a/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.kt b/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.kt index d0fcc68b6..4b443bdc4 100644 --- a/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.kt +++ b/store/src/main/java/com/nytimes/android/external/store3/base/impl/Store.kt @@ -1,10 +1,6 @@ package com.nytimes.android.external.store3.base.impl import com.nytimes.android.external.store.util.Result -import com.nytimes.android.external.store3.annotations.Experimental -import io.reactivex.Observable -import io.reactivex.Single -import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ReceiveChannel /** @@ -57,6 +53,7 @@ interface Store { * WARNING: stream is an endless observable, be careful when combining * with operators that expect an OnComplete event */ + // TODO should this method return a Pair ? fun stream(): ReceiveChannel /** diff --git a/store/src/test/java/com/nytimes/android/external/store3/StoreTest.kt b/store/src/test/java/com/nytimes/android/external/store3/StoreTest.kt index 5cdf08052..6e1359cf4 100644 --- a/store/src/test/java/com/nytimes/android/external/store3/StoreTest.kt +++ b/store/src/test/java/com/nytimes/android/external/store3/StoreTest.kt @@ -12,6 +12,7 @@ import io.reactivex.Single import kotlinx.coroutines.async import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat +import org.junit.Ignore import org.junit.Test import org.mockito.Mockito.* import java.util.concurrent.TimeUnit @@ -201,6 +202,7 @@ class StoreTest { // verify>(fetcher, times(1)).fetch(barCode) // } + @Ignore("Add mockito inline to fix it") @Test fun testNoopAndDefault() = runBlocking { From 9e54fcca818d90dd4965f1bb535857b32ffa95f9 Mon Sep 17 00:00:00 2001 From: fabioCollini Date: Fri, 22 Feb 2019 20:51:09 -0500 Subject: [PATCH 061/498] Stream example --- app/src/main/AndroidManifest.xml | 10 +++ .../nytimes/android/sample/StreamActivity.kt | 80 +++++++++++++++++++ app/src/main/res/layout/stream_activity.xml | 45 +++++++++++ 3 files changed, 135 insertions(+) create mode 100644 app/src/main/java/com/nytimes/android/sample/StreamActivity.kt create mode 100644 app/src/main/res/layout/stream_activity.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cfe18ffb3..ac927ce00 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,16 @@ + + + + + + + + + diff --git a/app/src/main/java/com/nytimes/android/sample/StreamActivity.kt b/app/src/main/java/com/nytimes/android/sample/StreamActivity.kt new file mode 100644 index 000000000..9185b6184 --- /dev/null +++ b/app/src/main/java/com/nytimes/android/sample/StreamActivity.kt @@ -0,0 +1,80 @@ +package com.nytimes.android.sample + +import android.os.Bundle +import android.support.v7.app.AppCompatActivity +import android.view.View +import android.widget.TextView +import com.nytimes.android.external.store3.base.impl.MemoryPolicy +import com.nytimes.android.external.store3.base.impl.StoreBuilder +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.consumeEach +import java.util.concurrent.TimeUnit +import kotlin.coroutines.CoroutineContext + +class StreamActivity : AppCompatActivity(), CoroutineScope { + + override val coroutineContext: CoroutineContext = Job() + Dispatchers.Main + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.stream_activity) + + var counter = 0 + + val store = StoreBuilder.key() + .fetcher { key -> key * 1000 + counter++ } + .memoryPolicy( + MemoryPolicy + .builder() + .setExpireAfterWrite(10) + .setExpireAfterTimeUnit(TimeUnit.SECONDS) + .build() + ) + .open() + + findViewById(R.id.get_1).onClick { + store.get(1) + } + + findViewById(R.id.fresh_1).onClick { + store.fresh(1) + } + + findViewById(R.id.get_2).onClick { + store.get(2) + } + + findViewById(R.id.fresh_2).onClick { + store.fresh(2) + } + + launch { + store.stream(1).consumeEach { + findViewById(R.id.stream_1).text = "Stream 1 $it" + } + } + launch { + store.stream(2).consumeEach { + findViewById(R.id.stream_2).text = "Stream 2 $it" + } + } + launch { + store.stream().consumeEach { + findViewById(R.id.stream).text = "Stream $it" + } + } + } + + fun View.onClick(f: suspend () -> Unit) { + setOnClickListener { + launch { + f() + } + } + } + + override fun onDestroy() { + super.onDestroy() + coroutineContext.cancel() + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/stream_activity.xml b/app/src/main/res/layout/stream_activity.xml new file mode 100644 index 000000000..fb520529c --- /dev/null +++ b/app/src/main/res/layout/stream_activity.xml @@ -0,0 +1,45 @@ + + + +