From 1aa35550e6fd2c00bb3f48d66bb7324184d58422 Mon Sep 17 00:00:00 2001 From: stepango Date: Tue, 6 Sep 2016 09:29:41 +0800 Subject: [PATCH 01/20] Dependencies updated --- build.gradle | 14 +++++++------- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/build.gradle b/build.gradle index 2f468e4..ed53c06 100755 --- a/build.gradle +++ b/build.gradle @@ -1,24 +1,24 @@ buildscript { - ext.kotlin_version = '1.0.2' + ext.kotlin_version = '1.0.3' repositories { jcenter() } - dependencies { classpath 'com.netflix.nebula:gradle-rxjava-project-plugin:2.+', + dependencies { classpath 'com.netflix.nebula:gradle-rxjava-project-plugin:3.+', "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } -apply plugin: 'rxjava-project' +apply plugin: 'nebula.rxjava-project' apply plugin: 'kotlin' dependencies { - compile 'io.reactivex:rxjava:1.1.5' + compile 'io.reactivex:rxjava:1.1.10' compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - testCompile 'org.funktionale:funktionale:0.8' + testCompile 'org.funktionale:funktionale:0.9' testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-core:1.8.5' + testCompile 'org.mockito:mockito-core:1.10.19' examplesCompile 'com.squareup.retrofit:retrofit:1.9.+' } task wrapper(type: Wrapper) { - gradleVersion = '2.2.1' + gradleVersion = '2.10' } // support for snapshot/final releases with the various branches RxJava uses diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8e63c7e..28a37ab 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Mar 07 02:11:57 MSK 2015 +#Mon Sep 05 19:30:11 SGT 2016 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip From 3f2e99a58ad30d589ba6031d215366044aaab0df Mon Sep 17 00:00:00 2001 From: stepango Date: Sat, 7 Jan 2017 18:59:27 +0200 Subject: [PATCH 02/20] Initial implementation of rxKotlin for rxJava2 --- LICENSE | 2 +- build.gradle | 12 +- .../rx/lang/kotlin/examples/examples.kt | 118 ++++++------ .../lang/kotlin/examples/retrofit/retrofit.kt | 2 +- src/main/kotlin/rx/lang/kotlin/completable.kt | 16 +- src/main/kotlin/rx/lang/kotlin/observables.kt | 107 +++++------ src/main/kotlin/rx/lang/kotlin/single.kt | 24 +-- src/main/kotlin/rx/lang/kotlin/subjects.kt | 20 +- src/main/kotlin/rx/lang/kotlin/subscribers.kt | 120 ++++++------ .../kotlin/rx/lang/kotlin/subscription.kt | 14 +- .../kotlin/rx/lang/kotlin/BasicKotlinTests.kt | 132 ++++++------- .../kotlin/rx/lang/kotlin/CompletableTest.kt | 24 +-- .../kotlin/rx/lang/kotlin/ExtensionTests.kt | 59 +++--- src/test/kotlin/rx/lang/kotlin/KotlinTests.kt | 4 +- .../kotlin/rx/lang/kotlin/ObservablesTest.kt | 176 ++++++++---------- src/test/kotlin/rx/lang/kotlin/SingleTest.kt | 6 +- .../kotlin/rx/lang/kotlin/SubscribersTest.kt | 147 +++++++-------- .../rx/lang/kotlin/SubscriptionTests.kt | 20 +- 18 files changed, 475 insertions(+), 528 deletions(-) diff --git a/LICENSE b/LICENSE index 7f8ced0..59f7b92 100644 --- a/LICENSE +++ b/LICENSE @@ -180,7 +180,7 @@ To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include + replaced with your own identifying information. (Don'value include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the diff --git a/build.gradle b/build.gradle index ed53c06..678111e 100755 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,17 @@ buildscript { - ext.kotlin_version = '1.0.3' - repositories { jcenter() } - dependencies { classpath 'com.netflix.nebula:gradle-rxjava-project-plugin:3.+', - "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } + ext.kotlin_version = '1.0.6' + repositories { jcenter() } + dependencies { + classpath 'com.netflix.nebula:gradle-rxjava-project-plugin:3.+', + "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } } apply plugin: 'nebula.rxjava-project' apply plugin: 'kotlin' dependencies { - compile 'io.reactivex:rxjava:1.1.10' + compile 'io.reactivex.rxjava2:rxjava:2.0.3' compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" testCompile 'org.funktionale:funktionale:0.9' testCompile 'junit:junit:4.12' diff --git a/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt b/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt index 5148d6c..6f1a0a4 100644 --- a/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt +++ b/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt @@ -1,115 +1,119 @@ package rx.lang.kotlin.examples -import rx.Observable -import rx.lang.kotlin.* -import rx.subscriptions.CompositeSubscription +import io.reactivex.Observable +import io.reactivex.disposables.CompositeDisposable +import rx.lang.kotlin.FunctionSubscriber +import rx.lang.kotlin.addTo +import rx.lang.kotlin.combineLatest +import rx.lang.kotlin.observable +import rx.lang.kotlin.plusAssign +import rx.lang.kotlin.subscribeBy +import rx.lang.kotlin.toObservable +import rx.lang.kotlin.zip import java.net.URL -import java.util.* +import java.util.Scanner import java.util.concurrent.TimeUnit import kotlin.concurrent.thread fun main(args: Array) { - - val subscription = CompositeSubscription() - + + val subscription = CompositeDisposable() + val printArticle = { art: String -> println("--- Article ---\n${art.substring(0, 125)}") } - + val printIt = { it: String -> println(it) } - + subscription += asyncObservable().subscribe(printIt) - + subscription += syncObservable().subscribe(printIt) - + subscription.clear() - + simpleComposition() - + asyncWiki("Tiger", "Elephant").subscribe(printArticle) - + asyncWikiWithErrorHandling("Tiger", "Elephant").subscribe(printArticle) { e -> println("--- Error ---\n${e.message}") } - + combineLatest(listOfObservables()) - + zip(listOfObservables()) - + simpleObservable().subscribe(FunctionSubscriber() .onNext { s -> println("1st onNext => $s") } .onNext { s -> println("2nd onNext => $s") }) - + addToCompositeSubscription() } -private fun URL.toScannerObservable() = observable { s -> +private fun URL.toScannerObservable() = observable({ s -> this.openStream().use { stream -> - Scanner(stream).useDelimiter("\\A").toObservable().subscribe(s) + Scanner(stream).useDelimiter("\\A").toObservable().subscribeBy { s } } -} +}) fun syncObservable(): Observable = - observable { subscriber -> - (0..75).toObservable() - .map { "Sync value_$it" } - .subscribe(subscriber) - } - -fun asyncObservable(): Observable = - observable { subscriber -> - thread { + observable { subscriber -> (0..75).toObservable() - .map { "Async value_$it" } - .subscribe(subscriber) + .map { "Sync value_$it" } + .subscribe { subscriber.onNext(it) } } + +fun asyncObservable(): Observable = observable { subscriber -> + thread { + (0..75).toObservable() + .map { "Async value_$it" } + .subscribe { subscriber.onNext(it) } } +} -fun asyncWiki(vararg articleNames: String): Observable = - observable { subscriber -> - thread { - articleNames.toObservable() - .flatMap { name -> URL("http://en.wikipedia.org/wiki/$name").toScannerObservable().first() } - .subscribe(subscriber) - } +fun asyncWiki(vararg articleNames: String): Observable = observable { subscriber -> + thread { + articleNames.toObservable() + .flatMapMaybe { name: String -> URL("http://en.wikipedia.org/wiki/$name").toScannerObservable().firstElement() } + .subscribe { subscriber.onNext(it) } } +} -fun asyncWikiWithErrorHandling(vararg articleNames: String): Observable = - observable { subscriber -> - thread { - articleNames.toObservable() - .flatMap { name -> URL("http://en.wikipedia.org/wiki/$name").toScannerObservable().first() } - .onError { e -> - subscriber.onError(e) } - .subscribe(subscriber) - } +fun asyncWikiWithErrorHandling(vararg articleNames: String): Observable = observable { subscriber -> + thread { + articleNames.toObservable() + .flatMapMaybe { name -> URL("http://en.wikipedia.org/wiki/$name").toScannerObservable().firstElement() } + .subscribe({ subscriber.onNext(it) }, { subscriber.onError(it) }) } +} fun simpleComposition() { - asyncObservable().skip(10).take(5) - .map { s -> "${s}_xform" } - .subscribe { s -> println("onNext => $s") } + asyncObservable() + .skip(10) + .take(5) + .map { "${it}_xform" } + .subscribe { println("onNext => $it") } } fun listOfObservables(): List> = listOf(syncObservable(), syncObservable()) fun combineLatest(observables: List>) { - observables.combineLatest { it.reduce { one, two -> one + two } }.subscribe { println(it) } + observables.combineLatest { it.reduce { one, two -> one + two } }.subscribe(::println) } fun zip(observables: List>) { - observables.zip { it.reduce { one, two -> one + two } }.subscribe { println(it) } + observables.zip { it.reduce { one, two -> one + two } }.subscribe(::println) } fun simpleObservable(): Observable = (0..17).toObservable().map { "Simple $it" } fun addToCompositeSubscription() { - val compositeSubscription = CompositeSubscription() - + val compositeSubscription = CompositeDisposable() + Observable.just("test") .delay(100, TimeUnit.MILLISECONDS) .subscribe() .addTo(compositeSubscription) - - compositeSubscription.unsubscribe() + + compositeSubscription.dispose() } \ No newline at end of file diff --git a/src/examples/kotlin/rx/lang/kotlin/examples/retrofit/retrofit.kt b/src/examples/kotlin/rx/lang/kotlin/examples/retrofit/retrofit.kt index 01b3fef..5e77a44 100644 --- a/src/examples/kotlin/rx/lang/kotlin/examples/retrofit/retrofit.kt +++ b/src/examples/kotlin/rx/lang/kotlin/examples/retrofit/retrofit.kt @@ -1,9 +1,9 @@ package rx.lang.kotlin.examples.retrofit +import io.reactivex.Observable import retrofit.RestAdapter import retrofit.http.GET import retrofit.http.Query -import rx.Observable data class SearchResultEntry(val id : String, val latestVersion : String) data class SearchResults(val docs : List) diff --git a/src/main/kotlin/rx/lang/kotlin/completable.kt b/src/main/kotlin/rx/lang/kotlin/completable.kt index f9b3b28..ac413f0 100644 --- a/src/main/kotlin/rx/lang/kotlin/completable.kt +++ b/src/main/kotlin/rx/lang/kotlin/completable.kt @@ -1,13 +1,13 @@ package rx.lang.kotlin -import rx.Completable -import rx.Single -import rx.functions.Action0 +import io.reactivex.Completable +import io.reactivex.Single +import io.reactivex.functions.Action import java.util.concurrent.Callable import java.util.concurrent.Future -fun Action0.toCompletable(): Completable = Completable.fromAction(this) -fun completableOf(f: Function0): Completable = Completable.fromAction { f.invoke() } -fun Callable.toCompletable(): Completable = Completable.fromCallable { this.call() } -fun Future.toCompletable(): Completable = Completable.fromFuture(this) -fun Single.toCompletable(): Completable = Completable.fromSingle(this) \ No newline at end of file +fun Action.toCompletable(): Completable = Completable.fromAction(this) +inline fun completableOf(crossinline f: () -> T): Completable = Completable.fromAction { f() } +fun Callable.toCompletable(): Completable = Completable.fromCallable { this.call() } +fun Future.toCompletable(): Completable = Completable.fromFuture(this) +fun Single.toCompletable(): Completable = Completable.fromSingle(this) \ No newline at end of file diff --git a/src/main/kotlin/rx/lang/kotlin/observables.kt b/src/main/kotlin/rx/lang/kotlin/observables.kt index 8655012..0246fcd 100644 --- a/src/main/kotlin/rx/lang/kotlin/observables.kt +++ b/src/main/kotlin/rx/lang/kotlin/observables.kt @@ -1,72 +1,61 @@ package rx.lang.kotlin -import rx.Observable -import rx.Subscriber -import rx.Subscription -import rx.observables.BlockingObservable +import io.reactivex.Observable +import io.reactivex.ObservableEmitter +import io.reactivex.Single +import io.reactivex.disposables.Disposable +import io.reactivex.functions.BiFunction -fun emptyObservable() : Observable = Observable.empty() -fun observable(body : (s : Subscriber) -> Unit) : Observable = Observable.create(body) /** - * Create deferred observable - * @see [rx.Observable.defer] and [http://reactivex.io/documentation/operators/defer.html] + * [Observable.empty] alias */ -fun deferredObservable(body : () -> Observable) : Observable = Observable.defer(body) -private fun Iterator.toIterable() = object : Iterable { - override fun iterator(): Iterator = this@toIterable -} +fun emptyObservable(): Observable = Observable.empty() -fun BooleanArray.toObservable() : Observable = this.toList().toObservable() -fun ByteArray.toObservable() : Observable = this.toList().toObservable() -fun ShortArray.toObservable() : Observable = this.toList().toObservable() -fun IntArray.toObservable() : Observable = this.toList().toObservable() -fun LongArray.toObservable() : Observable = this.toList().toObservable() -fun FloatArray.toObservable() : Observable = this.toList().toObservable() -fun DoubleArray.toObservable() : Observable = this.toList().toObservable() -fun Array.toObservable() : Observable = Observable.from(this) - -fun IntProgression.toObservable() : Observable = - if (step == 1 && last.toLong() - first < Integer.MAX_VALUE) Observable.range(first, Math.max(0, last - first + 1)) - else Observable.from(this) +/** + * [Observable.defer] alias + */ +fun Observable.defer() = Observable.defer { this } -fun Iterator.toObservable() : Observable = toIterable().toObservable() -fun Iterable.toObservable() : Observable = Observable.from(this) -fun Sequence.toObservable() : Observable = Observable.from(object : Iterable { - override fun iterator(): Iterator = this@toObservable.iterator() -}) +fun observable(body: (ObservableEmitter) -> Unit): Observable = Observable.create(body) -fun T.toSingletonObservable() : Observable = Observable.just(this) -fun Throwable.toObservable() : Observable = Observable.error(this) +private fun Iterator.toIterable() = object : Iterable { + override fun iterator(): Iterator = this@toIterable +} -fun Iterable>.merge() : Observable = Observable.merge(this.toObservable()) -fun Iterable>.mergeDelayError() : Observable = Observable.mergeDelayError(this.toObservable()) +fun BooleanArray.toObservable(): Observable = Observable.fromArray(*this.toTypedArray()) +fun ByteArray.toObservable(): Observable = Observable.fromArray(*this.toTypedArray()) +fun ShortArray.toObservable(): Observable = Observable.fromArray(*this.toTypedArray()) +fun IntArray.toObservable(): Observable = Observable.fromArray(*this.toTypedArray()) +fun LongArray.toObservable(): Observable = Observable.fromArray(*this.toTypedArray()) +fun FloatArray.toObservable(): Observable = Observable.fromArray(*this.toTypedArray()) +fun DoubleArray.toObservable(): Observable = Observable.fromArray(*this.toTypedArray()) +fun Array.toObservable(): Observable = Observable.fromArray(*this) +fun IntProgression.toObservable(): Observable = + if (step == 1 && last.toLong() - first < Integer.MAX_VALUE) Observable.range(first, Math.max(0, last - first + 1)) + else Observable.fromIterable(this) -fun Observable.fold(initial : R, body : (R, T) -> R) : Observable = reduce(initial, {a, e -> body(a, e)}) -fun Observable.onError(block : (Throwable) -> Unit) : Observable = doOnError(block) -@Suppress("BASE_WITH_NULLABLE_UPPER_BOUND") fun Observable.firstOrNull() : Observable = firstOrDefault(null) -fun BlockingObservable.firstOrNull() : T = firstOrDefault(null) +fun Iterator.toObservable(): Observable = toIterable().toObservable() +fun Iterable.toObservable(): Observable = Observable.fromIterable(this) +fun Sequence.toObservable(): Observable = Observable.fromIterable(iterator().toIterable()) -@Suppress("BASE_WITH_NULLABLE_UPPER_BOUND") fun Observable.onErrorReturnNull() : Observable = onErrorReturn {null} +fun T.toSingletonObservable(): Observable = Observable.just(this) +fun Throwable.toObservable(): Observable = Observable.error(this) -fun Observable.lift(operator : (Subscriber) -> Subscriber) : Observable = lift { t1 -> operator(t1!!) } +fun Iterable>.merge(): Observable = Observable.merge(this.toObservable()) +fun Iterable>.mergeDelayError(): Observable = Observable.mergeDelayError(this.toObservable()) -/** - * Returns [Observable] that requires all objects to be non null. Raising [NullPointerException] in case of null object - */ -fun Observable.requireNoNulls() : Observable = map { it ?: throw NullPointerException("null element found in rx observable") } +inline fun Observable.fold(initial: R, crossinline body: (R, T) -> R): Single + = reduce(initial) { a, e -> body(a, e) } -/** - * Returns [Observable] with non-null generic type T. Returned observable filter out null values - */ -@Suppress("CAST_NEVER_SUCCEEDS") fun Observable.filterNotNull(): Observable = filter { it != null } as Observable +fun Observable.onError(block: (Throwable) -> Unit): Observable = doOnError(block) /** * Returns Observable that wrap all values into [IndexedValue] and populates corresponding index value. * Works similar to [kotlin.withIndex] */ -fun Observable.withIndex() : Observable> = - zipWith(Observable.range(0, Int.MAX_VALUE)) { value, index -> IndexedValue(index,value) } +fun Observable.withIndex(): Observable> + = zipWith(Observable.range(0, Int.MAX_VALUE), BiFunction { value, index -> IndexedValue(index, value) }) /** * Returns Observable that emits objects from kotlin [Sequence] returned by function you provided by parameter [body] for @@ -74,34 +63,36 @@ fun Observable.withIndex() : Observable> = * Works similar to [Observable.flatMap] and [Observable.flatMapIterable] but with [Sequence] * * @param body is a function that applied for each item emitted by source observable that returns [Sequence] - * @returns Observable that merges all [Sequence]s produced by [body] functions + * @returns Observable that merges all [Sequence]s produced by [body] functions */ -fun Observable.flatMapSequence( body : (T) -> Sequence ) : Observable = flatMap { body(it).toObservable() } +inline fun Observable.flatMapSequence(crossinline body: (T) -> Sequence): Observable + = flatMap { body(it).toObservable() } /** * Subscribe with a subscriber that is configured inside body */ -inline fun Observable.subscribeWith( body : FunctionSubscriberModifier.() -> Unit ) : Subscription { +inline fun Observable.subscribeBy(body: FunctionSubscriberModifier.() -> Unit): Disposable { val modifier = FunctionSubscriberModifier(subscriber()) modifier.body() - return subscribe(modifier.subscriber) + subscribe(modifier.subscriber) + return modifier.subscriber.origin!! } -fun Observable>.switchOnNext(): Observable = Observable.switchOnNext(this) +fun Observable>.switchOnNext(): Observable = Observable.switchOnNext(this) /** * Observable.combineLatest(List> sources, FuncN combineFunction) */ @Suppress("UNCHECKED_CAST") -fun List>.combineLatest(combineFunction: (args: List) -> R): Observable = - Observable.combineLatest(this, { combineFunction(it.asList() as List) }) +inline fun List>.combineLatest(crossinline combineFunction: (args: List) -> R): Observable + = Observable.combineLatest(this) { combineFunction(it.asList().map { it as T }) } /** * Observable.zip(List> sources, FuncN combineFunction) */ @Suppress("UNCHECKED_CAST") -fun List>.zip(zipFunction: (args: List) -> R): Observable = - Observable.zip(this, { zipFunction(it.asList() as List) }) +inline fun List>.zip(crossinline zipFunction: (args: List) -> R): Observable = + Observable.zip(this) { zipFunction(it.asList().map { it as T }) } /** * Returns an Observable that emits the items emitted by the source Observable, converted to the specified type. diff --git a/src/main/kotlin/rx/lang/kotlin/single.kt b/src/main/kotlin/rx/lang/kotlin/single.kt index 6f1b785..1be5473 100644 --- a/src/main/kotlin/rx/lang/kotlin/single.kt +++ b/src/main/kotlin/rx/lang/kotlin/single.kt @@ -1,22 +1,24 @@ package rx.lang.kotlin -import rx.Single -import rx.SingleSubscriber -import rx.Subscription +import io.reactivex.Single +import io.reactivex.SingleEmitter +import io.reactivex.disposables.Disposable import java.util.concurrent.Callable import java.util.concurrent.Future -fun single(body: (s: SingleSubscriber) -> Unit): Single = Single.create(body) -fun singleOf(value: T): Single = Single.just(value) -fun Future.toSingle(): Single = Single.from(this) -fun Callable.toSingle(): Single = Single.fromCallable { this.call() } -fun Throwable.toSingle(): Single = Single.error(this) +inline fun single(crossinline body: (s: SingleEmitter) -> Unit): Single = Single.create { body(it) } +fun T.toSingle(): Single = Single.just(this) +fun singleOf(value: T): Single = Single.just(value) +fun Future.toSingle(): Single = Single.fromFuture(this) +fun Callable.toSingle(): Single = Single.fromCallable { this.call() } +fun Throwable.toSingle(): Single = Single.error(this) /** * Subscribe with a subscriber that is configured inside body */ -inline fun Single.subscribeWith(body: FunctionSingleSubscriberModifier.() -> Unit): Subscription { - val modifier = FunctionSingleSubscriberModifier(singleSubscriber()) +inline fun Single.subscribeBy(body: FunctionSubscriberModifier.() -> Unit): Disposable { + val modifier = FunctionSubscriberModifier(subscriber()) modifier.body() - return subscribe(modifier.subscriber) + subscribe(modifier.subscriber) + return modifier.subscriber.origin!! } diff --git a/src/main/kotlin/rx/lang/kotlin/subjects.kt b/src/main/kotlin/rx/lang/kotlin/subjects.kt index b6d7764..7dbd24a 100644 --- a/src/main/kotlin/rx/lang/kotlin/subjects.kt +++ b/src/main/kotlin/rx/lang/kotlin/subjects.kt @@ -1,13 +1,13 @@ package rx.lang.kotlin -import rx.schedulers.TestScheduler -import rx.subjects.* +import io.reactivex.Observable +import io.reactivex.subjects.AsyncSubject +import io.reactivex.subjects.BehaviorSubject +import io.reactivex.subjects.PublishSubject +import io.reactivex.subjects.ReplaySubject -fun BehaviorSubject() : BehaviorSubject = BehaviorSubject.create() -fun BehaviorSubject(default : T) : BehaviorSubject = BehaviorSubject.create(default) -fun AsyncSubject() : AsyncSubject = AsyncSubject.create() -fun PublishSubject() : PublishSubject = PublishSubject.create() -fun ReplaySubject(capacity : Int = 16) : ReplaySubject = ReplaySubject.create(capacity) - -fun Subject.synchronized() : Subject = SerializedSubject(this) -fun TestSubject(scheduler: TestScheduler) : TestSubject = TestSubject.create(scheduler) +fun BehaviorSubject(): BehaviorSubject = BehaviorSubject.create() +fun BehaviorSubject(default: T): BehaviorSubject = BehaviorSubject.createDefault(default) +fun AsyncSubject(): AsyncSubject = AsyncSubject.create() +fun PublishSubject(): PublishSubject = PublishSubject.create() +fun ReplaySubject(capacity: Int = Observable.bufferSize()): ReplaySubject = ReplaySubject.create(capacity) diff --git a/src/main/kotlin/rx/lang/kotlin/subscribers.kt b/src/main/kotlin/rx/lang/kotlin/subscribers.kt index 6c0930c..62adf24 100644 --- a/src/main/kotlin/rx/lang/kotlin/subscribers.kt +++ b/src/main/kotlin/rx/lang/kotlin/subscribers.kt @@ -1,20 +1,22 @@ package rx.lang.kotlin -import rx.SingleSubscriber -import rx.Subscriber -import rx.exceptions.OnErrorNotImplementedException -import rx.observers.SerializedSubscriber -import rx.subscriptions.Subscriptions -import java.util.* - -class FunctionSubscriber() : Subscriber() { - private val onCompletedFunctions = ArrayList<() -> Unit>() +import io.reactivex.CompletableObserver +import io.reactivex.MaybeObserver +import io.reactivex.Observer +import io.reactivex.SingleObserver +import io.reactivex.disposables.Disposable +import java.util.ArrayList + +class FunctionSubscriber : Observer, MaybeObserver, SingleObserver, CompletableObserver { + + private val onCompleteFunctions = ArrayList<() -> Unit>() private val onErrorFunctions = ArrayList<(e: Throwable) -> Unit>() private val onNextFunctions = ArrayList<(value: T) -> Unit>() private val onStartFunctions = ArrayList<() -> Unit>() - - override fun onCompleted() = onCompletedFunctions.forEach { it() } - + var origin: Disposable? = null + + override fun onComplete() = onCompleteFunctions.forEach { it() } + override fun onError(e: Throwable?) = (e ?: RuntimeException("exception is unknown")).let { ex -> if (onErrorFunctions.isEmpty()) { throw OnErrorNotImplementedException(ex) @@ -22,76 +24,58 @@ class FunctionSubscriber() : Subscriber() { onErrorFunctions.forEach { it(ex) } } } - - override fun onNext(t: T) = onNextFunctions.forEach { it(t) } - - override fun onStart() = onStartFunctions.forEach { it() } - - fun onCompleted(onCompletedFunction: () -> Unit): FunctionSubscriber = copy { onCompletedFunctions.add(onCompletedFunction) } + + override fun onNext(value: T) = onNextFunctions.forEach { it(value) } + override fun onSuccess(value: T) = onNext(value) + + override fun onSubscribe(d: Disposable?) { + origin = d + onStartFunctions.forEach { it() } + } + + fun onCompleted(onCompletedFunction: () -> Unit): FunctionSubscriber = copy { onCompleteFunctions.add(onCompletedFunction) } fun onError(onErrorFunction: (t: Throwable) -> Unit): FunctionSubscriber = copy { onErrorFunctions.add(onErrorFunction) } fun onNext(onNextFunction: (t: T) -> Unit): FunctionSubscriber = copy { onNextFunctions.add(onNextFunction) } - fun onStart(onStartFunction : () -> Unit) : FunctionSubscriber = copy { onStartFunctions.add(onStartFunction) } - + fun onStart(onStartFunction: () -> Unit): FunctionSubscriber = copy { onStartFunctions.add(onStartFunction) } + private fun copy(block: FunctionSubscriber.() -> Unit): FunctionSubscriber { val newSubscriber = FunctionSubscriber() - newSubscriber.onCompletedFunctions.addAll(onCompletedFunctions) + newSubscriber.onCompleteFunctions.addAll(onCompleteFunctions) newSubscriber.onErrorFunctions.addAll(onErrorFunctions) newSubscriber.onNextFunctions.addAll(onNextFunctions) newSubscriber.onStartFunctions.addAll(onStartFunctions) - + newSubscriber.block() - + return newSubscriber } } -class FunctionSingleSubscriber() : SingleSubscriber() { - private val onSuccessFunctions = ArrayList<(value: T) -> Unit>() - private val onErrorFunctions = ArrayList<(e: Throwable) -> Unit>() - - override fun onSuccess(t: T) = onSuccessFunctions.forEach { it(t) } - - override fun onError(e: Throwable?) = (e ?: RuntimeException("exception is unknown")).let { ex -> - if (onErrorFunctions.isEmpty()) { - throw OnErrorNotImplementedException(ex) - } else { - onErrorFunctions.forEach { it(ex) } - } - } +class OnErrorNotImplementedException(ex: Throwable) : RuntimeException(ex) - fun onSuccess(onSuccessFunction: (t: T) -> Unit): FunctionSingleSubscriber = copy { onSuccessFunctions.add(onSuccessFunction) } - fun onError(onErrorFunction: (e: Throwable) -> Unit): FunctionSingleSubscriber = copy { onErrorFunctions.add(onErrorFunction) } - - private fun copy(block: FunctionSingleSubscriber.() -> Unit): FunctionSingleSubscriber { - val newSubscriber = FunctionSingleSubscriber() - newSubscriber.onSuccessFunctions.addAll(onSuccessFunctions) - newSubscriber.onErrorFunctions.addAll(onErrorFunctions) - - newSubscriber.block() - - return newSubscriber - } -} - -class FunctionSubscriberModifier(init: FunctionSubscriber = subscriber()) { +class FunctionSubscriberModifier(init: FunctionSubscriber = subscriber()) { var subscriber: FunctionSubscriber = init private set - - fun onCompleted(onCompletedFunction: () -> Unit) : Unit { subscriber = subscriber.onCompleted(onCompletedFunction) } - fun onError(onErrorFunction: (t : Throwable) -> Unit) : Unit { subscriber = subscriber.onError(onErrorFunction) } - fun onNext(onNextFunction: (t : T) -> Unit) : Unit { subscriber = subscriber.onNext(onNextFunction) } - fun onStart(onStartFunction : () -> Unit) : Unit { subscriber = subscriber.onStart(onStartFunction) } -} - -class FunctionSingleSubscriberModifier(init: FunctionSingleSubscriber = singleSubscriber()) { - var subscriber: FunctionSingleSubscriber = init - private set - - fun onSuccess(onSuccessFunction: (t: T) -> Unit): Unit { subscriber = subscriber.onSuccess(onSuccessFunction) } - fun onError(onErrorFunction: (r: Throwable) -> Unit): Unit {subscriber = subscriber.onError(onErrorFunction) } + + fun onComplete(onCompletedFunction: () -> Unit): Unit { + subscriber = subscriber.onCompleted(onCompletedFunction) + } + + fun onError(onErrorFunction: (t: Throwable) -> Unit): Unit { + subscriber = subscriber.onError(onErrorFunction) + } + + fun onNext(onNextFunction: (t: T) -> Unit): Unit { + subscriber = subscriber.onNext(onNextFunction) + } + + fun onStart(onStartFunction: () -> Unit): Unit { + subscriber = subscriber.onStart(onStartFunction) + } + + fun onSuccess(onSuccessFunction: (t: T) -> Unit): Unit { + subscriber = subscriber.onNext(onSuccessFunction) + } } -fun subscriber(): FunctionSubscriber = FunctionSubscriber() -fun singleSubscriber(): FunctionSingleSubscriber = FunctionSingleSubscriber() -fun Subscriber.synchronized(): Subscriber = SerializedSubscriber(this) -fun Subscriber<*>.add(action: () -> Unit) = add(Subscriptions.create(action)) +fun subscriber(): FunctionSubscriber = FunctionSubscriber() \ No newline at end of file diff --git a/src/main/kotlin/rx/lang/kotlin/subscription.kt b/src/main/kotlin/rx/lang/kotlin/subscription.kt index 776ae5e..8dd4703 100644 --- a/src/main/kotlin/rx/lang/kotlin/subscription.kt +++ b/src/main/kotlin/rx/lang/kotlin/subscription.kt @@ -1,19 +1,19 @@ package rx.lang.kotlin -import rx.Subscription -import rx.subscriptions.CompositeSubscription +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable /** * subscription += observable.subscribe{} */ -operator fun CompositeSubscription.plusAssign(subscription: Subscription) = add(subscription) +operator fun CompositeDisposable.plusAssign(subscription: Disposable) { + add(subscription) +} /** * Add the subscription to a CompositeSubscription. * @param compositeSubscription CompositeSubscription to add this subscription to * @return this instance */ -fun Subscription.addTo(compositeSubscription: CompositeSubscription) : Subscription { - compositeSubscription.add(this) - return this -} \ No newline at end of file +fun Disposable.addTo(compositeSubscription: CompositeDisposable): Disposable + = apply { compositeSubscription.add(this) } \ No newline at end of file diff --git a/src/test/kotlin/rx/lang/kotlin/BasicKotlinTests.kt b/src/test/kotlin/rx/lang/kotlin/BasicKotlinTests.kt index 35705a6..20dba91 100644 --- a/src/test/kotlin/rx/lang/kotlin/BasicKotlinTests.kt +++ b/src/test/kotlin/rx/lang/kotlin/BasicKotlinTests.kt @@ -16,14 +16,18 @@ package rx.lang.kotlin +import io.reactivex.Notification +import io.reactivex.Observable +import io.reactivex.ObservableEmitter +import io.reactivex.ObservableOnSubscribe +import io.reactivex.functions.BiFunction +import io.reactivex.functions.Function3 import org.junit.Assert.assertEquals import org.junit.Assert.fail import org.junit.Test -import org.mockito.Mockito.* -import rx.Notification -import rx.Observable -import rx.Observable.OnSubscribe -import rx.Subscriber +import org.mockito.Mockito.any +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import kotlin.concurrent.thread /** @@ -33,10 +37,10 @@ class BasicKotlinTests : KotlinTests() { @Test fun testCreate() { - Observable.create(OnSubscribe { subscriber -> - subscriber.onNext("Hello") - subscriber.onCompleted() - }).subscribe { result -> + Observable.create { onSubscribe -> + onSubscribe.onNext("Hello") + onSubscribe.onComplete() + }.subscribe { result -> a.received(result) } @@ -44,18 +48,14 @@ class BasicKotlinTests : KotlinTests() { } @Test fun testFilter() { - Observable.from(listOf(1, 2, 3)).filter { it >= 2 }.subscribe(received()) - verify(a, times(0)).received(1); - verify(a, times(1)).received(2); - verify(a, times(1)).received(3); + Observable.fromIterable(listOf(1, 2, 3)).filter { it >= 2 }.subscribe(received()) + verify(a, times(0)).received(1) + verify(a, times(1)).received(2) + verify(a, times(1)).received(3) } @Test fun testLast() { - assertEquals("three", Observable.from(listOf("one", "two", "three")).toBlocking().last()) - } - - @Test fun testLastWithPredicate() { - assertEquals("two", Observable.from(listOf("one", "two", "three")).toBlocking().last { x -> x.length == 3 }) + assertEquals("three", Observable.fromIterable(listOf("one", "two", "three")).blockingLast()) } @Test fun testMap1() { @@ -64,27 +64,27 @@ class BasicKotlinTests : KotlinTests() { } @Test fun testMap2() { - Observable.from(listOf(1, 2, 3)).map { v -> "hello_$v" }.subscribe(received()) + Observable.fromIterable(listOf(1, 2, 3)).map { v -> "hello_$v" }.subscribe(received()) verify(a, times(1)).received("hello_1") verify(a, times(1)).received("hello_2") verify(a, times(1)).received("hello_3") } @Test fun testMaterialize() { - Observable.from(listOf(1, 2, 3)).materialize().subscribe(received()) + Observable.fromIterable(listOf(1, 2, 3)).materialize().subscribe(received()) verify(a, times(4)).received(any(Notification::class.java)) verify(a, times(0)).error(any(Exception::class.java)) } @Test fun testMerge() { Observable.merge( - Observable.from(listOf(1, 2, 3)), + Observable.fromIterable(listOf(1, 2, 3)), Observable.merge( Observable.just(6), Observable.error(NullPointerException()), Observable.just(7) ), - Observable.from(listOf(4, 5)) + Observable.fromIterable(listOf(4, 5)) ).subscribe(received()) { e -> a.error(e) } verify(a, times(1)).received(1) verify(a, times(1)).received(2) @@ -110,19 +110,19 @@ class BasicKotlinTests : KotlinTests() { @Test fun testFromWithIterable() { val list = listOf(1, 2, 3, 4, 5) - assertEquals(5, Observable.from(list).count().toBlocking().single()) + assertEquals(5, Observable.fromIterable(list).count().blockingGet()) } @Test fun testFromWithObjects() { val list = listOf(1, 2, 3, 4, 5) - assertEquals(2, Observable.from(listOf(list, 6)).count().toBlocking().single()) + assertEquals(2, Observable.fromIterable(listOf(list, 6)).count().blockingGet()) } @Test fun testStartWith() { val list = listOf(10, 11, 12, 13, 14) val startList = listOf(1, 2, 3, 4, 5) - assertEquals(6, Observable.from(list).startWith(0).count().toBlocking().single()) - assertEquals(10, Observable.from(list).startWith(startList).count().toBlocking().single()) + assertEquals(6, Observable.fromIterable(list).startWith(0).count().blockingGet()) + assertEquals(10, Observable.fromIterable(list).startWith(startList).count().blockingGet()) } @Test fun testScriptWithOnNext() { @@ -131,21 +131,21 @@ class BasicKotlinTests : KotlinTests() { } @Test fun testSkipTake() { - Observable.from(listOf(1, 2, 3)).skip(1).take(1).subscribe(received()) + Observable.fromIterable(listOf(1, 2, 3)).skip(1).take(1).subscribe(received()) verify(a, times(0)).received(1) verify(a, times(1)).received(2) verify(a, times(0)).received(3) } @Test fun testSkip() { - Observable.from(listOf(1, 2, 3)).skip(2).subscribe(received()) + Observable.fromIterable(listOf(1, 2, 3)).skip(2).subscribe(received()) verify(a, times(0)).received(1) verify(a, times(0)).received(2) verify(a, times(1)).received(3) } @Test fun testTake() { - Observable.from(listOf(1, 2, 3)).take(2).subscribe(received()) + Observable.fromIterable(listOf(1, 2, 3)).take(2).subscribe(received()) verify(a, times(1)).received(1) verify(a, times(1)).received(2) verify(a, times(0)).received(3) @@ -157,14 +157,17 @@ class BasicKotlinTests : KotlinTests() { } @Test fun testTakeWhile() { - Observable.from(listOf(1, 2, 3)).takeWhile { x -> x < 3 }.subscribe(received()) + Observable.fromIterable(listOf(1, 2, 3)).takeWhile { x -> x < 3 }.subscribe(received()) verify(a, times(1)).received(1) verify(a, times(1)).received(2) verify(a, times(0)).received(3) } @Test fun testTakeWhileWithIndex() { - Observable.from(listOf(1, 2, 3)).takeWhile { x -> x < 3 }.zipWith(Observable.range(0,Integer.MAX_VALUE)){ x, i -> x }.subscribe(received()) + Observable.fromIterable(listOf(1, 2, 3)) + .takeWhile { x -> x < 3 } + .zipWith(Observable.range(0, Integer.MAX_VALUE), BiFunction { x, i -> x }) + .subscribe(received()) verify(a, times(1)).received(1) verify(a, times(1)).received(2) verify(a, times(0)).received(3) @@ -176,56 +179,56 @@ class BasicKotlinTests : KotlinTests() { } @Test fun testForEach() { - Observable.create(AsyncObservable()).toBlocking().forEach(received()) + Observable.create(AsyncObservable()).blockingForEach(received()) verify(a, times(1)).received(1) verify(a, times(1)).received(2) verify(a, times(1)).received(3) } @Test(expected = RuntimeException::class) fun testForEachWithError() { - Observable.create(AsyncObservable()).toBlocking().forEach { throw RuntimeException("err") } + Observable.create(AsyncObservable()).blockingForEach { throw RuntimeException("err") } fail("we expect an exception to be thrown") } @Test fun testLastOrDefault() { - assertEquals("two", Observable.from(listOf("one", "two")).toBlocking().lastOrDefault("default") { x -> x.length == 3 }) - assertEquals("default", Observable.from(listOf("one", "two")).toBlocking().lastOrDefault("default") { x -> x.length > 3 }) + assertEquals("two", Observable.fromIterable(listOf("one", "two")).blockingLast("default")) + assertEquals("default", Observable.fromIterable(listOf("one", "two")).filter { it.length > 3 }.blockingLast("default")) } @Test(expected = IllegalArgumentException::class) fun testSingle() { - assertEquals("one", Observable.just("one").toBlocking().single { x -> x.length == 3 }) - Observable.from(listOf("one", "two")).toBlocking().single { x -> x.length == 3 } + assertEquals("one", Observable.just("one").blockingSingle()) + Observable.fromIterable(listOf("one", "two")).blockingSingle() fail() } @Test fun testDefer() { - Observable.defer { Observable.from(listOf(1, 2)) }.subscribe(received()) + Observable.defer { Observable.fromIterable(listOf(1, 2)) }.subscribe(received()) verify(a, times(1)).received(1) verify(a, times(1)).received(2) } @Test fun testAll() { - Observable.from(listOf(1, 2, 3)).all { x -> x > 0 }.subscribe(received()) + Observable.fromIterable(listOf(1, 2, 3)).all { x -> x > 0 }.subscribe(received()) verify(a, times(1)).received(true) } @Test fun testZip() { - val o1 = Observable.from(listOf(1, 2, 3)) - val o2 = Observable.from(listOf(4, 5, 6)) - val o3 = Observable.from(listOf(7, 8, 9)) + val o1 = Observable.fromIterable(listOf(1, 2, 3)) + val o2 = Observable.fromIterable(listOf(4, 5, 6)) + val o3 = Observable.fromIterable(listOf(7, 8, 9)) - val values = Observable.zip(o1, o2, o3) { a, b, c -> listOf(a, b, c) }.toList().toBlocking().single() + val values = Observable.zip(o1, o2, o3, Function3> { a, b, c -> listOf(a, b, c) }).toList().blockingGet() assertEquals(listOf(1, 4, 7), values[0]) assertEquals(listOf(2, 5, 8), values[1]) assertEquals(listOf(3, 6, 9), values[2]) } @Test fun testZipWithIterable() { - val o1 = Observable.from(listOf(1, 2, 3)) - val o2 = Observable.from(listOf(4, 5, 6)) - val o3 = Observable.from(listOf(7, 8, 9)) + val o1 = Observable.fromIterable(listOf(1, 2, 3)) + val o2 = Observable.fromIterable(listOf(4, 5, 6)) + val o3 = Observable.fromIterable(listOf(7, 8, 9)) - val values = Observable.zip(listOf(o1, o2, o3)) { args -> listOf(*args) }.toList().toBlocking().single() + val values = Observable.zip(listOf(o1, o2, o3), { args -> listOf(*args) }).toList().blockingGet() assertEquals(listOf(1, 4, 7), values[0]) assertEquals(listOf(2, 5, 8), values[1]) assertEquals(listOf(3, 6, 9), values[2]) @@ -234,13 +237,13 @@ class BasicKotlinTests : KotlinTests() { @Test fun testGroupBy() { var count = 0 - Observable.from(listOf("one", "two", "three", "four", "five", "six")) - .groupBy { s -> s.length } - .flatMap { groupObervable -> - groupObervable.map { s -> - "Value: $s Group ${groupObervable.key}" + Observable.fromIterable(listOf("one", "two", "three", "four", "five", "six")) + .groupBy(String::length) + .flatMap { groupObservable -> + groupObservable.map { s -> + "Value: $s Group ${groupObservable.key}" } - }.toBlocking().forEach { s -> + }.blockingForEach { s -> println(s) count++ } @@ -249,45 +252,42 @@ class BasicKotlinTests : KotlinTests() { } - class TestFactory() { var counter = 1 val numbers: Observable - get(){ - return Observable.from(listOf(1, 3, 2, 5, 4)) + get() { + return Observable.fromIterable(listOf(1, 3, 2, 5, 4)) } val onSubscribe: TestOnSubscribe - get(){ + get() { return TestOnSubscribe(counter++) } val observable: Observable - get(){ + get() { return Observable.create(onSubscribe) } } - class AsyncObservable : OnSubscribe { - override fun call(op: Subscriber) { + class AsyncObservable : ObservableOnSubscribe { + override fun subscribe(op: ObservableEmitter) { thread { Thread.sleep(50) op.onNext(1) op.onNext(2) op.onNext(3) - op.onCompleted() + op.onComplete() } - } } - class TestOnSubscribe(val count: Int) : OnSubscribe { - override fun call(op: Subscriber) { + class TestOnSubscribe(val count: Int) : ObservableOnSubscribe { + override fun subscribe(op: ObservableEmitter) { op.onNext("hello_$count") - op.onCompleted() + op.onComplete() } - } } diff --git a/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt b/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt index 85278f8..6f1a3d8 100644 --- a/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt +++ b/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt @@ -1,18 +1,18 @@ package rx.lang.kotlin +import io.reactivex.Single +import io.reactivex.functions.Action import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull -import rx.Single -import rx.functions.Action0 -import java.util.* +import org.junit.Test +import java.util.NoSuchElementException import java.util.concurrent.Callable -import org.junit.Test as test class CompletableTest { - @test fun testCreateFromAction() { + @Test fun testCreateFromAction() { var count = 0 - val c1 = Action0 { count++ }.toCompletable() + val c1 = Action { count++ }.toCompletable() assertNotNull(c1) c1.subscribe() assertEquals(1, count) @@ -24,7 +24,7 @@ class CompletableTest { assertEquals(1, count) } - @test fun testCreateFromCallable() { + @Test fun testCreateFromCallable() { var count = 0 val c1 = Callable { count++ }.toCompletable() assertNotNull(c1) @@ -32,16 +32,16 @@ class CompletableTest { assertEquals(1, count) } - @test(expected = NoSuchElementException::class) fun testCreateFromFuture() { - val c1 = 1.toSingletonObservable().toBlocking().toFuture().toCompletable() + @Test(expected = NoSuchElementException::class) fun testCreateFromFuture() { + val c1 = 1.toSingletonObservable().single(0).toCompletable() assertNotNull(c1) - c1.toObservable().toBlocking().first() + c1.toObservable().blockingFirst() } - @test(expected = NoSuchElementException::class) fun testCreateFromSingle() { + @Test(expected = NoSuchElementException::class) fun testCreateFromSingle() { val c1 = Single.just("Hello World!").toCompletable() assertNotNull(c1) - c1.toObservable().toBlocking().first() + c1.toObservable().blockingFirst() } } diff --git a/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt b/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt index ead462e..1f312a2 100644 --- a/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt +++ b/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt @@ -16,15 +16,20 @@ package rx.lang.kotlin +import io.reactivex.Notification +import io.reactivex.Observable +import io.reactivex.ObservableEmitter +import io.reactivex.functions.BiFunction +import io.reactivex.functions.Function3 +import io.reactivex.schedulers.TestScheduler import org.funktionale.partials.invoke import org.junit.Assert.assertEquals import org.junit.Assert.fail import org.junit.Test -import org.mockito.Mockito.* -import rx.Notification -import rx.Observable -import rx.Subscriber -import rx.schedulers.TestScheduler +import org.mockito.Mockito.any +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.times +import org.mockito.Mockito.verify import java.util.concurrent.TimeUnit import kotlin.concurrent.thread @@ -35,10 +40,9 @@ class ExtensionTests : KotlinTests() { @Test fun testCreate() { - observable { subscriber -> subscriber.onNext("Hello") - subscriber.onCompleted() + subscriber.onComplete() }.subscribe { result -> a.received(result) } @@ -55,11 +59,11 @@ class ExtensionTests : KotlinTests() { @Test fun testLast() { - assertEquals("three", listOf("one", "two", "three").toObservable().toBlocking().last()) + assertEquals("three", listOf("one", "two", "three").toObservable().blockingLast()) } @Test fun testLastWithPredicate() { - assertEquals("two", listOf("one", "two", "three").toObservable().toBlocking().last { x -> x.length == 3 }) + assertEquals("two", listOf("one", "two", "three").toObservable().filter { it.length == 3 }.blockingLast()) } @Test fun testMap1() { @@ -113,14 +117,14 @@ class ExtensionTests : KotlinTests() { @Test fun testFromWithIterable() { - assertEquals(5, listOf(1, 2, 3, 4, 5).toObservable().count().toBlocking().single()) + assertEquals(5, listOf(1, 2, 3, 4, 5).toObservable().count().blockingGet()) } @Test fun testStartWith() { val list = listOf(10, 11, 12, 13, 14) val startList = listOf(1, 2, 3, 4, 5) - assertEquals(6, list.toObservable().startWith(0).count().toBlocking().single()) - assertEquals(10, list.toObservable().startWith(startList).count().toBlocking().single()) + assertEquals(6, list.toObservable().startWith(0).count().blockingGet()) + assertEquals(10, list.toObservable().startWith(startList).count().blockingGet()) } @Test fun testScriptWithOnNext() { @@ -162,7 +166,7 @@ class ExtensionTests : KotlinTests() { } @Test fun testTakeWhileWithIndex() { - listOf(1, 2, 3).toObservable().takeWhile { x -> x < 3 }.zipWith((0..Integer.MAX_VALUE).toObservable()) { x, i -> x }.subscribe(received()) + listOf(1, 2, 3).toObservable().takeWhile { x -> x < 3 }.zipWith((0..Integer.MAX_VALUE).toObservable(), BiFunction { x, i -> x }).subscribe(received()) verify(a, times(1)).received(1) verify(a, times(1)).received(2) verify(a, times(0)).received(3) @@ -174,24 +178,24 @@ class ExtensionTests : KotlinTests() { } @Test fun testForEach() { - observable(asyncObservable).toBlocking().forEach(received()) + observable(asyncObservable).blockingForEach(received()) verify(a, times(1)).received(1) verify(a, times(1)).received(2) verify(a, times(1)).received(3) } @Test(expected = RuntimeException::class) fun testForEachWithError() { - observable(asyncObservable).toBlocking().forEach { throw RuntimeException("err") } + observable(asyncObservable).blockingForEach { throw RuntimeException("err") } fail("we expect an exception to be thrown") } @Test fun testLastOrDefault() { - assertEquals("two", listOf("one", "two").toObservable().toBlocking().lastOrDefault("default") { x -> x.length == 3 }) - assertEquals("default", listOf("one", "two").toObservable().toBlocking().lastOrDefault("default") { x -> x.length > 3 }) + assertEquals("two", listOf("one", "two").toObservable().blockingLast("default")) + assertEquals("default", listOf("one", "two").toObservable().filter { it.length > 3 }.blockingLast("default")) } @Test fun testDefer() { - deferredObservable { listOf(1, 2).toObservable() }.subscribe(received()) + listOf(1, 2).toObservable().defer().subscribe(received()) verify(a, times(1)).received(1) verify(a, times(1)).received(2) } @@ -206,7 +210,7 @@ class ExtensionTests : KotlinTests() { val o2 = listOf(4, 5, 6).toObservable() val o3 = listOf(7, 8, 9).toObservable() - val values = Observable.zip(o1, o2, o3) { a, b, c -> listOf(a, b, c) }.toList().toBlocking().single() + val values = Observable.zip(o1, o2, o3, Function3 > { a, b, c -> listOf(a, b, c) }).toList().blockingGet() assertEquals(listOf(1, 4, 7), values[0]) assertEquals(listOf(2, 5, 8), values[1]) assertEquals(listOf(3, 6, 9), values[2]) @@ -217,7 +221,7 @@ class ExtensionTests : KotlinTests() { val worker = testScheduler.createWorker() val observable = observable> { s -> - fun at(delay: Long, func : () -> Unit){ + fun at(delay: Long, func: () -> Unit) { worker.schedule({ func() }, delay, TimeUnit.MILLISECONDS) @@ -229,7 +233,7 @@ class ExtensionTests : KotlinTests() { val second = Observable.interval(5, TimeUnit.MILLISECONDS, testScheduler).take(3) at(11, { s.onNext(second) }) - at(40, { s.onCompleted() }) + at(40, { s.onComplete() }) } observable.switchOnNext().subscribe(received()) @@ -246,30 +250,29 @@ class ExtensionTests : KotlinTests() { inOrder.verifyNoMoreInteractions() } - val funOnSubscribe: (Int, Subscriber) -> Unit = { counter, subscriber -> + val funOnSubscribe: (Int, ObservableEmitter) -> Unit = { counter, subscriber -> subscriber.onNext("hello_$counter") - subscriber.onCompleted() + subscriber.onComplete() } - val asyncObservable: (Subscriber) -> Unit = { subscriber -> + val asyncObservable: (ObservableEmitter) -> Unit = { subscriber -> thread { Thread.sleep(50) subscriber.onNext(1) subscriber.onNext(2) subscriber.onNext(3) - subscriber.onCompleted() + subscriber.onComplete() } } - - inner class TestFactory() { + inner class TestFactory { var counter = 1 val numbers: Observable get() = listOf(1, 3, 2, 5, 4).toObservable() - val onSubscribe: (Subscriber) -> Unit + val onSubscribe: (ObservableEmitter) -> Unit get() = funOnSubscribe(p1 = counter++) // partial applied function val observable: Observable diff --git a/src/test/kotlin/rx/lang/kotlin/KotlinTests.kt b/src/test/kotlin/rx/lang/kotlin/KotlinTests.kt index d58d1b2..75f8e6e 100644 --- a/src/test/kotlin/rx/lang/kotlin/KotlinTests.kt +++ b/src/test/kotlin/rx/lang/kotlin/KotlinTests.kt @@ -16,10 +16,10 @@ package rx.lang.kotlin +import io.reactivex.Observable import org.junit.Before import org.mockito.Mock import org.mockito.MockitoAnnotations -import rx.Observable abstract class KotlinTests { @Mock var a: ScriptAssertion = uninitialized() @@ -30,7 +30,7 @@ abstract class KotlinTests { } @Suppress("BASE_WITH_NULLABLE_UPPER_BOUND") - fun received() = {result: T? -> a.received(result) } + fun received() = { result: T? -> a.received(result) } interface ScriptAssertion { fun error(e: Throwable?) diff --git a/src/test/kotlin/rx/lang/kotlin/ObservablesTest.kt b/src/test/kotlin/rx/lang/kotlin/ObservablesTest.kt index 165a7e3..155ae4e 100644 --- a/src/test/kotlin/rx/lang/kotlin/ObservablesTest.kt +++ b/src/test/kotlin/rx/lang/kotlin/ObservablesTest.kt @@ -1,26 +1,30 @@ package rx.lang.kotlin -import org.junit.Assert.* +import io.reactivex.Observable +import io.reactivex.observers.TestObserver +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull import org.junit.Ignore -import rx.Observable -import rx.observers.TestSubscriber import java.util.concurrent.atomic.AtomicInteger import org.junit.Test as test class ObservablesTest { @test fun testCreation() { - val o0 : Observable = emptyObservable() - observable { s -> s.onNext(1); s.onNext(777); s.onCompleted() }.toList().forEach { - assertEquals(listOf(1, 777), it) - } - val o1 : Observable = listOf(1, 2, 3).toObservable() - val o2 : Observable> = listOf(1, 2, 3).toSingletonObservable() - - val o3 : Observable = deferredObservable { observable { s -> s.onNext(1) } } - val o4 : Observable = Array(3) {0}.toObservable() - val o5 : Observable = IntArray(3).toObservable() - + val o0: Observable = emptyObservable() + val list = observable { s -> + s.onNext(1) + s.onNext(777) + s.onComplete() + }.toList().blockingGet() + assertEquals(listOf(1, 777), list) + val o1: Observable = listOf(1, 2, 3).toObservable() + val o2: Observable> = listOf(1, 2, 3).toSingletonObservable() + + val o3: Observable = observable { s -> s.onNext(1) }.defer() + val o4: Observable = Array(3) { 0 }.toObservable() + val o5: Observable = IntArray(3).toObservable() + assertNotNull(o0) assertNotNull(o1) assertNotNull(o2) @@ -28,7 +32,7 @@ class ObservablesTest { assertNotNull(o4) assertNotNull(o5) } - + @test fun testExampleFromReadme() { val result = observable { subscriber -> subscriber.onNext("H") @@ -37,138 +41,114 @@ class ObservablesTest { subscriber.onNext("") subscriber.onNext("l") subscriber.onNext("o") - subscriber.onCompleted() - }.filter { it.isNotEmpty() }. - fold (StringBuilder()) { sb, e -> sb.append(e) }. - map { it.toString() }. - toBlocking().single() - + subscriber.onComplete() + }.filter(String::isNotEmpty). + fold(StringBuilder(), StringBuilder::append). + map { it.toString() }. + blockingGet() + assertEquals("Hello", result) } - + @test fun iteratorObservable() { - assertEquals(listOf(1,2,3), listOf(1,2,3).iterator().toObservable().toList().toBlocking().single()) + assertEquals(listOf(1, 2, 3), listOf(1, 2, 3).iterator().toObservable().toList().blockingGet()) } - + @test fun intProgressionStep1Empty() { - assertEquals(listOf(1), (1..1).toObservable().toList().toBlocking().first()) + assertEquals(listOf(1), (1..1).toObservable().toList().blockingGet()) } + @test fun intProgressionStep1() { - assertEquals((1..10).toList(), (1..10).toObservable().toList().toBlocking().first()) + assertEquals((1..10).toList(), (1..10).toObservable().toList().blockingGet()) } - + @test fun intProgressionDownTo() { - assertEquals((1 downTo 10).toList(), (1 downTo 10).toObservable().toList().toBlocking().first()) + assertEquals((1 downTo 10).toList(), (1 downTo 10).toObservable().toList().blockingGet()) } - - @Ignore + + @Ignore("Too slow") @test fun intProgressionOverflow() { - // too slow - assertEquals((0..10).toList().reversed(), (-10 .. Integer.MAX_VALUE).toObservable().skip(Integer.MAX_VALUE).map{Integer.MAX_VALUE - it}.toList().toBlocking().first()) + assertEquals((0..10).toList().reversed(), (-10..Integer.MAX_VALUE).toObservable().skip(Integer.MAX_VALUE.toLong()).map { Integer.MAX_VALUE - it }.toList().blockingGet()) } - - @test fun filterNotNull() { - val o : Observable = listOf(1, null).toObservable().filterNotNull() - o.toList().forEach { - assertEquals(listOf(1), it) - } - } - - @test fun requireNoNullsWithoutNulls() { - (listOf(1,2) as List).toObservable().requireNoNulls().subscribe() - } - - @test fun requireNoNulls() { - try { - val o : Observable = listOf(1, null).toObservable().requireNoNulls() - - o.subscribe() - fail("shouldn't reach here") - } catch (expected : Throwable) { - } - } - + @test fun testWithIndex() { - listOf("a", "b", "c").toObservable(). - withIndex(). - toList(). - forEach { - assertEquals(listOf(IndexedValue(0, "a"), IndexedValue(1, "b"), IndexedValue(2, "c")), it) - } + listOf("a", "b", "c").toObservable() + .withIndex() + .toList() + .test() + .assertValues(listOf(IndexedValue(0, "a"), IndexedValue(1, "b"), IndexedValue(2, "c"))) } - + @test fun `withIndex() shouldn't share index between multiple subscribers`() { val o = listOf("a", "b", "c").toObservable().withIndex() - - val subscriber1 = TestSubscriber>() - val subscriber2 = TestSubscriber>() - + + val subscriber1 = TestObserver.create>() + val subscriber2 = TestObserver.create>() + o.subscribe(subscriber1) o.subscribe(subscriber2) - + subscriber1.awaitTerminalEvent() subscriber1.assertValues(IndexedValue(0, "a"), IndexedValue(1, "b"), IndexedValue(2, "c")) - + subscriber2.awaitTerminalEvent() subscriber2.assertValues(IndexedValue(0, "a"), IndexedValue(1, "b"), IndexedValue(2, "c")) } - + @test fun testFold() { - listOf(1, 2, 3).toObservable().fold(0) {acc, e -> acc + e}.single().forEach { - assertEquals(6, it) - } + val result = listOf(1, 2, 3).toObservable().fold(0) { acc, e -> acc + e }.blockingGet() + assertEquals(6, result) } - + @test fun `kotlin sequence should produce expected items and observable be able to handle em`() { - kotlin.sequences.generateSequence(0) {it + 1}.toObservable().take(3).toList().forEach { - assertEquals(listOf(0, 1, 2), it) - } + generateSequence(0) { it + 1 }.toObservable() + .take(3) + .toList() + .test() + .assertValues(listOf(0, 1, 2)) } - + @test fun `infinite iterable should not hang or produce too many elements`() { val generated = AtomicInteger() - kotlin.sequences.generateSequence { generated.incrementAndGet() }.toObservable(). + generateSequence { generated.incrementAndGet() }.toObservable(). take(100). toList(). subscribe() - + assertEquals(100, generated.get()) } - + @test fun testFlatMapSequence() { assertEquals( listOf(1, 2, 3, 2, 3, 4, 3, 4, 5), - listOf(1,2,3).toObservable().flatMapSequence { listOf(it, it + 1, it + 2).asSequence() }.toList().toBlocking().single() + listOf(1, 2, 3).toObservable().flatMapSequence { listOf(it, it + 1, it + 2).asSequence() }.toList().blockingGet() ) } - + @test fun testCombineLatest() { - val list = listOf(1,2,3,2,3,4,3,4,5) - assertEquals(list, list.map { it.toSingletonObservable() }.combineLatest { it }.toBlocking().first()) + val list = listOf(1, 2, 3, 2, 3, 4, 3, 4, 5) + assertEquals(list, list.map { it.toSingletonObservable() }.combineLatest { it }.blockingFirst()) } - + @test fun testZip() { - val list = listOf(1,2,3,2,3,4,3,4,5) - assertEquals(list, list.map { it.toSingletonObservable() }.zip { it }.toBlocking().first()) + val list = listOf(1, 2, 3, 2, 3, 4, 3, 4, 5) + assertEquals(list, list.map { it.toSingletonObservable() }.zip { it }.blockingFirst()) } - + @test fun testCast() { val source = Observable.just(1, 2) val observable = source.cast() - val subscriber = TestSubscriber() - observable.subscribe(subscriber) - subscriber.apply { - assertValues(1, 2) - assertNoErrors() - assertCompleted() - } + observable.test() + .await() + .assertValues(1, 2) + .assertNoErrors() + .assertComplete() } - + @test fun testCastWithWrongType() { val source = Observable.just(1, 2) val observable = source.cast() - val subscriber = TestSubscriber() - observable.subscribe(subscriber) - subscriber.assertError(ClassCastException::class.java) + observable.test() + .assertError(ClassCastException::class.java) } } \ No newline at end of file diff --git a/src/test/kotlin/rx/lang/kotlin/SingleTest.kt b/src/test/kotlin/rx/lang/kotlin/SingleTest.kt index a77e0e7..5bce1b2 100644 --- a/src/test/kotlin/rx/lang/kotlin/SingleTest.kt +++ b/src/test/kotlin/rx/lang/kotlin/SingleTest.kt @@ -1,10 +1,10 @@ package rx.lang.kotlin import org.mockito.Mockito +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify import java.util.concurrent.Callable import org.junit.Test as test -import org.mockito.Mockito.verify -import org.mockito.Mockito.mock class SingleTest : KotlinTests() { @test fun testCreate() { @@ -17,7 +17,7 @@ class SingleTest : KotlinTests() { } @test fun testCreateFromFuture() { - val future = "Hello World!".toSingletonObservable().toBlocking().toFuture() + val future = "Hello World!".toSingletonObservable().toFuture() val single = future.toSingle() single.subscribe { result -> a.received(result) diff --git a/src/test/kotlin/rx/lang/kotlin/SubscribersTest.kt b/src/test/kotlin/rx/lang/kotlin/SubscribersTest.kt index d049a49..051937d 100644 --- a/src/test/kotlin/rx/lang/kotlin/SubscribersTest.kt +++ b/src/test/kotlin/rx/lang/kotlin/SubscribersTest.kt @@ -1,10 +1,10 @@ package rx.lang.kotlin +import io.reactivex.Observer +import io.reactivex.disposables.Disposables import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue -import rx.Subscriber -import rx.exceptions.OnErrorNotImplementedException -import java.util.* +import java.util.ArrayList import org.junit.Test as test class SubscribersTest { @@ -12,169 +12,150 @@ class SubscribersTest { val s = subscriber() callSubscriberMethods(false, s) } - + @test fun testSubscriberConstruction() { val events = ArrayList() - + callSubscriberMethods(false, subscriber(). - onNext {events.add("onNext($it)")} + onNext { events.add("onNext($it)") } ) - + assertEquals(listOf("onNext(1)"), events) events.clear() - + callSubscriberMethods(true, subscriber(). - onNext {events.add("onNext($it)")}. + onNext { events.add("onNext($it)") }. onError { events.add(it.javaClass.simpleName) } ) - + assertEquals(listOf("onNext(1)", "RuntimeException"), events) events.clear() - + callSubscriberMethods(true, subscriber(). - onNext {events.add("onNext($it)")}. + onNext { events.add("onNext($it)") }. onError { events.add(it.javaClass.simpleName) }. onCompleted { events.add("onCompleted") } ) - + assertEquals(listOf("onNext(1)", "RuntimeException", "onCompleted"), events) events.clear() } - - @test(expected = OnErrorNotImplementedException::class) + + @test(expected = Exception::class) fun testNoErrorHandlers() { subscriber().onError(Exception("expected")) } - + @test fun testErrorHandlers() { var errorsCaught = 0 - + subscriber(). onError { errorsCaught++ }. onError { errorsCaught++ }. onError(Exception("expected")) - + assertEquals(2, errorsCaught) } - + @test fun testMultipleOnNextHandlers() { var nextCaught = 0 - + subscriber(). - onNext { nextCaught ++ }. - onNext { nextCaught ++ }. + onNext { nextCaught++ }. + onNext { nextCaught++ }. onNext(1) - + assertEquals(2, nextCaught) } - + @test fun testOnStart() { var started = false - subscriber().onStart { started = true }.onStart() + "".toSingletonObservable().subscribeBy { + onStart { started = true } + } assertTrue(started) } - - private fun callSubscriberMethods(hasOnError : Boolean, s: Subscriber) { - s.onStart() + + private fun callSubscriberMethods(hasOnError: Boolean, s: Observer) { + s.onSubscribe(Disposables.empty()) s.onNext(1) try { s.onError(RuntimeException()) - } catch (t : Throwable) { - if (hasOnError) { - throw t - } + } catch (t: Throwable) { + if (hasOnError) throw t } - s.onCompleted() + s.onComplete() } - + @test fun testSubscribeWith() { val completeObservable = observable { it.onNext(1) - it.onCompleted() + it.onComplete() } - + val events = ArrayList() - - completeObservable.subscribeWith { + + completeObservable.subscribeBy { onNext { events.add("onNext($it)") } } - + assertEquals(listOf("onNext(1)"), events) events.clear() - - completeObservable.subscribeWith { + + completeObservable.subscribeBy { onNext { events.add("onNext($it)") } - onCompleted { events.add("onCompleted") } + onComplete { events.add("onCompleted") } } - + assertEquals(listOf("onNext(1)", "onCompleted"), events) events.clear() - + val errorObservable = observable { it.onNext(1) it.onError(RuntimeException()) } - - errorObservable.subscribeWith { + + errorObservable.subscribeBy { onNext { events.add("onNext($it)") } onError { events.add("onError(${it.javaClass.simpleName})") } } - + assertEquals(listOf("onNext(1)", "onError(RuntimeException)"), events) - events.clear() - - try { - errorObservable.subscribeWith { - onNext { events.add("onNext($it)") } - } - } catch (t: Throwable) { - events.add("catch(${t.javaClass.simpleName})") - } - - assertEquals(listOf("onNext(1)", "catch(OnErrorNotImplementedException)"), events) - events.clear() } - + @test fun testSingleSubscribeWith() { val events = ArrayList() val successSingle = singleOf(1) - - successSingle.subscribeWith { + + successSingle.subscribeBy { onSuccess { events.add("onSuccess($it)") } } - + assertEquals(listOf("onSuccess(1)"), events) events.clear() - + val errorSingle = RuntimeException().toSingle() - - errorSingle.subscribeWith { + + errorSingle.subscribeBy { onSuccess { events.add("onSuccess($it)") } onError { events.add("onError(${it.javaClass.simpleName})") } } - + assertEquals(listOf("onError(RuntimeException)"), events) - events.clear() - + } + + @test fun testSubscribeByErrorNotImplemented() { + val events = ArrayList() + val errorSingle = RuntimeException().toSingle() try { - errorSingle.subscribeWith { + errorSingle.subscribeBy { onSuccess { events.add("onSuccess($it)") } } } catch (e: Throwable) { - events.add("catch(${e.javaClass.simpleName})") + val name = e.cause?.javaClass?.simpleName + events.add("catch($name)") } - + assertEquals(listOf("catch(OnErrorNotImplementedException)"), events) - events.clear() - } - - @test fun testIdiomaticAdd() { - var subscriptionCalled = false - val s = subscriber() - - s.add { subscriptionCalled = true } - s.unsubscribe() - - assertTrue(subscriptionCalled) } } diff --git a/src/test/kotlin/rx/lang/kotlin/SubscriptionTests.kt b/src/test/kotlin/rx/lang/kotlin/SubscriptionTests.kt index 2dfe412..01c528d 100644 --- a/src/test/kotlin/rx/lang/kotlin/SubscriptionTests.kt +++ b/src/test/kotlin/rx/lang/kotlin/SubscriptionTests.kt @@ -1,29 +1,29 @@ package rx.lang.kotlin +import io.reactivex.Observable +import io.reactivex.disposables.CompositeDisposable import org.junit.Test -import rx.Observable -import rx.subscriptions.CompositeSubscription import java.util.concurrent.TimeUnit class SubscriptionTest { @Test fun testSubscriptionAddTo() { - val compositeSubscription = CompositeSubscription() + val compositeSubscription = CompositeDisposable() // Create an asynchronous subscription // The delay ensures that we don't automatically unsubscribe because data finished emitting val subscription = Observable.just("test") .delay(100, TimeUnit.MILLISECONDS) - .subscribe(); + .subscribe() - assert(!subscription.isUnsubscribed) + assert(!subscription.isDisposed) - subscription.addTo(compositeSubscription); + subscription.addTo(compositeSubscription) - assert(compositeSubscription.hasSubscriptions()); - assert(!subscription.isUnsubscribed); + assert(compositeSubscription.size() > 0) + assert(!subscription.isDisposed) - compositeSubscription.unsubscribe() + compositeSubscription.dispose() - assert(compositeSubscription.isUnsubscribed); + assert(compositeSubscription.isDisposed) } } \ No newline at end of file From 0f2ee085d10bcd948b9beef0ec22ce072801a765 Mon Sep 17 00:00:00 2001 From: stepango Date: Sun, 8 Jan 2017 12:41:44 +0200 Subject: [PATCH 03/20] Code cleaning. Code review related fixes. --- LICENSE | 2 +- RELEASING.md | 19 ++++ build.gradle | 2 +- .../rx/lang/kotlin/examples/examples.kt | 2 +- src/main/kotlin/rx/lang/kotlin/observables.kt | 12 +-- src/main/kotlin/rx/lang/kotlin/subscribers.kt | 28 +++--- .../kotlin/rx/lang/kotlin/BasicKotlinTests.kt | 3 +- .../kotlin/rx/lang/kotlin/CompletableTest.kt | 6 -- .../kotlin/rx/lang/kotlin/ExtensionTests.kt | 13 +-- .../kotlin/rx/lang/kotlin/ObservablesTest.kt | 56 ++++++------ src/test/kotlin/rx/lang/kotlin/SingleTest.kt | 13 +-- .../kotlin/rx/lang/kotlin/SubscribersTest.kt | 90 ++++++++++--------- 12 files changed, 124 insertions(+), 122 deletions(-) create mode 100644 RELEASING.md diff --git a/LICENSE b/LICENSE index 59f7b92..7f8ced0 100644 --- a/LICENSE +++ b/LICENSE @@ -180,7 +180,7 @@ To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don'value include + replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..773245a --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,19 @@ +Release Process +=============== + + 1. Ensure `VERSION_NAME` in `gradle.properties` is set to the version you want to release. + 2. Add an entry in `CHANGELOG.md` with the changes for the release. + 3. Update `README.md` with the version about to be released. Also update the RxJava version in + this file to its latest. + 4. Update the RxJava version in `rxkotlin/build.gradle` to its latest. (We tell people that we + won't be tracking RxJava releases, and we don't, but we do it anyway when we are releasing for + those who ignore the advice.) + 5. Commit: `git commit -am "Prepare version X.Y.X"` + 6. Tag: `git tag -a X.Y.Z -m "Version X.Y.Z"` + 7. Update `VERSION_NAME` in `gradle.properties` to the next development version. For example, if + you just tagged version 1.0.4 you would set this value to 1.0.5. Do NOT append "-SNAPSHOT" to + this value, it will be added automatically. + 8. Commit: `git commit -am "Prepare next development version."` + 9. Push: `git push && git push --tags` + 10. Paste the `CHANGELOG.md` contents for this version into a Release on GitHub along with the + Groovy for depending on the new version (https://github.com/ReactiveX/RxKotlin/releases). diff --git a/build.gradle b/build.gradle index 678111e..34d7c57 100755 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ apply plugin: 'nebula.rxjava-project' apply plugin: 'kotlin' dependencies { - compile 'io.reactivex.rxjava2:rxjava:2.0.3' + compile 'io.reactivex.rxjava2:rxjava:2.0.4' compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" testCompile 'org.funktionale:funktionale:0.9' testCompile 'junit:junit:4.12' diff --git a/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt b/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt index 6f1a0a4..f23f211 100644 --- a/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt +++ b/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt @@ -74,7 +74,7 @@ fun asyncObservable(): Observable = observable { subscriber -> fun asyncWiki(vararg articleNames: String): Observable = observable { subscriber -> thread { articleNames.toObservable() - .flatMapMaybe { name: String -> URL("http://en.wikipedia.org/wiki/$name").toScannerObservable().firstElement() } + .flatMapMaybe { name -> URL("http://en.wikipedia.org/wiki/$name").toScannerObservable().firstElement() } .subscribe { subscriber.onNext(it) } } } diff --git a/src/main/kotlin/rx/lang/kotlin/observables.kt b/src/main/kotlin/rx/lang/kotlin/observables.kt index 0246fcd..6a356b9 100644 --- a/src/main/kotlin/rx/lang/kotlin/observables.kt +++ b/src/main/kotlin/rx/lang/kotlin/observables.kt @@ -11,11 +11,6 @@ import io.reactivex.functions.BiFunction */ fun emptyObservable(): Observable = Observable.empty() -/** - * [Observable.defer] alias - */ -fun Observable.defer() = Observable.defer { this } - fun observable(body: (ObservableEmitter) -> Unit): Observable = Observable.create(body) private fun Iterator.toIterable() = object : Iterable { @@ -39,9 +34,6 @@ fun Iterator.toObservable(): Observable = toIterable().toObserva fun Iterable.toObservable(): Observable = Observable.fromIterable(this) fun Sequence.toObservable(): Observable = Observable.fromIterable(iterator().toIterable()) -fun T.toSingletonObservable(): Observable = Observable.just(this) -fun Throwable.toObservable(): Observable = Observable.error(this) - fun Iterable>.merge(): Observable = Observable.merge(this.toObservable()) fun Iterable>.mergeDelayError(): Observable = Observable.mergeDelayError(this.toObservable()) @@ -91,8 +83,8 @@ inline fun List>.combineLatest(crossinline combineFunction: * Observable.zip(List> sources, FuncN combineFunction) */ @Suppress("UNCHECKED_CAST") -inline fun List>.zip(crossinline zipFunction: (args: List) -> R): Observable = - Observable.zip(this) { zipFunction(it.asList().map { it as T }) } +inline fun List>.zip(crossinline zipFunction: (args: List) -> R): Observable + = Observable.zip(this) { zipFunction(it.asList().map { it as T }) } /** * Returns an Observable that emits the items emitted by the source Observable, converted to the specified type. diff --git a/src/main/kotlin/rx/lang/kotlin/subscribers.kt b/src/main/kotlin/rx/lang/kotlin/subscribers.kt index 62adf24..d595547 100644 --- a/src/main/kotlin/rx/lang/kotlin/subscribers.kt +++ b/src/main/kotlin/rx/lang/kotlin/subscribers.kt @@ -8,15 +8,15 @@ import io.reactivex.disposables.Disposable import java.util.ArrayList class FunctionSubscriber : Observer, MaybeObserver, SingleObserver, CompletableObserver { - + private val onCompleteFunctions = ArrayList<() -> Unit>() private val onErrorFunctions = ArrayList<(e: Throwable) -> Unit>() private val onNextFunctions = ArrayList<(value: T) -> Unit>() private val onStartFunctions = ArrayList<() -> Unit>() var origin: Disposable? = null - + override fun onComplete() = onCompleteFunctions.forEach { it() } - + override fun onError(e: Throwable?) = (e ?: RuntimeException("exception is unknown")).let { ex -> if (onErrorFunctions.isEmpty()) { throw OnErrorNotImplementedException(ex) @@ -24,29 +24,29 @@ class FunctionSubscriber : Observer, MaybeObserver, SingleObserve onErrorFunctions.forEach { it(ex) } } } - + override fun onNext(value: T) = onNextFunctions.forEach { it(value) } override fun onSuccess(value: T) = onNext(value) - + override fun onSubscribe(d: Disposable?) { origin = d onStartFunctions.forEach { it() } } - + fun onCompleted(onCompletedFunction: () -> Unit): FunctionSubscriber = copy { onCompleteFunctions.add(onCompletedFunction) } fun onError(onErrorFunction: (t: Throwable) -> Unit): FunctionSubscriber = copy { onErrorFunctions.add(onErrorFunction) } fun onNext(onNextFunction: (t: T) -> Unit): FunctionSubscriber = copy { onNextFunctions.add(onNextFunction) } fun onStart(onStartFunction: () -> Unit): FunctionSubscriber = copy { onStartFunctions.add(onStartFunction) } - + private fun copy(block: FunctionSubscriber.() -> Unit): FunctionSubscriber { val newSubscriber = FunctionSubscriber() newSubscriber.onCompleteFunctions.addAll(onCompleteFunctions) newSubscriber.onErrorFunctions.addAll(onErrorFunctions) newSubscriber.onNextFunctions.addAll(onNextFunctions) newSubscriber.onStartFunctions.addAll(onStartFunctions) - + newSubscriber.block() - + return newSubscriber } } @@ -56,23 +56,23 @@ class OnErrorNotImplementedException(ex: Throwable) : RuntimeException(ex) class FunctionSubscriberModifier(init: FunctionSubscriber = subscriber()) { var subscriber: FunctionSubscriber = init private set - + fun onComplete(onCompletedFunction: () -> Unit): Unit { subscriber = subscriber.onCompleted(onCompletedFunction) } - + fun onError(onErrorFunction: (t: Throwable) -> Unit): Unit { subscriber = subscriber.onError(onErrorFunction) } - + fun onNext(onNextFunction: (t: T) -> Unit): Unit { subscriber = subscriber.onNext(onNextFunction) } - + fun onStart(onStartFunction: () -> Unit): Unit { subscriber = subscriber.onStart(onStartFunction) } - + fun onSuccess(onSuccessFunction: (t: T) -> Unit): Unit { subscriber = subscriber.onNext(onSuccessFunction) } diff --git a/src/test/kotlin/rx/lang/kotlin/BasicKotlinTests.kt b/src/test/kotlin/rx/lang/kotlin/BasicKotlinTests.kt index 20dba91..dc74cf1 100644 --- a/src/test/kotlin/rx/lang/kotlin/BasicKotlinTests.kt +++ b/src/test/kotlin/rx/lang/kotlin/BasicKotlinTests.kt @@ -35,9 +35,8 @@ import kotlin.concurrent.thread */ class BasicKotlinTests : KotlinTests() { - @Test fun testCreate() { - Observable.create { onSubscribe -> + Observable.create { onSubscribe -> onSubscribe.onNext("Hello") onSubscribe.onComplete() }.subscribe { result -> diff --git a/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt b/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt index 6f1a3d8..1264af2 100644 --- a/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt +++ b/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt @@ -32,12 +32,6 @@ class CompletableTest { assertEquals(1, count) } - @Test(expected = NoSuchElementException::class) fun testCreateFromFuture() { - val c1 = 1.toSingletonObservable().single(0).toCompletable() - assertNotNull(c1) - c1.toObservable().blockingFirst() - } - @Test(expected = NoSuchElementException::class) fun testCreateFromSingle() { val c1 = Single.just("Hello World!").toCompletable() assertNotNull(c1) diff --git a/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt b/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt index 1f312a2..c41b09c 100644 --- a/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt +++ b/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt @@ -66,11 +66,6 @@ class ExtensionTests : KotlinTests() { assertEquals("two", listOf("one", "two", "three").toObservable().filter { it.length == 3 }.blockingLast()) } - @Test fun testMap1() { - 1.toSingletonObservable().map { v -> "hello_$v" }.subscribe(received()) - verify(a, times(1)).received("hello_1") - } - @Test fun testMap2() { listOf(1, 2, 3).toObservable().map { v -> "hello_$v" }.subscribe(received()) verify(a, times(1)).received("hello_1") @@ -87,9 +82,9 @@ class ExtensionTests : KotlinTests() { @Test fun testMerge() { listOf(listOf(1, 2, 3).toObservable(), - listOf(6.toSingletonObservable(), - NullPointerException().toObservable(), - 7.toSingletonObservable() + listOf(Observable.just(6), + Observable.error(NullPointerException()), + Observable.just(7) ).merge(), listOf(4, 5).toObservable() ).merge().subscribe(received(), { e -> a.error(e) }) @@ -195,7 +190,7 @@ class ExtensionTests : KotlinTests() { } @Test fun testDefer() { - listOf(1, 2).toObservable().defer().subscribe(received()) + Observable.defer { listOf(1, 2).toObservable() }.subscribe(received()) verify(a, times(1)).received(1) verify(a, times(1)).received(2) } diff --git a/src/test/kotlin/rx/lang/kotlin/ObservablesTest.kt b/src/test/kotlin/rx/lang/kotlin/ObservablesTest.kt index 155ae4e..263bc51 100644 --- a/src/test/kotlin/rx/lang/kotlin/ObservablesTest.kt +++ b/src/test/kotlin/rx/lang/kotlin/ObservablesTest.kt @@ -19,12 +19,12 @@ class ObservablesTest { }.toList().blockingGet() assertEquals(listOf(1, 777), list) val o1: Observable = listOf(1, 2, 3).toObservable() - val o2: Observable> = listOf(1, 2, 3).toSingletonObservable() - - val o3: Observable = observable { s -> s.onNext(1) }.defer() + val o2: Observable> = Observable.just(listOf(1, 2, 3)) + + val o3: Observable = Observable.defer { observable { s -> s.onNext(1) } } val o4: Observable = Array(3) { 0 }.toObservable() val o5: Observable = IntArray(3).toObservable() - + assertNotNull(o0) assertNotNull(o1) assertNotNull(o2) @@ -32,7 +32,7 @@ class ObservablesTest { assertNotNull(o4) assertNotNull(o5) } - + @test fun testExampleFromReadme() { val result = observable { subscriber -> subscriber.onNext("H") @@ -46,31 +46,31 @@ class ObservablesTest { fold(StringBuilder(), StringBuilder::append). map { it.toString() }. blockingGet() - + assertEquals("Hello", result) } - + @test fun iteratorObservable() { assertEquals(listOf(1, 2, 3), listOf(1, 2, 3).iterator().toObservable().toList().blockingGet()) } - + @test fun intProgressionStep1Empty() { assertEquals(listOf(1), (1..1).toObservable().toList().blockingGet()) } - + @test fun intProgressionStep1() { assertEquals((1..10).toList(), (1..10).toObservable().toList().blockingGet()) } - + @test fun intProgressionDownTo() { assertEquals((1 downTo 10).toList(), (1 downTo 10).toObservable().toList().blockingGet()) } - + @Ignore("Too slow") @test fun intProgressionOverflow() { assertEquals((0..10).toList().reversed(), (-10..Integer.MAX_VALUE).toObservable().skip(Integer.MAX_VALUE.toLong()).map { Integer.MAX_VALUE - it }.toList().blockingGet()) } - + @test fun testWithIndex() { listOf("a", "b", "c").toObservable() .withIndex() @@ -78,28 +78,28 @@ class ObservablesTest { .test() .assertValues(listOf(IndexedValue(0, "a"), IndexedValue(1, "b"), IndexedValue(2, "c"))) } - + @test fun `withIndex() shouldn't share index between multiple subscribers`() { val o = listOf("a", "b", "c").toObservable().withIndex() - + val subscriber1 = TestObserver.create>() val subscriber2 = TestObserver.create>() - + o.subscribe(subscriber1) o.subscribe(subscriber2) - + subscriber1.awaitTerminalEvent() subscriber1.assertValues(IndexedValue(0, "a"), IndexedValue(1, "b"), IndexedValue(2, "c")) - + subscriber2.awaitTerminalEvent() subscriber2.assertValues(IndexedValue(0, "a"), IndexedValue(1, "b"), IndexedValue(2, "c")) } - + @test fun testFold() { val result = listOf(1, 2, 3).toObservable().fold(0) { acc, e -> acc + e }.blockingGet() assertEquals(6, result) } - + @test fun `kotlin sequence should produce expected items and observable be able to handle em`() { generateSequence(0) { it + 1 }.toObservable() .take(3) @@ -107,34 +107,34 @@ class ObservablesTest { .test() .assertValues(listOf(0, 1, 2)) } - + @test fun `infinite iterable should not hang or produce too many elements`() { val generated = AtomicInteger() generateSequence { generated.incrementAndGet() }.toObservable(). take(100). toList(). subscribe() - + assertEquals(100, generated.get()) } - + @test fun testFlatMapSequence() { assertEquals( listOf(1, 2, 3, 2, 3, 4, 3, 4, 5), listOf(1, 2, 3).toObservable().flatMapSequence { listOf(it, it + 1, it + 2).asSequence() }.toList().blockingGet() ) } - + @test fun testCombineLatest() { val list = listOf(1, 2, 3, 2, 3, 4, 3, 4, 5) - assertEquals(list, list.map { it.toSingletonObservable() }.combineLatest { it }.blockingFirst()) + assertEquals(list, list.map { Observable.just(it) }.combineLatest { it }.blockingFirst()) } - + @test fun testZip() { val list = listOf(1, 2, 3, 2, 3, 4, 3, 4, 5) - assertEquals(list, list.map { it.toSingletonObservable() }.zip { it }.blockingFirst()) + assertEquals(list, list.map { Observable.just(it) }.zip { it }.blockingFirst()) } - + @test fun testCast() { val source = Observable.just(1, 2) val observable = source.cast() @@ -144,7 +144,7 @@ class ObservablesTest { .assertNoErrors() .assertComplete() } - + @test fun testCastWithWrongType() { val source = Observable.just(1, 2) val observable = source.cast() diff --git a/src/test/kotlin/rx/lang/kotlin/SingleTest.kt b/src/test/kotlin/rx/lang/kotlin/SingleTest.kt index 5bce1b2..01017d3 100644 --- a/src/test/kotlin/rx/lang/kotlin/SingleTest.kt +++ b/src/test/kotlin/rx/lang/kotlin/SingleTest.kt @@ -1,13 +1,14 @@ package rx.lang.kotlin +import io.reactivex.Observable +import org.junit.Test import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.verify import java.util.concurrent.Callable -import org.junit.Test as test class SingleTest : KotlinTests() { - @test fun testCreate() { + @Test fun testCreate() { single { s -> s.onSuccess("Hello World!"); }.subscribe { result -> @@ -16,8 +17,8 @@ class SingleTest : KotlinTests() { verify(a, Mockito.times(1)).received("Hello World!") } - @test fun testCreateFromFuture() { - val future = "Hello World!".toSingletonObservable().toFuture() + @Test fun testCreateFromFuture() { + val future = Observable.just("Hello World!").toFuture() val single = future.toSingle() single.subscribe { result -> a.received(result) @@ -25,7 +26,7 @@ class SingleTest : KotlinTests() { verify(a, Mockito.times(1)).received("Hello World!") } - @test fun testCreateFromCallable() { + @Test fun testCreateFromCallable() { val callable = mock(Callable::class.java) Mockito.`when`(callable.call()).thenReturn("value") @@ -37,7 +38,7 @@ class SingleTest : KotlinTests() { verify(a, Mockito.times(1)).received("value") } - @test fun testCreateFromJust() { + @Test fun testCreateFromJust() { singleOf("Hello World!").subscribe { result -> a.received(result) } diff --git a/src/test/kotlin/rx/lang/kotlin/SubscribersTest.kt b/src/test/kotlin/rx/lang/kotlin/SubscribersTest.kt index 051937d..b4390cf 100644 --- a/src/test/kotlin/rx/lang/kotlin/SubscribersTest.kt +++ b/src/test/kotlin/rx/lang/kotlin/SubscribersTest.kt @@ -1,81 +1,83 @@ package rx.lang.kotlin +import io.reactivex.Observable import io.reactivex.Observer import io.reactivex.disposables.Disposables import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue +import org.junit.Test import java.util.ArrayList -import org.junit.Test as test class SubscribersTest { - @test fun testEmptySubscriber() { + + @Test fun testEmptySubscriber() { val s = subscriber() callSubscriberMethods(false, s) } - - @test fun testSubscriberConstruction() { + + @Test fun testSubscriberConstruction() { val events = ArrayList() - + callSubscriberMethods(false, subscriber(). onNext { events.add("onNext($it)") } ) - + assertEquals(listOf("onNext(1)"), events) events.clear() - + callSubscriberMethods(true, subscriber(). onNext { events.add("onNext($it)") }. onError { events.add(it.javaClass.simpleName) } ) - + assertEquals(listOf("onNext(1)", "RuntimeException"), events) events.clear() - + callSubscriberMethods(true, subscriber(). onNext { events.add("onNext($it)") }. onError { events.add(it.javaClass.simpleName) }. onCompleted { events.add("onCompleted") } ) - + assertEquals(listOf("onNext(1)", "RuntimeException", "onCompleted"), events) events.clear() } - - @test(expected = Exception::class) + + @Test(expected = Exception::class) fun testNoErrorHandlers() { subscriber().onError(Exception("expected")) } - - @test fun testErrorHandlers() { + + @Test fun testErrorHandlers() { var errorsCaught = 0 - + subscriber(). onError { errorsCaught++ }. onError { errorsCaught++ }. onError(Exception("expected")) - + assertEquals(2, errorsCaught) } - - @test fun testMultipleOnNextHandlers() { + + @Test fun testMultipleOnNextHandlers() { var nextCaught = 0 - + subscriber(). onNext { nextCaught++ }. onNext { nextCaught++ }. onNext(1) - + assertEquals(2, nextCaught) } - - @test fun testOnStart() { + + @Test fun testOnStart() { var started = false - "".toSingletonObservable().subscribeBy { + Observable.just("").subscribeBy { onStart { started = true } } assertTrue(started) } - + private fun callSubscriberMethods(hasOnError: Boolean, s: Observer) { s.onSubscribe(Disposables.empty()) s.onNext(1) @@ -86,65 +88,65 @@ class SubscribersTest { } s.onComplete() } - - @test fun testSubscribeWith() { + + @Test fun testSubscribeWith() { val completeObservable = observable { it.onNext(1) it.onComplete() } - + val events = ArrayList() - + completeObservable.subscribeBy { onNext { events.add("onNext($it)") } } - + assertEquals(listOf("onNext(1)"), events) events.clear() - + completeObservable.subscribeBy { onNext { events.add("onNext($it)") } onComplete { events.add("onCompleted") } } - + assertEquals(listOf("onNext(1)", "onCompleted"), events) events.clear() - + val errorObservable = observable { it.onNext(1) it.onError(RuntimeException()) } - + errorObservable.subscribeBy { onNext { events.add("onNext($it)") } onError { events.add("onError(${it.javaClass.simpleName})") } } - + assertEquals(listOf("onNext(1)", "onError(RuntimeException)"), events) } - - @test fun testSingleSubscribeWith() { + + @Test fun testSingleSubscribeWith() { val events = ArrayList() val successSingle = singleOf(1) - + successSingle.subscribeBy { onSuccess { events.add("onSuccess($it)") } } - + assertEquals(listOf("onSuccess(1)"), events) events.clear() - + val errorSingle = RuntimeException().toSingle() - + errorSingle.subscribeBy { onSuccess { events.add("onSuccess($it)") } onError { events.add("onError(${it.javaClass.simpleName})") } } - + assertEquals(listOf("onError(RuntimeException)"), events) } - - @test fun testSubscribeByErrorNotImplemented() { + + @Test fun testSubscribeByErrorNotImplemented() { val events = ArrayList() val errorSingle = RuntimeException().toSingle() try { @@ -155,7 +157,7 @@ class SubscribersTest { val name = e.cause?.javaClass?.simpleName events.add("catch($name)") } - + assertEquals(listOf("catch(OnErrorNotImplementedException)"), events) } } From 54b37af08b548f454e0677e4f43219112b7ebac3 Mon Sep 17 00:00:00 2001 From: stepango Date: Sun, 8 Jan 2017 12:44:08 +0200 Subject: [PATCH 04/20] Whitespaces removed. --- .../rx/lang/kotlin/examples/examples.kt | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt b/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt index f23f211..1c59cb0 100644 --- a/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt +++ b/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt @@ -16,37 +16,37 @@ import java.util.concurrent.TimeUnit import kotlin.concurrent.thread fun main(args: Array) { - + val subscription = CompositeDisposable() - + val printArticle = { art: String -> println("--- Article ---\n${art.substring(0, 125)}") } - + val printIt = { it: String -> println(it) } - + subscription += asyncObservable().subscribe(printIt) - + subscription += syncObservable().subscribe(printIt) - + subscription.clear() - + simpleComposition() - + asyncWiki("Tiger", "Elephant").subscribe(printArticle) - + asyncWikiWithErrorHandling("Tiger", "Elephant").subscribe(printArticle) { e -> println("--- Error ---\n${e.message}") } - + combineLatest(listOfObservables()) - + zip(listOfObservables()) - + simpleObservable().subscribe(FunctionSubscriber() .onNext { s -> println("1st onNext => $s") } .onNext { s -> println("2nd onNext => $s") }) - + addToCompositeSubscription() } @@ -109,11 +109,11 @@ fun simpleObservable(): Observable = (0..17).toObservable().map { "Simpl fun addToCompositeSubscription() { val compositeSubscription = CompositeDisposable() - + Observable.just("test") .delay(100, TimeUnit.MILLISECONDS) .subscribe() .addTo(compositeSubscription) - + compositeSubscription.dispose() } \ No newline at end of file From ef24fa4aa790771dc881a539dce13c69bdd386aa Mon Sep 17 00:00:00 2001 From: stepango Date: Sun, 8 Jan 2017 12:54:26 +0200 Subject: [PATCH 05/20] gitignore update --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index fb5b676..5a21392 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,4 @@ bin/ # Scala build *.cache /.nb-gradle/private/ +local.properties From 2adeec25417dff5dee881dc901d7a70734a72460 Mon Sep 17 00:00:00 2001 From: stepango Date: Fri, 13 Jan 2017 11:46:29 +0200 Subject: [PATCH 06/20] subscribers replaced by named args extension --- .../rx/lang/kotlin/examples/examples.kt | 31 ++-- .../{completable.kt => completables.kt} | 0 src/main/kotlin/rx/lang/kotlin/observables.kt | 13 -- .../rx/lang/kotlin/{single.kt => singles.kt} | 10 -- src/main/kotlin/rx/lang/kotlin/subscribers.kt | 81 --------- .../kotlin/rx/lang/kotlin/subscription.kt | 19 -- .../kotlin/rx/lang/kotlin/subscriptions.kt | 57 ++++++ .../kotlin/rx/lang/kotlin/SubscribersTest.kt | 163 ------------------ 8 files changed, 74 insertions(+), 300 deletions(-) rename src/main/kotlin/rx/lang/kotlin/{completable.kt => completables.kt} (100%) rename src/main/kotlin/rx/lang/kotlin/{single.kt => singles.kt} (62%) delete mode 100644 src/main/kotlin/rx/lang/kotlin/subscribers.kt delete mode 100644 src/main/kotlin/rx/lang/kotlin/subscription.kt create mode 100644 src/main/kotlin/rx/lang/kotlin/subscriptions.kt delete mode 100644 src/test/kotlin/rx/lang/kotlin/SubscribersTest.kt diff --git a/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt b/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt index 1c59cb0..22dd9d4 100644 --- a/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt +++ b/src/examples/kotlin/rx/lang/kotlin/examples/examples.kt @@ -2,7 +2,6 @@ package rx.lang.kotlin.examples import io.reactivex.Observable import io.reactivex.disposables.CompositeDisposable -import rx.lang.kotlin.FunctionSubscriber import rx.lang.kotlin.addTo import rx.lang.kotlin.combineLatest import rx.lang.kotlin.observable @@ -23,6 +22,7 @@ fun main(args: Array) { println("--- Article ---\n${art.substring(0, 125)}") } + @Suppress("ConvertLambdaToReference") val printIt = { it: String -> println(it) } subscription += asyncObservable().subscribe(printIt) @@ -43,25 +43,26 @@ fun main(args: Array) { zip(listOfObservables()) - simpleObservable().subscribe(FunctionSubscriber() - .onNext { s -> println("1st onNext => $s") } - .onNext { s -> println("2nd onNext => $s") }) + simpleObservable().subscribeBy( + onNext = { s: String -> println("1st onNext => $s") } andThen { println("2nd onNext => $it") } + ) addToCompositeSubscription() } -private fun URL.toScannerObservable() = observable({ s -> +private fun URL.toScannerObservable() = observable { s -> this.openStream().use { stream -> - Scanner(stream).useDelimiter("\\A").toObservable().subscribeBy { s } + Scanner(stream).useDelimiter("\\A") + .toObservable() + .subscribe { s.onNext(it) } } -}) +} -fun syncObservable(): Observable = - observable { subscriber -> - (0..75).toObservable() - .map { "Sync value_$it" } - .subscribe { subscriber.onNext(it) } - } +fun syncObservable(): Observable = observable { subscriber -> + (0..75).toObservable() + .map { "Sync value_$it" } + .subscribe { subscriber.onNext(it) } +} fun asyncObservable(): Observable = observable { subscriber -> thread { @@ -116,4 +117,6 @@ fun addToCompositeSubscription() { .addTo(compositeSubscription) compositeSubscription.dispose() -} \ No newline at end of file +} + +infix inline fun ((T) -> Unit).andThen(crossinline block: (T) -> Unit): (T) -> Unit = { this(it); block(it) } \ No newline at end of file diff --git a/src/main/kotlin/rx/lang/kotlin/completable.kt b/src/main/kotlin/rx/lang/kotlin/completables.kt similarity index 100% rename from src/main/kotlin/rx/lang/kotlin/completable.kt rename to src/main/kotlin/rx/lang/kotlin/completables.kt diff --git a/src/main/kotlin/rx/lang/kotlin/observables.kt b/src/main/kotlin/rx/lang/kotlin/observables.kt index 6a356b9..b7f408c 100644 --- a/src/main/kotlin/rx/lang/kotlin/observables.kt +++ b/src/main/kotlin/rx/lang/kotlin/observables.kt @@ -3,7 +3,6 @@ package rx.lang.kotlin import io.reactivex.Observable import io.reactivex.ObservableEmitter import io.reactivex.Single -import io.reactivex.disposables.Disposable import io.reactivex.functions.BiFunction /** @@ -40,8 +39,6 @@ fun Iterable>.mergeDelayError(): Observable = Obs inline fun Observable.fold(initial: R, crossinline body: (R, T) -> R): Single = reduce(initial) { a, e -> body(a, e) } -fun Observable.onError(block: (Throwable) -> Unit): Observable = doOnError(block) - /** * Returns Observable that wrap all values into [IndexedValue] and populates corresponding index value. * Works similar to [kotlin.withIndex] @@ -60,16 +57,6 @@ fun Observable.withIndex(): Observable> inline fun Observable.flatMapSequence(crossinline body: (T) -> Sequence): Observable = flatMap { body(it).toObservable() } -/** - * Subscribe with a subscriber that is configured inside body - */ -inline fun Observable.subscribeBy(body: FunctionSubscriberModifier.() -> Unit): Disposable { - val modifier = FunctionSubscriberModifier(subscriber()) - modifier.body() - subscribe(modifier.subscriber) - return modifier.subscriber.origin!! -} - fun Observable>.switchOnNext(): Observable = Observable.switchOnNext(this) /** diff --git a/src/main/kotlin/rx/lang/kotlin/single.kt b/src/main/kotlin/rx/lang/kotlin/singles.kt similarity index 62% rename from src/main/kotlin/rx/lang/kotlin/single.kt rename to src/main/kotlin/rx/lang/kotlin/singles.kt index 1be5473..0cd9442 100644 --- a/src/main/kotlin/rx/lang/kotlin/single.kt +++ b/src/main/kotlin/rx/lang/kotlin/singles.kt @@ -2,7 +2,6 @@ package rx.lang.kotlin import io.reactivex.Single import io.reactivex.SingleEmitter -import io.reactivex.disposables.Disposable import java.util.concurrent.Callable import java.util.concurrent.Future @@ -13,12 +12,3 @@ fun Future.toSingle(): Single = Single.fromFuture(this) fun Callable.toSingle(): Single = Single.fromCallable { this.call() } fun Throwable.toSingle(): Single = Single.error(this) -/** - * Subscribe with a subscriber that is configured inside body - */ -inline fun Single.subscribeBy(body: FunctionSubscriberModifier.() -> Unit): Disposable { - val modifier = FunctionSubscriberModifier(subscriber()) - modifier.body() - subscribe(modifier.subscriber) - return modifier.subscriber.origin!! -} diff --git a/src/main/kotlin/rx/lang/kotlin/subscribers.kt b/src/main/kotlin/rx/lang/kotlin/subscribers.kt deleted file mode 100644 index d595547..0000000 --- a/src/main/kotlin/rx/lang/kotlin/subscribers.kt +++ /dev/null @@ -1,81 +0,0 @@ -package rx.lang.kotlin - -import io.reactivex.CompletableObserver -import io.reactivex.MaybeObserver -import io.reactivex.Observer -import io.reactivex.SingleObserver -import io.reactivex.disposables.Disposable -import java.util.ArrayList - -class FunctionSubscriber : Observer, MaybeObserver, SingleObserver, CompletableObserver { - - private val onCompleteFunctions = ArrayList<() -> Unit>() - private val onErrorFunctions = ArrayList<(e: Throwable) -> Unit>() - private val onNextFunctions = ArrayList<(value: T) -> Unit>() - private val onStartFunctions = ArrayList<() -> Unit>() - var origin: Disposable? = null - - override fun onComplete() = onCompleteFunctions.forEach { it() } - - override fun onError(e: Throwable?) = (e ?: RuntimeException("exception is unknown")).let { ex -> - if (onErrorFunctions.isEmpty()) { - throw OnErrorNotImplementedException(ex) - } else { - onErrorFunctions.forEach { it(ex) } - } - } - - override fun onNext(value: T) = onNextFunctions.forEach { it(value) } - override fun onSuccess(value: T) = onNext(value) - - override fun onSubscribe(d: Disposable?) { - origin = d - onStartFunctions.forEach { it() } - } - - fun onCompleted(onCompletedFunction: () -> Unit): FunctionSubscriber = copy { onCompleteFunctions.add(onCompletedFunction) } - fun onError(onErrorFunction: (t: Throwable) -> Unit): FunctionSubscriber = copy { onErrorFunctions.add(onErrorFunction) } - fun onNext(onNextFunction: (t: T) -> Unit): FunctionSubscriber = copy { onNextFunctions.add(onNextFunction) } - fun onStart(onStartFunction: () -> Unit): FunctionSubscriber = copy { onStartFunctions.add(onStartFunction) } - - private fun copy(block: FunctionSubscriber.() -> Unit): FunctionSubscriber { - val newSubscriber = FunctionSubscriber() - newSubscriber.onCompleteFunctions.addAll(onCompleteFunctions) - newSubscriber.onErrorFunctions.addAll(onErrorFunctions) - newSubscriber.onNextFunctions.addAll(onNextFunctions) - newSubscriber.onStartFunctions.addAll(onStartFunctions) - - newSubscriber.block() - - return newSubscriber - } -} - -class OnErrorNotImplementedException(ex: Throwable) : RuntimeException(ex) - -class FunctionSubscriberModifier(init: FunctionSubscriber = subscriber()) { - var subscriber: FunctionSubscriber = init - private set - - fun onComplete(onCompletedFunction: () -> Unit): Unit { - subscriber = subscriber.onCompleted(onCompletedFunction) - } - - fun onError(onErrorFunction: (t: Throwable) -> Unit): Unit { - subscriber = subscriber.onError(onErrorFunction) - } - - fun onNext(onNextFunction: (t: T) -> Unit): Unit { - subscriber = subscriber.onNext(onNextFunction) - } - - fun onStart(onStartFunction: () -> Unit): Unit { - subscriber = subscriber.onStart(onStartFunction) - } - - fun onSuccess(onSuccessFunction: (t: T) -> Unit): Unit { - subscriber = subscriber.onNext(onSuccessFunction) - } -} - -fun subscriber(): FunctionSubscriber = FunctionSubscriber() \ No newline at end of file diff --git a/src/main/kotlin/rx/lang/kotlin/subscription.kt b/src/main/kotlin/rx/lang/kotlin/subscription.kt deleted file mode 100644 index 8dd4703..0000000 --- a/src/main/kotlin/rx/lang/kotlin/subscription.kt +++ /dev/null @@ -1,19 +0,0 @@ -package rx.lang.kotlin - -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.disposables.Disposable - -/** - * subscription += observable.subscribe{} - */ -operator fun CompositeDisposable.plusAssign(subscription: Disposable) { - add(subscription) -} - -/** - * Add the subscription to a CompositeSubscription. - * @param compositeSubscription CompositeSubscription to add this subscription to - * @return this instance - */ -fun Disposable.addTo(compositeSubscription: CompositeDisposable): Disposable - = apply { compositeSubscription.add(this) } \ No newline at end of file diff --git a/src/main/kotlin/rx/lang/kotlin/subscriptions.kt b/src/main/kotlin/rx/lang/kotlin/subscriptions.kt new file mode 100644 index 0000000..13b214b --- /dev/null +++ b/src/main/kotlin/rx/lang/kotlin/subscriptions.kt @@ -0,0 +1,57 @@ +package rx.lang.kotlin + +import io.reactivex.Completable +import io.reactivex.Maybe +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable + +/** + * subscription += observable.subscribe() + */ +operator fun CompositeDisposable.plusAssign(subscription: Disposable) { + add(subscription) +} + +/** + * Add the subscription to a CompositeSubscription. + * @param compositeSubscription CompositeSubscription to add this subscription to + * @return this instance + */ +fun Disposable.addTo(compositeSubscription: CompositeDisposable): Disposable + = apply { compositeSubscription.add(this) } + +/** + * Overloaded subscribe function that allow passing named parameters + */ +fun Observable.subscribeBy( + onNext: ((T) -> Unit)? = null, + onError: ((Throwable) -> Unit)? = null, + onComplete: (() -> Unit)? = null +): Disposable = subscribe(onNext, onError, onComplete) + +/** + * Overloaded subscribe function that allow passing named parameters + */ +fun Single.subscribeBy( + onSuccess: ((T) -> Unit)? = null, + onError: ((Throwable) -> Unit)? = null +): Disposable = subscribe(onSuccess, onError) + +/** + * Overloaded subscribe function that allow passing named parameters + */ +fun Maybe.subscribeBy( + onSuccess: ((T) -> Unit)? = null, + onError: ((Throwable) -> Unit)? = null, + onComplete: (() -> Unit)? = null +): Disposable = subscribe(onSuccess, onError, onComplete) + +/** + * Overloaded subscribe function that allow passing named parameters + */ +fun Completable.subscribeBy( + onError: ((Throwable) -> Unit)? = null, + onComplete: (() -> Unit)? = null +): Disposable = subscribe(onComplete, onError) \ No newline at end of file diff --git a/src/test/kotlin/rx/lang/kotlin/SubscribersTest.kt b/src/test/kotlin/rx/lang/kotlin/SubscribersTest.kt deleted file mode 100644 index b4390cf..0000000 --- a/src/test/kotlin/rx/lang/kotlin/SubscribersTest.kt +++ /dev/null @@ -1,163 +0,0 @@ -package rx.lang.kotlin - -import io.reactivex.Observable -import io.reactivex.Observer -import io.reactivex.disposables.Disposables -import org.junit.Assert.assertEquals -import org.junit.Assert.assertTrue -import org.junit.Test -import java.util.ArrayList - -class SubscribersTest { - - @Test fun testEmptySubscriber() { - val s = subscriber() - callSubscriberMethods(false, s) - } - - @Test fun testSubscriberConstruction() { - val events = ArrayList() - - callSubscriberMethods(false, subscriber(). - onNext { events.add("onNext($it)") } - ) - - assertEquals(listOf("onNext(1)"), events) - events.clear() - - callSubscriberMethods(true, subscriber(). - onNext { events.add("onNext($it)") }. - onError { events.add(it.javaClass.simpleName) } - ) - - assertEquals(listOf("onNext(1)", "RuntimeException"), events) - events.clear() - - callSubscriberMethods(true, subscriber(). - onNext { events.add("onNext($it)") }. - onError { events.add(it.javaClass.simpleName) }. - onCompleted { events.add("onCompleted") } - ) - - assertEquals(listOf("onNext(1)", "RuntimeException", "onCompleted"), events) - events.clear() - } - - @Test(expected = Exception::class) - fun testNoErrorHandlers() { - subscriber().onError(Exception("expected")) - } - - @Test fun testErrorHandlers() { - var errorsCaught = 0 - - subscriber(). - onError { errorsCaught++ }. - onError { errorsCaught++ }. - onError(Exception("expected")) - - assertEquals(2, errorsCaught) - } - - @Test fun testMultipleOnNextHandlers() { - var nextCaught = 0 - - subscriber(). - onNext { nextCaught++ }. - onNext { nextCaught++ }. - onNext(1) - - assertEquals(2, nextCaught) - } - - @Test fun testOnStart() { - var started = false - Observable.just("").subscribeBy { - onStart { started = true } - } - assertTrue(started) - } - - private fun callSubscriberMethods(hasOnError: Boolean, s: Observer) { - s.onSubscribe(Disposables.empty()) - s.onNext(1) - try { - s.onError(RuntimeException()) - } catch (t: Throwable) { - if (hasOnError) throw t - } - s.onComplete() - } - - @Test fun testSubscribeWith() { - val completeObservable = observable { - it.onNext(1) - it.onComplete() - } - - val events = ArrayList() - - completeObservable.subscribeBy { - onNext { events.add("onNext($it)") } - } - - assertEquals(listOf("onNext(1)"), events) - events.clear() - - completeObservable.subscribeBy { - onNext { events.add("onNext($it)") } - onComplete { events.add("onCompleted") } - } - - assertEquals(listOf("onNext(1)", "onCompleted"), events) - events.clear() - - val errorObservable = observable { - it.onNext(1) - it.onError(RuntimeException()) - } - - errorObservable.subscribeBy { - onNext { events.add("onNext($it)") } - onError { events.add("onError(${it.javaClass.simpleName})") } - } - - assertEquals(listOf("onNext(1)", "onError(RuntimeException)"), events) - } - - @Test fun testSingleSubscribeWith() { - val events = ArrayList() - val successSingle = singleOf(1) - - successSingle.subscribeBy { - onSuccess { events.add("onSuccess($it)") } - } - - assertEquals(listOf("onSuccess(1)"), events) - events.clear() - - val errorSingle = RuntimeException().toSingle() - - errorSingle.subscribeBy { - onSuccess { events.add("onSuccess($it)") } - onError { events.add("onError(${it.javaClass.simpleName})") } - } - - assertEquals(listOf("onError(RuntimeException)"), events) - } - - @Test fun testSubscribeByErrorNotImplemented() { - val events = ArrayList() - val errorSingle = RuntimeException().toSingle() - try { - errorSingle.subscribeBy { - onSuccess { events.add("onSuccess($it)") } - } - } catch (e: Throwable) { - val name = e.cause?.javaClass?.simpleName - events.add("catch($name)") - } - - assertEquals(listOf("catch(OnErrorNotImplementedException)"), events) - } -} From 5478cec5a12f30d7c4f3af4a7dd5dd6a1e6de530 Mon Sep 17 00:00:00 2001 From: stepango Date: Fri, 13 Jan 2017 17:43:23 +0200 Subject: [PATCH 07/20] subscribeBy non-null params --- .../kotlin/rx/lang/kotlin/subscriptions.kt | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/kotlin/rx/lang/kotlin/subscriptions.kt b/src/main/kotlin/rx/lang/kotlin/subscriptions.kt index 13b214b..529c000 100644 --- a/src/main/kotlin/rx/lang/kotlin/subscriptions.kt +++ b/src/main/kotlin/rx/lang/kotlin/subscriptions.kt @@ -22,36 +22,40 @@ operator fun CompositeDisposable.plusAssign(subscription: Disposable) { fun Disposable.addTo(compositeSubscription: CompositeDisposable): Disposable = apply { compositeSubscription.add(this) } +private val onNextStub: (Any) -> Unit = {} +private val onErrorStub: (Throwable) -> Unit = {} +private val onCompleteStub: () -> Unit = {} + /** * Overloaded subscribe function that allow passing named parameters */ fun Observable.subscribeBy( - onNext: ((T) -> Unit)? = null, - onError: ((Throwable) -> Unit)? = null, - onComplete: (() -> Unit)? = null + onNext: (T) -> Unit = onNextStub, + onError: (Throwable) -> Unit = onErrorStub, + onComplete: () -> Unit = onCompleteStub ): Disposable = subscribe(onNext, onError, onComplete) /** * Overloaded subscribe function that allow passing named parameters */ fun Single.subscribeBy( - onSuccess: ((T) -> Unit)? = null, - onError: ((Throwable) -> Unit)? = null + onSuccess: (T) -> Unit = onNextStub, + onError: (Throwable) -> Unit = onErrorStub ): Disposable = subscribe(onSuccess, onError) /** * Overloaded subscribe function that allow passing named parameters */ fun Maybe.subscribeBy( - onSuccess: ((T) -> Unit)? = null, - onError: ((Throwable) -> Unit)? = null, - onComplete: (() -> Unit)? = null + onSuccess: (T) -> Unit = onNextStub, + onError: (Throwable) -> Unit = onErrorStub, + onComplete: () -> Unit = onCompleteStub ): Disposable = subscribe(onSuccess, onError, onComplete) /** * Overloaded subscribe function that allow passing named parameters */ fun Completable.subscribeBy( - onError: ((Throwable) -> Unit)? = null, - onComplete: (() -> Unit)? = null -): Disposable = subscribe(onComplete, onError) \ No newline at end of file + onError: (Throwable) -> Unit = onErrorStub, + onComplete: () -> Unit = onCompleteStub +): Disposable = subscribe(onComplete, onError) From 06f3f0381614c3bc2ad8082bcac20b8bf1600db0 Mon Sep 17 00:00:00 2001 From: stepango Date: Tue, 24 Jan 2017 14:45:28 +0200 Subject: [PATCH 08/20] empty* methods removed flowable extensions added minor fixes --- src/main/kotlin/rx/lang/kotlin/completable.kt | 11 ++ .../kotlin/rx/lang/kotlin/completables.kt | 13 -- src/main/kotlin/rx/lang/kotlin/disposable.kt | 19 +++ src/main/kotlin/rx/lang/kotlin/flowable.kt | 83 ++++++++++ src/main/kotlin/rx/lang/kotlin/maybe.kt | 13 ++ .../kotlin/{observables.kt => observable.kt} | 10 +- .../rx/lang/kotlin/{singles.kt => single.kt} | 6 +- .../lang/kotlin/{subjects.kt => subject.kt} | 0 .../{subscriptions.kt => subscription.kt} | 16 -- .../kotlin/rx/lang/kotlin/CompletableTest.kt | 12 +- .../kotlin/rx/lang/kotlin/FlowableTest.kt | 148 ++++++++++++++++++ .../{ObservablesTest.kt => ObservableTest.kt} | 40 ++--- src/test/kotlin/rx/lang/kotlin/SingleTest.kt | 12 +- 13 files changed, 315 insertions(+), 68 deletions(-) create mode 100644 src/main/kotlin/rx/lang/kotlin/completable.kt delete mode 100644 src/main/kotlin/rx/lang/kotlin/completables.kt create mode 100644 src/main/kotlin/rx/lang/kotlin/disposable.kt create mode 100644 src/main/kotlin/rx/lang/kotlin/flowable.kt create mode 100644 src/main/kotlin/rx/lang/kotlin/maybe.kt rename src/main/kotlin/rx/lang/kotlin/{observables.kt => observable.kt} (95%) rename src/main/kotlin/rx/lang/kotlin/{singles.kt => single.kt} (74%) rename src/main/kotlin/rx/lang/kotlin/{subjects.kt => subject.kt} (100%) rename src/main/kotlin/rx/lang/kotlin/{subscriptions.kt => subscription.kt} (74%) create mode 100644 src/test/kotlin/rx/lang/kotlin/FlowableTest.kt rename src/test/kotlin/rx/lang/kotlin/{ObservablesTest.kt => ObservableTest.kt} (84%) diff --git a/src/main/kotlin/rx/lang/kotlin/completable.kt b/src/main/kotlin/rx/lang/kotlin/completable.kt new file mode 100644 index 0000000..8fe11e1 --- /dev/null +++ b/src/main/kotlin/rx/lang/kotlin/completable.kt @@ -0,0 +1,11 @@ +package rx.lang.kotlin + +import io.reactivex.Completable +import io.reactivex.functions.Action +import java.util.concurrent.Callable +import java.util.concurrent.Future + +fun Action.toCompletable(): Completable = Completable.fromAction(this) +fun Callable.toCompletable(): Completable = Completable.fromCallable(this) +fun Future.toCompletable(): Completable = Completable.fromFuture(this) +fun (() -> Any).toCompletable(): Completable = Completable.fromCallable(this) diff --git a/src/main/kotlin/rx/lang/kotlin/completables.kt b/src/main/kotlin/rx/lang/kotlin/completables.kt deleted file mode 100644 index ac413f0..0000000 --- a/src/main/kotlin/rx/lang/kotlin/completables.kt +++ /dev/null @@ -1,13 +0,0 @@ -package rx.lang.kotlin - -import io.reactivex.Completable -import io.reactivex.Single -import io.reactivex.functions.Action -import java.util.concurrent.Callable -import java.util.concurrent.Future - -fun Action.toCompletable(): Completable = Completable.fromAction(this) -inline fun completableOf(crossinline f: () -> T): Completable = Completable.fromAction { f() } -fun Callable.toCompletable(): Completable = Completable.fromCallable { this.call() } -fun Future.toCompletable(): Completable = Completable.fromFuture(this) -fun Single.toCompletable(): Completable = Completable.fromSingle(this) \ No newline at end of file diff --git a/src/main/kotlin/rx/lang/kotlin/disposable.kt b/src/main/kotlin/rx/lang/kotlin/disposable.kt new file mode 100644 index 0000000..ca85585 --- /dev/null +++ b/src/main/kotlin/rx/lang/kotlin/disposable.kt @@ -0,0 +1,19 @@ +package rx.lang.kotlin + +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable + +/** + * subscription += observable.subscribe() + */ +operator fun CompositeDisposable.plusAssign(subscription: Disposable) { + add(subscription) +} + +/** + * Add the subscription to a CompositeSubscription. + * @param compositeSubscription CompositeSubscription to add this subscription to + * @return this instance + */ +fun Disposable.addTo(compositeSubscription: CompositeDisposable): Disposable + = apply { compositeSubscription.add(this) } \ No newline at end of file diff --git a/src/main/kotlin/rx/lang/kotlin/flowable.kt b/src/main/kotlin/rx/lang/kotlin/flowable.kt new file mode 100644 index 0000000..1a348ee --- /dev/null +++ b/src/main/kotlin/rx/lang/kotlin/flowable.kt @@ -0,0 +1,83 @@ +package rx.lang.kotlin + +import io.reactivex.BackpressureStrategy +import io.reactivex.Flowable +import io.reactivex.FlowableEmitter +import io.reactivex.Single +import io.reactivex.functions.BiFunction + +fun flowable( + strategy: BackpressureStrategy = BackpressureStrategy.BUFFER, + body: (FlowableEmitter) -> Unit +): Flowable = Flowable.create(body, strategy) + +private fun Iterator.toIterable() = object : Iterable { + override fun iterator(): Iterator = this@toIterable +} + +fun BooleanArray.toFlowable(): Flowable = Flowable.fromArray(*this.toTypedArray()) +fun ByteArray.toFlowable(): Flowable = Flowable.fromArray(*this.toTypedArray()) +fun ShortArray.toFlowable(): Flowable = Flowable.fromArray(*this.toTypedArray()) +fun IntArray.toFlowable(): Flowable = Flowable.fromArray(*this.toTypedArray()) +fun LongArray.toFlowable(): Flowable = Flowable.fromArray(*this.toTypedArray()) +fun FloatArray.toFlowable(): Flowable = Flowable.fromArray(*this.toTypedArray()) +fun DoubleArray.toFlowable(): Flowable = Flowable.fromArray(*this.toTypedArray()) +fun Array.toFlowable(): Flowable = Flowable.fromArray(*this) + +fun IntProgression.toFlowable(): Flowable = + if (step == 1 && last.toLong() - first < Integer.MAX_VALUE) Flowable.range(first, Math.max(0, last - first + 1)) + else Flowable.fromIterable(this) + +fun Iterator.toFlowable(): Flowable = toIterable().toFlowable() +fun Iterable.toFlowable(): Flowable = Flowable.fromIterable(this) +fun Sequence.toFlowable(): Flowable = Flowable.fromIterable(iterator().toIterable()) + +fun Iterable>.merge(): Flowable = Flowable.merge(this.toFlowable()) +fun Iterable>.mergeDelayError(): Flowable = Flowable.mergeDelayError(this.toFlowable()) + +inline fun Flowable.fold(initial: R, crossinline body: (R, T) -> R): Single + = reduce(initial) { a, e -> body(a, e) } + +/** + * Returns Flowable that wrap all values into [IndexedValue] and populates corresponding index value. + * Works similar to [kotlin.withIndex] + */ +fun Flowable.withIndex(): Flowable> + = zipWith(Flowable.range(0, Int.MAX_VALUE), BiFunction { value, index -> IndexedValue(index, value) }) + +/** + * Returns Flowable that emits objects from kotlin [Sequence] returned by function you provided by parameter [body] for + * each input object and merges all produced elements into one flowable. + * Works similar to [Flowable.flatMap] and [Flowable.flatMapIterable] but with [Sequence] + * + * @param body is a function that applied for each item emitted by source flowable that returns [Sequence] + * @returns Flowable that merges all [Sequence]s produced by [body] functions + */ +inline fun Flowable.flatMapSequence(crossinline body: (T) -> Sequence): Flowable + = flatMap { body(it).toFlowable() } + +fun Flowable>.switchOnNext(): Flowable = Flowable.switchOnNext(this) + +/** + * Flowable.combineLatest(List> sources, FuncN combineFunction) + */ +@Suppress("UNCHECKED_CAST") +inline fun List>.combineLatest(crossinline combineFunction: (args: List) -> R): Flowable + = Flowable.combineLatest(this) { combineFunction(it.asList().map { it as T }) } + +/** + * Flowable.zip(List> sources, FuncN combineFunction) + */ +@Suppress("UNCHECKED_CAST") +inline fun List>.zip(crossinline zipFunction: (args: List) -> R): Flowable + = Flowable.zip(this) { zipFunction(it.asList().map { it as T }) } + +/** + * Returns an Flowable that emits the items emitted by the source Flowable, converted to the specified type. + */ +inline fun Flowable<*>.cast(): Flowable = cast(R::class.java) + +/** + * Filters the items emitted by an Observable, only emitting those of the specified type. + */ +inline fun Flowable<*>.ofType(): Flowable = ofType(R::class.java) \ No newline at end of file diff --git a/src/main/kotlin/rx/lang/kotlin/maybe.kt b/src/main/kotlin/rx/lang/kotlin/maybe.kt new file mode 100644 index 0000000..35479bf --- /dev/null +++ b/src/main/kotlin/rx/lang/kotlin/maybe.kt @@ -0,0 +1,13 @@ +package rx.lang.kotlin + +import io.reactivex.Maybe +import java.util.concurrent.Callable +import java.util.concurrent.Future + +fun T?.toMaybe(): Maybe = Maybe.create { s -> if (this != null) s.onSuccess(this); s.onComplete() } +fun Future.toMaybe(): Maybe = Maybe.fromFuture(this) +fun Callable.toMaybe(): Maybe = Maybe.fromCallable(this) +fun (() -> T).toMaybe(): Maybe = Maybe.fromCallable(this) + +inline fun Maybe<*>.cast(): Maybe = cast(R::class.java) +inline fun Maybe<*>.ofType(): Maybe = ofType(R::class.java) \ No newline at end of file diff --git a/src/main/kotlin/rx/lang/kotlin/observables.kt b/src/main/kotlin/rx/lang/kotlin/observable.kt similarity index 95% rename from src/main/kotlin/rx/lang/kotlin/observables.kt rename to src/main/kotlin/rx/lang/kotlin/observable.kt index b7f408c..0c30616 100644 --- a/src/main/kotlin/rx/lang/kotlin/observables.kt +++ b/src/main/kotlin/rx/lang/kotlin/observable.kt @@ -5,11 +5,6 @@ import io.reactivex.ObservableEmitter import io.reactivex.Single import io.reactivex.functions.BiFunction -/** - * [Observable.empty] alias - */ -fun emptyObservable(): Observable = Observable.empty() - fun observable(body: (ObservableEmitter) -> Unit): Observable = Observable.create(body) private fun Iterator.toIterable() = object : Iterable { @@ -77,3 +72,8 @@ inline fun List>.zip(crossinline zipFunction: (args: List Observable<*>.cast(): Observable = cast(R::class.java) + +/** + * Filters the items emitted by an Observable, only emitting those of the specified type. + */ +inline fun Observable<*>.ofType(): Observable = ofType(R::class.java) diff --git a/src/main/kotlin/rx/lang/kotlin/singles.kt b/src/main/kotlin/rx/lang/kotlin/single.kt similarity index 74% rename from src/main/kotlin/rx/lang/kotlin/singles.kt rename to src/main/kotlin/rx/lang/kotlin/single.kt index 0cd9442..c2e2de4 100644 --- a/src/main/kotlin/rx/lang/kotlin/singles.kt +++ b/src/main/kotlin/rx/lang/kotlin/single.kt @@ -7,8 +7,8 @@ import java.util.concurrent.Future inline fun single(crossinline body: (s: SingleEmitter) -> Unit): Single = Single.create { body(it) } fun T.toSingle(): Single = Single.just(this) -fun singleOf(value: T): Single = Single.just(value) fun Future.toSingle(): Single = Single.fromFuture(this) -fun Callable.toSingle(): Single = Single.fromCallable { this.call() } -fun Throwable.toSingle(): Single = Single.error(this) +fun Callable.toSingle(): Single = Single.fromCallable(this) +fun (() -> T).toSingle(): Single = Single.fromCallable(this) +inline fun Single<*>.cast(): Single = cast(R::class.java) diff --git a/src/main/kotlin/rx/lang/kotlin/subjects.kt b/src/main/kotlin/rx/lang/kotlin/subject.kt similarity index 100% rename from src/main/kotlin/rx/lang/kotlin/subjects.kt rename to src/main/kotlin/rx/lang/kotlin/subject.kt diff --git a/src/main/kotlin/rx/lang/kotlin/subscriptions.kt b/src/main/kotlin/rx/lang/kotlin/subscription.kt similarity index 74% rename from src/main/kotlin/rx/lang/kotlin/subscriptions.kt rename to src/main/kotlin/rx/lang/kotlin/subscription.kt index 529c000..1c77878 100644 --- a/src/main/kotlin/rx/lang/kotlin/subscriptions.kt +++ b/src/main/kotlin/rx/lang/kotlin/subscription.kt @@ -4,24 +4,8 @@ import io.reactivex.Completable import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Single -import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable -/** - * subscription += observable.subscribe() - */ -operator fun CompositeDisposable.plusAssign(subscription: Disposable) { - add(subscription) -} - -/** - * Add the subscription to a CompositeSubscription. - * @param compositeSubscription CompositeSubscription to add this subscription to - * @return this instance - */ -fun Disposable.addTo(compositeSubscription: CompositeDisposable): Disposable - = apply { compositeSubscription.add(this) } - private val onNextStub: (Any) -> Unit = {} private val onErrorStub: (Throwable) -> Unit = {} private val onCompleteStub: () -> Unit = {} diff --git a/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt b/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt index 1264af2..d179edc 100644 --- a/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt +++ b/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt @@ -16,12 +16,6 @@ class CompletableTest { assertNotNull(c1) c1.subscribe() assertEquals(1, count) - - count = 0 - val c2 = completableOf { count++ } - assertNotNull(c2) - c2.subscribe() - assertEquals(1, count) } @Test fun testCreateFromCallable() { @@ -30,6 +24,12 @@ class CompletableTest { assertNotNull(c1) c1.subscribe() assertEquals(1, count) + + count = 0 + val c2 = { count++ }.toCompletable() + assertNotNull(c2) + c2.subscribe() + assertEquals(1, count) } @Test(expected = NoSuchElementException::class) fun testCreateFromSingle() { diff --git a/src/test/kotlin/rx/lang/kotlin/FlowableTest.kt b/src/test/kotlin/rx/lang/kotlin/FlowableTest.kt new file mode 100644 index 0000000..2f9f981 --- /dev/null +++ b/src/test/kotlin/rx/lang/kotlin/FlowableTest.kt @@ -0,0 +1,148 @@ +package rx.lang.kotlin + +import io.reactivex.Flowable +import org.junit.Assert +import org.junit.Ignore +import org.junit.Test +import java.util.concurrent.atomic.AtomicInteger + +class FlowableTest { + + @Test fun testCreation() { + val o0: Flowable = Flowable.empty() + val list = flowable { s -> + s.onNext(1) + s.onNext(777) + s.onComplete() + }.toList().blockingGet() + Assert.assertEquals(listOf(1, 777), list) + val o1: Flowable = listOf(1, 2, 3).toFlowable() + val o2: Flowable> = Flowable.just(listOf(1, 2, 3)) + + val o3: Flowable = Flowable.defer { flowable { s -> s.onNext(1) } } + val o4: Flowable = Array(3) { 0 }.toFlowable() + val o5: Flowable = IntArray(3).toFlowable() + + Assert.assertNotNull(o0) + Assert.assertNotNull(o1) + Assert.assertNotNull(o2) + Assert.assertNotNull(o3) + Assert.assertNotNull(o4) + Assert.assertNotNull(o5) + } + + @Test fun testExampleFromReadme() { + val result = flowable { subscriber -> + subscriber.onNext("H") + subscriber.onNext("e") + subscriber.onNext("l") + subscriber.onNext("") + subscriber.onNext("l") + subscriber.onNext("o") + subscriber.onComplete() + }.filter(String::isNotEmpty). + fold(StringBuilder(), StringBuilder::append). + map { it.toString() }. + blockingGet() + + Assert.assertEquals("Hello", result) + } + + @Test fun iteratorFlowable() { + Assert.assertEquals(listOf(1, 2, 3), listOf(1, 2, 3).iterator().toFlowable().toList().blockingGet()) + } + + @Test fun intProgressionStep1Empty() { + Assert.assertEquals(listOf(1), (1..1).toFlowable().toList().blockingGet()) + } + + @Test fun intProgressionStep1() { + Assert.assertEquals((1..10).toList(), (1..10).toFlowable().toList().blockingGet()) + } + + @Test fun intProgressionDownTo() { + Assert.assertEquals((1 downTo 10).toList(), (1 downTo 10).toFlowable().toList().blockingGet()) + } + + @Ignore("Too slow") + @Test fun intProgressionOverflow() { + Assert.assertEquals((0..10).toList().reversed(), (-10..Integer.MAX_VALUE).toFlowable().skip(Integer.MAX_VALUE.toLong()).map { Integer.MAX_VALUE - it }.toList().blockingGet()) + } + + @Test fun testWithIndex() { + listOf("a", "b", "c").toFlowable() + .withIndex() + .toList() + .test() + .assertValues(listOf(IndexedValue(0, "a"), IndexedValue(1, "b"), IndexedValue(2, "c"))) + } + + @Test fun `withIndex() shouldn't share index between multiple subscribers`() { + val o = listOf("a", "b", "c").toFlowable().withIndex() + + o.test() + .await() + .assertValues(IndexedValue(0, "a"), IndexedValue(1, "b"), IndexedValue(2, "c")) + + o.test() + .await() + .assertValues(IndexedValue(0, "a"), IndexedValue(1, "b"), IndexedValue(2, "c")) + } + + @Test fun testFold() { + val result = listOf(1, 2, 3).toFlowable().fold(0) { acc, e -> acc + e }.blockingGet() + Assert.assertEquals(6, result) + } + + @Test fun `kotlin sequence should produce expected items and flowable be able to handle em`() { + generateSequence(0) { it + 1 }.toFlowable() + .take(3) + .toList() + .test() + .assertValues(listOf(0, 1, 2)) + } + + @Test fun `infinite iterable should not hang or produce too many elements`() { + val generated = AtomicInteger() + generateSequence { generated.incrementAndGet() }.toFlowable(). + take(100). + toList(). + subscribe() + + Assert.assertEquals(100, generated.get()) + } + + @Test fun testFlatMapSequence() { + Assert.assertEquals( + listOf(1, 2, 3, 2, 3, 4, 3, 4, 5), + listOf(1, 2, 3).toFlowable().flatMapSequence { listOf(it, it + 1, it + 2).asSequence() }.toList().blockingGet() + ) + } + + @Test fun testCombineLatest() { + val list = listOf(1, 2, 3, 2, 3, 4, 3, 4, 5) + Assert.assertEquals(list, list.map { Flowable.just(it) }.combineLatest { it }.blockingFirst()) + } + + @Test fun testZip() { + val list = listOf(1, 2, 3, 2, 3, 4, 3, 4, 5) + Assert.assertEquals(list, list.map { Flowable.just(it) }.zip { it }.blockingFirst()) + } + + @Test fun testCast() { + val source = Flowable.just(1, 2) + val flowable = source.cast() + flowable.test() + .await() + .assertValues(1, 2) + .assertNoErrors() + .assertComplete() + } + + @Test fun testCastWithWrongType() { + val source = Flowable.just(1, 2) + val flowable = source.cast() + flowable.test() + .assertError(ClassCastException::class.java) + } +} \ No newline at end of file diff --git a/src/test/kotlin/rx/lang/kotlin/ObservablesTest.kt b/src/test/kotlin/rx/lang/kotlin/ObservableTest.kt similarity index 84% rename from src/test/kotlin/rx/lang/kotlin/ObservablesTest.kt rename to src/test/kotlin/rx/lang/kotlin/ObservableTest.kt index 263bc51..bbd2e64 100644 --- a/src/test/kotlin/rx/lang/kotlin/ObservablesTest.kt +++ b/src/test/kotlin/rx/lang/kotlin/ObservableTest.kt @@ -5,13 +5,13 @@ import io.reactivex.observers.TestObserver import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Ignore +import org.junit.Test import java.util.concurrent.atomic.AtomicInteger -import org.junit.Test as test +class ObservableTest { -class ObservablesTest { - @test fun testCreation() { - val o0: Observable = emptyObservable() + @Test fun testCreation() { + val o0: Observable = Observable.empty() val list = observable { s -> s.onNext(1) s.onNext(777) @@ -33,7 +33,7 @@ class ObservablesTest { assertNotNull(o5) } - @test fun testExampleFromReadme() { + @Test fun testExampleFromReadme() { val result = observable { subscriber -> subscriber.onNext("H") subscriber.onNext("e") @@ -50,28 +50,28 @@ class ObservablesTest { assertEquals("Hello", result) } - @test fun iteratorObservable() { + @Test fun iteratorObservable() { assertEquals(listOf(1, 2, 3), listOf(1, 2, 3).iterator().toObservable().toList().blockingGet()) } - @test fun intProgressionStep1Empty() { + @Test fun intProgressionStep1Empty() { assertEquals(listOf(1), (1..1).toObservable().toList().blockingGet()) } - @test fun intProgressionStep1() { + @Test fun intProgressionStep1() { assertEquals((1..10).toList(), (1..10).toObservable().toList().blockingGet()) } - @test fun intProgressionDownTo() { + @Test fun intProgressionDownTo() { assertEquals((1 downTo 10).toList(), (1 downTo 10).toObservable().toList().blockingGet()) } @Ignore("Too slow") - @test fun intProgressionOverflow() { + @Test fun intProgressionOverflow() { assertEquals((0..10).toList().reversed(), (-10..Integer.MAX_VALUE).toObservable().skip(Integer.MAX_VALUE.toLong()).map { Integer.MAX_VALUE - it }.toList().blockingGet()) } - @test fun testWithIndex() { + @Test fun testWithIndex() { listOf("a", "b", "c").toObservable() .withIndex() .toList() @@ -79,7 +79,7 @@ class ObservablesTest { .assertValues(listOf(IndexedValue(0, "a"), IndexedValue(1, "b"), IndexedValue(2, "c"))) } - @test fun `withIndex() shouldn't share index between multiple subscribers`() { + @Test fun `withIndex() shouldn't share index between multiple subscribers`() { val o = listOf("a", "b", "c").toObservable().withIndex() val subscriber1 = TestObserver.create>() @@ -95,12 +95,12 @@ class ObservablesTest { subscriber2.assertValues(IndexedValue(0, "a"), IndexedValue(1, "b"), IndexedValue(2, "c")) } - @test fun testFold() { + @Test fun testFold() { val result = listOf(1, 2, 3).toObservable().fold(0) { acc, e -> acc + e }.blockingGet() assertEquals(6, result) } - @test fun `kotlin sequence should produce expected items and observable be able to handle em`() { + @Test fun `kotlin sequence should produce expected items and observable be able to handle em`() { generateSequence(0) { it + 1 }.toObservable() .take(3) .toList() @@ -108,7 +108,7 @@ class ObservablesTest { .assertValues(listOf(0, 1, 2)) } - @test fun `infinite iterable should not hang or produce too many elements`() { + @Test fun `infinite iterable should not hang or produce too many elements`() { val generated = AtomicInteger() generateSequence { generated.incrementAndGet() }.toObservable(). take(100). @@ -118,24 +118,24 @@ class ObservablesTest { assertEquals(100, generated.get()) } - @test fun testFlatMapSequence() { + @Test fun testFlatMapSequence() { assertEquals( listOf(1, 2, 3, 2, 3, 4, 3, 4, 5), listOf(1, 2, 3).toObservable().flatMapSequence { listOf(it, it + 1, it + 2).asSequence() }.toList().blockingGet() ) } - @test fun testCombineLatest() { + @Test fun testCombineLatest() { val list = listOf(1, 2, 3, 2, 3, 4, 3, 4, 5) assertEquals(list, list.map { Observable.just(it) }.combineLatest { it }.blockingFirst()) } - @test fun testZip() { + @Test fun testZip() { val list = listOf(1, 2, 3, 2, 3, 4, 3, 4, 5) assertEquals(list, list.map { Observable.just(it) }.zip { it }.blockingFirst()) } - @test fun testCast() { + @Test fun testCast() { val source = Observable.just(1, 2) val observable = source.cast() observable.test() @@ -145,7 +145,7 @@ class ObservablesTest { .assertComplete() } - @test fun testCastWithWrongType() { + @Test fun testCastWithWrongType() { val source = Observable.just(1, 2) val observable = source.cast() observable.test() diff --git a/src/test/kotlin/rx/lang/kotlin/SingleTest.kt b/src/test/kotlin/rx/lang/kotlin/SingleTest.kt index 01017d3..7fc571c 100644 --- a/src/test/kotlin/rx/lang/kotlin/SingleTest.kt +++ b/src/test/kotlin/rx/lang/kotlin/SingleTest.kt @@ -10,7 +10,7 @@ import java.util.concurrent.Callable class SingleTest : KotlinTests() { @Test fun testCreate() { single { s -> - s.onSuccess("Hello World!"); + s.onSuccess("Hello World!") }.subscribe { result -> a.received(result) } @@ -39,9 +39,11 @@ class SingleTest : KotlinTests() { } @Test fun testCreateFromJust() { - singleOf("Hello World!").subscribe { result -> - a.received(result) - } - Mockito.verify(a, Mockito.times(1)).received("Hello World!") + "Hello World!".toSingle() + .subscribe { result -> + a.received(result) + } + Mockito.verify(a, Mockito.times(1)) + .received("Hello World!") } } \ No newline at end of file From 8b1bbd6e20c04299c7cb31c750636f1fe4e30997 Mon Sep 17 00:00:00 2001 From: stepango Date: Sun, 12 Feb 2017 16:36:03 +0800 Subject: [PATCH 09/20] dependencies updated --- build.gradle | 6 +- gradle/wrapper/gradle-wrapper.jar | Bin 51018 -> 54208 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 74 +++++++++++++---------- gradlew.bat | 14 ++--- 5 files changed, 50 insertions(+), 48 deletions(-) diff --git a/build.gradle b/build.gradle index 34d7c57..dd4ee5d 100755 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext.kotlin_version = '1.0.6' repositories { jcenter() } dependencies { - classpath 'com.netflix.nebula:gradle-rxjava-project-plugin:3.+', + classpath 'com.netflix.nebula:gradle-rxjava-project-plugin:4.+', "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -11,7 +11,7 @@ apply plugin: 'nebula.rxjava-project' apply plugin: 'kotlin' dependencies { - compile 'io.reactivex.rxjava2:rxjava:2.0.4' + compile 'io.reactivex.rxjava2:rxjava:2.0.5' compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" testCompile 'org.funktionale:funktionale:0.9' testCompile 'junit:junit:4.12' @@ -20,7 +20,7 @@ dependencies { } task wrapper(type: Wrapper) { - gradleVersion = '2.10' + gradleVersion = '2.14' } // support for snapshot/final releases with the various branches RxJava uses diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c97a8bdb9088d370da7e88784a7a093b971aa23a..cc0ac2e1c507c335a5b15f3c1fbcfd2450e58e68 100644 GIT binary patch delta 33042 zcmZ6yV{j(Gwgno_#I|kQ$;7s8+x{lDZQHi-mG1{?wn1Oy5S1jd(CIst(Y^*_X2ndL3f{-%CIrhEtturXd ze-b`@{v}l6!2G`n{~Jva|9A9CjrpIwwXZPHQ790QWK<9ks-yxsj3nJIbU=zmgb(U6 z@^?)FotGBeAFO)j2oP}LL}rinL^NELjWvw_gc>f%)u4;ATf`|L#=dJFH=dAt7zdAj4d6))2>hZr3b{mig z5celp&Td4Z6vme`%kpxgjELML{H@qC1T?}Kc3`cdpk<|5~jijx9)YbQEh`iuT8vrbxSc$udOc0OK z{iH@XZ{&XT(Cxz|Maa&Kf25S(4&U|-?mVG&}V;VQtxmwJbWN|1}b$yb&w zPGx82oyqEy=H~Gb8A-JzFlt+G@su7(X9h3P{H_%_B|sErXLYlDDG%%DjLZ=5w)7pn z;@>|_@Q1D%@mASC-31I3@tNEiRbO0P#Nt!b5|-}sJB~i(LU^BZxPZ;aiLV8V};bEU0!cyUov#NAt!brHoQ-$7;92Qno zXn|Tqr*refvjP@u$CG{-@he1BRj}3)9=JO6DmGqRf)mpz=9jTe*kEai@v3Ri5hK3? z?ud(_x5pw-8&KO)69|mqApZmKWTi5GgHkYabe1j&|X&$ih`nN>?EP> zJd~-7fWe4gb};?RXq`AZ7v;<%0*s|M+{;30_0;ZFP5_;c3$^UEYix!tH*O7$(UMp$~=kJWulIf3X~4E z!Qk7s9?L;sn>hKf^?_aE5OJG&XJ#NJp6n$|@`P8-~v%(Nw-cbZ*mlvit>bha@T4lXs;!NoV zr(1mhxm$Zc>4C;GeG(s-9!R(oR!n(Ly~xl~PTd}h3j;&+?LVAZNv2%MUb8bwcMHRs z)${kES=vFqldWinl0#f-KtW5^OO>(sl!Q%Z2r!-geoqq${{d_Mk%%oP;FlP%pQY{JYR!XVL**#VE~xy?;CJH|A^J-`$@h%XRG%9w_%_R?eE^gq_2~A ziQ$aN=_Ikjx(Po(@kZiLmCW}c+FnkA z5HM?gIl+k094$~`vs#|}>~?CNsiD9JatlK0c9f(fi?0B-q2YlLA-cb;oBW82ntyv*kAI< zN|r?FuIAeNSG~d8P;VrEblihn1R-b)j3;Y7*QVOw@bhipw0{6wP%NMCBMC)oS?*Yl z-Z!GEZj6x3DJ<&CYT5u)WA|{-I(wwzWb>Q;h#Pwu28<6rnq#$vfdE$Lf)n7){6TA03wYg<@v*vSxqH~%a@fj241M!psPE&;vy z*%})o@uuGiP>Kd3s? z8F(ybP(oqJo5WkM;Jnc@NVaHkz(K!^CSNC2ZZAwpH2Jco)`o%vDaI@oWi-Uv%jsEmY%9Yl(HNwq1%tjg=`KBax0yygUv77xB%c$c95l-w*c4@=Mgxg|2A3Jzv`LAk zxcV(y+@e|Y-=f7W9h2*ph@O_GE&ivi9<=lmzNOWy^UFzi;rh+3b<1lz0E$l*W1*Xn zXCLrMU|`E58M)6r;v#NOi!UsF-vcvswmqhMRY(xD%>>e2(bXey!M<~xf4C#A)`w9) z&M$qyKYIw2=Np6=Zy0hT4)bJ9g~S=OP{SBvRFv%4F?&j=n#(^?)5&;YmXLpy?XJe` zR2P5p5-nepP>kLyNF{I%u*;HpH2aHJhnoIqmOHI{LtRv~FU!A|;0FM)niAg@D2+QY zi$C1M6~GyP#fgHR|4O_U*VQ?1j7UU{!r2b{}?ynVV$1YL~o9|+Kw+4BEO|Ef$u0VF&koNVg7tGfWaQV3@k8kEmnkiME z;cMl-*s2WwyB~+DGp3kA7}>V8BgWSVwQ#Vy#lEx_J)yK#`nke5P|-d!r2ybPos+rx zixro!_1*$tH_3nD#B{+$^=qiurFWj2@Zi2Btc))#=xUc*mX~=fVfI(=-{P9j{x2bk z!jRgxosvvXLE(nK03&X4BGRiyg$`(pfY>BqnP4&(5=auVtg47_ubuRPIFFxTwYy7Nnwep8ABnj8%5 zYszV@gi=O)CsW2=*3$pah^6o5iD4y)tqFd~4_~Ur?Llk#m1>Q;dGwV(Zzm3}+s&zC zFNc@=B-%W=xOsGv`bR$%Fb@FzzbGjw9Af9cppfy47469n5D**$5D<$0g+fdi7ytv@ ze>jLiQd_Iqj?RSFP|{2ml^C>gbHq+=FO{|x1tOUiC@+lBjBy^G(%98lpyAyicTLMb z*ZJ(1w3M(hqj2x@UEaFAswa~lPVxHddeSG8^FHVLZ&us)*Yx=}m?Oe>^e$htNOJ9{ zV!wEFBJ~hFcCLm}@}-=XCXF=nFu?GSyjC%0VZE8VvEuHyyI5!zjXls2Plwrd0&slO zKqWvhM0qoYB@lus_SPIZ{pVv9OMc&l#aAC_hi(wQ7sX6=ql{*Na>zJwgUN866x(_!|--9|oUl(^lb>ZPPKP`~v<7gTh#?QE{aq?8+NnQV!)0i@DFYA-dy z`4pD(#_m#axq129I(u8YSs7XTKaShlTe}%K3GljT{qlVa^;eljbE}Hi$_K4ts@%B| z9dp**bWVFT1}Zw5+WS96R73#ZDo7!9rZHp4N9*g7ImCuIO;TCnvc!TYNa8Y6PxX~& za`AN4kus?cE8Jbznxiwu0Y{lejAu4tA|J3XW}6&l__m=N@JZ$GWisx>`(5K3jgQW& z^{g(PUCblp92HjcvkeFS%X^oW1CG!44VkuP=hOi6=H*FY;F6e!>kWMmLU)AYuB~4k zIwbvMvuim~XDRPUG=r%4J==K{zI(+mlP#cRoKP2XPOYihkmL0TaM2UQrDLjJvjU{; zZ2A>}_ckX&-`&jT>pAa;*JCJ~2|471{_rXt+o0bd=fRt_{?7S0WV2qYYO&tp1PNop z*D!jvu+*u9@Djn53rn%Y!6CyLQ$fF6j~90of7nnI4^J*L75Mi(`|;-=`k_g>Nee_M ztrOkGs7I3CI=Ou_K&jrZXz|eu;<(SRpjAs%fF#Oz5Nt_gkNqDCT0aB$m_N|)MV_id zKb@3-v^W$uBJj&M&7oyXRm4$N`UgF!>>p4bYzaqMZ-kI>&}vTJ*jprXa1YvyB5%8vmkQK-)`BpUC`7>l!g&2?pRi9B*4;B z4}~dGZz$LoERG#8#uD&+gey0 z>A^XdZmm8uKjf}UB;6Yyr^WKu@!+ANK2@~0B5n_|$ilIQ6`A)oEG=nnpQFjuDqN zy7$@S%9`uErw|aLh29E_G4jYgeKg|ZcX#Fm+KsYv_~{>CpfnxZ6M~pxTfC^b<{B7Y zo{W$J+Cd<%k1OG&Y6a^pa`xi<;+>OyY@_f9_s@E=(WiD*;m50T&&Xq$r-TTFJ&_0t zi(jQ*m`1pw0&e^J_ZQGM)3GE=3`BH>#*qiX1WSa2y`VmGOOkrs4W-Be$cZSJuvb19 z`75#=%I4IXmyyOxLam{ui$mwxMHxcf3u^xW3}$f zPDSm$caA=tjy#`w&nfOJnqrP>C~xjwNy6*#K010M?WUVj1#wA>D-g@gh^!3OW3HhS zy7D)#r<3qkqx8K`SGb<_vizDhFr!W`nVe>xOUv z5R1d>dm>TUv$L&4_kswY2Cmu2Z}3n(wuVyg>=qi}J-N^nGVc=0cR>&MZ0+uTKyF>% zWM|_=FG$>7BwvW0mr)d9cu9kr}93&o0cQBy!ssNVok*|C~b3m=` z&+94DOJfDSyZ4__kq%<1f&LN!9=tW|Ggy8N;1GNqB{nveOuCb0baB$ke)HBJfV9G< z4Pvelo~;vCfLfg~n?A`cqt>p6)Xq0~E~+@i{H-Z^P#sZbXH;vBE8k%aPsz#tsgEH; zs0SJSubyEEO-lV)(<_a4pDjz_H@5RfY;V6*ZcQ_{6Q9~kQ~Xo1r9il9pX2+#`KtH^ z;_CQaRNrM(rSFF7GyR>xt|ape zb%bQq9lC*Szkuib>-fvFS8w%}`f7poEB1QVYy0_iYjOMQc~lUTE7IW96R!lp7X@>H zsb&`t#k1!rKNKBHCT)G+t=%&di@Eqg?sk`m!%(?v#;%EDzOJM`5gDs5nN!MOm2NZI z9g8pR`_mRbts&nY4_~eQ@+P@Qo40oZn$Qg&6o;pF#~SRc?s#}Ur#(~*-eC4d4c-9t zgg$p7uY*gUDz}@<&B(X=CR@nhw$TkZ&XWq@P0NAtUC+TqJ{kA1=da%|Tyw@z&L^#} znzMmGbpusGI;r)FRdEDAHE6(dOUG-`?n%Uy8oO8DQ=Nz#;U3fP-e@*W&*^K>`HRDWJNAw%Q49UCV|AhtkILju zd-4erd(TSPx%)1#|4MSEWb>~3uyd{=3;_!mD4nbu6C0rwB`jvXXl1@Fv4d=>@kK(q zo|-njB9Pz(qg?0EfnJ9LB#gM!y>$clZ{kl{(Kpl9a*D>saU@?F=p+KyoV5__W4xPH zak%CEa>x@~`Y9|VQ>cBWS^`5AJ|kw8?M({f0M*K3@+^26m)zLNe}KdFxG$vv8n^T)Rx8wu zSTC(SSR?S@b-Qlh+e$+ow>+yi{UfiTRa@uMs$dd#`a>1zwV3H<2HF8^HmT!{T3Y7m zUSs!iK;C;4r;H!HnA~r@0rLPZ(wmi`b_%5XI-D0!|MFdUaKXVicr@mMUCJN+k$KRr zsfAlc-f6Pv6Is5;6u**>&4At3tDyVvJ#~EKtlkUeJ0XV7rJ-$bA9xNno}>L;?X|n5 z0R}tKp*lbOBhITAD1c~KZxhKYwI`lR#@YU;x2%PH*`}%TGOc;rNfoeC}RQ%+m(q!YJL9<0e$D%&pu z>qtRe0z$Tq&p$1F1XTraDuLVxjvH|ZctV^Ad(gS*2{^gKWrYAj=mK2i+T3rz3hiox#|Q zg5IxnPqs8k@=SmyU79R;mSjla^LK*u*1(i%Ah=xfW_ys{_2zdI3v=_@qy3vIL*~kS zTiUsZjUirdlph#3KM7NA5v2>iPxgnLAlNKpK?%af*6WzPk!31LMZ+NjuobOim_k1=Y+7BpW^tg<@Skn4@7Gx?rO${P1k1jlfXfww&rO)o zq}|sLq$cFC=9|+q)P*@ffD4^34dB=EjvgMGC*tkiJ726yeUv?^%co>8Bdt7o&e*jv zN$0JzWp5tKX7==+?st1wY{XsJPs0BpasQ=f7v0E${TGXS_O9+n1OovPf&c+w z``>oe-wg#ymh`I~3y`96X^-*?jUPN2XlCW4R=*@|XA@FM_ks>T5^N|d8*Zg+XZ&UZ zgR0rINjyY!s3q(BCRH|^0XDIWqU`P=Et!b7K|FKXB%y}N~O%= zopvNA`0gY6wQKO5{vUrV$*m1Oo&3T24HWONxp!rDN+yF)INVp^PyX-z9z{zBjtb*l z25_vUdl#QHT7db@AEgLl4}^q)trY%6Bt}10|1&fi9BKq*&0?J6KgOE2Hk;J$fB!gi z32U_<=R?I!-7-36+QcV@GzPtU8uIWp--H*u0K&%NUyH0^G;?^FMZD9oZt4#&5jwT; zSjZ-OsH9~14OhmODXFwyjjEKnaE(1#f?nb(PEWBSW&n*#N0pUml*kRfMtvjkIkU!n z=CvMXQ|X#TeIo&{h2iNs#S%@Tym53rM0B=AVFPjPl2xoE{Xq0aX480l zN)2l3`U?-y?T!Xix^|~!o?m;k40uCdd}>$oq8%6IcmdS9xM@Dp94t$N<~C!|P$u*v zIs=bJYYb=c0PwW2&I59#e)5%eX^hihhySYxn0o)+PW)Fm{LmmEO#fRvpGbrN9Tgol zv@Z@RE@Hg#9yUpx;+9|?WkyNzW*RgybTD$L68$scY>Kc@m%TA5P>07iZ38@uvmghA zDXyu7G^Rh%e$NsW+wvp_OXH@EPir=QK9?Z|`w}pxxmPekZ`rqdxe zS=#j50n!+U*L*{Dc@pKWf*m^mqzlZEpXFdNrV=-UWCImYj!>0O%Cj1I!m|vSRx@QU zb>pBE(?(!pq)WM*68Mht_KWmtM+7o6&@G^KMcdZP`)g`%Zjc1{AbWrjD^0&|UT7HYy6w@QX(%nX{Ev@A- znITBaiN2+9H8dMD#-7s36e9GPju%;aWldZf(QaLvr)Iri#eQVA4Z|)9b##WcDO0L# z5^W8&Q{@z$i;$|=Mza8$v?S~&%zU*;vGBci=6QGMn90dn>hC@{T;_SLvKB7)E5ozD~(G;A($3t+CX^_LO|8c3efyJZ2%(9#%KK)o$WyQ~u1{VSJ9`rg?mwT*K*Vl-W(Ghp zjwK^^sF1_=T+@Usztf_;2}fbG7YC;6;t|0Q$+d;?ASI@E1ctA@Bn8ddO@C+p&9261x{2)$d7k?5DlW?}&S#JC3 zwn2~H_dZ78T@%HPioz*K+!;>PlZ!+brFh>!T`#4Y&7 z9O}e2M~X-&#(Svc>4Y$O(2xT6HfBJmb$)CS-VDJVtAtBQZ-mkmiM-M*KvaZ?{rL0C z=oPYVMnMsC8iB5jNBW10{7u2BaNnGFO-r)aUUO81*FAM*O8V6!hzBo1DALm2U z!1h;oVwo`ijil|UV>v6nLI)39pa>lR18pagCWI>tVdB=vYx{$O_56Y^t@1Int<}0p zy~MglHOiwRksQtLyk_k!>ic)!o5oA|HD^=Srfn+Jk=Ii;_qK1^v+p+VHn-RNc3tle zL)@=cIGvkzIKt#z5f&^K*|B(l0!dWh57r0>d21?)1kqm@^LM7?j?zc-Bn@Wi2?GfO za>ghnWCC!E0gaNr392Yuq!boOTw(VlUWKTH0}^7pCXK$0sDz`GJhxK5(xD2Jj=zTU zr1qL#bW!rs43l1j!4$}En)w7shcJO(GE%}{jiDCod}X_|FnShnU8C-R`%u3@8h*>w z(!t0L>iy`1mW}Fa4_enq`2m{uq13Ya&fdG~ib#i!sdkHLT<+35e1L7Rc$FyqH4kNt zF_2YMp@8$)8)!Sr3e%dwe~w*}cxpyD=vA(j@>H?JWHK-Q9h+m4f*o?jly1ibyud=w zCV}7kYF4{it*o!rOGXBiUEsFTdk3YOy!xOs6sIZcrU$!Mz3Dr6t3)Y16R3nEbU>Q> znN2zWogM$1e2ULrT8yyCO+CRp)MToMDsGkrqc|gh7lv$Gq{~6wVG}X=V7uR2`p7mfMeZLroR{RHFJ#NshfTYR6Lc%wx%aQ-zYjF2`DP|>r(L=ULk-&YLr_}}x zZ2_94*fyIjOb5I0-JaSam)Wi{+_Nh)`?#DIihubLeTi=4`BmBP8dqEP2d2GgoguFy zgF`Brxz>i{m^~F9L zrp5m94eHGlIenoZP}N+QF8V~DU`Wr6jq?08+IC~7?iA&tJ?de~ACBqfi@802>xb=6 z)R^{9=@gv1WnrKmZcT=pUcNQ)`b7OmRyuzyzK7qtwZH-R*Y0D!l*AhBHil}re&F=Y zIzcw^RJK&TP^;*EV}L+%{BK!?;;m zMu)Qa@@pvgI+P^DXYSHl+s5Urd3@+`4$|?}YqZ8W+=blfDvPGe9mpAn^~Hv2a&T{+ zq322a?s%2@Zj)ul=ImyHYZ231BsGowOvf?!F!%)6SodZD!VXF|1-NJB+Zl}TthG{V zOwnTmH8W6&)P&Kgg)YG7FpsuQoNa8?){o-EzeC>b)QAvNSlB+p3zUe9qlUbWcIha0 z*0TlD{(4GmkkBLepdUsVUodj`-mce)#>_*Z#gN8YugsC-u_h>p_(Nez_eLi?2lyXey&i1 zx_@tL6q##0Z#(0ZV2Ad&E%4Ej*zWkTPpnU=4|jDQ)I)GN)J^iS1Z9+&Gu`jNndeq_ zcS^kA4iji!AI>AY3hj3@H+f;uxLnimu=oL*!cCQDwM1DfR_Kf~RDCgS|EgKmDK>#4 zI!4bEM1SkYdf#*-zcayg1l>v&V)S_Mg`|6GFd9hgc^<*=RBF28kL_#BV%I*Bb z+rv@}POHo?%V8xChxX{^w;FDu8Cd{6t4Di+7hX7|lZng9U)^oqgh)Mv72-udJR9yO zQaSlcl00YAIWjL*qOj7j!s7!1aRfc1l7Eew1-TcZO zWH->Tg?lVD#=UxP=(r!%8N)1ZULc6f^2w=Lw@i6^FO*trfkCDSUMl;l+WdeBi5GPv zWqh(skpqoim!nh}1V#sKj_I;;;bulnziba-GDQO9nP4pCNqxmhd*|wTdy0xfEXIRG~GhbL^{gu}>kXmeL zcTOmDmjv@Z_vIzK%DhiY?=d=pfLnytq^oPH&T;etka4f1w_nOJdVNlsPs{#wnz+}| zYPGT{RZ7fPqQ1*akf|^u?HZu_`PZ?TT4C}Ju=_|=qNTdX)UYO`jx`8tu<$oR41)6- z-M_3I%aC<+hADa~u0SP7RwC4Q{dgWwX?}?Ty^W-NTB^Z#LzVj!a!hA>!3F5f)~TU);eoUl=>ZX6x!;@Zr9JJX`>1VHw5h=kXueU` zC+HX6`qa3s>ZZIYXiRx*6W$EQ>F%Os&xTEa@SXt@hSqQkR_D_VX;D@_XZa!)%X2e( zVio~)oH*JS0>PUZe>A2qL7&)63(oq^@Q+q6zYu*P1vd5VpHWRQTrP^dFWZW~V z5ZA~LnCUrN46D^ED^9r~=I~{D8GdRl>A!}EkKlh!&%-vq1eepkw7^GIEX4UVnML6O z?yf%J?e%C7dxtMfvpSg2{35#KbEaAbm@w>er0GFyX1cYUT4ScPqvS8lP|d!hJlQ7M z_j|qKwr)#M$l-qZ}u zq28L8CCw?cRuPE@2DnrAN(tSUiV4dBgw_w5;Ails?PBWYEMA%X@?P^0!ScMC2}*PV z-2>deMbQUMcw7ymmwt`sIOg#Nyi z(%xtwNeTw2{;H3h!oGz*sMj3Gt;bTPS>fGBd>7Fp+swIrETGi;buCMThZK$i7=NKS z-SQx%`wIDz(7on_gYvw9m@eWCmDdjY^9~B;jFj|nC>UX++YhCVJYWYl5k6GZcOtGC zm77Pwd`L8Ig5xBN(vL4Kl5HDD60!fFBTi;RdGf6$JZYCZY(wT&nF^cs%yYX%6e*cJ zZyM4|)RL?|X7c-KLV-Cw1Mad4U<`oVEvc|@=~dbL?RjDmEejt`>G^{O3kJV@XNMGG z@XLTOa**-+>mDtPrND?0QGrQW>|i=)5H3+C_cs)1CWYkasc$5aQ@P6lM<78qF_XV5 z^0Qo_TYehb_MjHnib9H@1Z6^4TTLm#b-CuwyQcwseZ9f& zASonC?wEt6InoBKms2PO(TVDX^9paII1>Fzm@y2GEBoPr4OoF5T4Nnz**J!4IFq_e z*SGWO$8OXc+N&!Y67bln@Jo{C=xQy^9IZ5g;^m6&C>sebSZKl5o5goCbUrL9iWT2b z>TIobDFY(0+vOA}UglO1ssdH%^-eotjZWzk6mjmxL>GJDXc1KNerth;ju`vCtGapn z@*Sp*=(L1H0WfphsN~6(C`>|)HSt7PlUGlJmG@CK+=d@`k_Tw*%GMAb&2jkcLQSW0 zd2j?-hF6roY!O6daj@}96NI&eSz~Q6p7MXQ_@VPeBxxOA;oUXr|ok5Eg$&=r<;2xzmpI)>j(E{>`4 zt^AJ20AA1+HIS*-on*w=zO+8N0@~nSdE$52lMTF_A#~}hq6tv#A_kL(srQp;p)?N* zO`Q-8Xnj)2&7cYY6}();9pUwoEt1?;3GD?LPx|h(Ik-7%o{%|qs$%RpnGhcIo{gKm zr|v1Pp}u|C4DE06Ch_zG%ySU%Aa)tT;c2F_7q1>Q_df@M-r~ z@b>Bx;td-Znk7vKR-)BOt!}L?_*r59*w1y=&g0o_Qdr@3+0AiS;U8PtE$b&BjoP2b zzxUR0$n70jx;zNXQM_)gFA~g!9%&eEckcswt^t1mFEer5pZXxYz~lg~Tr~ceW3EtPZ5apNl5G>A?g&}(_mgT@Z z{6B>_d6Y15D9p`wxL@LyncH`xpYlcuJa4e<@Fs)8`n5WC8vBK>H-9&Zr`MDylJRH3 z7o3EfFR)+Zmh;CKhQWM7VpLxeK)9E|qqlY!cP`jrd{>P7qX(~-Jj`KYm)H5FmTjNV z^`j_&*jHin+3iDU2oe4QsKWClCGf44_z{);Sp{*p4Tm?ce{<_PuBJ& zG5US>(h#a)`4T})6wH7_rN(AWk{^-JAlx~_R&ZW8*6(C=VjRLCJC05Zu!^u;0@CO; zpBT3=40dW3MYVKlQbIOc8~0FfSdx%{fU@RJuGt%m-0`iWLdA7X0bfPD083MEr^$!B zwLe>I*%54f6pPSE3)Pf^DsB zP{Axyf1~B7vwq-vl=_SST1r{mpCOmW4{h6g6!EW6XW9u`k8CrUJg0aZ+$^duWIYBw z=2x)^aQSlU^%yTrsht5lSx<;C53PD)N6yR>9`3SG5O3Mkm3r*yQ)@%79a8K3MsnuK z#PZ|gvYPhVwK7?t01mnA+M2SLdKcm>r)ugoAGz@{`7(>U+8#83qFc}iM}<}%4KtLj z6)jF$%jeZ?gXh>n^E`k2)XKQ*WM-zbU0i3Dja0;2^5yHPt(3b9J!8XpUwWgg!EDygq?r!3f^%{b53R(UlxPsDXeCktcRhh? za|fP_Sz#N11-cOO?Cz{av)1}*dI~4mab_pRBRNwh3li8-QF#PRW`$vMKS9iiv5V9v zYvUy9+5u9qhAP!i+|)FUN~C3L<0k6X@N$aQ%sn2b9%_4}by>dO?{Xg${u5}Nk(YEd zQ5`c?D6_ehQ`ASwZd)3JQs{QhD+LkmDz4%3sCAZ%tSaYW!DQqNke@LQmpJ1QT`(Zd@w!9Kzc1;<{ZED?q zVSG+{TE4+kf_9bQG3Uhi@}GgwTeO3VC*=t%CCN()H7eHJb_sOss3e1Y)>q4jWMn7t z43E>#pyH<50B5b|)m7X~uiP?M)NFVd_a+=bEq+KJ7saeXHVln{Fos*SCR?qMLLe8N zWh|UsEJ4d2vgjrCmbq6#q6L#`{e(M?Hwnyi9;9AVRaVo}VsjctD+A^PdwmQGEfc6@ zBYP~)&^OjrsMtdkIzJmC!`WZ8qCOonx6juP6 z$Q&b_a^6$5iC*T+f5~>-eD}o|i$7PZUIZ@1nqmnb?>e$Ya7zm}vMkKP39_)13%ogP zRGI5cO1e`shMuJ_k1Q(+rM7d}L&#lZQ_5Ffl?Pa4s+5Vf)pV!S+}TE>(9eqov?g0i zq?bzV^q)Bx*IFmn%f2qlGc%2qizNZhDJ;Xv%ExT_+`UiM?6#C48kA8AP&Cx?x0-9r@MYGNhMGQyo|;ppZy*~g&O%f~%1qN2$rD~F zYwC69(3a_=9MYtl&@#I$XfC}g#dv`)PxwzDYRM^ImTv2J6!dfIecKf)%6kBdRODz| z12xtLr-;Z*^YWH2XwpQ(=+)<=RZ#VtP5rX>^6d`})P%~ZNb(Ap#I;l#xg5JEr`j{n zAiFXjk6{~KfN`s5b7F|9aBq99UkRoMwZGX~l*ShPV$ODXHN?bZ`C6&J8p9v8y6eQ) z*Nkmw*A89tin~(lqh75EIZ1$F6LnW<40mz);W9{#md^Ah+i_sl6Sg6aQN(BoXv_!P zo$1gpN=iet2G%CtXm)}h8N(~Py#`AtY__^~Fo|PqZHv60()vSA2Q@I{jMurzP zLD#1|@yj+Je*s=U>JbFm2j)SjsmdxpjNfhYBkrtG~G|K!_1OubGaXFNhW(7xxIB? zi{WoN3j9C|d-k!}nmq)9MxJUdkapB>q{t_a+mVT5v!r@LSPCsm*=-#0m-f96yyoc$3AO{%FimITBt`wj^`QTTAs-HvLA2Os8Du<-x3Xu4tE!Jm2Fh9TtnCJA|zs zWZW^62GVrn^+`?{?D6&I7)=pss#5riT2vp5MO{?-1mdU7==N9U&X_fmF`Bp~Mh?Mh zvf@Fb*SAv6Y#{@n=?w83>@H_X0&d>I)%!yw^M6Y!I6gk5KA-S=&)butM4v#~%3fJp zd!mXn7^#3R^98yo?5^D$fwk(2vte&_{yxvOVP9hBH@)Q0j%jK4uRVQqD84A0McPvD zkD**F?O8vGJc&8agjxAoQ6SJLvxB?{ho}>ZgMhWX?i>Udfa1sPz7aHibx=%`9=Ryo z&HehI*53&+=tqB~6Ml0ZfplcmXp1+-G}@fBTn(~Yk;(V*Y>Wdu7h(Ry8fr5_YTHLc zAO`06%t&B9pk9G?qe$Fn;{l7f#Q8;DTn0bc#BUhzMw_sTMu>g0ee!mruKbb+ z(ir|4K|TU{LHnYAV%Koa*BTggTP=9}K-@)leTERft@@ipaV99HR~JaEr{VqFf>O8=$ez4i{_ZNzfN+U;zbj%l`3$;HU`&rSpS|9=MH9Gx577Z8pP}F<-_p{C0VA zTkhlX;Q9!%`yhhFml)`%p=Rhn4#&9fq69pb9AgA5Bt~*7cgVH}UE;&Go3)>RLh)hu zwgh#;tUOXh+Hc5lx`#gNj%*!(c@MtBtZZ+!{08^MaEy1-7z>{ahE6ZWrxs=rBPz>S zWBnV+hVNcW14K+6;L-eXPUIif&lc~4=&w=oQn;veb*EC3ez6DA@`vS)=^CH|+J@*B ziii%F!M(Ea#R!8vA-sY^{PUOqvOG4z&Xu`iuaGYjPo&@9kZVYW1bxSrYkL8;t<+Bh z9m^kAK>3Gk0sbY#eTip8f5k5; z(FYCqqZR0NE0H}0_TY%U6^5Sd>%eUMjpd-;@aOtPdDB;|!uc95&u|kDCe8)#symSW z?~)vOT+_#;!zs04Vb#ekB^DtiE%S0@J_?%g?<~067PxEqAMU_q-ieV5)!9GGSUl-~ z)W+;A+3->6!;c!zZqubSY8COhj2juN8AGm$XkPLd*u0|^RH$ug!hsv?SE!I{>)8|^ ztooP;)8BDIBvOo&i3O5O2@50zqRQ8jWIDn*c+IuLT;liv6TfSuqBBIF+? z$t!jiJ7ZON^f;=W(omGQ93fRwaeaEydgOnj{lYq>A;NCur@)dOK>#CK@y_lIFx&^W zLQ3~ZvPiGuc7}u5klL)0f{_0q`~SoBf7lUb;{W@}c|o1zh))U7g7#HCY!Ren z&EU<@C4vyfT0)L6MdZanf|rV4L5i=(Lb|1JNz%TcM{w z#6;R{wzR5O)wHyZ3W-qOX~BD-FonUnkAy5nY` zmHW=G)$ygunU%h^M>rwJ?_L3eux2BLAd~yRxxV3PnZpj*VF16%$P|ns^YWWl2cO2l z?P@nm|M5)+X!jy@H;vFlKdi~)9Gm1k6`DG1$o)sRxOXO+fWhwcOULT8O-T{C(D5CQ z)zZ%wu&sPYo8L<}o`Md^#}rnTVBHyQRK5j+b}Ux^mTy}@T5iN(91s<&ZtQlWKD$K( z7!pdQjp&8!02{TA62DuWy7Gqnmp(VB@7xcaIB^RXOZ>N}r^BI8)wnS7f+mIfs`6lzUZjT6><&f z_Dd4YqQVMHkhnM+%XZdV?L`F@Zu_C7YFQi{H8!gafL{B3oYKJ~nHD{5IngM$#b)hN z?jGl$j`24q`{C0VlbS^MT1a%mMIq~3CM6^{=m=5kH^;VEB^ z-JBQ^Hb$jd_NZAn%KL*Q5>1`wM8?hhB+19^^qP76Kch-d{qPe^2Ukk!I7qzpB*Jnf zs~3$k0rWaCxFI9^3?SUwFr?<1v>6NxpAbC)T1lu;daW5_I)sSlvZ$Fc>061>t>;JL z@IiY4o?#fP)fu6_WjsCVZJ^VWj)!!;5u!wX`9llrG7d~A81q`SIJQo?j+SM!DQxT$ z;iIW(-13}z?8c~0ViHeWDyvdSf#OE>oH0+u0AB|dQrUg{JLb*G)#d_dmB_}jW_KId zG&(yu@eB%a(Z|s0UmZPP+A&uoiEVJ~1<=l%4wDMzHoe)c#>#%2b8Iy5v6$!A? z3X@`OvpTRCY`so3Ojw5{xuPb)b#>f5Cqv6Gs?U*1&L2F$)n2<9RQ%G}{r11O9J1vq zJJtQ3#igtDkJ&6Jcc1)LD`Dc3dd!LI0BNnRi3EOhzsF7Q(W&4UbE#8JD}{1+FqH@% z{on8{2(%SqwhG7Ods0mzZMWHV&yOegsRXSOAILwI73mKU!fe%gGOeb>;%#y5I2}ae zjEF{sl9sJqfo!x^XC`_~nWa%ur4+7J4T$-T!;$B1tkz^GV5Y%ymeyGzW+Xw#fCt`{ znIT+P`iq0%VDwnZ8aO1orK){%)h0ThK+ZtCA3Xe*e@3;$^&CdXyK1Fzki$@(*fQDN z1EWt!9eWv2eJ5Cn>NVJa{d2NU{V%I3VDcoIsBwW)T*pNM1Togkq8X9sPA+yMRpQ1` z$V0slHT)%JIt7M&vxQ^h-Sq^s0CGSkL+YR?c|!9u?4Kw`fCMKRtDBI|9LI7$B5c8i z>4h_RA?eVq>fG_XA2lhRERx01J z@$DUlN?*)tl~?;-iA*20UIn&xgTtREb{AknQJiEZsmP10tC^>X_qHLzt0BUc`clUF z|4(0E0aRC$v`ug)xVuAe_uz8T;2MH^@SwQ_4K6phySuwXaEIV-!GrsMlW%w5uv_(? zs#7&n^E}f%bEM{+?&+o@n#0W0T0+cxsNtl()*<8?pG)xHn0izVH9G^zkXF#toa*=$A1Uem82RD2D}N>_x8CEfT~ zToZY$aZ3i2-ahGKSh9`RmI_LB5malDGsM_)-UqUYU}-KpY@1<32+ta zX!#@jQ`2|UpNa%!J{EiVrz_vex)b^W$M*7lx863R10*v#cIgjC`x)(}I+*o6cU_&O zH|fL#hvMF-+*+yi(uowGUaLK|`Bb{ul;qL{>bM}m1Lhnv)7Ficr}25L#WB1^ zlyCTA(|4iv3y4*F*N0E4lmv_!+B;Z6)L3NCf59V}7750_Ul*dI>mkJel;`CG!%Jq> zJ}cyn+->=W7q$qeh4~*TE2X>@wV64fY>IoG60n8!h7BQ~awANU(63czIL1QWC}NET zn=Lsh3jUj07JuM^;x_581H@;UZPIf>hG+lP9<6d!*rErVXSMD6TjJ@Avf4(=T|Osj!Q9uGpVYCkqfi;=*rcBzU`=L=akvkaLR#O6vd*;>Wywt!k*zn%F?xg&E2 zlWUs&5jrm->6=Smn%+;3BGgnFjy&RgN2p(gWtfQ4!M5o{>_1U7vtA7kEdk#gK24jB z$rPv7I38anC;?JUf9j#pa9360x>7}-8EYtjcap&qJM^#HlquWa(dwD3bs%5bJ{l2^ z1HTM)wj4t9kc0FlaVOD9K6Gx?shcW{)6HYfzM%(>uXjx2Dh97HF@XqUf+;yb-hhOU zy>rO#PB8?A_8mPeAAj5?-p3AZ9{l89{@6R^7)GkSB2G^^ zfp=7CFMQ#$U^y?`6PDR#TjHsAN$&`fKc>d)+h)y#V$v; z$w}QSw^r(*lm*1Q5j4tPWwZN`3jLs{8hbtX(#l ztwtV#D6I_}Q=yVJTV+z{I0M~H#vHaYGknMIXI)5WQ<4swEB!Y=&oO2hTGk;*84O@G zqLSq_)%Gkj6!J)2(6MtsfJmqAOQv`CP&hEF5r_BmoNluOQj38<)+z}5m!_vp77crt zrq52<=fmUFI-(o!xax5e_4B6+(g(lN#rrzL=*!A$t}_Oe<%#j>Zni3;E?i+so7@MV zv!{VrKcRWQI>J43L7{UyOVJgUao-llvS1uemtjGP@kr>4)g+?HZTZD|J6Mc5btBFl zL^!TUNxHzNg`Wi+aOO8j08|%1e>5Kt$D{vbF&V!%BypAez2_=fCf2w;SdZl5b3j`N zN%Rgpj7+DKX)lC=R%l*E?t=qCWPEV#O2#K2L~EyQw!+z&R zSC@vie9L0>;??`(=crAX)jQ+Jcd#p77YZd;M%(&Ye&1F(q##DR3wa1F;}^@H*?~ESwTQU38$@G(IfvW3U=o#V+OeBI z+I{RdKx2SXW4DQ$;Y2?Qc-``i*?8E_4Ze*bMW;gY|JR7$MJa0cpvEGOY@ zvxNw;k2Vs$=xP@ti-p7<`5pIg6NVld_@UjEyF>NHwHxFrO3poJV?I6}OV<0Xk$8V}wx+Mxn`js|Kr!id}@F(M&8KL2U^32^z9$ znDJvEq?bn_2{#_B2S92HhY-)Eo!D2>vBD8WNG|fr%I2QBEu+lXhYOPf@FIaeH7yXv z-`aQvN$tG>pA1*+3WMn5gMbA(M5Q5MU91{i^9AlK1oo=#xIBezMvY~4F0QGDq_p3`qy#e`X=9lsJWmduwGLr)8A3q8yV=c+mD z;V0}_0*XQ$acA#*+g|B>7ATArUB#@S2&y|Ag^5`$C%PJWlf0&&d|-C7gTs)ki#}Tq z6kXHZBl;SPdipNo@w0#ottyO=xOC-b2K%Ry@TUFK{p-^>#x7H=62R>Qn$<>YO4xNu zTKC>ifsM^NyAp=GbPzio2?LWakV84l?KPCQSBinac8^a6U!p&bF4Do z1H^-M-kb7FmneeNhdP2=lrn)6RDNOvpdx-27psQq`YqH2z5^!-)U@DR>+7kXFQw z2Ir}Bp*j%FRW>_|(j4)fU7=@=@J+bnS!iUD-_O~$(BW)wRoNDrKRkLGR^C_a1D7K4 z(IzP*yqR{&#fIxCyFAf%=g8VVzLh@_?-BH&s*Okdt}qPV>L2{w--btYlB;R zM6JMBy@E(-{o04SC#m&FU?ajZF|^hYWH!m@lSR5D?-6U@7jJDz`xs>q6i?c928|w zC&>@}5CKld`Fba!W1aGQ*b9OYkRoG~SQ)kq%9I514sHuuUcvGj-FIykNquM#>EJE z2-Jpt!_oPsC3t?P-4Y8r>eJGP*G|_#^@jIyAeNxeV zw1Vy@cpcL0vRe*1?=Y`Br9>)D+{Gif3&J0+0(JANMzuLNt@r}RFeSj$aATGN#6!fW zWFT4*$@dC4q9^$Ism5BAO&}y3ZT*Um@E4f@2^Oo6xDbYE3|m0!r^%Z^hUS>PW#Hi$ zgg9vyFJIHwd)%65RMY2>cRTIMqmL+APoH4n$&%13p{xsZ$m zLL=i`Rn^P@?LemJtb4d1NGM2;o=-AVM0`LWO>WfgI4CuBc}b~X z>Uam{^N(Qt<;2a=9niA7d7Qu(?9mUA$!tGTuN=Ze`Q8+5+cSI5jyChjIE{{#3c}RB zdKP*4d#yg$@E{8j0)hb#eExA-P!hxk!g*oN;XjK+dH=c5y(ZG}asvtwW z=+g<+@|7HcZ>t+8+F6R9Eomm&naXxyGOhvp<8u(5nH4?J=IJ4?$zG(Yr~)wX)!h~MFwHDtVW_06!gNWo3N6% z(`^4DZxlT2|3(^FaJs3|2$b*S17b&Rb~6b3Mi$-R372hPx`}ntVtdK;atju2Ox=W$ zrpI!opKqQcwvman)1jru1ig<5OZ&As6f>dua8u-4E~|SSH#GOyApZ2ZJMv@E4NhC> zn!sl-O$?w?@AVl}XNT(9^gM2T}Y-p!DkcGO%@-ILPxF z#rf(A9=a)FpZ3CDUMVc_zSM-1ZGl30){N#|Tv|ooh1SArcohde3TcQdhJM$wvfAgMfE3!|>_yKQ}j}*jt8HNbFsgJvc?Py&Rqp-xLa{SyVCU zD@DY-kL|DY`RTZlO$#$f99Dq8f*+1A$V94?Bv_LrB_{^`Eyq&lNM!>j@?hXwXCzmn zhm_HTeKLOJK`^@Ip)g@km>h-K1~k5`FW?;EPAUHlWg814Ly+hqCqB@fZl?j;^g&Q! z!BNU_@2f%LpfLcK?<%*{;*B6wyeoeTY~i6DhoV5J@y=d>+7(4Po%4K9PO&{XaekQ0 z0()@#EYfp_&71wJ2vv0Bg%k#F6RK4>$l{-7(#BVcuo^BsyaeUL;^kwJ+Z39~dib#} zFc!Wo8oV9;9|GX+`R{>Bv~M2OG(K$H7|ZM4rYhj$sy9tybC^)6a!Xy-gtZjAb&1*^ zL#fLW&~v1Q;g!;F;Cbj7I%w37_P8xLc2LbkubH9r+jA681TX_*0^hG$HZiQGh7mlg z^XCvw^(^uB<&2*K9^^X9!3LRj>QRR;S)2O>qUpA`B4rtYtLxVUPd*|HH=sa~O&0m0 zM5J0>rEr&nO@gP8cFP-qr%dty0j`uj4lb<*K&J@T1JW~&*3NpHqISG7n*ncRx9hnH zh1pNal2@YxvM9i@-lNIT=ftBr%MkA-uGu6xd1rt<$Q%nYKwoR^b+)lB=&BK;gk45Ar|_p>6N)9sHy4*4G5M~}(=&?BpTxPD z37@=#E@bL&HJ59ZY?fY{5I4A8r|;SqNHaI^?u>J) z+_i{1@r6Z;yY+VEQKhEoPiNR!3o!5KLauVsRQk2T$Hp>nY+B7(LZXivq%(;Zx$j6P zo99+hYG?4_f?&~VS6c3z(JaHZ%7K@Al0UQ*=!2cH?7YHco%c~Uq!J)iI<@Xa6r#_^ z6SxU?JLnk5B0P@RFq#sxI(eR285gap>Do4;_+8QI$SN@nVMz!OV_LP3V>qboW?5DZ z+ZVGVd)rmr3L46pLeK@83|Fj@^qtj^WVhq&#t1OgyTLdD zE8$ALgASpq`cmvKv5~hg4V-uJr@cqV>uN^N_2d{+>09$}w}eDa<_9(1PmQT4*i^ZL z0vny2UCDkm>xyEV+y7Y36G+Wp!~-3r6}|uZsSdX+os=fsz&YVQ9oHcHuuS{p2lG@5 zo6solG;s-Omz7N)DuggJn?Dp$VEMWo@ReKx>F9j%66x@KyaSNV{JkO~IVr1NIV}}3 zpB9uP3g&CH1-hU{oW+jVhA+iCWN-V?pW?`T(T$?DQZgIfb&;g^_?Gp_zB%04I7k$m zIE4;L_7;5)JNPA|d{5;j2p3lFF39^;t%%_YofefPE)d2W z1s^&olvZV?1R7f`G+$ZaTQB~bfef1OpxlwC4B#e}uVZydqe(Xvvz3OQRX0`~8XO?6 z!q?hxn@U50j5A=STl5)vR(@@RzJxm81-~S{(i06*Z*F4bz|xXwSv`_~(-4NB5}UAs zP~nGCzP_Kr#Rv|be_EGm1InU395CsO!loZzFWAYV)|=<}Cdf_tXiccgd=u|c+ZRFN zmKy5UYtkrVEg3$vHL0GjwWKr~GUK~Er;)PEy<7J1{n7qn+NDIo{cHI(rCuqy14qc0 z{9iy}{q(vKZ+Y5pJ)F9U$jf0KN^@!Rl?9ew7bL%Zq}p2{z@1KjN^+3l!v;bvs05%k zndI9}V+3K_WnombN6bqV{-QoaY?&S@ephAf{FF8_R5qG&$WZ$TS=B&2lr_^^T!}qu z2=#1>%n8$lugCsH;r6h}>Bo%8{8{XARdZw+$m682YX`Z`q=L&2z|mlH@didNaRf0@$LWp(ZX!wO~f{8gHFV_Rrt;5;$Xb@~2?v(D^qH@1nXiA*BS%@{Q_CB& zibUECuD?5WL~pUeIOu(bdbzNLF7#N|13QJQK!eiW;DeL|VL_)2Sk>h)hLBkQ#0nlp zF_4M|qi$jUpz<0CfZ-rpB}~!R{m+ZLvwp(9K3Q25N8~9PYa9q@>TK%B*fL(?W%hBD z+Yw9pTxY756l`Y!q#bCcDXqF0C5t^g9Mv4>>5oTx_xEFC?;&4E?W1BTq8L}AFs z4kG;{f;`ua?j0jy1fiCKCgWGOBBzHMJQeyuA1WLyKC`3r0};bYM=tY`16J-r_H~hS z3-XQmZL?*$@|mt%IsBjkGgs8kJ5t_j*|FQBjU2obGoYK+N>CJ<>)vu)pn}-R+|d5^UD#(G(p)M_d;6ZYGv41n!A^cd!OQ# zrv7gB^05bvIgotGYY}mlxur$9bl~IIy`yTSM`ciTT&~K*6hr#e~;Bh<;8aaWtftF$eQX(H}*a9K&0d1-66C?>o;Ce z_Au2CDNi_qEFfa37#kRb5XW!yhTpQ-!jv^aVO7k+1iP{Iu;cNz+?fi2(mMz972jT` zgo4aB9P}`O8`M7|&g{JJpc9?G=_RC+jf~iL*-ul`oXQ}r4%!p*V39fCo4i9{7v01L zmvW>CTG8O2SsylZ<)U(s*|VV=OJQXP$((W$PoUntCIiVul^|DTKsRQ?4h)h-%AV(- z$*B0q(~G`i|L#OBqmB|2BN``LJqedX_0~!*ss@?qou==e6}V8Pm^A18b`a8pe|qT= z>E+G688H5y1iaya2;`~3@P?6Ya7<2tnZ8beMNWP|c1B@zV2goagmGY4p%&(^*jJao zw@$!oQU7b}#P6!~gxO&X`2J4;Z=K)(eXwQ#wR9s_OC`ra{wo~GZ%{Q_!q0y{G{5T< z9s@ctU_v4a=t}?@=yfM@^-qU5n71xLb^{<%Xzv}_HHMr&&5DV`jfU_Oq(B=mj)4&KIc1kn`v%Pl9~k5&qZspLKt3T6LZu*Un&DEV zILpvmv#?Y{QueD#N$H7^ZL#;Vu+7QQ<%;g2=SR;)4{P21_N&CiVR=-VtIf?rAygN5}ct*JCmq)z}p9)q%i5gZaKjtQ~Z-6>A_Z}D*ut;WMQ!OCQ+)h zr9MCY;p}|3LEQ}R2-+;zH?`t!E-Ky z42N*VA-7D$+7_9wIMyu^`RBce0GB3ZGo15Ag1Mj0m8^PwPF{Mn8hlD-arYMaGJNla zareB)%cE8#3ErnjAGqa49&p(KqmCKS8yEVmB~AcdOLXmXR$hgQn(&)0dStDa8zDXy zkOc2{HfiSPU{iJ6voE-B-Kw>A9aHO1vH6o*X8K365FRG7s(VX??LY&r;ePTxCMEDe zr55_%!+J~brV`Xs&kU)S@FHQmZC#=x6gjN=C{@Di*%Zn+jAujahY|aMzGrKSjLBc= zR}E6z>;$G70Nsn-(fS`e9c%H!wI})-@as8IMK^l$6Aug-?D0@JuxH?m*}m49TW8x^ zA$k_thU=<$a^^YsTh~;rRjb`xtdZd?b`9Wqq$TS!;I;?qh(k0tks^Q)q!U`y^v~`wYAXc4Ls!H*^TAL3R z9U9?6m0vUiSB)@1535!>n}Qg9QCu2sSfZ{t&YNR>EM&M%K!P51?m@zmOEeCE8>yC{ ziN8)~qE-xEg^_Z^%pw&ZW$>gYLtur(pFmak!^b*v2`^c>a(_7r77@>=fhl@G!cMFW zkxlQUjYv9& z6s##)gIlRR58(yj3@da9Gi1$&3^6Q<4(Rr%Fw`|_a6C%qq%I=D2f;Po_TfEnCN!q*chu{O~@|xWNLatXh7dduxlYc zmJVVML_cuWY5-x4vkI-7XHb6puncS>n>G&ERJmJc{QfpY zocJg2cN2%tj(D7u9Nz*R(_WW+nVtD?Y?hQ{E82gW8dsY+6~*Md5IDuQ+2CDa#^B+RTdF{s08iq2u zTQEsIl!dP$Uc+&_W#U`+(>QDh?9m~8vTRvgvum5o2-;`M+sKUY6FrRXBY1CmO0vR; zEJB%HoLCejctv!eqwO8ucaPi_S3c#|-kH9AfbvAN3WP`*ieU2lxCQmqF9gv~WQ+&A zl%wZt{fVQ^vx!$w@WbtPV`Lk4wF?fpEwHr@8hljgatLgI2R$LH`Ww(bpS44 zkJ;~JM;yYIi)D%(5Y%Jo9$XnR5^E|m#L26mkvx<=Av3+!opte}Q=1UERz{5D$^{KL zu;zIy4D>s}6&LLIg!e!uVshH;^?i+HN(dGlU(d>`XRRE*-UbeJklT-Rvz!Ks0<)d^ zb6e{ATCK{EuV11WO0^GRgJQ5tJ572&%4gs9v?X)M>F&kSI5qoGLYnhKKuV|5FI9Zq z!f>e(J3e;K&jer4>A3geFBttILtdq?o1+N#DFH2)jK`H4=him0hg9>wc6wtt9Pd}| zuZMM1vn{Oc-oX0Z-@3ymmbF|!LegvKy+J}2Cb#76!w={Btg_esg@U?N3TmM)ksPa8 zpP08qHGtQO#q*$-Bd^~K3(mOL3?6=bQh&*pg3P8?Ed+-pII-RGw4RF4>gTXMF9r*@ zt`0DS($ZR?1cg>Q$u>!ADXcH_6U^pOF9(B}8i6y?a?#lkYX8}VVlZrSX!7EErJ27L@)>=VTD7UvHZ5$)kyrS8o*NVu6S0B3 zTu*Kn19d{k)mB2)))1!TAvu)KAoLd1z%U@S9_lD@jK%j|KN#^U-eo|S{l@Zb>KDta z{B~&+xDcgtDYj2;_WXMTMh&+qkM^BdH=Oag>ucUS+0wWB`^4{99$WS!dP^fj(Gad2 zTI@4Q#`p`=wM9I}nSg#xJLZ`{9?%ndHoow~q=jL!Z=IF}7VDyNwA; zc|9(Z^O|1z77s&s^1}#x!lY5JuSzks3mPW$UYGxDn7w-axsr@WPFamE zX0DqQi?rQI&xPbnJMptawC1B<5z{wMVU;J|M#hb6y77&Lh?DqJYqrLTZVPP@_oRrJ z`uG}yUqYpLOQGcvaTfI9IZpe+7s$ye$N6wyv5Mg^OSnPO6N+yRa`h^vl0E28@EXUt zk_MMsIXCdmAU4HhXmkRBCy*7z*$B>Q7_5C}F|Wy5oA=1S{*XqSif1#oPR%ZG92$(A z`c6V{sTNYIxnIu^u5K5i&MYJrY4iktS9Mgr_S}7e*XUn(Kz1<(Mfgc%o99whRCT_3 z>u0uDo&kS$ywu#as*0+%kEf-)xI1Zh?@a&Er|SvcJaMmg9-s3*0>^I%lmb?##LY=I zXdlwL5QI3-UTav%0vI+Z3J748C+{?%`&;O{|ouGYhGrK#hqXFwrE0@UG(0vxUBCK)Udd75hsQ z(R2`fc{=2&T5qTpBruDh+-C9QD0`4dVi<3ITR}!`VWJsfg)1p*8spnN!(Aa_m>JIK z&&+u-!RDaRXjiN7v6F*LzMz8f6M!9V(fBL;w8gc;QKms`kh+M(c_rgRjQ2>v1c&xd zggB3o?WMrSMz5{uA!!SG=K)zw9yXwV9$Iev>-|B~bAkvjL11-O&<-3`njS-1#5;)9 zaLx$s3AG+KCVfTR2^<}OW>he6(UQ?cl|x^F$r)2yrcy&fn@|XFbb$f0-&_;^8Mf$} zPJ;9uTxu3;Bshp+u40;uKtn>7o}5FPwEmKefIH>UhVAr7sx$RTZt)Go(YmxHi%7(3 zzXhp?(%e~b4KRH*kMIVnDH^E6b56Ri@x-{?M>=)H7xRR1FC*ceVCGJRwN%|ZxS-l65+4-_z-}D3Do(l-wX@68lq;W_%9zUVYn=^B zeS9_RfzfV#%jW?rgcf@)P|?FCdr9<(9c{uB`fh!;1K2KUM=Twuxcy#*R0uBlnwCvU zMJPSGl)IC%FJWs}EKZm#WeU zv$d#VD(E{9Wo1m(eUQXm9REz=uFe*Hj^Pw4=@(GRyqpfH4CW`&-DuCz+=B9 zFa)dZYQixgD}6L*pSi$6lg@e?q8sCim%i0sw1qnvDPRP#LPh3lSUcUomJzFV@bIh+ zbK}zzbiB(X;B5^v*}J39K6y9Zig;7fmxiwXG-&clc!J(PRocrd)_F^Pu?i}5ezeKC zhg<_cOd`%1b{vge9(X$k1NO`*$hI;pz6pi88X&;tE>rDO*Gz>7`YO%zTiVvbt&;)64tCG7>LjvB?;36DKBgO;8n&)NKX5f)9EeWNPj>VGX;+D}$70E81 zU|wB0`A^)#$FGw63yY0=$0`U|y9I+0xwW zf8c-gSMmj5te|EY@zNVg&Oszln>dl*vw0x3aLKf%wuyhF6kN@>glMK^yBF}kx}FAx z%#2^sbu9z9OvJyzRiVIqCQy23>3}@`1GeE=xHYtrYptMw>@bK++S z5h;wDTkSgpPWuU)Hn@cqDZ9f%n7cwb4%s9=3tJ&z+~oN#gu3o53#-|=8)fgNP);s0 z%TVy$ZoEY%oc^?uW=En~tEcR(^%fIQuez!vvMvvohEqr-VcLy`ByR~~^kL`n72lyw z&Msut1AJO7Y)9_9NsWsyG!#x$IXL6xuSlLzGu%nvo~P5nJR`*}ezf58&SwiaW~ z7pr$8-^sk0nh;;_Mo5z39{J1zao_h@XNfA;oW*4p4(ldo^ExL)jiEW#;P@@Zs}&?< zB9<9W$W@UEi#raoNAV;j;1^qAVAjyyM>>ipMNigGOks{l9AL`_+YiaH%>r4mdy8*) z(lczye#oya#zcYTzOJ~3Ub()mJgENY3}wr_DEYY=j}nLrN>mw-I5(P(6tt^B^0y~F z`&t1Sf|NL}B5CU8n)#`rBzL4AzV4l+-s;y!n(IF6oDJ(E6806(yyiL)PG z=1-Rv=#0D$7x8nwP{z_a78bLc<51JU6Hq)er@N>rB8Ls}x7zwHc=1$1<^8n;CyYo^ zzo9(X2sdjvTtJs+fpLOa+!K32$l}_!-+bJl^r6A;s?nth=g0*w-QQJYVJxeaIF+?6D%8e@!!j=Dh%rH_VlcWzyHqZy4fXs1NYdE4vKbX2F?QsHL;(h7QatQRI2w! zEYqk@@y40*1yYT>6^3roC=e~uJILDWAYximC`FlnoA@5AD+kCN56I1hNs9C42>p`7 zi9jO~$8&QwO8Bt_9IU2IfP(-q+rY-cvAe=|EwJll-Z&}pbl)!mb;G)%lz|zD%<<)Q z7|xbToCE!r0$_7RPc}ujVn*+BAx)+KkgQ&3m75^trZ#>$AOl+pa`6lt{IK5?e~k7F zvh8($D0^GNS|tnhctpI37vC2lg4 ziGGXLme`Z*2H5ig2(~eid>pum@INw}2X8WQG;yD;a|ULAPc01n8N;ttdpWsQqeBus z&R&d*GE=6LMF4`6g9;a-YEx#a z)e#~Qg4Hzj7O+ns%>5PqmpxlBm_olZD8OHCU9aec^)4SB-Kfl0`2-jRx3N>RqR?Hg1B5dLF^|C% zO@O%?0N46sb?y0y1Y}7Kta#;7p=;6E@jf{X1 z%U&$RGL6^IL9_6yE3!Z;XU@qAy)qRC-nYPu<6~fOO&GMPF~D51Z=+6|@#f0ry)wo( zfcn&IUwBteAySqfh}4-~<1ETlvjG0M*tO9LuxG_@pHmc%m?TqLq8YKT5uh{47d@YG z3d!i~Jk<2%sqRfdtPXga3%B*fMDi@mOmX9sU4Aqf2_a9MHEna13yi>rIJt$E?t0$V zRudpjehqc$*0s5GMU}taT95)HDSRD1(On~o#7x$r2V5;S*Zp2#V@#R4(xb`zTuq}a z#NxDAr>J0dT+sr}MYUcw;P@Kn=Nvqc|Lyhh_A}7GxIw(utUTXx7CK{UuBSx1cBj_3 z$>KDE=H{`>5+>-38$q9Tzd=WPEg;Jf;vSeRY=6qw@)3=W*B=VwSo@jZPWxvSnO3Lt zsS3U(c8lL9Q!mZV>r-zSkq$I!lj9v8jyf(4tOt0JxHC-d9;su~2erTH^kEJ%VCE3STpZNinBF$xFUm6%ajsV(&Rq&u^!2bHh1hH-Anc{ ze{aZJ2CT|Td78nly^J`;5l*Ba=aUk(uGSc zC1y*J++_U}OeNu)dN7t&2P=L&7G?`XAw;p(pbqDqy0@>xZ*iv`_xk}erK1+ihBaQN z^qKYh-AGp4P?CxksaLq4&3Q4k@JE8WqR_oh&^=`EHfTvX$SrhxKX9Gt=!Vrce{8xd zzFBw6WHn$=u?LW%ooZUq0wtuQaEQ?m{PY4&$;;;Vp6|8DO*l8}n$bOeMcNSE10Qo{|$4bE;{R z-Waa&vj#;P){dez(31*Tx;ZP&KuRXU=-&R#wJ?Q#W!ZNESAPfB?a_9P7 zp9qycWqayRpx)q(c>OWv2Gi}ED@j*>YB{;+tB4J}HJM0s$u5LJ#MR@`9Hk9exfCL= z7%?wj4Sz*o6Xg}H^Jsum7@#d6wb}`toQ9ZXvrm~dY8d>a(84B=_c-b2^%8ur z!;Trk^SS^yx6I-mP5rf^bf4Z+5T&nB(7d?VuY~Yp54d2jySC&XF5KxWM=ecRF2$!m z22MS`~j zPD&B6Q+BDQ4^QQ2Md9xP2d3o3zV7v(L0cltp}(may@5{8?GZgh_wp5>5VdW5O8%aL zeaL{GKlIhx4iWNsFg5^UXzT9;Fo2} z@p<{`_V{95wdDG)30olj%JLDJ+Q8M|0Xxe-Ai_eNznMp3S111**gd+qmk2ls<(*k# zLA=88c#e5rzEFet;fq3^O50mnTq0TyX2Vo*W)b?j5{uT?+vf?_2?WjwP+3{B+;tL# zWz<`rJ_wQ^;V-g*a7$N^dYfVMPVfSMOh)1#W`D`kL}h> zrM~H}BwBcLvQ->^97NEOPhrTPj7evrxN&tAn38eKLFnJb(jk`XnGd}wF~3I{yHW<) zy53!aN{dOG*ee3{zAnE;^Zr4f z2kqIeAurxGBp#7XAx;hFbO!v#viHY|Ri=CM!uEDk$p6C1g;t^PqP@ak<3&9;cmG8_ zubtFMpfun0Wn!gbG-xQ?32oMl$;M#A7x#I^D8`J+U@%Om@Ire}1*fRdN zZZA?6{{se>h(Mk7zY`b}(Ee-6e(SY+sO3}yZd?SP6ffZPh@eU~Y)~;Y+MEBD@gf)C z3lb(0=nGhgVH57(LS7_EdVv?g_#;FRp6q{wSdoEE*RLQTG@u|LcwdlS;r~krGKfq8 z4+Lz1`CHhF6c;aeNQ8fc{X63Y@!zZ$xeZ>hY>5A`9$ypxkBa}q6#wtZlc)GYLTthM zTgZQhVf{yrA^jhG{TqV+5n|zMVwMd?&VWzKe;D9W67#>u16r*|`CHJ7ForKUpILr$ zc>j)S_}^Feujq3SzINk65 zaFhgb|7Rxt@x%GQD7OFbO7;Im`t6y6|F?4gc;);zN6iov-N^Yj_CKx=;ErF;G^;Vl zM}rzf+WMbC{C>gymjCb90NOvwfJ>3apy*bdfA{?x`@%co1-8u?gr3axVz9pn@6m)O zv%$U7gTKExUJx?PzywOr+XRgNA^qb{0Z!8Sf)r~BCJ})hEYOku_4NN10#fY41HZtm zKmO@4yo=#QBYNBKMtsLVjmmAvFB;!;{%(YE0yolw>N{QxHdK_$#tZ!DB7q-W{ugRM zx`3d*2>y@Ww9qoh_+Wzh?*YFc%)0*3Te$Z>57KYF-Doz{48h_Fz^_t)7kF+r&}b_@ z2&ISq@7donnxS0>_iYEB%-_l3|2a;){*11ugYs|ue;hs-{(Byoe86~$m~N!M;s1Pj zgOj8D(>J*EBj!){3+?28<3SJtiNELP0jv_=3;bOYNUlua_1}-=pX(IS_j|TSQvUSz zHUS2dQic7$DAAtZDB}!JStkVu*n|GJ%zwV+|9eQ5*I^|CO%a`&+tr*&jqoFWHORh563_f~x*+02S!}OP~02x2@EG5iFo1gV+Ca zlhk!oI3<9!DgtZua+U|{!6bUnS`Xn1nS$-V6>4n)HxhyRKD>OP{GL`KYn;jvaF2`N z9(i7jQ?=z!kAM#Bzvm_D`$e1}7_SQUSYmsDcWDRTcD#RM|Fil5<49j%A-X__b>#nk pnf!b8^Y^+2yn*JgL!}FZu1SES41fWv`CBkpG)b+de{{Z3H0Q~>} delta 29904 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≫7?%4+5O~cL2qw0XdoCy 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` @@ -154,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec9973..e95643d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,10 +46,9 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line From 02492c254ddf91b1f9b76f4616e990601bfb3a85 Mon Sep 17 00:00:00 2001 From: stepango Date: Sun, 12 Feb 2017 16:56:07 +0800 Subject: [PATCH 10/20] subscribeBy method for Flowable --- src/main/kotlin/rx/lang/kotlin/subscription.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/kotlin/rx/lang/kotlin/subscription.kt b/src/main/kotlin/rx/lang/kotlin/subscription.kt index 1c77878..69f0e2e 100644 --- a/src/main/kotlin/rx/lang/kotlin/subscription.kt +++ b/src/main/kotlin/rx/lang/kotlin/subscription.kt @@ -1,6 +1,7 @@ package rx.lang.kotlin import io.reactivex.Completable +import io.reactivex.Flowable import io.reactivex.Maybe import io.reactivex.Observable import io.reactivex.Single @@ -19,6 +20,15 @@ fun Observable.subscribeBy( onComplete: () -> Unit = onCompleteStub ): Disposable = subscribe(onNext, onError, onComplete) +/** + * Overloaded subscribe function that allow passing named parameters + */ +fun Flowable.subscribeBy( + onNext: (T) -> Unit = onNextStub, + onError: (Throwable) -> Unit = onErrorStub, + onComplete: () -> Unit = onCompleteStub +): Disposable = subscribe(onNext, onError, onComplete) + /** * Overloaded subscribe function that allow passing named parameters */ From 484cbab043d1f0508b9907d7b7fc357d009312a5 Mon Sep 17 00:00:00 2001 From: stepango Date: Sun, 12 Feb 2017 17:33:32 +0800 Subject: [PATCH 11/20] tests updated --- .../kotlin/rx/lang/kotlin/ObservableTest.kt | 86 +++++++++++++++---- 1 file changed, 67 insertions(+), 19 deletions(-) diff --git a/src/test/kotlin/rx/lang/kotlin/ObservableTest.kt b/src/test/kotlin/rx/lang/kotlin/ObservableTest.kt index bbd2e64..90e727b 100644 --- a/src/test/kotlin/rx/lang/kotlin/ObservableTest.kt +++ b/src/test/kotlin/rx/lang/kotlin/ObservableTest.kt @@ -6,18 +6,23 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Ignore import org.junit.Test +import java.math.BigDecimal import java.util.concurrent.atomic.AtomicInteger class ObservableTest { @Test fun testCreation() { + val observable = observable { s -> + s.apply { + onNext(1) + onNext(777) + onComplete() + } + } + + assertEquals(listOf(1, 777), observable.toList().blockingGet()) + val o0: Observable = Observable.empty() - val list = observable { s -> - s.onNext(1) - s.onNext(777) - s.onComplete() - }.toList().blockingGet() - assertEquals(listOf(1, 777), list) val o1: Observable = listOf(1, 2, 3).toObservable() val o2: Observable> = Observable.just(listOf(1, 2, 3)) @@ -34,18 +39,22 @@ class ObservableTest { } @Test fun testExampleFromReadme() { - val result = observable { subscriber -> - subscriber.onNext("H") - subscriber.onNext("e") - subscriber.onNext("l") - subscriber.onNext("") - subscriber.onNext("l") - subscriber.onNext("o") - subscriber.onComplete() - }.filter(String::isNotEmpty). - fold(StringBuilder(), StringBuilder::append). - map { it.toString() }. - blockingGet() + val observable = observable { s -> + s.apply { + onNext("H") + onNext("e") + onNext("l") + onNext("") + onNext("l") + onNext("o") + onComplete() + } + } + val result = observable + .filter(String::isNotEmpty) + .fold(StringBuilder(), StringBuilder::append) + .map { it.toString() } + .blockingGet() assertEquals("Hello", result) } @@ -68,7 +77,12 @@ class ObservableTest { @Ignore("Too slow") @Test fun intProgressionOverflow() { - assertEquals((0..10).toList().reversed(), (-10..Integer.MAX_VALUE).toObservable().skip(Integer.MAX_VALUE.toLong()).map { Integer.MAX_VALUE - it }.toList().blockingGet()) + val result = (-10..Integer.MAX_VALUE).toObservable() + .skip(Integer.MAX_VALUE.toLong()) + .map { Integer.MAX_VALUE - it } + .toList() + .blockingGet() + assertEquals((0..10).toList().reversed(), result) } @Test fun testWithIndex() { @@ -151,4 +165,38 @@ class ObservableTest { observable.test() .assertError(ClassCastException::class.java) } + + @Test fun testOfType() { + val source = Observable.just(BigDecimal.valueOf(15, 1), 2, BigDecimal.valueOf(42), 15) + + source.ofType() + .test() + .await() + .assertValues(2, 15) + .assertNoErrors() + .assertComplete() + + source.ofType() + .test() + .await() + .assertValues(BigDecimal.valueOf(15, 1), BigDecimal.valueOf(42)) + .assertNoErrors() + .assertComplete() + + source.ofType() + .test() + .await() + .assertNoValues() + .assertNoErrors() + .assertComplete() + + source.ofType>() + .test() + .await() + .assertValues(BigDecimal.valueOf(15, 1), 2, BigDecimal.valueOf(42), 15) + .assertNoErrors() + .assertComplete() + + } + } \ No newline at end of file From e117048ddf039c9bf7753c21aa255c2cf578663d Mon Sep 17 00:00:00 2001 From: stepango Date: Sun, 12 Feb 2017 17:46:03 +0800 Subject: [PATCH 12/20] minor tests refactoring --- .../kotlin/rx/lang/kotlin/BasicKotlinTests.kt | 28 +++++++++++++------ .../kotlin/rx/lang/kotlin/CompletableTest.kt | 4 ++- src/test/kotlin/rx/lang/kotlin/KotlinTests.kt | 4 +-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/test/kotlin/rx/lang/kotlin/BasicKotlinTests.kt b/src/test/kotlin/rx/lang/kotlin/BasicKotlinTests.kt index dc74cf1..80fcd0d 100644 --- a/src/test/kotlin/rx/lang/kotlin/BasicKotlinTests.kt +++ b/src/test/kotlin/rx/lang/kotlin/BasicKotlinTests.kt @@ -20,6 +20,7 @@ import io.reactivex.Notification import io.reactivex.Observable import io.reactivex.ObservableEmitter import io.reactivex.ObservableOnSubscribe +import io.reactivex.Single import io.reactivex.functions.BiFunction import io.reactivex.functions.Function3 import org.junit.Assert.assertEquals @@ -39,15 +40,17 @@ class BasicKotlinTests : KotlinTests() { Observable.create { onSubscribe -> onSubscribe.onNext("Hello") onSubscribe.onComplete() - }.subscribe { result -> - a.received(result) - } + }.subscribeBy( + onNext = { a.received(it) } + ) verify(a, times(1)).received("Hello") } @Test fun testFilter() { - Observable.fromIterable(listOf(1, 2, 3)).filter { it >= 2 }.subscribe(received()) + Observable.fromIterable(listOf(1, 2, 3)) + .filter { it >= 2 } + .subscribeBy(onNext = received()) verify(a, times(0)).received(1) verify(a, times(1)).received(2) verify(a, times(1)).received(3) @@ -58,7 +61,9 @@ class BasicKotlinTests : KotlinTests() { } @Test fun testMap1() { - Observable.just(1).map { v -> "hello_$v" }.subscribe(received()) + Single.just(1) + .map { v -> "hello_$v" } + .subscribeBy(onSuccess = received()) verify(a, times(1)).received("hello_1") } @@ -84,7 +89,10 @@ class BasicKotlinTests : KotlinTests() { Observable.just(7) ), Observable.fromIterable(listOf(4, 5)) - ).subscribe(received()) { e -> a.error(e) } + ).subscribeBy( + onNext = received(), + onError = { a.error(it) } + ) verify(a, times(1)).received(1) verify(a, times(1)).received(2) verify(a, times(1)).received(3) @@ -96,13 +104,17 @@ class BasicKotlinTests : KotlinTests() { } @Test fun testScriptWithMaterialize() { - TestFactory().observable.materialize().subscribe(received()) + TestFactory() + .observable + .materialize() + .subscribeBy(onNext = received()) verify(a, times(2)).received(any(Notification::class.java)) } @Test fun testScriptWithMerge() { val factory = TestFactory() - Observable.merge(factory.observable, factory.observable).subscribe(received()) + Observable.merge(factory.observable, factory.observable) + .subscribeBy(onNext = received()) verify(a, times(1)).received("hello_1") verify(a, times(1)).received("hello_2") } diff --git a/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt b/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt index d179edc..110714a 100644 --- a/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt +++ b/src/test/kotlin/rx/lang/kotlin/CompletableTest.kt @@ -24,8 +24,10 @@ class CompletableTest { assertNotNull(c1) c1.subscribe() assertEquals(1, count) + } - count = 0 + @Test fun createFromLambda() { + var count = 0 val c2 = { count++ }.toCompletable() assertNotNull(c2) c2.subscribe() diff --git a/src/test/kotlin/rx/lang/kotlin/KotlinTests.kt b/src/test/kotlin/rx/lang/kotlin/KotlinTests.kt index 75f8e6e..1f3cb39 100644 --- a/src/test/kotlin/rx/lang/kotlin/KotlinTests.kt +++ b/src/test/kotlin/rx/lang/kotlin/KotlinTests.kt @@ -16,21 +16,19 @@ package rx.lang.kotlin -import io.reactivex.Observable import org.junit.Before import org.mockito.Mock import org.mockito.MockitoAnnotations abstract class KotlinTests { @Mock var a: ScriptAssertion = uninitialized() - @Mock var w: Observable = uninitialized() @Before fun before() { MockitoAnnotations.initMocks(this) } @Suppress("BASE_WITH_NULLABLE_UPPER_BOUND") - fun received() = { result: T? -> a.received(result) } + fun received() = { result: T -> a.received(result) } interface ScriptAssertion { fun error(e: Throwable?) From 564b0437a5323e64a195051eb055a7b34034f1ab Mon Sep 17 00:00:00 2001 From: stepango Date: Tue, 7 Mar 2017 21:58:19 +0800 Subject: [PATCH 13/20] * JoinToString method and tests ported from rxKotlin 1.0 * Subjects removed * Operators added --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +-- src/main/kotlin/rx/lang/kotlin/operators.kt | 36 +++++++++++++++++++ src/main/kotlin/rx/lang/kotlin/subject.kt | 13 ------- .../{subscription.kt => subscribers.kt} | 0 .../kotlin/rx/lang/kotlin/ExtensionTests.kt | 23 ++++++++++-- 6 files changed, 59 insertions(+), 19 deletions(-) create mode 100644 src/main/kotlin/rx/lang/kotlin/operators.kt delete mode 100644 src/main/kotlin/rx/lang/kotlin/subject.kt rename src/main/kotlin/rx/lang/kotlin/{subscription.kt => subscribers.kt} (100%) diff --git a/build.gradle b/build.gradle index dd4ee5d..e12f734 100755 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.0.6' + ext.kotlin_version = '1.1.0' repositories { jcenter() } dependencies { classpath 'com.netflix.nebula:gradle-rxjava-project-plugin:4.+', diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b080e76..9bc0a69 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Feb 12 16:33:43 SGT 2017 +#Sun Mar 05 07:47:21 SGT 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-all.zip diff --git a/src/main/kotlin/rx/lang/kotlin/operators.kt b/src/main/kotlin/rx/lang/kotlin/operators.kt new file mode 100644 index 0000000..01dc3b4 --- /dev/null +++ b/src/main/kotlin/rx/lang/kotlin/operators.kt @@ -0,0 +1,36 @@ +package rx.lang.kotlin + +import io.reactivex.Observable +import io.reactivex.Single + +/** + * Merges the emissions of an Observable>. Same as calling `flatMap { it }`. + */ +fun Observable>.mergeAll() = flatMap { it } + +/** + * Concatenates the emissions of an Observable>. Same as calling `concatMap { it }`. + */ +fun Observable>.concatAll() = concatMap { it } + +/** + * Emits the latest `Observable` emitted through an `Observable>`. Same as calling `switchMap { it }`. + */ +fun Observable>.switchLatest() = switchMap { it } + + +/** + * Joins the emissions of a finite `Observable` into a `String`. + * + * @param separator is the dividing character(s) between each element in the concatenated `String` + * + * @param prefix is the preceding `String` before the concatenated elements (optional) + * + * @param postfix is the succeeding `String` after the concatenated elements (optional) + */ +fun Observable.joinToString(separator: String? = null, + prefix: String? = null, + postfix: String? = null +): Single = collect({ StringBuilder(prefix ?: "") }) { builder: StringBuilder, next: T -> + builder.append(if (builder.length == prefix?.length ?: 0) "" else separator ?: "").append(next) +}.map { it.append(postfix ?: "").toString() } \ No newline at end of file diff --git a/src/main/kotlin/rx/lang/kotlin/subject.kt b/src/main/kotlin/rx/lang/kotlin/subject.kt deleted file mode 100644 index 7dbd24a..0000000 --- a/src/main/kotlin/rx/lang/kotlin/subject.kt +++ /dev/null @@ -1,13 +0,0 @@ -package rx.lang.kotlin - -import io.reactivex.Observable -import io.reactivex.subjects.AsyncSubject -import io.reactivex.subjects.BehaviorSubject -import io.reactivex.subjects.PublishSubject -import io.reactivex.subjects.ReplaySubject - -fun BehaviorSubject(): BehaviorSubject = BehaviorSubject.create() -fun BehaviorSubject(default: T): BehaviorSubject = BehaviorSubject.createDefault(default) -fun AsyncSubject(): AsyncSubject = AsyncSubject.create() -fun PublishSubject(): PublishSubject = PublishSubject.create() -fun ReplaySubject(capacity: Int = Observable.bufferSize()): ReplaySubject = ReplaySubject.create(capacity) diff --git a/src/main/kotlin/rx/lang/kotlin/subscription.kt b/src/main/kotlin/rx/lang/kotlin/subscribers.kt similarity index 100% rename from src/main/kotlin/rx/lang/kotlin/subscription.kt rename to src/main/kotlin/rx/lang/kotlin/subscribers.kt diff --git a/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt b/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt index c41b09c..9561374 100644 --- a/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt +++ b/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt @@ -52,9 +52,9 @@ class ExtensionTests : KotlinTests() { @Test fun testFilter() { listOf(1, 2, 3).toObservable().filter { it >= 2 }.subscribe(received()) - verify(a, times(0)).received(1); - verify(a, times(1)).received(2); - verify(a, times(1)).received(3); + verify(a, times(0)).received(1) + verify(a, times(1)).received(2) + verify(a, times(1)).received(3) } @@ -260,6 +260,23 @@ class ExtensionTests : KotlinTests() { } } + @Test + fun testJoinToString1() { + Observable.range(1, 5) + .joinToString(separator = ",") + .test() + .await() + .assertResult("1,2,3,4,5") + } + + @Test + fun testJoinToString2() { + Observable.range(1, 5) + .joinToString(separator = ",", prefix = "(", postfix = ")") + .test() + .await() + .assertResult("(1,2,3,4,5)") + } inner class TestFactory { var counter = 1 From 48c057051db9477d8bc148e191546f775ac4d97a Mon Sep 17 00:00:00 2001 From: stepango Date: Tue, 7 Mar 2017 22:07:48 +0800 Subject: [PATCH 14/20] Minor formatting fix --- src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt b/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt index 922dc3f..9f4366b 100644 --- a/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt +++ b/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt @@ -270,14 +270,16 @@ class ExtensionTests : KotlinTests() { } -@Test + @Test fun testJoinToString2() { Observable.range(1, 5) .joinToString(separator = ",", prefix = "(", postfix = ")") .test() .await() .assertResult("(1,2,3,4,5)") - } inner class TestFactory { + } + + inner class TestFactory { var counter = 1 val numbers: Observable From b677898c912aeef72d8e34a9fa71d56217297fb9 Mon Sep 17 00:00:00 2001 From: stepango Date: Tue, 7 Mar 2017 22:10:27 +0800 Subject: [PATCH 15/20] More formatting fixes --- .../kotlin/rx/lang/kotlin/ExtensionTests.kt | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt b/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt index 9f4366b..fc93a33 100644 --- a/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt +++ b/src/test/kotlin/rx/lang/kotlin/ExtensionTests.kt @@ -57,7 +57,6 @@ class ExtensionTests : KotlinTests() { verify(a, times(1)).received(3) } - @Test fun testLast() { assertEquals("three", listOf("one", "two", "three").toObservable().blockingLast()) } @@ -79,7 +78,6 @@ class ExtensionTests : KotlinTests() { verify(a, times(0)).error(any(Exception::class.java)) } - @Test fun testMerge() { listOf(listOf(1, 2, 3).toObservable(), listOf(Observable.just(6), @@ -110,7 +108,6 @@ class ExtensionTests : KotlinTests() { verify(a, times(1)).received("hello_2") } - @Test fun testFromWithIterable() { assertEquals(5, listOf(1, 2, 3, 4, 5).toObservable().count().blockingGet()) } @@ -123,45 +120,64 @@ class ExtensionTests : KotlinTests() { } @Test fun testScriptWithOnNext() { - TestFactory().observable.subscribe(received()) + TestFactory().observable + .subscribe(received()) verify(a, times(1)).received("hello_1") } @Test fun testSkipTake() { - listOf(1, 2, 3).toObservable().skip(1).take(1).subscribe(received()) + listOf(1, 2, 3) + .toObservable() + .skip(1) + .take(1) + .subscribe(received()) verify(a, times(0)).received(1) verify(a, times(1)).received(2) verify(a, times(0)).received(3) } @Test fun testSkip() { - listOf(1, 2, 3).toObservable().skip(2).subscribe(received()) + listOf(1, 2, 3) + .toObservable() + .skip(2) + .subscribe(received()) verify(a, times(0)).received(1) verify(a, times(0)).received(2) verify(a, times(1)).received(3) } @Test fun testTake() { - listOf(1, 2, 3).toObservable().take(2).subscribe(received()) + listOf(1, 2, 3) + .toObservable() + .take(2) + .subscribe(received()) verify(a, times(1)).received(1) verify(a, times(1)).received(2) verify(a, times(0)).received(3) } @Test fun testTakeLast() { - TestFactory().observable.takeLast(1).subscribe(received()) + TestFactory().observable + .takeLast(1) + .subscribe(received()) verify(a, times(1)).received("hello_1") } @Test fun testTakeWhile() { - listOf(1, 2, 3).toObservable().takeWhile { x -> x < 3 }.subscribe(received()) + listOf(1, 2, 3) + .toObservable() + .takeWhile { x -> x < 3 } + .subscribe(received()) verify(a, times(1)).received(1) verify(a, times(1)).received(2) verify(a, times(0)).received(3) } @Test fun testTakeWhileWithIndex() { - listOf(1, 2, 3).toObservable().takeWhile { x -> x < 3 }.zipWith((0..Integer.MAX_VALUE).toObservable(), BiFunction { x, i -> x }).subscribe(received()) + listOf(1, 2, 3).toObservable() + .takeWhile { x -> x < 3 } + .zipWith((0..Integer.MAX_VALUE).toObservable(), BiFunction { x: Int, i: Int -> x }) + .subscribe(received()) verify(a, times(1)).received(1) verify(a, times(1)).received(2) verify(a, times(0)).received(3) @@ -269,7 +285,6 @@ class ExtensionTests : KotlinTests() { .assertResult("1,2,3,4,5") } - @Test fun testJoinToString2() { Observable.range(1, 5) @@ -290,6 +305,5 @@ class ExtensionTests : KotlinTests() { val observable: Observable get() = observable(onSubscribe) - } } From c5d5aa6c759bdb4f5a74b43ae20adc48dac9a381 Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Tue, 7 Mar 2017 19:01:39 -0600 Subject: [PATCH 16/20] Refactor "subscription" references to "disposable" --- src/main/kotlin/rx/lang/kotlin/disposable.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/rx/lang/kotlin/disposable.kt b/src/main/kotlin/rx/lang/kotlin/disposable.kt index ca85585..d442e90 100644 --- a/src/main/kotlin/rx/lang/kotlin/disposable.kt +++ b/src/main/kotlin/rx/lang/kotlin/disposable.kt @@ -4,16 +4,16 @@ import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable /** - * subscription += observable.subscribe() + * disposable += observable.subscribe() */ -operator fun CompositeDisposable.plusAssign(subscription: Disposable) { - add(subscription) +operator fun CompositeDisposable.plusAssign(disposable: Disposable) { + add(disposable) } /** * Add the subscription to a CompositeSubscription. - * @param compositeSubscription CompositeSubscription to add this subscription to + * @param compositeDisposable CompositeDisposable to add this subscription to * @return this instance */ -fun Disposable.addTo(compositeSubscription: CompositeDisposable): Disposable - = apply { compositeSubscription.add(this) } \ No newline at end of file +fun Disposable.addTo(compositeDisposable: CompositeDisposable): Disposable + = apply { compositeDisposable.add(this) } \ No newline at end of file From 816c227a39df88f0930b0ba0cf1c87fb03387766 Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Tue, 7 Mar 2017 19:10:20 -0600 Subject: [PATCH 17/20] refactor `flowable.kt` and `observable.kt` to match 1.x changes --- src/main/kotlin/rx/lang/kotlin/flowable.kt | 43 ++++++++------------ src/main/kotlin/rx/lang/kotlin/observable.kt | 32 +++++++-------- 2 files changed, 30 insertions(+), 45 deletions(-) diff --git a/src/main/kotlin/rx/lang/kotlin/flowable.kt b/src/main/kotlin/rx/lang/kotlin/flowable.kt index 1a348ee..12ba34e 100644 --- a/src/main/kotlin/rx/lang/kotlin/flowable.kt +++ b/src/main/kotlin/rx/lang/kotlin/flowable.kt @@ -1,28 +1,16 @@ package rx.lang.kotlin -import io.reactivex.BackpressureStrategy import io.reactivex.Flowable -import io.reactivex.FlowableEmitter -import io.reactivex.Single import io.reactivex.functions.BiFunction -fun flowable( - strategy: BackpressureStrategy = BackpressureStrategy.BUFFER, - body: (FlowableEmitter) -> Unit -): Flowable = Flowable.create(body, strategy) -private fun Iterator.toIterable() = object : Iterable { - override fun iterator(): Iterator = this@toIterable -} - -fun BooleanArray.toFlowable(): Flowable = Flowable.fromArray(*this.toTypedArray()) -fun ByteArray.toFlowable(): Flowable = Flowable.fromArray(*this.toTypedArray()) -fun ShortArray.toFlowable(): Flowable = Flowable.fromArray(*this.toTypedArray()) -fun IntArray.toFlowable(): Flowable = Flowable.fromArray(*this.toTypedArray()) -fun LongArray.toFlowable(): Flowable = Flowable.fromArray(*this.toTypedArray()) -fun FloatArray.toFlowable(): Flowable = Flowable.fromArray(*this.toTypedArray()) -fun DoubleArray.toFlowable(): Flowable = Flowable.fromArray(*this.toTypedArray()) -fun Array.toFlowable(): Flowable = Flowable.fromArray(*this) +fun BooleanArray.toFlowable(): Flowable = this.asIterable().toFlowable() +fun ByteArray.toFlowable(): Flowable = this.asIterable().toFlowable() +fun ShortArray.toFlowable(): Flowable = this.asIterable().toFlowable() +fun IntArray.toFlowable(): Flowable = this.asIterable().toFlowable() +fun LongArray.toFlowable(): Flowable = this.asIterable().toFlowable() +fun FloatArray.toFlowable(): Flowable = this.asIterable().toFlowable() +fun DoubleArray.toFlowable(): Flowable = this.asIterable().toFlowable() fun IntProgression.toFlowable(): Flowable = if (step == 1 && last.toLong() - first < Integer.MAX_VALUE) Flowable.range(first, Math.max(0, last - first + 1)) @@ -30,14 +18,11 @@ fun IntProgression.toFlowable(): Flowable = fun Iterator.toFlowable(): Flowable = toIterable().toFlowable() fun Iterable.toFlowable(): Flowable = Flowable.fromIterable(this) -fun Sequence.toFlowable(): Flowable = Flowable.fromIterable(iterator().toIterable()) +fun Sequence.toFlowable(): Flowable = asIterable().toFlowable() fun Iterable>.merge(): Flowable = Flowable.merge(this.toFlowable()) fun Iterable>.mergeDelayError(): Flowable = Flowable.mergeDelayError(this.toFlowable()) -inline fun Flowable.fold(initial: R, crossinline body: (R, T) -> R): Single - = reduce(initial) { a, e -> body(a, e) } - /** * Returns Flowable that wrap all values into [IndexedValue] and populates corresponding index value. * Works similar to [kotlin.withIndex] @@ -62,14 +47,14 @@ fun Flowable>.switchOnNext(): Flowable = Flowable.switc * Flowable.combineLatest(List> sources, FuncN combineFunction) */ @Suppress("UNCHECKED_CAST") -inline fun List>.combineLatest(crossinline combineFunction: (args: List) -> R): Flowable +inline fun Iterable>.combineLatest(crossinline combineFunction: (args: List) -> R): Flowable = Flowable.combineLatest(this) { combineFunction(it.asList().map { it as T }) } /** * Flowable.zip(List> sources, FuncN combineFunction) */ @Suppress("UNCHECKED_CAST") -inline fun List>.zip(crossinline zipFunction: (args: List) -> R): Flowable +inline fun Iterable>.zip(crossinline zipFunction: (args: List) -> R): Flowable = Flowable.zip(this) { zipFunction(it.asList().map { it as T }) } /** @@ -78,6 +63,10 @@ inline fun List>.zip(crossinline zipFunction: (args: List) inline fun Flowable<*>.cast(): Flowable = cast(R::class.java) /** - * Filters the items emitted by an Observable, only emitting those of the specified type. + * Filters the items emitted by an Flowable, only emitting those of the specified type. */ -inline fun Flowable<*>.ofType(): Flowable = ofType(R::class.java) \ No newline at end of file +inline fun Flowable<*>.ofType(): Flowable = ofType(R::class.java) + +private fun Iterator.toIterable() = object : Iterable { + override fun iterator(): Iterator = this@toIterable +} \ No newline at end of file diff --git a/src/main/kotlin/rx/lang/kotlin/observable.kt b/src/main/kotlin/rx/lang/kotlin/observable.kt index 0c30616..eefd51e 100644 --- a/src/main/kotlin/rx/lang/kotlin/observable.kt +++ b/src/main/kotlin/rx/lang/kotlin/observable.kt @@ -5,19 +5,14 @@ import io.reactivex.ObservableEmitter import io.reactivex.Single import io.reactivex.functions.BiFunction -fun observable(body: (ObservableEmitter) -> Unit): Observable = Observable.create(body) -private fun Iterator.toIterable() = object : Iterable { - override fun iterator(): Iterator = this@toIterable -} - -fun BooleanArray.toObservable(): Observable = Observable.fromArray(*this.toTypedArray()) -fun ByteArray.toObservable(): Observable = Observable.fromArray(*this.toTypedArray()) -fun ShortArray.toObservable(): Observable = Observable.fromArray(*this.toTypedArray()) -fun IntArray.toObservable(): Observable = Observable.fromArray(*this.toTypedArray()) -fun LongArray.toObservable(): Observable = Observable.fromArray(*this.toTypedArray()) -fun FloatArray.toObservable(): Observable = Observable.fromArray(*this.toTypedArray()) -fun DoubleArray.toObservable(): Observable = Observable.fromArray(*this.toTypedArray()) +fun BooleanArray.toObservable(): Observable = this.asIterable().toObservable() +fun ByteArray.toObservable(): Observable = this.asIterable().toObservable() +fun ShortArray.toObservable(): Observable = this.asIterable().toObservable() +fun IntArray.toObservable(): Observable = this.asIterable().toObservable() +fun LongArray.toObservable(): Observable = this.asIterable().toObservable() +fun FloatArray.toObservable(): Observable = this.asIterable().toObservable() +fun DoubleArray.toObservable(): Observable = this.asIterable().toObservable() fun Array.toObservable(): Observable = Observable.fromArray(*this) fun IntProgression.toObservable(): Observable = @@ -26,14 +21,11 @@ fun IntProgression.toObservable(): Observable = fun Iterator.toObservable(): Observable = toIterable().toObservable() fun Iterable.toObservable(): Observable = Observable.fromIterable(this) -fun Sequence.toObservable(): Observable = Observable.fromIterable(iterator().toIterable()) +fun Sequence.toObservable(): Observable = asIterable().toObservable() fun Iterable>.merge(): Observable = Observable.merge(this.toObservable()) fun Iterable>.mergeDelayError(): Observable = Observable.mergeDelayError(this.toObservable()) -inline fun Observable.fold(initial: R, crossinline body: (R, T) -> R): Single - = reduce(initial) { a, e -> body(a, e) } - /** * Returns Observable that wrap all values into [IndexedValue] and populates corresponding index value. * Works similar to [kotlin.withIndex] @@ -58,14 +50,14 @@ fun Observable>.switchOnNext(): Observable = Observab * Observable.combineLatest(List> sources, FuncN combineFunction) */ @Suppress("UNCHECKED_CAST") -inline fun List>.combineLatest(crossinline combineFunction: (args: List) -> R): Observable +inline fun Iterable>.combineLatest(crossinline combineFunction: (args: List) -> R): Observable = Observable.combineLatest(this) { combineFunction(it.asList().map { it as T }) } /** * Observable.zip(List> sources, FuncN combineFunction) */ @Suppress("UNCHECKED_CAST") -inline fun List>.zip(crossinline zipFunction: (args: List) -> R): Observable +inline fun Iterable>.zip(crossinline zipFunction: (args: List) -> R): Observable = Observable.zip(this) { zipFunction(it.asList().map { it as T }) } /** @@ -77,3 +69,7 @@ inline fun Observable<*>.cast(): Observable = cast(R::class * Filters the items emitted by an Observable, only emitting those of the specified type. */ inline fun Observable<*>.ofType(): Observable = ofType(R::class.java) + +private fun Iterator.toIterable() = object : Iterable { + override fun iterator(): Iterator = this@toIterable +} \ No newline at end of file From d8881f79f31f6e1efcf1b8a0e45586d4569dce47 Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Tue, 7 Mar 2017 19:13:49 -0600 Subject: [PATCH 18/20] add `Flowable` support for operators --- src/main/kotlin/rx/lang/kotlin/operators.kt | 37 +++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/main/kotlin/rx/lang/kotlin/operators.kt b/src/main/kotlin/rx/lang/kotlin/operators.kt index 01dc3b4..eda0e67 100644 --- a/src/main/kotlin/rx/lang/kotlin/operators.kt +++ b/src/main/kotlin/rx/lang/kotlin/operators.kt @@ -1,5 +1,6 @@ package rx.lang.kotlin +import io.reactivex.Flowable import io.reactivex.Observable import io.reactivex.Single @@ -8,17 +9,35 @@ import io.reactivex.Single */ fun Observable>.mergeAll() = flatMap { it } +/** + * Merges the emissions of a Flowable>. Same as calling `flatMap { it }`. + */ +fun Flowable>.mergeAll() = flatMap { it } + + /** * Concatenates the emissions of an Observable>. Same as calling `concatMap { it }`. */ fun Observable>.concatAll() = concatMap { it } +/** + * Concatenates the emissions of an Flowable>. Same as calling `concatMap { it }`. + */ +fun Flowable>.concatAll() = concatMap { it } + + /** * Emits the latest `Observable` emitted through an `Observable>`. Same as calling `switchMap { it }`. */ fun Observable>.switchLatest() = switchMap { it } +/** + * Emits the latest `Flowable` emitted through an `Flowable>`. Same as calling `switchMap { it }`. + */ +fun Flowable>.switchLatest() = switchMap { it } + + /** * Joins the emissions of a finite `Observable` into a `String`. * @@ -33,4 +52,22 @@ fun Observable.joinToString(separator: String? = null, postfix: String? = null ): Single = collect({ StringBuilder(prefix ?: "") }) { builder: StringBuilder, next: T -> builder.append(if (builder.length == prefix?.length ?: 0) "" else separator ?: "").append(next) +}.map { it.append(postfix ?: "").toString() } + + + +/** + * Joins the emissions of a finite `Flowable` into a `String`. + * + * @param separator is the dividing character(s) between each element in the concatenated `String` + * + * @param prefix is the preceding `String` before the concatenated elements (optional) + * + * @param postfix is the succeeding `String` after the concatenated elements (optional) + */ +fun Flowable.joinToString(separator: String? = null, + prefix: String? = null, + postfix: String? = null +): Single = collect({ StringBuilder(prefix ?: "") }) { builder: StringBuilder, next: T -> + builder.append(if (builder.length == prefix?.length ?: 0) "" else separator ?: "").append(next) }.map { it.append(postfix ?: "").toString() } \ No newline at end of file From 307b2282326d5fe7329908ab976db35cde177411 Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Tue, 7 Mar 2017 19:14:49 -0600 Subject: [PATCH 19/20] update `single` to match 1.x changes --- src/main/kotlin/rx/lang/kotlin/single.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/rx/lang/kotlin/single.kt b/src/main/kotlin/rx/lang/kotlin/single.kt index c2e2de4..6f6f7be 100644 --- a/src/main/kotlin/rx/lang/kotlin/single.kt +++ b/src/main/kotlin/rx/lang/kotlin/single.kt @@ -5,7 +5,6 @@ import io.reactivex.SingleEmitter import java.util.concurrent.Callable import java.util.concurrent.Future -inline fun single(crossinline body: (s: SingleEmitter) -> Unit): Single = Single.create { body(it) } fun T.toSingle(): Single = Single.just(this) fun Future.toSingle(): Single = Single.fromFuture(this) fun Callable.toSingle(): Single = Single.fromCallable(this) From 2ca695a7b3d8d53cae8d42a20bd4fa1ffa0be6fe Mon Sep 17 00:00:00 2001 From: Thomas Nield Date: Tue, 7 Mar 2017 19:15:14 -0600 Subject: [PATCH 20/20] rid Subject functions --- src/main/kotlin/rx/lang/kotlin/subject.kt | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 src/main/kotlin/rx/lang/kotlin/subject.kt diff --git a/src/main/kotlin/rx/lang/kotlin/subject.kt b/src/main/kotlin/rx/lang/kotlin/subject.kt deleted file mode 100644 index 7dbd24a..0000000 --- a/src/main/kotlin/rx/lang/kotlin/subject.kt +++ /dev/null @@ -1,13 +0,0 @@ -package rx.lang.kotlin - -import io.reactivex.Observable -import io.reactivex.subjects.AsyncSubject -import io.reactivex.subjects.BehaviorSubject -import io.reactivex.subjects.PublishSubject -import io.reactivex.subjects.ReplaySubject - -fun BehaviorSubject(): BehaviorSubject = BehaviorSubject.create() -fun BehaviorSubject(default: T): BehaviorSubject = BehaviorSubject.createDefault(default) -fun AsyncSubject(): AsyncSubject = AsyncSubject.create() -fun PublishSubject(): PublishSubject = PublishSubject.create() -fun ReplaySubject(capacity: Int = Observable.bufferSize()): ReplaySubject = ReplaySubject.create(capacity)